Object.defineProperty
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。IE8不兼容。
Object.defineProperty(obj, prop, descriptor)
参数
obj: 要定义属性的对象。
prop: 要定义或修改的属性的名称或 Symbol 。
descriptor: 要定义或修改的属性描述符。
返回值
被传递给函数的对象。
Object.defineProperty(obj, 'name', {
// configurable: false,//默认为false
// enumerable: false,//默认为false
value: 'kongzhi',
// writable: false,/默认为false
// get(){},
// set(v){}
});
定义了 value
或 writable
, 一定不能有 get
或 set
, 反之亦然, 否则报错
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。
数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。
存取描述符是由 getter 函数和 setter 函数所描述的属性。
一个描述符只能是这两者其中之一;不能同时是两者。
这两种描述符都是对象。它们共享以下可选键值(默认值是指在使用 Object.defineProperty() 定义属性时的默认值):
默认值:
configurable
当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
默认为 false。
enumerable
当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。
默认为 false。
数据描述符还具有以下可选键值:
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
默认为 undefined。
writable
当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。
默认为 false。
存取描述符还具有以下可选键值:
get
属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
默认为 undefined。
set
属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
默认为 undefined。
描述符默认值汇总
拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false。
属性值和函数的键 value、get 和 set 字段的默认值为 undefined。
描述符可拥有的键值
- | configurable | enumerable | value | writable | get | set |
---|---|---|---|---|---|---|
数据描述符 | 可以 | 可以 | 可以 | 可以 | 不可以 | 不可以 |
存取描述符 | 可以 | 可以 | 不可以 | 不可以 | 可以 | 可以 |
let obj={a:2,b:{c:3}};
Object.defineProperty(obj,'a',{
configurable:true,
enumerable:true,
get(){
return obj['a']
},
set(value){
return obj['a']=value;
}
})
let obj={a:2,b:{c:3}};
Object.defineProperty(obj,'b',{
writable:false,
configurable:false
})
obj.b=3;
obj.b.c=4;
console.log(obj)//{a:2,b:{c:4}}
const obj = {};
Object.defineProperty(obj, 'name', {
// configurable: false,//默认为false
// enumerable: false,//默认为false
value: 'kongzhi',
// writable: false,/默认为false
// get(){},
// set(v){}
});
console.log(obj.name); // 输出 kongzhi
// 改写obj.name 的值
obj.name = 111;
console.log(obj.name); // 还是打印出 kongzhi,因为writable默认为false
var o = {};
o.a = 1;
// 等同于:
Object.defineProperty(o, "a", {
value: 1,
writable: true,
configurable: true,
enumerable: true
});
// 另一方面,
Object.defineProperty(o, "a", { value : 1 });
// 等同于:
Object.defineProperty(o, "a", {
value: 1,
writable: false,
configurable: false,
enumerable: false
});
全局变量Date不可修改,为了安全性考虑
Object.defineProperty(window,'Date',{
writable:false,
configurable:false
})
Object.defineProperties
Object.defineProperties本质上定义了obj 对象上props的可枚举属性相对应的所有属性。
var obj = {};
Object.defineProperties(obj, {
'property1': {
value: true,
writable: true
},
'property2': {
value: 'Hello',
writable: false
}
// etc. etc.
});
Proxy
Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。IE不兼容。
const p = new Proxy(target, handler)
参数
target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
返回值
一个Proxy代理的对象,操作这个对象会触发handler对应操作。改变原始对象不会触发。
handler 对象的方法
handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。
所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。
handler.getPrototypeOf()
handler.setPrototypeOf()
handler.isExtensible()
handler.preventExtensions()
handler.getOwnPropertyDescriptor()
handler.defineProperty()
handler.has()//in 操作符的捕捉器。
handler.get(target, property)
handler.set(target, property, value)
handler.deleteProperty()//delete 操作符的捕捉器。
handler.ownKeys()
handler.apply()
handler.construct()//new 操作符的捕捉器。
基本操作
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 37;
}
};
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
Proxy只代理对象外层属性
let obj={a:1,b:{c:2}};
let handler={
get:function(obj,prop){
const v = Reflect.get(obj,prop);
return v; // 返回obj[prop]
},
set(obj,prop,value){
return Reflect.set(obj,prop,value);//设置成功返回true
}
};
let p=new Proxy(obj,handler);
p.a//会触发get方法
p.b.c//会触发get方法获取p.b,不会触发.c的set,因为c没被代理。
递归代理对象内部对象
let obj={a:1,b:{c:2}};
let handler={
get:function(obj,prop){
const v = Reflect.get(obj,prop);
if(v !== null && typeof v === 'object'){
return new Proxy(v,handler);//代理内层
}else{
return v; // 返回obj[prop]
}
},
set(obj,prop,value){
return Reflect.set(obj,prop,value);//设置成功返回true
}
};
let p=new Proxy(obj,handler);
p.a//会触发get方法
p.b.c//会先触发get方法获取p.b,然后触发返回的新代理对象的.c的set。
可以看出Proxy代理对象时只会在调用时递归,不会一开始就全部递归,优化了性能。
Proxy对象和原始对象
let target = {};
let p = new Proxy(target, {});
p.a = 37; // 操作转发到目标
console.log(target.a); // 37\. 操作已经被正确地转发
target.a=4;
console.log(p.a)//4
console.log(target==p)//false
总结
- Proxy使用上比Object.defineProperty方便的多。
- Proxy代理整个对象,Object.defineProperty只代理对象上的某个属性。
- 如果对象内部要全部递归代理,则Proxy可以只在调用时递归,而Object.defineProperty需要在一开始就全部递归,Proxy性能优于Object.defineProperty。
- 对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到。
- 数组新增删除修改时,Proxy可以监听到,Object.defineProperty监听不到。
- Proxy不兼容IE,Object.defineProperty不兼容IE8及以下。
三、总结
Object.defineProperty只能遍历对象属性进行劫持
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的
function reactive(obj) {
if (typeof obj !== 'object' && obj != null) {
return obj
}
// Proxy相当于在对象外层加拦截
const observed = new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
console.log(`获取${key}:${res}`)
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
console.log(`设置${key}:${value}`)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log(`删除${key}:${res}`)
return res
}
})
return observed
}
Proxy可以直接监听数组的变化(push、shift、splice)
const obj = [1,2,3]
const proxtObj = reactive(obj)
obj.psuh(4) // ok
Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等,这是Object.defineProperty不具备的
正因为defineProperty自身的缺陷,导致Vue2在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外set、delete方法)
// 数组重写
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto)
['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => {
arrayProto[method] = function () {
originalProto[method].apply(this.arguments)
dep.notice()
}
});
// set、delete
Vue.set(obj,'bar','newbar')
Vue.delete(obj),'bar')
Proxy 不兼容IE,也没有 polyfill, defineProperty 能支持到IE9