Object.defineProperty
注意:
- 不可以在set,get内部使用obj,name,因为这样也是执行了get操作,会造成循环调用,陷入死循环
- 定义一个变量,将,set,get方法都代理到此变量,所以对象所指地址的值未发生变化,发生变化的是代理的value
ES5提供了Object.defineProperty方法,该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象
基础
-
语法 Oject.deineProperty(obj, prop, descriptor)
-
参数
obj:要在其上定义属性的对象 prop:要定义或修改的属性的名称 descroptor: 将被定义或修改的属性的描述符
var obj = {};
Object.defineProperty(obj, "num", {
value : 1,
writable : true,
enumerable : true,
configurable : true
});
// 对象 obj 拥有属性 num,值为 1
- 第三个参数:数据描述符 存取描述符 ame | 数据描述符 | 存取描述符 |意义 -|-|-|- configurable | enumerable | enumerable |属性是否可被便利| value | writable | |是否可写| get | | get | set | | set |
属性描述符必须是数据描述符或者存取描述符两种形式之一,不能同时是两者
可以
Object.defineProperty({}, "num", {
value: 1,
writable: true,
enumerable: true,
configurable: true
});
//或者
var value = 1;
Object.defineProperty({}, "num", {
get : function(){
return value;
},
set : function(newValue){
value = newValue;
},
enumerable : true,
configurable : true
});
//但是不可以
// 报错
Object.defineProperty({}, "num", {
value: 1,
get: function() {
return 1;
}
})
此外,所有的属性描述符都是非必需的,但是descriptor这个字段是必须的,如果不进行配置,可以
var obj = Object.defineProperty({}, "num", {});
console.log(obj.num); // undefined
setters和getters
存储描述符中的get和set,这两种方法又被称作setter和getter。
由getter和setter定义的属性称作‘存取器属性’
当查询存储器属性时,调用getter方法,这个方法的返回就是属性存取表达式的值
当设置存储器属性时,调用setter方法,将赋值表达式右边的值当作参数传入setter。可以忽略setter方法的返回值
例子:数据绑定例子
function myProtype() {
var value = 1;
Object.defineProperty(this, 'num', {
get: function () {
console.log('取')
console.log(value);
},
set: function (newValue) {
console.log('存');
value = newValue;
}
})
}
let obj = new myProtype();
obj.num;
obj.num = 2;
obj.num;
多个属性绑定的例子
function myProtype(obj) {
for (let key in obj) {
let value;
Object.defineProperty(this, key, {
get: function () {
console.log('取get', value);
},
set: function (newValue) {
value = newValue;
console.log('存set', value);
},
});
this[key] = obj[key];
}
}
let obj = { age: 12, name: 'li' };
let obj2 = new myProtype(obj);
obj2.age;
obj2.age = 2;
obj2.age;
obj2.name;
obj2.name = 'liu';
obj2.name;
watch API
tips:自执行函数的好处
(function () {
var num = 1;
function a() {
console.log(num);
}
function b() {
console.log(num);
}
a();
b();
})();
没有定义任何一个全局变量,也不用每次使用都调用外面函数获取里面的内容 同时若不考虑以上因素,可以简写为
function watch(object, name, fn){
let value = object[name];
Object.defineProperty(obj, name, {
get: function(){
return value;
},
set: function(newValue){
console.log(newValue);
value = newValue;
fn(value);
}
});
}
object.defineProperty和object.creat的区别
Object.create()是继承于某个对象创建的新对象,Object.defineProperties()是对对象属性的扩展。
proxy
defineProperty只能重新定义函数的读取,但是到了ES6,提供了Proxy,可以重定义更多的行为
基础
表示代理某些操作,ES6提供Proxy构造函数,用来生成Proxy实例
- 语法
var proxy = new Proxy(target, handler);
new Proxy表示生成一个Proxy实例 target是要拦截的目标对象 handler参数也是一个对象,用来定制拦截行为
var proxy = new Proxy({},{
get: function (obj, prop) {
console.log('取操作');
return obj[prop];
},
set: function (obj, prop, value) {
console.log('赋值操作')
obj[prop] = value;
}
})
proxy.time = 35; // 设置 set 操作
console.log(proxy.time); // 设置 get 操作 // 35
- 除了get和set外,proxy可以拦截13种操作,比如has,可以拦截propKey in proxy的操作,返回一个布尔值
// 使用 has 方法隐藏某些属性,不被 in 运算符发现
var handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
console.log('_prop' in proxy); // false
- apply方法拦截函数的调用、call和apply操作:拦截Proxy实例作为函数的调用,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)
apply方法可以接受三个参数,分别是目标对象,目标对象的上下文对象(this),目标对象的参数数组
var twice = {
//目标对象,目标对象的上下文对象,目标对象的参数数组
apply:function (target,ctx,args) {
//return console.log("target",target,"ctx",ctx,"args",args);
return Reflect.apply(...arguments)*2;
}
};
function sum (left,right) {
return left*right;
};
var proxy = new Proxy(sum,twice);
console.log("proxy1",proxy(1,2));
console.log("proxy.call",proxy.call(null,5,6));
console.log("proxy,apply",proxy.apply(null,[7,8]));
console.log("proxy,apply",Reflect.apply(proxy,null,[7,8]));
// proxy1 4
// proxy.call 60
// proxy,apply 112
// proxy,apply 112
- ownKeys方法拦截对象自身属性的读取操作 拦截以下操作: Object.getOwnPropertyNames() Object.getOwnProppertySymbols() Object.Keys() 拦截第一个字符为下划线的属性名,不让它被for of遍历到
let target = {
_bar: 'foo',
_prop: 'bar',
prop: 'baz'
};
let handler = {
ownKeys (target) {
return Reflect.ownKeys(target).filter(key => key[0] !== '_');
}
};
let proxy = new Proxy(target, handler);
for (let key of Object.keys(proxy)) {
console.log(target[key]);
}
// "baz"
总结
definePorperty
- 并没有修改原对象所指地址的值,修改的是被代理的值(value),这样读取都是这个值,不再读取原对象的值
- 不可以在内部使用obj.prop来直接修改值,这样会造成自身调用自身,造成堆栈溢出,修改的都是被代理的值(value)
Proxy
- 操作的是new Proxy返回的对象,而不是原对象,如使用Proxy方法实现的watch方法(上面),如果后来点击事件,是obj.value += 2;则不起效,操作的是watch返回的Proxy对象,即Obj.value += 2;