咱们发现 $set
和 $delete
定义在 stateMixin
函数中,以下代码:
react
export function stateMixin (Vue: Class<Component>) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.', this )
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object ): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}}复制代码
是否是太长,太复杂?看不懂?express
不急咱们慢慢往下看,逐步介绍:数组
上面定义常量和环境判断就不说了直接看核心:bash
Vue.prototype.$set = set
Vue.prototype.$delete = del复制代码
能够看到 $set
和 $delete
的值分别是是 set
和 del
函数
其实咱们发现initGlobalAPI
函数中定义了:ui
Vue.set = set
Vue.delete = del复制代码
不难看出其实 Vue.set == $set ,Vue.delete == $deletethis
export function set (target: Array<any> | 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)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val) return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val return val
}
const ob = (target: any).__ob__
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 }
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}复制代码
set
函数接收三个参数:第一个参数 target
是将要被添加属性的对象,第二个参数 key
以及第三个参数 val
分别是要添加属性的键名和值。
spa
if判断中isUndef
函数用来判断一个值是不是 undefined
或 null
prototype
函数用来判断一个值是不是原始类型值isPrimitive
code
(ECMAScript 有 5 种原始类型(primitive type),即 Undefined、Null、Boolean、Number 和 String)
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}复制代码
这个判断主要是对target与key作了校验判断是不是个数组和key是否为有效的数组索引
target.length = Math.max(target.length, key)
target.splice(key, 1, val)复制代码
这就涉及到上篇博客讲的(数组变异处理)
target.length = Math.max(target.length, key)复制代码
将数组的长度修改成 target.length
和 key
中的较大者,不然若是当要设置的元素的索引大于数组长度时 splice
无效。
target.splice(key, 1, val)复制代码
数组的 splice
变异方法可以完成数组元素的删除、添加、替换等操做。而 target.splice(key, 1, val)
就利用了替换元素的能力,将指定位置元素的值替换为新值,同时因为 splice
方法自己是可以触发响应的
而后接下来一个if:
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}复制代码
若是 target
不是一个数组,那么必然就是纯对象了,当给一个纯对象设置属性的时候,假设该属性已经在对象上有定义了,那么只须要直接设置该属性的值便可,这将自动触发响应,由于已存在的属性是响应式的
key in target复制代码
判断key
在 target
对象上,或在 target
的原型链上
!(key in Object.prototype)复制代码
同时必须不能在 Object.prototype
const ob = (target: any).__ob__
复制代码
定义了 ob
常量,它是数据对象 __ob__
属性的引用
defineReactive(ob.value, key, val) ob.dep.notify()复制代码
defineReactive
函数设置属性值,这是为了保证新添加的属性是响应式的。
__ob__.dep.notify()
从而触发响应。这就是添加全新属性触发响应的原理
if (!ob) { target[key] = val return val }复制代码
target
也许本来就是非响应的,这个时候 target.__ob__
是不存在的,因此当发现 target.__ob__
不存在时,就简单的赋值便可
if (target._isVue || (ob && ob.vmCount)) {
复制代码
Vue
实例对象拥有 _isVue
属性,因此当第一个条件成立时,那么说明你正在使用 Vue.set/$set
函数为 Vue
实例对象添加属性,为了不属性覆盖的状况出现,Vue.set/$set
函数不容许这么作,在非生产环境下会打印警告信息
(ob && ob.vmCount)复制代码
这个就涉及比较深:主要是观测一个数据对象是否为根数据对象,因此所谓的根数据对象就是 data
对象
当使用 Vue.set/$set
函数为根数据对象添加属性时,是不被容许的。
由于这样作是永远触发不了依赖的。缘由就是根数据对象的 Observer
实例收集不到依赖(观察者)
set讲完了 讲讲delete
仍是同样先看源码:
export function del (target: Array<any> | 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)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1) return
}
const ob = (target: any).__ob__
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 target[key]
if (!ob) {
return
}
ob.dep.notify()
}
复制代码
del
函数接收两个参数,分别是将要被删除属性的目标对象 target
以及要删除属性的键名 key
第一个if判断和set同样 就不讲了
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}复制代码
第二个判断其实和set也差很少。。。删除数组索引(一样是变异数组方法,触发响应)
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
}复制代码
其实也不用说了 判断都同样(不能删除Vue
实例对象或根数据的属性)
if (!hasOwn(target, key)) { return }复制代码
检测key
是不是 target
对象自身拥有的属性
if (!ob) { return }复制代码
判断ob对象是否存在若是不存在说明 target
对象本来就不是响应的,因此直接返回(return
)便可
若是 ob
对象存在,说明 target
对象是响应的,须要触发响应才行,即执行 ob.dep.notify()
。
进行观测和依赖收集。