属性描述符
Property Descriptor 属性描述符,用于描述一个属性的相关信息。
通过Object.getOwnPropertyDescriptor()
可以得到一个对象的某个属性的属性描述符。
const obj = {
a: 1,
b: 2
}
const desc = Object.getOwnPropertyDescriptor(obj, "a");
console.log(desc); // {value: 1, writable: true, enumerable: true, configurable: true}
- value: 属性值。
const obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, "a", {
value: 4,
});
obj.a = 10;
const desc = Object.getOwnPropertyDescriptor(obj, "a");
console.log(desc); // {value: 10, writable: true, enumerable: true, configurable: true}
- configurable: 该属性的描述符是否可以修改。
const obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, "a", {
value: 4,
configurable: false,
});
obj.a = 10;
// Cannot redefine property: a at Function.defineProperty
Object.defineProperty(obj, "a", {
value: 4,
configurable: true,
});
const desc = Object.getOwnPropertyDescriptor(obj, "a");
console.log(desc);
- enumerable: 该属性是否可以被枚举。
const obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, "a", {
value: 4,
configurable: false,
enumerable: false
});
// enumerable: true
// for(const prop in obj) {
// console.log(prop); // a b
// }
// enumerable: false
for(const prop in obj) {
console.log(prop); // b
}
const desc = Object.getOwnPropertyDescriptor(obj, "a");
console.log(desc); // {value: 4, writable: true, enumerable: true, configurable: true}
- writable: 该属性是否可以被重新赋值。
const obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, "a", {
value: 4,
configurable: false,
enumerable: false,
writable: false
});
obj.a = 10;
const desc = Object.getOwnPropertyDescriptor(obj, "a");
console.log(desc); // {value: 4, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptors(对象)
可以得到某个对象的所有属性描述符。
const obj = {
a: 1,
b: 2
}
const desc = Object.getOwnPropertyDescriptors(obj);
console.log(desc); // {a: {…}, b: {…}}
如果需要为某个对象添加属性时 或 修改属性时,配置其属性描述符,可以使用下面的代码:
Object.defineProperty(对象,属性名,描述符);
Object.defineProperties(对象,多个属性的描述符);
const obj = {
a: 1,
b: 2,
c: 3
}
Object.defineProperty(obj, "a", {
value: "a",
configurable: false,
enumerable: false,
writable: false
})
Object.defineProperties(obj, {
b: {
value: 33,
configurable: false,
enumerable: false,
writable: false
},
c: {
value: 55,
configurable: false,
enumerable: false,
writable: false
},
})
const desc = Object.getOwnPropertyDescriptors(obj);
console.log(desc); // {a: {…}, b: {…}, c: {…}}
存取器属性
属性描述符中,如果配置了 get 和 set 中的任何一个,则该属性,不再是一个普通属性,而变成了存取器属性。
get 和 set 配置均为函数,如果一个属性是存取器属性,则读取该属性时,会运行 get 方法,将 get 方法得到的返回值作为属性值;如果给该属性赋值,则会运行 set 方法。
const obj = {
a: 1
}
Object.defineProperty(obj, "a", {
get() {
console.log("运行了属性a的get函数");
},
set() {
console.log("运行了属性a的set函数");
}
})
// obj.a = 20; // set(20)
// console.log(obj.a); // console.log(get())
obj.a = obj.a + 1; // set(obj.a + 1) set(get() + 1)
console.log(obj.a);
const obj = {
a: 1
}
Object.defineProperty(obj, "a", {
get() {
console.log("运行了属性a的get函数");
return obj._a;
},
set(val) {
console.log("运行了属性a的set函数");
obj._a = val;
}
})
obj.a = 20; // set(20)
console.log(obj.a); // console.log(get())
存取器属性最大的意义,在于可以控制属性的读取和赋值。
const obj = {
name: "qwee"
}
Object.defineProperty(obj, "age", {
get() {
console.log("运行了属性a的get函数");
return obj._age;
},
set(val) {
console.log("运行了属性a的set函数");
if(typeof val !== "number") {
throw new TypeError("年龄必须是数字");
}
if(val < 0) {
val = 0;
}else if(val > 200) {
val = 200;
}
obj._age = val;
}
})
// obj.age = "aa"; // 报错
obj.age = -100;
console.log(obj.age); // console.log(get())
姓名:
年龄:
Reflect 反射
1. Reflect 是什么?
Reflect 是一个内置的 JS 对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些 JS 底层功能。
由于它类似于其他语言的反射,因此取名为 Reflect。
- 它可以做什么?
使用 Reflect 可以实现诸如属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在于对象中等等功能。
- 这些功能不是已经存在了吗?为什么还需要用 Reflect 实现一次?
有一个重要的理念,在 ES5 就被提出:减少魔法,让代码更加纯粹。
这种理念很大程度上是受到 函数式编程的影响。
ES6 进一步贯彻了这种理念,它认为,对属性内存的控制、原型链的修改、函数的调用等等,这些都属于底层实现,属于一种魔法,需要将它们提取出来,形成一个正常的 API,并高度聚合到某个对象中,于是,就早就了 Reflect 对象。
因此,你可以看到 Reflect 对象中有很多的 API 都可以使用过去的某种语法或其他 API 实现。
- 它里面到底提供了哪些 API 呢?
- Reflect.set(target, propertyKey, value): 设置对象 target 的属性 propertyKey 的值为 value,等同于给对象的属性赋值。
const obj = {
a: 1,
b: 2
}
// obj.a = 20; // 魔法
Reflect.set(obj, "a", 33);
console.log(obj.a); // 33
- Reflect.get(target, propertyKey): 读取对象 target 的属性 propertyKey,等同于读取对象的属性值。
const obj = {
a: 1,
b: 2
}
// console.log(obj.a); // 魔法
console.log(Reflect.get(obj, "a")); // 1
- Reflect.apply(target, thisArgument, argumentsList): 调用一个指定的函数,并绑定 this 和参数列表。等同于函数调用。
thisArgument: 绑定的 this。
function test(a, b) {
console.log(a, b); // 3 4
}
// test(10, 20); // 调用
Reflect.apply(test, null, [3, 4]);
- Reflect.deleteProperty(target, propertyKey): 删除一个对象属性。
const obj = {
a: 1,
b: 2
}
Reflect.deleteProperty(obj, "b");
console.log(obj); // {a: 1}
- Reflect.defineProperty(target, propertyKey, attributes): 类似于 Object.defineProperty,不同的是如果配置出现问题,返回 false 而不是报错。
const obj = {
a: 1,
b: 2
}
Reflect.defineProperty(obj, "a", {
enumerable: false
})
console.log(Object.getOwnPropertyDescriptors(obj));
- Reflect.construct(target, argumentsList): 用构造函数的方式创建一个对象。
function test(a, b) {
this.a = a;
this.b = b;
}
const t = Reflect.construct(test, [1, 2]);
console.log(t); // test {a: 1, b: 2}
- Reflect.has(target, propertyKey): 判断一个对象是否拥有一个属性。
const obj = {
a: 1,
b: 2
}
// console.log("a" in obj); // true
console.log(Reflect.has(obj, "a")); // true
- 其他 API: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
Proxy 代理
代理:提供了修改底层实现的方式。
// 代理一个目标
// target:目标对象
// handler:是一个普通对象,其中可以重写底层实现
// 返回一个代理对象
new Proxy(target, handler);
const obj = {
a: 1,
b: 2
}
const proxy = new Proxy(obj, {
set(target, propertyKey, value) {
// console.log(target, propertyKey, value); // {a: 1, b: 2} "a" 3
// target[propertyKey] = value;
Reflect.set(target, propertyKey, value);
},
get(target, propertyKey) {
if(Reflect.has(target, propertyKey)) {
return Reflect.get(target, propertyKey);
}else{
return -1;
}
},
has(target, propertyKey) {
// 重写has方法
}
})
proxy.a = 3;
console.log(obj,proxy); // {a: 3, b: 2} Proxy {a: 3, b: 2}
console.log(proxy.d); // -1
应用-观察者模式
有一个对象,是观察者,它用于观察另外一个对象的属性值变化,当属性值变化后会收到一个通知,可能会做一些事。
以前的实现方式:缺点有两个对象
代理实现:
应用-偷懒的构造函数
构造函数:
class User {
constructor(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
console.log(this.firstName, this.lastName, this.age); // 12 13 14
}
}
const a = new User(12, 13, 14);
偷懒的构造函数:
class User {
}
function ConstructorProxy(Class, ...propNames) {
return new Proxy(Class, {
construct(target, argumentsList) {
console.log("构造函数被调用了");
const obj = Reflect.construct(target, argumentsList);
propNames.forEach((name, i) => {
obj[name] = argumentsList[i];
})
return obj;
}
})
}
const UserProxy = ConstructorProxy(User, "firstName", "lastName", "age");
const a = new UserProxy(12, 13, 14);
console.log(a); // User {firstName: 12, lastName: 13, age: 14}
class Monster {
}
const MonsterProxy = ConstructorProxy(Monster, "sttack", "defence", "hp", "rate", "name");
const b = new MonsterProxy(12, 13, 14, 100, "aaa");
console.log(b); // Monster {sttack: 12, defence: 13, hp: 14, rate: 100, name: "aaa"}
应用-可验证的函数参数
function sum(a, b) {
return a + b;
}
function validatorFunction(func, ...types) {
const proxy = new Proxy(func, {
apply(target, thisArgument, argumentsList) {
types.forEach((t, i) => {
const arg = argumentsList[i];
if(typeof arg !== t) {
throw new TypeError(`第${i + 1}参数${argumentsList[i]}不满足类型${t}`);
}
})
Reflect.apply(target, thisArgument, argumentsList);
}
})
return proxy;
}
const sumProxy = validatorFunction(sum, "number", "number");
sumProxy(1, 2);
以前的做法:
function sum(a, b) {
return a + b;
}
function validatorFunction(func, ...types) {
return function(...argumentsList) {
types.forEach((t, i) => {
const arg = argumentsList[i];
if(typeof arg !== t) {
throw new TypeError(`第${i + 1}参数${argumentsList[i]}不满足类型${t}`);
}
})
return func(...argumentsList);
}
}
const sumProxy = validatorFunction(sum, "number", "number");
sumProxy(1, "2");