defineProperty
作用
Object.defineProperty(obj,prop,descriptor)方法是在对象中定义或者修改一个属性,并且对该属性进行描述。它可以接收三个参数:
- obj:需要定义或修改属性的目标对象
- prop:需要定义或修改的属性名
- descriptor:需要定义和修改的属性描述符
属性描述符分类
属性描述符的类型分为两种:
- 数据属性(Data Properties)描述符(Descriptor)
- 存取属性(Accessor Properties)描述符(Descriptor)
数据属性描述符
- Configurable:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符。当我们直接在一个对象上定义某个属性时,这个属性的[[Configurable]]为true,当我们通过属性描述符定义一个属性时,这个属性的[[Configurable]]默认为false。
- Enumerable:表示是否可以通过for-in或者Object.keys()遍历时返回该属性。当我们直接在一个对象上定义某个属性时,这个属性的[[Enumerable]]为true,当我们通过属性描述符定义一个属性时,这个属性的[[Enumerable]]默认为false。
- Writable:表示是否可以修改属性的值。当我们直接在一个对象上定义某个属性时,这个属性的[[Writable]]为true,当我们通过属性描述符定义一个属性时,这个属性的[[Writable]]默认为false。
- value:读取属性时会返回该值,修改属性时,会对其进行修改,默认情况下这个值是undefined。
let obj = {
name: "aaa",
};
Object.defineProperty(obj, "num", {
value: 10,
writable: false,//不可修改
enumerable: false,//不可遍历
configurable: false,//不可删除
});
console.log(obj);//{ name: 'aaa' }
console.log(obj.num);//10
delete obj.num;
console.log(obj.num);//10
obj.num = 20;
console.log(obj.num);//10
存取属性描述符
在存取属性描述符中,也有Configurable和Enumerable属性,且与数据属性描述符一致,但存取属性描述符没有Writable和value属性,只有get和set属性:
- get:获取属性时会执行的函数。默认为undefined
- set:设置属性时会执行的函数。默认为undefined
let obj = {
name: "aaa",
};
let num = 10;
Object.defineProperty(obj, "num", {
enumerable: false,
configurable: false,
get: function () {
return num;
},
set: function (newValue) {
num = newValue;
},
});
console.log(obj);//{ name: 'aaa' }
console.log(obj.num);//10
delete obj.num;
console.log(obj.num);//10
obj.num = 20;
console.log(obj.num);//20
注意:,数据属性描述符和存取属性描述符不能一起使用,即存在get或者set的时候不能使用value或者writable,不然会报错。
同时定义多个属性Object.defineProperties()
通过上面我们发现,Object.defineProperty()只能为对象定义一个属性,但js还为我们提供了一个方法Object.defineProperties()来同时定义或修改多个属性:
let obj = {
_age: 10,
};
Object.defineProperties(obj, {
name: {
configurable: true,
enumerable: true,
writable: true,
value: "aaa",
},
age: {
configurable: true,
enumerable: true,
get: function () {
return this._age;
},
set: function (value) {
this._age = value;
},
},
});
obj.age = 20;
console.log(obj.age);//20
console.log(obj);//{ _age: 20, name: 'aaa', age: [Getter/Setter] }
Proxy
Proxy是一个类,用来创建一个代理对象。而代理对象顾名思义,就是对一个对象进行代理,以后我们对这个对象的操作就不用直接对该对象进行操作了(比如修改属性、新增属性、删除属性等),而是通过操作它的代理对象来间接的操作它本身属性。并且,代理对象还能监听原对象进行了哪些操作。
const objProxy = new Proxy(obj,trap)
如上所示,Proxy传入两个参数,第一个参数表示需要代理的对象,第二个参数表示需要添加的捕获器。而捕捉器的作用就是我们需要捕捉我们操作代理对象的某些操作。
Proxy的常用捕获器
const obj = {
name: "aaa",
};
const objProxy = new Proxy(obj, {
get: function (target, property, receiver) {
console.log("获取属性捕获器");
return target[property];
},
set: function (target, property, value, receiver) {
console.log("修改属性捕获器");
target[property] = value;
},
has: function (target, property) {
console.log("存在属性捕获器");
return property in target;
},
deleteProperty: function (target, property) {
console.log("删除属性捕获器");
delete target[property];
},
});
console.log(objProxy.name);
objProxy.name = "bbb";
console.log("name" in objProxy);
delete objProxy.name;
控制台打印:
Proxy中apply和construct捕捉器
apply和construct捕捉器较为特殊,因为它们是作用于函数的捕捉器。注意,apply捕捉器并不是指捕捉调用函数的apply方法,而是代理函数一被调用就会触发apply捕捉器。而construct捕捉器是对于构造函数被new关键字调用时触发的。
function Foo(num1) {
this.num = num1;
console.log("foo");
}
const FooProxy = new Proxy(Foo, {
//target:foo函数本身,thisArg:绑定的this对象,otherArgs,传入的函数参数
apply: function (target, thisArg, otherArgs) {
console.log("apply捕捉器");
},
//target:foo函数本身,argArray:传入的函数参数,newTarget:最初被调用的构造函数,就上面的例子而言是FooProxy
construct: function (target, argArray, newTarget) {
console.log("construct捕捉器");
return new target(...argArray);
},
});
FooProxy(10);
FooProxy.call(111, 10);
const obj = new FooProxy(100);
console.log(obj);
Proxy所有的捕获器
- handler.getPrototypeOf():Object.getPrototypeOf 方法的捕捉器。
- handler.setPrototypeOf():Object.setPrototypeOf 方法的捕捉器。
- handler.isExtensible():Object.isExtensible 方法的捕捉器。
- handler.preventExtensions():Object.preventExtensions 方法的捕捉器。
- handler.getOwnPropertyDescriptor():Object.getOwnPropertyDescriptor 方法的捕捉器。
- handler.defineProperty():Object.defineProperty 方法的捕捉器。
- handler.ownKeys():Object.getOwnPropertyNames方法和Object.getOwnPropertySymbols 方法的捕捉器。
- handler.has():in 操作符的捕捉器。
- handler.get():属性读取操作的捕捉器。
- handler.set():属性设置操作的捕捉器。
- handler.deleteProperty():delete 操作符的捕捉器。
- handler.apply():函数调用操作的捕捉器。
- handler.construct():new 操作符的捕捉器
Reflect
Reflect不像Proxy那样作为一个构造函数使用,它是一个对象,它本身的作用是提供操作JS对象来使用的,有点类似Object的方法。事实上,Reflect也包含了很多与Object一样的方法,像getPrototypeOf或者getOwnPropertyDescriptor等方法。
既然Object对象提供了这些方法,那么为什么还需要Reflect对象呢?这是因为在早期的ECMA规范中没有考虑到这种对对象本身的操作如何设计会更加规范,所以将这些API放到了Object上面,但是Object作为一个构造函数,这些操作实际上放到它身上并不合适,另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪。所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上。总而言之,出现Reflect的原因就是因为有了更好的规范,对比Proxy内提供的13种触发器,你也会发现与Reflect中提供的方法一一对应,这样也会让我们开发者更方便的记忆和使用。
Proxy与Reflect结合使用
在上面介绍Proxy的get和set触发器时,可以发现,我们实际上返回和修改值的时候也是直接作用于对象本身的,这样不是很妥当,我们可以使用Relfect中的get和set来操作原对象:
const obj = {
_name: "aaa",
get name() {
return this._name;
},
set name(newValue) {
this._name = newValue;
},
};
const objProxy = new Proxy(obj, {
get: function (target, key, receiver) {
console.log("get触发器", key, receiver);
return Reflect.get(target, key, receiver);
},
set: function (target, key, newValue, receiver) {
console.log("set触发器", key);
Reflect.set(target, key, newValue, receiver);
},
});
objProxy.name = "bbb";
上面我们还用到了receiver属性,在这里,receiver指代理对象本身。实际上,receiver的值是,如果target对象中指定了getter,receiver则为getter调用时的this值。因为这里是objProxy.name进行调用的,调用时触发get函数,这时因为隐式绑定,this指向objProxy,所以receiver就是objProxy。那么为什么这里我们这样使用呢Reflect.get(target, key, receiver)。这时为了将obj内的get方法的this指向我们的代理对象objProxy。我们上面虽然只访问了objProxy.name属性,但是看代码,我们实际上给的值是obj._name的值,obj._name又何尝不是获取属性应该触发get呢?但是this刚开始绑定的是obj,直接访问obj对象的属性是不会触发get触发器的,只有代理对象才能触发get触发器,所以我们需要将obj内的this指向为代理对象,使访问_name的时候是通过getter访问的代理对象的_name的值,那么就会触发get属性。
Reflect全部方法
-
Reflect.apply(target, thisArgument, argumentsList):对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和Function.prototype.apply()功能类似。 -
Reflect.construct(target, argumentsList[, newTarget]):对构造函数进行new操作,相当于执行new target(...args)。 -
Reflect.defineProperty(target, propertyKey, attributes):和Object.defineProperty()类似。如果设置成功就会返回true -
Reflect.deleteProperty(target, propertyKey):作为函数的delete操作符,相当于执行delete target[name]。 -
Reflect.get(target, propertyKey[, receiver]):获取对象身上某个属性的值,类似于target[name]。 -
Reflect.getOwnPropertyDescriptor(target, propertyKey): 类似于Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符,否则返回undefined。 -
Reflect.has(target, propertyKey):判断一个对象是否存在某个属性,和in运算符 的功能完全相同。 -
Reflect.ownKeys(target):返回一个包含所有自身属性(不包含继承属性)的数组。(类似于Object.keys(), 但不会受enumerable影响). -
Reflect.preventExtensions(target):类似于Object.preventExtensions()。返回一个Boolean。 -
Reflect.set(target, propertyKey, value[, receiver]):将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。 -
Reflect.setPrototypeOf(target, prototype):设置对象原型的函数。返回一个Boolean,如果更新成功,则返回true。