Object.defineProperty 定义
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
Object.defineProperty 拦截
在 es5 通过 Object.defineProperty()
可以拦截,vue2 就是通过这个方法来拦截属性的 get,set 方法
const obj = { a: 1 };let value = null;Object.defineProperty(obj, 'a', { get: function () { const val = value || 100; console.log(`get object['a'] value ${val}`); return val; }, set(val) { console.log(`set object['a'] value ${val}`); value = val; },});obj.a; // get object['a'] value 100obj.a = 10; // set object['a'] value 10
上面一个方法一个缺点就是没法监听到数组的变化,在 vue 内部做了一层数组方法拦截,源码是这样实现的,通过Object.defineProperty(arr, 'push', { ... })
改写对应的数组方法
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function(method) { // 获取原始的数组操作方法 var original = arrayProto[method]; // 在 arrayMethods 上新建属性 method,并为 method 指定值(函数) // 即改写 arrayMethods 上的同名数组方法 def(arrayMethods, method, function mutator() { var arguments$1 = arguments; var i = arguments.length; var args = new Array(i); while(i--) { args[i] = arguments$1[i]; } var result = original.apply(this, args); // 因 arrayMethods 是为了作为 Observer 中的 value 的原型或者直接作为属性,所以此处的 this 一般就是指向 Observer 中的 value // 当然,还需要修改 Observer,使得其中的 value 有一个指向 Observer 自身的属性,__ob__,以此将两者关联起来 var ob = this.__ob__; // 存放新增的数组元素 var inserted; // 对几个可能有新增元素的方法单独考虑 switch(method) { case 'push': inserted = args; break; case 'unshift': inserted = args; break; case 'splice': // splice 方法第三个参数开始才是新增的元素 inserted =args.slice(2); break; } if(inserted) { // 对新增元素进行 getter、setter 绑定 ob.observerArray(inserted); } // 触发方法 ob.dep.notify(); return result; })}
如果是新增对象属性或者修改数组某个值(arr[0]),vue 还是无法拦截到,我们一般通过 Vue.set
或者 this.$set
来实现,set 的方法实现如下
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;}
Proxy 的定义
Proxy 可以在目标对象之前设置拦截,外界访问该对象必须通过这层拦截,根据阮老师的文档可以找出,我们可以拦截对象的操作有 13 中,分别是
get(target, propKey, receiver)
:拦截对象属性的读取,比如proxy.foo
和proxy['foo']
。set(target, propKey, value, receiver)
:拦截对象属性的设置,比如proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。has(target, propKey)
:拦截propKey in proxy
的操作,返回一个布尔值。deleteProperty(target, propKey)
:拦截delete proxy[propKey]
的操作,返回一个布尔值。ownKeys(target)
:拦截Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。getOwnPropertyDescriptor(target, propKey)
:拦截Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。defineProperty(target, propKey, propDesc)
:拦截Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。preventExtensions(target)
:拦截Object.preventExtensions(proxy)
,返回一个布尔值。getPrototypeOf(target)
:拦截Object.getPrototypeOf(proxy)
,返回一个对象。isExtensible(target)
:拦截Object.isExtensible(proxy)
,返回一个布尔值。setPrototypeOf(target, proto)
:拦截Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。apply(target, object, args)
:拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。construct(target, args)
:拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)
。
Proxy 拦截
通过 Proxy 对象也可以监听到属性的变化,一个监听对象 get,set 的实现
const obj = {};const objProxy = new Proxy(obj, { get: function (target, prop) { console.log(`get object['${prop}'] value ${target[prop]}`); return Reflect.get(target, prop); }, set(target, prop, value) { console.log(`set object['${prop}'] value ${value}`); target[prop] = value; },});objProxy.a;objProxy.a = 10;
当然也很简单监听数组的变化或者对应下标值的变化
const arr = [1, 2, 3];const arrProxy = new Proxy(arr, { get(target, prop) { return Reflect.get(target, prop); }, set(target, prop, value) { console.log(`set prop = ${prop} value = ${value}`); Reflect.set(target, prop, value); return true; },});arrProxy[0] = 4;