先来说一下对象代理的应用场景:对对象的属性进行添加或者修改,我们常常是通过obj.key或者obj[key]来对对象进行操作;但是当我们有某种需求如某某属性不能被修改又或者对象的属性不能被枚举等等时,我们又该怎么处理对象才能得到想要的需求呢。这时候就要用到对象代理了。
所谓的对象代理其实就是:在我们访问对象前添加了一层拦截,可以过滤掉一些操作,而这些过滤是由开发者来定义,例如对数据的处理,对构造函数的处理,对数据的验证等等。
在ES5中,ECMAscript提供了一个对象代理的方法:Object.defineProperty()。其语法格式为:Object.defineProperty(obj,value,descriptor)
Object.defineProperty(obj,value,descriptor)的作用是设置新的属性或者修改原有的属性。
obj是要修改的目标对象;
value是要设置的属性(没有就是添加,原有就是修改);
对象里目前存在的属性描述参数(descriptor)有两种主要形式:
数据描述符(data decriptor):是以对象包含属性的值,并说明这个值可读或不可读的对象
访问器描述符(accessor descriptor):是由一对getter-setter方法的对象。
一个完整的属性描述必须是两种形式之一;不能同时是两者
数据描述符和访问器描述符必须含有有以下键值对:
configurable:仅当该属性的configurable为true时,该属性才能够被修改或可以通过delete来删除该属性,默认为false
enumerable:仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中,默认false(属性即能否被遍历得到)
数据描述符可以包含以下可选键值对:
value:该属性对应的值,可以是任何有效的javascript值(数值、对象、函数等),默认为undefined
writable:仅当该属性的writable为true时,该属性才能被赋值运算改变,默认为false
访问器描述符可以包含以下可选键值对:
get:一个给属性提供getter的方法,如果没有getter则为undefined,该方法返回值被用作属性值,默认为undefined
set:一个给属性提供setter的方法,如果没有setter则为undefined,该方法将接受唯一参数 ,并将该参数的新值分配给该属性,默认为undefined
具体实例如下:
// ES5
var nan='xialuo',nv='dongmei';
const person = {};
Object.defineProperty(person,'name',{
get(){return value},
set(newval){value=newval},
enumerable:true;//可被枚举
configurable:true;//属性可被修改
})
person.name=nan;//赋值:person.name=xialuo
nan=JSON.parse(JSON.stringify(person));//先转为json字符串然后转换为json对象,保存nan.name数据
person.name=nv;//同上,赋值
nv=person;//nv.name=dongmei
console.log(nan.name,nv.name) ====xialuo,dongmei
在ES6中,则是提供一种新的方法来进行对象代理:Proxy对象
其语法格式为: let p = new Proxy(target, handler)
arget :需要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理,而ES5的Object.defineProperty则只能是对象)。
handler: 一个对象,其属性是当执行一个操作时定义代理的行为的函数(可以理解为某种触发器,里面有两个函数,get和set)
如果handler
没有设置任何拦截,那就等同于直接通向原对象。
下面是具体实例代码:
let xiaohong = {
name: "小红",
age: 15
};
xiaohong = new Proxy(xiaohong, {
get(target, key) {
let result = target[key];
//如果是获取 年龄 属性,则添加 岁字
if (key === "age") result += "岁";
return result;
},
set(target, key, value) {
if (key === "age" && typeof value !== "number") {
throw Error("age字段必须为Number类型");
}
target[key]=value;
//return Reflect.set(target, key, value);
return true;
}
});
console.log(`我叫${xiaohong.name} 我今年${xiaohong.age}了`);
xiaohong.age = "aa";
//tips:Reflect配合Proxy使用会更加方便
//(注意:严格模式下,set代理如果没有返回true,就会报错。这是因为set函数必须返回一个boolean值,只有返回值为true时才表示修改成功,我们没有手动return,函数会自动返回undefined,undefined != true,所以会报错)
当理解了对象代理就能应对这道面试高频问题:vue的响应式原理!!!
回答:通过数据劫持侦测数据变化,发布订阅和观察者模式进行依赖收集与视图更新。
vue2通过 ES5 提供的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 getter函数,当修改 data 中的数据时,自动调用 setter函数,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。
vue3是通过ES6提供的Proxy代理,Proxy
是 JavaScript 2015 的一个新特性。Proxy
的代理是针对整个对象的,而不是对象的某个属性,因此不同于 Object.defineProperty
的必须遍历对象每个属性,Proxy
只需要做一层代理就可以监听同级结构下的所有属性变化,当然对于深层结构,递归还是需要进行的。此外Proxy
支持代理数组的变化。