内部分为 macroTask 和 microTask,一般用户外部调用 nextTick都是调用的 microTask
macroTask依次通过 setImmediate、MessageChannel、setTimeout降级实现,microTask先尝试通过 Promise来实现,如果当前浏览器环境不支持 Promise,则降级为 macroTask
// 将 microTask降级为 macroTask
// fallback to macro
microTimerFunc = macroTimerFuncVue内置组件,其代码定义在 src/core/components/keep-alive.js
keep-alive 同样也是通过渲染一个组件来实现,只不过相比于我们手写的组件,其 DOM是直接通过一个 render函数渲染获得而非 template:
// src/core/components/keep-alive.js
render () {
const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot)
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
...
}其在内部制定了一个 abstract属性,使得在组件实例建立父子关系的时候被忽略,keep-alive的子组件的父组件被设定为 keep-alive的父组件:
// src\core\instance\lifecycle.js
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}并且 keep-alive最后返回的组件也是其子组件:
render () {
const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot)
...
return vnode || (slot && slot[0])
}keep-alive每次只会渲染其第一个子元素,所以诸如 v-for等能够渲染出多个子组件的方式都是不生效的:
// 只获取第一个子组件实例
const vnode: VNode = getFirstComponentChild(slot)如果指定了 keep-alive的 max属性,则其将在内部通过 LRU(最新最少使用)算法来管理缓存的组件:
// src/core/components/keep-alive.js
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key)
}代码在 src\platforms\web\runtime\components\transition.js 和 src/platforms/web/modules/transition.js中
<transition> 组件和 <keep-alive> 组件有⼏点实现类似,同样是抽象组件,同样直接实现 render 函数,同样利⽤了默认插槽(slot)
通过 autoCssTransition 处理 name 属性,⽣成⼀个⽤来描述各个阶段的 class 名称的对象,扩展到 def 中并返回给data ,这样我们就可以从 data 中获取到过渡
相关的所有数据,vue后续通过对这些 class名的处理,结合用户自定义的 css来控制动画的实现
const autoCssTransition: (name: string) => Object = cached(name => {
return {
enterClass: `${name}-enter`,
enterToClass: `${name}-enter-to`,
enterActiveClass: `${name}-enter-active`,
leaveClass: `${name}-leave`,
leaveToClass: `${name}-leave-to`,
leaveActiveClass: `${name}-leave-active`
}
})通过 requestAnimationFrame(降级为 setTimeout)来控制动画间的过渡状态,以及控制动画钩子函数的执行
// binding to window is necessary to make hot reload work in IE in strict mode
const raf = inBrowser
? window.requestAnimationFrame
? window.requestAnimationFrame.bind(window)
: setTimeout
: /* istanbul ignore next */ fn => fn()
export function nextFrame (fn: Function) {
raf(() => {
raf(fn)
})
}除去一部分对 platform、缓存等逻辑外,编译的核心分为三步:
- 解析模板字符串生成
AST
const ast = parse(template.trim(), options)主要是通过大量的正则表达式、压栈出栈等算法对 template字符串进行一段段的逐步解析,来构造初步的 AST Tree
- 优化语法树
optimize(ast, options)对上一步构造出的 AST Tree进行优化,例如标记静态节点、增加缓存等手段,来提升 AST Tree的解析速度
- 生成代码
const code = generate(ast, options)将上一步的 AST Tree转换成可执行的代码,
这个指令的内部实现,就是先调用了一次 v-on绑定事件,等到事件执行一遍之后,再通过回调函数调用 v-off来取消事件实现的
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on () {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}v-model 实际上都是语法糖,通过给 $el增加对应的 prop变量,以及绑定响应事件来实现的,
v-html 以及v-text同样有异曲同工之妙
// v-model
// input textarea
addProp(el, 'value', `(${value})`)
// check
addProp(el, 'checked',
`Array.isArray(${value})` +
`?_i(${value},${valueBinding})>-1` + (
trueValueBinding === 'true'
? `:(${value})`
: `:_q(${value},${trueValueBinding})`
)
)v-model内部借助了 compositionstart 和 compositionend事件来处理输入,所以能够更好地处理拼音字符的输入
在不支持 composition 事件的浏览器上,则降级为 change事件
el.addEventListener('compositionstart', onCompositionStart)
el.addEventListener('compositionend', onCompositionEnd)
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
el.addEventListener('change', onCompositionEnd)