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 100
obj.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
本文为原创,未经授权,禁止任何媒体或个人自媒体转载
商业侵权必究,如需授权请联系340443366@qq.com
最新评论