新功能
createApp
emits 属性
多事件处理
Fragment
不再限于模板中的单个根节点
移除 .sync 改为 v-model 参数
异步组件的引用方式
移除 filter
Teleport
以前称为 <Portal>,译作传送门(之前都是放在 APP 里,用这个可随意放置)
Suspense
可以嵌套层级中等待嵌套的异步依赖项
Composition API
reactive
ref、toRef、toRefs
readonly
computed
watch、watchEffect
钩子函数生命周期
原理
Proxy 实现响应式
编译优化
PatchFlag 静态标记
hoistStatic 静态提升
cacheHandler 缓存事件
SSR 优化
Tree-shaking 优化
可以将无用模块"剪辑",仅打包需要
Vite
ES6 module面试题
Composition API 和 Options APIref、toRef 和 toRefsComposition API 如何实现代码逻辑复用watch 和 watchEffect 的区别是什么setup 中如何获取组件实例Composition API 和 React Hooks 的对比| 选项式 API | 组合式 API |
|---|---|
beforeCreate | 不需要(直接写到 setup 函数中) |
created | 不需要(直接写到 setup 函数中) |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy Vue 3:beforeUnmount | onBeforeUnmount |
destroyed Vue 3: unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
activated | onActivated |
deactivated | onDeactivated |
composition API 优点:
如何选择:
Options APIComposition API选项式 API(Options API)
methods 中,如果 data 中数据越来越多,找数据会非常困难<template>
<h1 @click="changeCount">{{ count }}</h1>
</template>
<script>
export default {
name: 'App',
data() {
return {
count: 0,
}
},
methods: {
changeCount() {
this.count++
},
},
}
</script>
组合式 API(Composition API)
<template>
<h1 @click="changeNum">{{ num }}</h1>
</template>
<script>
import { ref } from 'vue'
function useNum() {
const num = ref(0)
function changeNum() {
num.value++
}
return { changeNum, num }
}
export default {
name: 'App',
setup() {
const { changeNum, num } = useNum()
return {
changeNum,
num,
}
},
}
</script>
reactive.value 修改值<template>
<p>值类型响应式:{{ ageRef }} {{ state.name }}</p>
<p ref="elemRef">templateRef</p>
</template>
<script>
import { ref, reactive, onMounted } from 'vue'
export default {
name: 'Ref',
setup() {
const ageRef = ref(20)
const nameRef = ref('cat')
const elemRef = ref('elemRef')
const state = reactive({
name: nameRef,
})
setTimeout(() => {
ageRef.value = 30 // .value 修改值
nameRef.value = 'dog'
}, 1500)
onMounted(() => {
console.log(elemRef.value)
})
return {
ageRef,
state,
elemRef,
}
},
}
</script>
reactive 封装)的 propref,具有响应式<template>
<p>{{ ageRef }} {{ state.age }}</p>
</template>
<script>
import { toRef, reactive } from 'vue'
export default {
name: 'ToRef',
setup() {
const state = reactive({
age: 20,
name: 'cat',
})
const ageRef = toRef(state, 'age')
setTimeout(() => {
state.age = 25
}, 1000)
setTimeout(() => {
ageRef.value = 30
}, 3000)
return { state, ageRef }
},
}
</script>
toRef 如果用于普通对象(非响应式对象),产出的结果不具备响应式
const state = {
age: 20,
name: 'cat',
}
reactive 封装)转换为普通对象prop 都是对应的 ref注意:直接解构 reactive 返回的 state ,页面能显示内容,但内容不是响应式的
<template>
<p>{{ age }} {{ name }}</p>
</template>
<script>
import { toRefs, reactive } from 'vue'
export default {
name: 'ToRef',
setup() {
const state = reactive({
age: 20,
name: 'cat',
})
// 将响应式对象,变为普通对象
const stateAsRefs = toRefs(state)
setTimeout(() => {
state.age = 25
}, 1000)
// const { age: ageRef, name: nameRef } = stateAsRefs
return { ...stateAsRefs }
},
}
</script>
用 reactive 做对象的响应式,用 ref 做值类型响应式
setup 中返回 toRefs(state) 或者 toRef(state, 'xxx')
ref 的变量命名都用 xxxRef
合成函数返回响应式对象,使用 toRefs
function useFeatureX() {
const state = reactive({
x: 1,
y: 2
})
return toRefs(state)
}
export default {
name: 'WhyRef',
setup() {
const { x, y } = useFeatureX()
return {
x,
y
}
}
}
返回值类型,会丢失响应式
在 setup、computed、合成函数,都有可能返回值类型
Vue 如不定义 ref,用户将自造 ref,反而混乱
<template>
<p>{{ age }}</p>
<p>{{ age1 }}</p>
</template>
<script>
import { reactive, computed } from 'vue'
export default {
name: 'WhyRef',
setup() {
const state = reactive({
age: 20,
name: 'dog',
})
// computed返回的是一个类似于ref的对象,也有.value
const age1 = computed(() => {
return state.age + 1
})
setTimeout(() => {
state.age = 25
}, 1000)
return {
...state, // 这样不是响应式的
age1,
}
},
}
</script>
ref 是一个对象(不丢失响应式),value 存储值.value 属性的 get 和 set 实现响应式reactive 时,不需要 .value,其他情况都需要简单理解 computed 运算逻辑
// 错误
function computed(getter) {
let value
watchEffect(() => { // 可以改为setTimeout进行模拟测试
value = getter()
})
return value
}
// 正确
function computed(getter) {
const ref = {
value: null,
}
watchEffect(() => {
ref.value = getter()
})
return ref
}
reactive 封装)非普通对象createAppemits 属性Fragment.syncfilterTeleportSuspenseComposition API// vue2.x
const app = new Vue({ /* ... */ })
Vue.use(/* ... */)
Vue.mixin(/* ... */)
Vue.component(/* ... */)
Vue.directive(/* ... */)
// vue3
const app = Vue.createApp({ /* ... */ })
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)
<template>
<son :msg="msg" @onSayHello="sayHello" />
</template>
<script>
import Son from './views/Son.vue'
export default {
components: { Son },
data() {
return {
msg: 'hello vue3',
}
},
methods: {
sayHello(info) {
console.log('hello', info)
},
},
}
</script>
<template>
<p>{{ msg }}</p>
</template>
<script>
export default {
props: {
msg: String,
},
emits: ['onSayHello'],
setup(props, { emit }) {
emit('onSayHello', 'vue3')
},
}
</script>
<button @click="one($event), two($event)">Submit</button>
<!-- vue2.x组件模板 -->
<template>
<div>
<p>{{ msg }}</p>
<p>{{ content }}</p>
</div>
</template>
<!-- vue3组件模板 -->
<template>
<p>{{ msg }}</p>
<p>{{ content }}</p>
</template>
<!-- vue2.x -->
<MyComponent :title.sync="title" />
<!-- vue3.x -->
<MyComponent v-model:title="title" />
// vue2.x
new Vue({
components: {
'my-component': () => import('./async-com.vue'),
},
})
// vue3.x
import { createApp, defineAsyncComponent } from 'vue'
createApp({
components: {
AsyncComponent: defineAsyncComponent(() => import('./async-com.vue')),
},
})
<!-- vue2.x -->
<div>{{ message | capitalize }}</div>
<div :id="rawId | formatId">/div>
可以把一些组件放到外面去,Vue2 只能操作 DOM 来实现,Vue3 可以通过 Teleport 来实现
<button @click="modalOpen = true">Open full screen modal!</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
telePort 弹窗(父元素是 body)
<button @click="modalOpen = false">Close</button>
</div>
</div>
</teleport>
场景:比如一个列表,一刷新就需要加载数据,在数据未加载之前会显示 loading,加载完之后在显示列表数据
Vue2 这种场景一般会写一个 data 控制显示隐藏,Element UI 库对此进行封装,Vue3 自己做了一个封装,封装成 Suspense
<Suspense>
<template>
<!-- 是一个异步组件 -->
<Test1 />
</template>
<!-- #fallback 就是一个具名插槽。即Suspense组件内部,有两个slot,其中一个具名为fallback -->
<template #fallback> Loading.. </template>
</Suspense>
reactiveref 相关readonlywatch 和 watchEffectsetupuseXxx 格式(React Hooks 也是)setup 中引用 useXxx 函数<template>
<p>mouse position {{ x }} {{ y }}</p>
</template>
<script>
import useMousePosition from './useMousePosition'
export default {
name: 'MousePosition',
setup() {
const { x, y } = useMousePosition()
return {
x,
y,
}
},
}
</script>
useMousePosition.jsimport { ref, onMounted, onUnmounted } from 'vue'
function useMousePosition() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
export default useMousePosition
如果不使用 ref 使用 reactive,需要将整个 reactive 暴露出去。在父组件接收的时候不能直接解构,否则会失去响应式
Object.defineProperty 的缺点:
Vue.set、Vue.delete)Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
observer(newValue) // 值修改后进行监听
// value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
value = newValue
updateView() // 触发更新视图
}
},
})
Proxy 实现响应式优点
性能是如何提升的
Proxy 实现不是一次性监听的,这里深度监听是在 get 中处理的,什么时候用到什么时候处理(惰性)。而 Object.defineProperty 实现是在一开始就进行处理,一次性全部处理完成
// 测试数据
const data = {
name: 'bird',
age: 20,
info: {
city: 'beijing',
},
}
function reactive(target = {}) {
if (typeof target !== 'object' || target == null) {
// 不是对象或数组,则返回
return target
}
// 代理配置
const proxyConf = {
get(target, key, receiver) {
// 只处本身(非原型)属性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) // 监听
}
const result = Reflect.get(target, key, receiver)
// 惰性深度监听。什么时候用什么时候监听
return reactive(result) // 返回结果
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true
}
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('已有的key', key)
} else {
console.log('新增的key', key)
}
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
return result // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key)
return result // 是否删除成功
},
}
// 生成代理对象
const observed = new Proxy(target, proxyConf)
return observed
}
const proxyData = reactive(data)
Reflect 的作用:
和 Proxy 能力一一对应
规范化、标准化、函数式
'a' in obj -> Reflect.has(obj, 'a')
delete obj.b -> Reflect.deleteProperty(obj, 'b')
替代 Object 上的工具函数
Object.getOwnPropertyNames(obj) -> Reflect.ownKeys(obj)
总结
Proxy 能规避 Object.defineProperty 的问题Proxy 无法兼容所有浏览器,无法 polyfillVue2 的 .sync 修饰符
<text-document v-bind:title.sync="doc.title" />
<!-- 语法糖 -->
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
/>
Vue3 的 v-model
<ChildComponent :title.sync="pageTitle" />
<!-- 替换为 -->
<ChildComponent v-model:title="pageTitle" />
v-model 相当于传递了 modelValue prop 并接受抛出的 update:modelValue 事件
<template>
<p>{{ name }} {{ age }}</p>
<user-info v-model:name="name" v-model:age="age" />
</template>
<script>
import { reactive, toRefs } from 'vue'
import UserInfo from './UserInfo.vue'
export default {
components: { UserInfo },
setup() {
const state = reactive({
name: 'bird',
age: '20',
})
return toRefs(state)
},
}
</script>
UserInfo.vue<template>
<input :value="name" @input="$emit('update:name', $event.target.value)" />
<input :value="age" @input="$emit('update:age', $event.target.value)" />
</template>
<script>
export default {
props: {
name: String,
age: String,
},
}
</script>
两者都可监听 data 属性变化
watch 需要明确监听哪个属性
默认是惰性执行,监听源可以是一个具有返回值的 getter 函数,也可以直接是一个 ref
watchEffect 会根据其中的属性,自动监听其变化
<template>
<p>{{ numberRef }}</p>
<p>{{ name }} {{ age }}</p>
</template>
<script>
import { reactive, toRefs, ref, watch, watchEffect } from 'vue'
export default {
setup() {
const numberRef = ref(10)
const state = reactive({
name: 'bird',
age: 20,
})
watchEffect(() => {
// 初始化时,一定会执行一次(收集需要监听的数据)
console.log('watchEffect', state.age)
})
watchEffect(() => {
console.log('watchEffect', numberRef)
})
// watch监听ref属性
watch(numberRef, (newNum, oldNum) => {
console.log('ref watch', newNum, oldNum)
})
// watch监听state属性
watch(
// 1.确定监听哪个属性
() => state.age,
// 2.回调函数
(newState, oldState) => {
console.log('state watch', newState, oldState)
},
// 3.配置项
{
immediate: true, // 初始化之前就监听
// deep: true // 深度监听
}
)
setTimeout(() => {
numberRef.value = 100
}, 1000)
setTimeout(() => {
state.age = 25
}, 1500)
return { numberRef, ...toRefs(state) }
},
}
</script>
setup 和其他 Composition API 中没有 thisgetCurrentInstance 获取当前实例Options API 可照常使用 this<template>
<p>getInstance</p>
</template>
<script>
import { getCurrentInstance, onMounted } from 'vue'
export default {
data() {
return {
x: 1,
y: 2,
}
},
setup() { // created beforeCreate 组件还没有正式初始化
console.log('setup this', this) // undefined
onMounted(() => {
console.log('onMounted this', this) // undefined
console.log('x', instance.data.x) // 1
})
const instance = getCurrentInstance()
console.log('instance', instance) // 组件实例
console.log('x', instance.data.x) // undefined
},
mounted() {
console.log('mounted this', this) // Proxy 实例
console.log('y', this.y) // 2
},
}
</script>
proxy 响应式PatchFlaghoistStaticcacheHandlerSSR 优化tree-shakingTEXT、PROPSdiff 算法时,可以区分静态节点,以及不同类型的动态节点Vue2 和 Vu3 diff 算法比较

patchFlag 与上次虚拟节点比较时,只比较有 patchFlag 的节点<div>
<div>1</div>
<div>2</div>
<div>{{ name }}</div>
</div>
<script>
export function render() {
return (
_openBlock(),
_createBlock('div', null, [
_createVNode('div', null, '1'),
_createVNode('div', null, '2'),
_createVNode('div', null, _toDisplayString(_ctx.name), 1 /* TEXT */),
])
)
}
</script>
Vue2 无论元素是否参与更新,每次都会重新创建然后再渲染。Vue3 对于不参与更新的元素,做静态提升,只会被创建一次,在渲染时直接复用即可
<div>
<div>1</div>
<div>2</div>
<div>{{ name }}</div>
</div>
<script>
const _hoisted_1 = /*#__PURE__*/ _createVNode('div', null, '1', -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/ _createVNode('div', null, '2', -1 /* HOISTED */)
export function render() {
return (
_openBlock(),
_createBlock('div', null, [
_hoisted_1,
_hoisted_2,
_createVNode('div', null, _toDisplayString(_ctx.name), 1 /* TEXT */),
])
)
}
</script>
Vue2 绑定事件每次触发都要重新生成全新 Function 去更新。Vue3 提供事件缓存对象,当开启 cacheHandler 会自动生成一个内联函数,同时生成一个静态节点,当事件再次触发时,只需从缓存中调用即可
<div>
<div @click="todo">something</div>
</div>
<script>
export function render() {
return (
_openBlock(),
_createBlock('div', null, [
_createVNode(
'div',
{
onClick: _cache[1] || (_cache[1] = (...args) => _ctx.todo(...args)),
},
'something'
),
])
)
}
</script>
vdom,当做字符串推进 buffer 里编译时,根据不同的情况,引入不同的 API
可以静态分析模块依赖并删除未使用的导出相关的代码
Vite 为何启动快
CommonJs 实现原理
;(function (exports, require, module, __filename, __dirname) {
const sayName = require('./hello.js')
module.exports = function say() {
return {
name: sayName(),
author: '我不是外星人',
}
}
})
在 CommonJs 规范下模块中,会形成一个包装函数,包装函数本质如下:
最后将包装函数执行
function wrapper(script) {
return '(function (exports, require, module, __filename, __dirname) {' + script + '\n})'
}
require 加载原理
module:在 Node 中每一个 JS 文件都是一个 module,module 上保存了 exports 等信息之外,还有一个 loaded 表示该模块是否被加载Module:以 Node 为例,整个系统运行之后,会用 Module 缓存每一个模块加载的信息// id 为路径标识符
function require(id) {
/* 查找 Module 上有没有已经加载的 js 对象*/
const cachedModule = Module._cache[id]
/* 如果已经加载了那么直接取走缓存的 exports 对象 */
if (cachedModule) {
return cachedModule.exports
}
/* 创建当前模块的 module */
const module = { exports: {}, loaded: false }
/* 将 module 缓存到 Module 的缓存属性中,路径标识符作为 id */
Module._cache[id] = module
/* 加载文件 */
runInThisContext(wrapper('module.exports = "123"'))(
module.exports,
require,
module,
__filename,
__dirname
)
/* 加载完成 */
module.loaded = true
/* 返回值 */
return module.exports
}
require 大致流程如下:
require 会接收一个参数——文件标识符,然后分析定位文件,接下来会从 Module 上查找有没有缓存,如果有缓存,那么直接返回缓存的内容module 对象,缓存到 Module 上,然后执行文件,加载完文件后,将 loaded 设置为 trueexports 和 module.exports 持有相同的引用,所以对 exports 赋值会导致 exports 操作的不再是 module.exports 的引用ES6 之后,JS 才有了真正意义上的模块化规范。ES Module 的优势:
tree shakingimport() 懒加载方式实现代码分割基本使用
export default 导出<script type="module">
import add from './add.js'
console.log(add(10, 20)) // 30
</script>
<script>
// add.js
export default function add(a, b) {
return a + b
}
</script>
export 导出,需要解构<script type="module">
import { add } from './math.js'
console.log(add(10, 20)) // 30
</script>
<script>
// math.js
export function add(a, b) {
return a + b
}
</script>
外链使用
<script type="module">
import { createStore } from 'https://unpkg.com/redux@latest/es/redux.mjs'
console.log(createStore) // Function
</script>
动态引入
<button id="btn1">load1</button>
<script type="module">
document.getElementById('btn1').addEventListener('click', async () => {
const res = await import('./math.js')
console.log(res.add(10, 20)) // 30
})
</script>
<script>
// math.js
export function add(a, b) {
return a + b
}
</script>
setup 只会被调用一次,React Hooks 函数(组件)会被多次调用useMemo、useCallback,因为 setup 只调用一次hooks 的顺序一致(不能放到循环里、判断里,必须放在最外层的顺序代码里)reactive + ref 比 React Hooks 的 useState 要难理解我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU
目录第1题连续问题分析:解法:第2题分组问题分析:解法:第3题间隔连续问题分析:解法:第4题打折日期交叉问题分析:解法:第5题同时在线问题分析:解法:第1题连续问题如下数据为蚂蚁森林中用户领取的减少碳排放量iddtlowcarbon10012021-12-1212310022021-12-124510012021-12-134310012021-12-134510012021-12-132310022021-12-144510012021-12-1423010022021-12-154510012021-12-1523.......找出连续3天及以上减少碳排放量在100以上的用户分析:遇到这类
我想找到在某些文本中找到一些(让它是两个)句子的好方法。什么会更好-使用正则表达式或拆分方法?你的想法?应JeremyStein的要求-有一些例子示例:输入:ThefirstthingtodoistocreatetheCommentmodel.We’llcreatethisinthenormalway,butwithonesmalldifference.IfwewerejustcreatingcommentsforanArticlewe’dhaveanintegerfieldcalledarticle_idinthemodeltostoretheforeignkey,butinthis
我正在使用ruby1.8.7。p=lambda{return10;}deflab(block)puts'before'putsblock.callputs'after'endlabp以上代码输出为before10after我将相同的代码重构到这里deflab(&block)puts'before'putsblock.callputs'after'endlab{return10;}现在我收到LocalJumpError:意外返回。对我来说,这两个代码都在做同样的事情。是的,在第一种情况下我传递了一个过程,在第二种情况下我传递了一个block。但是&block将该block转换为pro
我在Ruby中有一个哈希:hash=Hash.new里面有一些键值对,比如说:hash[1]="One"hash[2]="Two"如果散列包含键2,那么我想将“Bananas”添加到它的值中。如果散列没有键2,我想创建一个新的键值对2=>"Bananas"。我知道我可以通过首先使用has_key?检查散列是否具有key2来做到这一点,然后采取相应的行动。但这需要一个if语句和不止一行。那么是否有一种简单、优雅的单行代码可以实现这一目标? 最佳答案 这个有效:hash[2]=(hash[2]||'')+'Bananas'如果您希望所有
参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍 介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。 内容有: ①:Hub模型的方法介绍 ②:服务器端代码介绍 ③:前端vue3安装并调用后端方法 ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke() 去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on
我正在使用gmailgem发送电子邮件,我需要跟踪这些电子邮件。我该怎么做?我正在尝试搜索带有message_id的电子邮件,但它会从我的收件箱中提取所有电子邮件,而我只想要特定电子邮件的回复。这是我的实际代码:*使用message_id保存电子邮件*mail=gmail.deliver(email)Email.create(:message_id=>mail.message_id,:from=>user.email,:to=>annotation.to,:body=>annotation.content,:title=>annotation.title,:annotation=>an
我的背景是PHP和C#,但我真的很想学习RoR。为此,我开始阅读官方文档。我对一些代码示例有一些疑问。第一个是迭代器:classArraydefinject(n)each{|value|n=yield(n,value)}nenddefsuminject(0){|n,value|n+value}enddefproductinject(1){|n,value|n*value}endend我理解yield的意思是“在这里执行关联的block”。令我震惊的是|value|n=each的一部分。其他block对我来说更有意义,因为它们似乎模仿C#风格的lambda:publicintsum(in
?作者主页:静Yu?简介:CSDN全栈优质创作者、华为云享专家、阿里云社区博客专家,前端知识交流社区创建者?社区地址:前端知识交流社区?博主的个人博客:静Yu的个人博客?博主的个人笔记本:前端面试题个人笔记本只记录前端领域的面试题目,项目总结,面试技巧等等。接下来会更新蓝桥杯官方系统基础练习的VIP试题,依然包括解题思路,源代码等等。问题描述:给定当前的时间,请用英文的读法将它读出来。时间用时h和分m表示,在英文的读法中,读一个时间的方法是: 如果m为0,则将时读出来,然后加上“o’clock”,如3:00读作“threeo’clock”。 如果m不为0,则将时读出来,然后将分读出来,如5