Vue3.0 中,响应式数据部分弃用了 Object.defineProperty
,使用Proxy
来代替它。
- Object.defineProperty 无法监听数组变化
Object.defineProperty 有一个缺陷是无法监听数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。
所以 Vue 才设置了 7 个变异数组(push、pop、shift、unshift、splice、sort、reverse)的 hack 方法来解决问题。
事实上,
Object.defineProperty
本身是可以监控到数组下标的变化的,只是在 Vue2.x 的实现中,从性能体验的性价比考虑,放弃了这个特性。
Object.defineProperty 在数组中的表现和在对象中的表现是一致的,数组的索引就可以看做是对象中的 key。
- 通过索引访问或设置对应元素的值时,可以触发 getter 和 setter 方法。
- 通过 push 或 unshift 会增加索引,对于新增加的属性,需要再手动初始化才能被 observe。
- 通过 pop 或 shift 删除元素,会删除并更新索引,也会触发 setter 和 getter 方法。
- Proxy
Proxy在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,可以对外界的访问进行过滤和改写。
Proxy
是 Object.defineProperty
的加强版。
- 对比
Object.defineProperty
只能劫持对象的属性,对新增属性需要手动进行 Observe,而Proxy
是直接代理对象。
由于 Object.defineProperty 只能对属性进行劫持,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历。而 Proxy 直接代理对象,并返回一个新对象,不需要遍历操作。
// Object.defineProperty,对象obj的text属性进行劫持
const obj = {};
Object.defineProperty(obj, 'text', {
get: function() {
console.log('get val');
},
set: function(newVal) {
console.log('set val:' + newVal);
document.getElementById('input').value = newVal;
document.getElementById('span').innerHTML = newVal;
}
});
const input = document.getElementById('input');
input.addEventListener('keyup', function(e){
obj.text = e.target.value;
})
// Proxy,劫持了整个obj对象
const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};
const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key === 'text') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
},
});
input.addEventListener('keyup', function(e) {
newObj.text = e.target.value;
});
Proxy
可以直接监听数组的变化
当我们对数组进行操作(push、shift、splice等)时,会触发对应的方法名称和length的变化。
const list = document.getElementById('list');
const btn = document.getElementById('btn');
// 渲染列表
const Render = {
// 初始化
init: function(arr) {
const fragment = document.createDocumentFragment();
for (let i = 0; i < arr.length; i++) {
const li = document.createElement('li');
li.textContent = arr[i];
fragment.appendChild(li);
}
list.appendChild(fragment);
},
change: function(val) {
const li = document.createElement('li');
li.textContent = val;
list.appendChild(li);
},
};
// 初始数组
const arr = [1, 2, 3, 4];
// 监听数组
const newArr = new Proxy(arr, {
get: function(target, key, receiver) {
console.log(key);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key !== 'length') {
Render.change(value);
}
return Reflect.set(target, key, value, receiver);
},
});
// 初始化
window.onload = function() {
Render.init(arr);
}
// push数字
btn.addEventListener('click', function() {
newArr.push(6);
});
- 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 作为新标准,从长远来看,JS 引擎会继续优化 ,但是兼容性差
参考: