vue@2.x实现分析
本文最后更新于:2022年4月21日 上午
生命周期
初始化阶段
Vue.prototype._init = function (options?: Object){
// 设置uid
vm._uid = uid++
// 不为根组件设置响应式
vm._isVue = true
// 合并配置
// 合并实例的option对象,里面会包含像生命周期钩子回调用,指令,过滤器,vue内置对象等,与用户自定义的全局xx合并
// 实例内部执行会比外部new Vue快很多
if (options && options._isComponent) {
// 实例内部执行new Vue
initInternalComponent(vm, options)
} else {
// 从外部执行new Vue时
vm.$options = mergeOptions(
√s || {},
vm
)
}
// 初始化生命周期
initLifecycle(vm){
//关联`$parent`,并将自己添加进父组件的`$children`数组,生成自己的`$children`数组和`$refs`对象,并指定`$root`
var parent = options.parent;
parent.$children.push(vm);
vm.$parent = parent;
vm.$root = parent ? parent.$root : vm;
vm.$children = [];
vm.$refs = {};
vm._watcher = null;
vm._isMounted = false;// 判断是否mount的flag
}
initEvents(vm){// 初始化事件中心
vm._events = Object.create(null);
var listeners = vm.$options._parentListeners;
if (listeners) {
updateComponentListeners(vm, listeners); // 组件上的事件
}
}
initRender(vm){
vm.$slots = resolveSlots(options._renderChildren, renderContext);// 初始化插槽,得到vm.$slots对象
// key是插槽名 value是vnode
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
// 模版编译的createElement函数
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
// 初始化用户自己写的createElement函数
defineReactive$$1(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
!isUpdatingChildComponent && warn("$attrs is readonly.", vm);
}, true);
defineReactive$$1(vm, '$listeners', options._parentListeners || emptyObject, function () {
!isUpdatingChildComponent && warn("$listeners is readonly.", vm);
}, true);
}
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState (vm) {
initProps(vm, opts.props);
// 子组件标签上的prop将会编译为attrs对象
// render函数在校验这些props后会会生成propData传入VNode构造器
// 实例化子组件的同时也为prop绑定了响应式
initMethods(vm, opts.methods);
// 确保方法命名规范并添加到实例上
initData(vm);
// 确保data中命名规范并绑定响应式
initComputed(vm, opts.computed);
initWatch(vm, opts.watch);
}
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
}
挂载阶段
mountComponent () {
callHook(vm, '⭐️beforeMount');
let updateComponent;
updateComponent = () => {
vm._update(vm._render(), hydrating);
// update函数
Vue.prototype._update = function (vnode, hydrating) {
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode);
};
};
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate');
}
}
subs[i].notify ---> Watcher.prototype.update()
callHook(vm, '⭐️beforeDestroy');
}, true /* isRenderWatcher init阶段生成的是渲染watcher*/);
vm._isMounted = true;
callHook(vm, 'mounted');
}
销毁阶段
Vue.prototype.$destroy = function () {
callHook(vm, '⭐️beforeDestroy');
// 销毁实例,子实例,从父组件children中删除,销毁所有事件和指令
remove(parent.$children, vm);
vm._watcher.teardown();
vm.__patch__(vm._vnode, null);
// fire destroyed hook
callHook(vm, '⭐️destroyed');
vm.$el.__vue__ = null;
vm.$vnode.parent = null;
};
}
keep-alive
callHook(vm, '⭐️activated');
callHook(vm, '⭐️deactivated');
响应式原理
defineReactive
defineReactive (
...
) {
const dep = new Dep()
...
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// render函数接触data触发getter
get: function reactiveGetter () {
if (Dep.target) {
// 此时Dep.target已经是渲染watcher
// 执行depend
⭐️ dep.depend()
if (childOb) {
...
}
}
return value
},
set: function reactiveSetter (newVal) {
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
val = newVal
🔥 dep.notify()
}
})
}
mountComponent
mountComponent (...): Component {
callHook(vm, 'beforeMount')
let updateComponent;
⭐️ // render函数生成虚拟DOM,期间会访问数据
updateComponent = () => {
vm._update(vm._render(), hydrating);
};
⭐️ //组件`mount`过程中会实例化一个渲染`watcher`,传入updateComponent函数
vm._watcher = new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
}
Watcher
对象
class Watcher {
constructor (
vm,
expOrFn,
cb,
options
) {
this.vm = vm;
vm._watchers.push(this);
this.deps = [];
this.newDeps = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
// parse expression for getter
if (typeof expOrFn === 'function') {
⭐️ // getter此时为传入的updateComponent函数
this.getter = expOrFn;
}
}
get () {
⭐️ //首先触发watcher中的get()
// pushTarget方法
pushTarget(this)
let value
⭐️ // 调用getter
value = this.getter.call(vm, vm)
if (this.deep) {
traverse(value)
}
⭐️ // 当前组件收集完成后清理
popTarget()
this.cleanupDeps()
return value
}
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
⭐️ // 添加订阅
dep.addSub(this)
}
}
}
⭐️// 对比新旧订阅列表,清空依赖
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
//清空依赖
dep.removeSub(this)
}
}
// 每次更新页面,render依赖收集的时候
// 将不展示于页面上的数据的依赖移除
// 考虑v-if
[this.depIds,this.newDepIds]=[this.newDepIds,this.depIds]
this.newDepIds.clear()
[this.deps,this.newDeps] = [this.newDeps,this.deps]
this.newDeps.length = 0
}
🔥 update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
// 同步立即run
this.run();
} else {
🔥 queueWatcher(this);
}
}
// 通过一个队列queue管理需要更新的watcher,重复的将会只执行最后一个
function queueWatcher (watcher) {
var id = watcher.id;
if (has[id] == null) {
has[id] = true;
if (!flushing) {
queue.push(watcher);
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
var i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
queue.splice(i + 1, 0, watcher);
}
// queue the flush
if (!waiting) {
waiting = true;
if (!config.async) {
flushSchedulerQueue();
return
}
// 在下一个tick执行这个队列
nextTick(flushSchedulerQueue);
}
}
}
}
Dep
对象
class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
// 将watcher加入subs
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
// 执行当前watcher中的addDep函数
Dep.target.addDep(this)
}
}
🔥 notify () {
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
⭐️ // target置空
Dep.target = null
const targetStack = []
function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
⭐️ // 将Dep.target 指向当前watcher
Dep.target = _target
}
function popTarget () {
Dep.target = targetStack.pop()
}
⭐️ 收集依赖 ⭐️
实例初始化阶段为每个标记为响应式的数据(拥有不可枚举属性
__ob__
)进行响应式绑定,生成各自的Dep
实例;组件渲染函数执行过程中实例化一个渲染
Watcher
,并指定为此时的Dep.target
(Dep 的 Target 指向不是固定的,在渲染函数的执行过程中会一直变化来为各类数据data
prop
computed
watch
)创建维护 subs 数组;当渲染函数访问到绑定了响应式的数据时,触发数据的 getter,将当前组件渲染
Watcher
添加到该数据持有的Dep.subs
数组中;同时渲染
Watcher
自己也有一个deps
数组保存,用来记录自己订阅了谁,这样做的意义是在下次Watcher.update
时将不再显示页面上的数据绑定移除(对比新旧deps
数组)可以理解成 data 数据的 subs 数组中每个 watcher 即代表一个受该数据响应式影响的组件实例
🔥 派发更新 🔥
- 修改数据触发 setter,执行该数据持有的
Dep.notify
方法 - 遍历当前数据
Dep.subs
中的每一个Watcher
,执行其Watcher.update()
- vue 不会每次数据更新就立刻去更新视图,而是用队列管理这些回调任务
queueWatcher
- 队列会判断 watcher 是否重复,并且进行排序【先父后子】【自定义 watcher> 渲染 watcher】
- 在下一个
tick
执行这个队列的回调updateComponent
函数,patch
实例(diff 并更换新 DOM)
nextTick
- js事件循环,主线程的一轮循环就是一个
tick
,只有在执行完所有同步函数和macrotask
,microtask
后才能进入下一个tick
- 派发更新时
queueWatcher
调用nextTick()
来执行需要更新的Watcher队列 - 手动调用
nextTick
的回调也会加入callback数组,在下一个tick
执行const callbacks = [] let pending = false function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } let microTimerFunc // 在下一轮微任务执行cb数组 let macroTimerFunc // 在下一轮宏任务执行cb数组 let useMacroTask = false // 依次尝试使用setImmediate,messageChanel,setTimeout来实现macroTimerFunc if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { macroTimerFunc = () => { setImmediate(flushCallbacks) } } else if (typeof MessageChannel !== 'undefined' && ( isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === '[object MessageChannelConstructor]' )) { const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () => { port.postMessage(1) } } else { macroTimerFunc = () => { setTimeout(flushCallbacks, 0) } } // if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() microTimerFunc = () => { p.then(flushCallbacks) } } else { // 回退到使用macroTimerFunc microTimerFunc = macroTimerFunc } export function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } // 如果调用了nextTick但没传cb,默认给一个Promise.resolve()来resolve if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
组件生命周期关系
- 组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。
- 组件的销毁操作是先父后子,销毁完成的顺序是先子后父。
加载渲染过程
父 beforeCreate->父 created->父 beforeMount->子 beforeCreate->子 created->子 beforeMount- >子 mounted->父 mounted
子组件更新过程
父 beforeUpdate->子 beforeUpdate->子 updated->父 updated
销毁过程
父 beforeDestroy->子 beforeDestroy->子 destroyed->父 destroyed
计算属性和侦听属性
Computed
initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
...
⭐️1 watchers[key] = new Watcher(
vm,
getter || noop,
noop,
{ computed: true }
)
if (!(key in vm)) {
⭐️2 defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
if (key in vm.$data) {
// ... key名称被data或者prop占用的警告
}
}
}
}
createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
⭐️2 watcher.depend()
return watcher.evaluate()
}
}
}
- 实例 initState 阶段遍历 Computed 内的 key,为每个 key 创建 computed watcher
- 利用 Object.defineProperty 给 key 值添加 getter
- 渲染函数访问到 computed 内的属性时候触发 getter,生成一个 Dep 实例,并且此时 Dep.target 是渲染 watcher,渲染 watcher 就订阅了这个 key 的变化
- 执行这个 compute key 的计算逻辑,访问依赖的数据会触发其中响应式数据的 getter,则 computed watcher 也就订阅了 data 的变化,最后也返回计算值并缓存起来
- 当依赖的响应式数据变化时,响应的 computed watcher 会收到通知
- computed watcher 会重新求值,比较两次运算结果,结果改变时才会通知渲染 watcher 更新
Watch
- deep
- immediate
initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
// 遍历handlers
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
- 遍历 Watch 内的 key
- 遍历每一个 handler,为每一个 handler 创建 user watcher
- user watcher 会被添加进订阅响应数据的 subs 数组完成订阅
- 响应式数据的变化会通知到 user watcher
- 渲染函数如果访问这个 watch 属性,也同理会被推入该 watch 属性的 subs 数组,订阅其变化
Vue事件
Vue 支持 2 种事件类型,原生 DOM 事件和自定义事件,它们主要的区别在于添加和删除事件的方式不一样
- 原生事件使用
add/removeEventListener
直接添加到el
上 - 自定义事件使用一个事件中心来维护
- 自定义事件的派发是往当前实例上派发
- 只有组件节点才可以添加自定义事件,并且添加原生 DOM 事件需要使用
.native
修饰符;而普通元素使用.native
修饰符是没有作用的,也只能添加原生 DOM 事件。vue3中已经舍弃.native,全局的所有事件都交由vue管理
// 简化 export function eventsMixin (Vue) { Vue.prototype.$on = function (event, fn){ const vm = this if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { this.$on(event[i], fn) } } else { (vm._events[event] || (vm._events[event] = [])).push(fn) } return vm } Vue.prototype.$once = function (event, fn){ const vm = this vm.$on(event, function(/*参数*/){ vm.$off(event, on) fn.apply(vm, arguments) }) return vm } Vue.prototype.$off = function (event, fn){ const vm = this // all if (!arguments.length) { vm._events = Object.create(null) return vm } // array of events if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { this.$off(event[i], fn) } return vm } // specific event const cbs = vm._events[event] if (!cbs) { return vm } if (!fn) { vm._events[event] = null return vm } if (fn) { // specific handler let cb let i = cbs.length while (i--) { cb = cbs[i] if (cb === fn || cb.fn === fn) { cbs.splice(i, 1) break } } } return vm } Vue.prototype.$emit = function (event,...args){ const vm = this let cbs = vm._events[event] if (cbs) { for (let i = 0, l = cbs.length; i < l; i++) { try { cbs[i].apply(vm, args) } catch (e) { // handle Error } } } return vm } }