这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战
最近在使用Proxy的时候,发现了几个奇怪的现象,打算深入去了解下原因。不了解Proxy的小伙伴可以先来这里 JS Proxy对象的介绍与实践 看看。
set()和defineProperty()的触发
先上代码,Case 1:
var foo = {
b: 'lol'
}
var handler = {
defineProperty(obj, prop, descriptor) {
console.log(`defineProperty method starts foo.${prop} = ${descriptor.value}`)
obj[prop] = descriptor.value
return true
},
set(obj, prop, val) {
console.log(`set method starts foo.${prop} = ${val}`)
obj[prop] = val
}
}
var newFoo = new Proxy(foo, handler);
newFoo.a = 1
// output: set method starts foo.a = 1
newFoo.b = 'Zzz'
// output: set method starts foo.b = Zzz
Object.defineProperty(newFoo, 'num', {
value: 666
})
// output: defineProperty method starts foo.num = 666
console.log(foo.a, foo.b, foo.num)
// output: 1 "Zzz" 666
这符合我们的期望,然后来看看另一个例子,Case 2:
var foo = {
b: 'lol'
}
var handler = {
defineProperty(obj, prop, descriptor) {
console.log(`defineProperty method starts foo.${prop} = ${descriptor.value}`)
obj[prop] = descriptor.value
return true
}
}
var newFoo = new Proxy(foo, handler);
newFoo.a = 1
// output: defineProperty method starts foo.a = 1
newFoo.b = 'Zzz'
// output: defineProperty method starts foo.b = Zzz
Object.defineProperty(newFoo, 'num', {
value: 666
})
// output: defineProperty method starts foo.num = 666
console.log(foo.a, foo.b, foo.num)
// output: 1 "Zzz" 666
神奇的事情发生了,我们Case 2只是把Case 1的set()劫持去掉,但结果发现常规的属性赋值语句,也触发defineProperty()的劫持,这跟我们通常的理解不太一致。而根据MDN上面,Proxy/defineProperty的描述,是劫持Object.defineProperty()的操作。
原因
百思不得其解之际,我在ECMA官网上查找到了对象的set和defineProperty的内部调用逻辑,大体如下:
对象set操作
对象defineProperty操作
简而言之,set操作,会先执行内部的[[Set]]方法从而率先触发Proxy的Set劫持(如有),进而不再进行后续的一系列操作;如果没有定义Proxy的Set劫持,则会一直执行到内部的[[DefineOwnProperty]]方法,继而出发Proxy的DefineProperty劫持(如有)。
has()的触发
惯例先上代码
var foo = [233, 555, 666]
var handler = {
has(obj, prop) {
console.log(`has foo[${prop}]`)
return prop in obj
}
}
var proxy = new Proxy(foo, handler);
'4' in proxy
// output: has foo[4]
console.log('==========')
proxy.indexOf(666)
// output: has foo[0]
// output: has foo[1]
// output: has foo[2]
console.log('==========')
proxy.forEach((v, i) => {
console.log(v, i)
})
// output: has foo[0]
// output: 233 0
// output: has foo[1]
// output: 555 1
// output: has foo[2]
// output: 666 2
console.log('==========')
proxy.concat([985, 211])
// output: has foo[0]
// output: has foo[1]
// output: has foo[2]
神奇的事情再次出现了,根据MDN上面,Proxy/has的描述,是劫持in操作符的执行,但是这里确是好几个数组方法都触发了。
原因
有了前面的经验,我们再次来到ECMA的官网,找到in操作符的官方文档(要手动滚动下去找到in),可以找到Proxy处理函数中的has()所劫持Object的HasProperty方法。
接着来到Array对象的原型链,查看原型链方法就会发现,以下的方法都会在遍历数组元素的时候调用到HasProperty():
- concat()
- copyWithin()
- every()
- filter()
- flat()
- forEach()
- indexOf()
- lastIndexOf()
- map()
- reduce()
- reduceRight()
- reverse()
- shift()
- slice()
- some()
- sort()
- splice()
- unshift()
我们可以发现这样的规律,涉及需要遍历数组元素或者改变数组元素排列顺序的方法,都会触发。
关于Proxy handlers
最后,关于Proxy Handlers,具体可以在这查看到每个处理函数对应所劫持的内部方法。