上一篇文章 Vue 源码解读(5)—— 全局 API 详细介绍了 Vue 的各个全局 API 的实现原理,本篇文章将会详细介绍各个实例方法的实现原理。
深入理解以下实例方法的实现原理。
vm.$set
vm.$delete
vm.$watch
vm.$on
vm.$emit
vm.$off
vm.$once
vm._update
vm.$forceUpdate
vm.$destroy
vm.$nextTick
vm._render
/src/core/instance/index.js
该文件是 Vue 实例的入口文件,包括 Vue 构造函数的定义、各个实例方法的初始化。
// Vue 的构造函数
function Vue (options) {
// 调用 Vue.prototype._init 方法,该方法是在 initMixin 中定义的
this._init(options)
}
// 定义 Vue.prototype._init 方法
initMixin(Vue)
/**
* 定义:
* Vue.prototype.$data
* Vue.prototype.$props
* Vue.prototype.$set
* Vue.prototype.$delete
* Vue.prototype.$watch
*/
stateMixin(Vue)
/**
* 定义 事件相关的 方法:
* Vue.prototype.$on
* Vue.prototype.$once
* Vue.prototype.$off
* Vue.prototype.$emit
*/
eventsMixin(Vue)
/**
* 定义:
* Vue.prototype._update
* Vue.prototype.$forceUpdate
* Vue.prototype.$destroy
*/
lifecycleMixin(Vue)
/**
* 执行 installRenderHelpers,在 Vue.prototype 对象上安装运行时便利程序
*
* 定义:
* Vue.prototype.$nextTick
* Vue.prototype._render
*/
renderMixin(Vue)
src/core/instance/state.js
这是两个实例属性,不是实例方法,这里简单介绍以下,当然其本身实现也很简单
// data
const dataDef = {}
dataDef.get = function () { return this._data }
// props
const propsDef = {}
propsDef.get = function () { return this._props }
// 将 data 属性和 props 属性挂载到 Vue.prototype 对象上
// 这样在程序中就可以通过 this.$data 和 this.$props 来访问 data 和 props 对象了
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
/src/core/instance/state.js
Vue.prototype.$set = set
/src/core/observer/index.js
/**
* 通过 Vue.set 或者 this.$set 方法给 target 的指定 key 设置值 val
* 如果 target 是对象,并且 key 原本不存在,则为新 key 设置响应式,然后执行依赖通知
*/
export function set (target: Array | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 更新数组指定下标的元素,Vue.set(array, idx, val),通过 splice 方法实现响应式更新
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
// 更新对象已有属性,Vue.set(obj, key, val),执行更新即可
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
// 不能向 Vue 实例或者 $data 添加动态添加响应式属性,vmCount 的用处之一,
// this.$data 的 ob.vmCount = 1,表示根组件,其它子组件的 vm.vmCount 都是 0
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
// target 不是响应式对象,新属性会被设置,但是不会做响应式处理
if (!ob) {
target[key] = val
return val
}
// 给对象定义新属性,通过 defineReactive 方法设置响应式,并触发依赖更新
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
/src/core/instance/state.js
Vue.prototype.$delete = del
/src/core/observer/index.js
/**
* 通过 Vue.delete 或者 vm.$delete 删除 target 对象的指定 key
* 数组通过 splice 方法实现,对象则通过 delete 运算符删除指定 key,并执行依赖通知
*/
export function del (target: Array | Object, key: any) {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// target 为数组,则通过 splice 方法删除指定下标的元素
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
const ob = (target: any).__ob__
// 避免删除 Vue 实例的属性或者 $data 的数据
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
// 如果属性不存在直接结束
if (!hasOwn(target, key)) {
return
}
// 通过 delete 运算符删除对象的属性
delete target[key]
if (!ob) {
return
}
// 执行依赖通知
ob.dep.notify()
}
/src/core/instance/state.js
/**
* 创建 watcher,返回 unwatch,共完成如下 5 件事:
* 1、兼容性处理,保证最后 new Watcher 时的 cb 为函数
* 2、标示用户 watcher
* 3、创建 watcher 实例
* 4、如果设置了 immediate,则立即执行一次 cb
* 5、返回 unwatch
* @param {*} expOrFn key
* @param {*} cb 回调函数
* @param {*} options 配置项,用户直接调用 this.$watch 时可能会传递一个 配置项
* @returns 返回 unwatch 函数,用于取消 watch 监听
*/
Vue.prototype.$watch = function ( expOrFn: string | Function,
cb: any,
options?: Object ): Function {
const vm: Component = this
// 兼容性处理,因为用户调用 vm.$watch 时设置的 cb 可能是对象
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
// options.user 表示用户 watcher,还有渲染 watcher,即 updateComponent 方法中实例化的 watcher
options = options || {}
options.user = true
// 创建 watcher
const watcher = new Watcher(vm, expOrFn, cb, options)
// 如果用户设置了 immediate 为 true,则立即执行一次回调函数
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
// 返回一个 unwatch 函数,用于解除监听
return function unwatchFn() {
watcher.teardown()
}
}
/src/core/instance/events.js
const hookRE = /^hook:/
/**
* 监听实例上的自定义事件,vm._event = { eventName: [fn1, ...], ... }
* @param {*} event 单个的事件名称或者有多个事件名组成的数组
* @param {*} fn 当 event 被触发时执行的回调函数
* @returns
*/
Vue.prototype.$on = function (event: string | Array, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
// event 是有多个事件名组成的数组,则遍历这些事件,依次递归调用 $on
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
// 将注册的事件和回调以键值对的形式存储到 vm._event 对象中 vm._event = { eventName: [fn1, ...] }