一、前言
Vue 的一个特点就是数据的双向绑定。
在 Vue2.x 中使用的是 defineProperty 对数据进行的拦截监听,由于这种方式对 Object 和 Array 的拦截有缺陷。
所以在 Vue2.x 中有全局的 Vue.set API 来对这个缺陷进行修正(对于 Array 来说也是部分)。
二、什么时候使用
1、Object 使用
Object 在 data 初始化的时候,就已经有的属性,在后面修改是可以直接修改,并被监听到的。
对于后面新添加的属性,这时需要用 Vue.set 来新增添加,set 会对这个新增的属性进行拦截。
2、Array 使用
由于 Array 的操作都是监听不到的,所以 Vue 里面是做了两个操作的。
- 对几个常用的操作单独处理
- 下标操作转换成 splice (回到上面)
对 Array 几个操作的特殊处理是下面这样:
// 路径:src\core\observer\array.js const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) // 特殊处理的方法列表 const methodsToPatch = [ ‘push‘, ‘pop‘, ‘shift‘, ‘unshift‘, ‘splice‘, ‘sort‘, ‘reverse‘ ] /** * 拦截方法并抛出事件 */ methodsToPatch.forEach(function (method) { // 缓存原始方法 const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case ‘push‘: case ‘unshift‘: inserted = args break case ‘splice‘: inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // 通知更改 ob.dep.notify() return result }) })
三、源码分析
在 Vue 源码中,是把 set 放在 observer 中定义的。
set 是对这些进行监听处理。
// 路径:src\core\observer\index.js /** * 给 Object set 属性,当不存在时新增并做拦截代理,已经存在的不做处理 * Array 的下标处理转换成 splice */ 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)}`) } // 是数组,并且是已有的下标处理,转换成 splice 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 }
这里定义后,在 global-api 中赋值给全局 Vue
// 路径:src\core\global-api\index.js // 下面是截取部分代码 import { set} from ‘../observer/index‘ export function initGlobalAPI (Vue: GlobalAPI) { Vue.set = set }