文档:Object.defineProperty() - JavaScript | MDN
作用:对一个对象进行操作的方法。可以为一个对象增加一个属性,同时也可以对一个属性进行修改和删除。
它是在 ES5 中引入的,使用了 getter 和 setter 方法来实现 Vue2 的响应式。
Object.defineProperty()
的问题主要有三个:
无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应
只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历。
如果属性值是对象,还需要深度遍历。
Proxy
可以劫持整个对象,并返回一个新的对象
兼容性好,支持 IE9
而Proxy
的存在浏览器兼容性问题,而且无法用polyfill
磨平
Object.defineProperty(obj, prop, descriptor);
// obj 要定义属性的对象
// prop 要定义或修改的属性的名称
// descriptor 要定义或修改的属性描述符
Object.defineProperty(obj, "name", {
value: "小草莓", // 初始值
writable: true, // 该属性是否可写入
enumerable: true, // 该属性是否可被遍历得到(for...in, Object.keys等)
configurable: true, // 定该属性是否可被删除,且除writable外的其他描述符是否可被修改
get: function () {},
set: function (newVal) {},
});
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
ES6 入门教程
var obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
});
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get
)和设置(set
)行为。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例
var proxy = new Proxy(target, handler);
第一个参数:
target
参数表示所要拦截的目标对象
第二个参数:
handler
参数也是一个对象,用来定制拦截行为。 它是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。
Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。
针对整个对象,而不是对象的某个属性 ,所以也就不需要对
keys
进行遍历
Proxy
不需要对数组的方法进行重载,省去了众多 hack,减少代码量等于减少了维护成本,而且标准的就是最好的
Proxy
的第二个参数可以有 13
种拦截方法不限于
apply
、ownKeys
、deleteProperty
、has
等等,是Object.defineProperty
不具备的
Proxy
返回的是一个新对象我们可以只操作新的对象达到目的。而
Object.defineProperty
只能遍历对象属性直接修改
Proxy
作为新标准将受到浏览器厂商重点持续的性能优化也就是传说中的新标准的性能红利
import { isObject } from "./util"; // 工具方法
// 创建一个响应式对象
export function reactive(target) {
// 根据不同参数创建不同响应式对象
return createReactiveObject(target, mutableHandlers);
}
// 根据不同参数创建不同响应式对象
function createReactiveObject(target, baseHandler) {
if (!isObject(target)) {
return target;
}
const observed = new Proxy(target, baseHandler);
return observed;
}
const get = createGetter();
const set = createSetter();
function createGetter() {
return function get(target, key, receiver) {
// 对获取的值进行放射
const res = Reflect.get(target, key, receiver);
console.log("属性获取", key);
if (isObject(res)) {
// 如果获取的值是对象类型,则返回当前对象的代理对象
return reactive(res);
}
return res;
};
}
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key];
const hadKey = hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
console.log("属性新增", key, value);
} else if (hasChanged(value, oldValue)) {
console.log("属性值被修改", key, value);
}
return result;
};
}
export const mutableHandlers = {
get, // 当获取属性时调用此方法
set // 当修改属性时调用此方法
};
1、Proxy
只会代理对象的第一层,那么 Vue3 又是怎样处理这个问题的呢?
判断当前 Reflect.get 的返回值是否为 Object ,如果是则再通过 reactive 方法做代理, 这样就实现了深度观测。
2、监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?
我们可以判断 key 是否为当前被代理对象 target 自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行 trigger
主要是从性能方面考量
defineProperty:该 API 存在一些局限性,比如对于数组的拦截有问题,为此 Vue 需要专门为数组响应式做一套实现。另外不能拦截那些新增、删除属性。最后 defineProperty 方案在初始化时需要深度递归遍历待处理的对象才能对它进行完全拦截,明显增加了初始化的时间。
以上两点在 Proxy 出现之后迎刃而解。
Proxy:不仅可以对数组实现拦截,还能对 Map、Set 实现拦截。另外 Proxy 的拦截也是懒处理行为。如果用户没有访问嵌套对象,那么也不会实施拦截,这就让初始化的速度和内存占用都改善了。
Vue的代理也是最开始只代理最外层的对象,在访问的时候去判断是否为一个 object,然后再去用 proxy 包裹
当然 Proxy 是有兼容性问题的,IE 完全不支持,所以如果需要 IE 兼容就不合适
Vue2 和 Vue3 响应式上有什么区别? / 使用 Object.defineProperty() 来进行数据劫持有什么缺点?_vue 2响应式和vue 3响应式区别-CSDN博客
都可以用来实现 JavaScript 对象的响应式,但是它们有一些区别:
① 实现方式
Proxy 是 ES6 新增的一种特性,使用了一种代理机制来实现响应式。
Object.defineProperty 是在 ES5 中引入的,使用了 getter 和 setter 方法来实现。
② 作用对象
Proxy 可以代理整个对象,包括对象的所有属性、数组的所有元素以及类似数组对象的所有元素。
Object.defineProperty 只能代理对象上定义的属性。
③ 监听属性
Proxy 可以监听到新增属性和删除属性的操作
Object.defineProperty 只能监听到已经定义的属性的变化。
④ 性能
由于 Proxy 是 ES6 新增特性,其内部实现采用了更加高效的算法,相对于 Object.defineProperty来说在性能方面有一定的优势。
综上所述,虽然 Object.defineProperty 在 Vue.js 2.x 中用来实现响应式,但是在 Vue.js 3.0 中已经采用了 Proxy 来替代。
这是因为 Proxy 相对于 Object.defineProperty 拥有更优异的性能和更强大的能力。