Property Descriptor 属性描述符 是一个普通对象,用于描述一个属性的相关信息
通过Object.getOwnPropertyDescriptor(对象, 属性名)可以得到一个对象的某个属性的属性描述符
1const desc = Object.getOwnPropertyDescriptor(obj, "a");
2console.log(desc);
Object.getOwnPropertyDescriptors(对象)可以得到某个对象的所有属性描述符
如果需要为某个对象添加属性时 或 修改属性时, 配置其属性描述符,可以使用下面的代码: Object.defineProperty(对象, 属性名, 描述符);
1const obj = {
2 a: 1,
3 b: 2,
4};
5Object.defineProperty(obj, "a", {
6 value: 3,
7 configurable: false, //描述符不可以修改
8 enumerable: false, //不可枚举
9 writable: false, //不可写
10});
11console.log(obj);
12for (const prop in obj) {
13 console.log(prop); //只有b,没有a了
14}
15obj.a = 10; //不可写
16console.log(obj);
17const props = Object.keys(obj); //得到对象的所有属性
18console.log(props);
19const values = Object.values(obj); //得到对象的所有值
20console.log(values);Object.defineProperties(对象, 多个属性的描述符)
1Object.defineProperties(obj, {
2 a: {
3 value: 3,
4 configurable: false,
5 enumerable: false,
6 writable: false,
7 },
8});属性描述符中,如果配置了 get 和 set 中的任何一个,则该属性,不再是一个普通属性,而变成了存取器属性。 get 和 set 配置均为函数,如果一个属性是存取器属性,则读取该属性时,会运行 get 方法,将 get 方法得到的返回值作为属性值;如果给该属性赋值,则会运行 set 方法。 属性已经不再内存里面了,而是 get 和 set
1const obj = {
2 b: 2,
3};
4Object.defineProperty(obj, "a", {
5 get() {
6 console.log("运行了属性a的get函数");
7 },
8 set(val) {
9 console.log("运行了属性a的set函数", val);
10 },
11});
12// obj.a的时候运行get函数
13// obj.a赋值的时候运行set函数
14// 所以console.log(obj.a)//undefined,因为get函数没有返回结果
15obj.a = 1; //相当于set(20)
16console.log(obj.a); // 相当于console.log(get())
17// 运行set
18// 运行get
19// undefined1// 演示1
2obj.a = 20 + 10; // set(20 + 10) 相当于运行set()
3console.log(obj.a); // console.log(get()) 读取属性a,相当于运行get()
4// 演示2
5obj.a = obj.a + 1; // set(obj.a + 1) 相当于读取 set(get() + 1) undefined+1=NaN
6console.log(obj.a); //读取属性一定运行get undefined1const obj = {
2 b: 2,
3};
4Object.defineProperty(obj, "a", {
5 get() {
6 console.log("运行了属性a的get函数");
7 return obj._a;
8 // 错误的思路:return obj.a
9 // 这里就成了无限递归,无限读取,导致浏览器卡死,同理set
10 },
11 set(val) {
12 console.log("运行了属性a的set函数", val);
13 obj._a = val;
14 },
15});
16obj.a = 10;
17console.log(obj.a);存取器属性最大的意义,在于可以控制属性的读取和赋值
1obj = {
2 name: "adsf",
3};
4Object.defineProperty(obj, "age", {
5 get() {
6 return obj._age;
7 },
8 set(val) {
9 if (typeof val !== "number") {
10 throw new TypeError("年龄必须是一个数字");
11 }
12 if (val < 0) {
13 val = 0;
14 } else if (val > 200) {
15 val = 200;
16 }
17 obj._age = val;
18 },
19});
20obj.age = "Asdfasasdf";
21console.log(obj.age);应用:将对象的属性和元素关联
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <meta http-equiv="X-UA-Compatible" content="ie=edge" />
7 <title>Document</title>
8 </head>
9
10 <body>
11 <p>
12 <span>姓名:</span>
13 <span id="name"></span>
14 </p>
15 <p>
16 <span>年龄:</span>
17 <span id="age"></span>
18 </p>
19 <script>
20 const spanName = document.getElementById("name");
21 const spanAge = document.getElementById("age");
22 const user = {};
23 Object.defineProperties(user, {
24 name: {
25 get() {
26 return spanName.innerText;
27 //获取页面窗口中对应的元素内容
28 },
29 set(val) {
30 spanName.innerText = val;
31 },
32 },
33 age: {
34 get() {
35 return +spanAge.innerText;
36 },
37 set(val) {
38 if (typeof val !== "number") {
39 throw new TypeError("年龄必须是一个数字");
40 }
41 if (val < 0) {
42 val = 0;
43 } else if (val > 200) {
44 val = 200;
45 }
46 spanAge.innerText = val;
47 },
48 },
49 });
50 </script>
51 </body>
52</html>1var obj = {
2 b: 2,
3};
4
5// 得到属性描述符
6// var desc = Object.getOwnPropertyDescriptor(obj, 'a');
7// console.log(desc);
8
9// 设置属性描述符
10Object.defineProperty(obj, "a", {
11 value: 10,
12 writable: false, // 不可重写
13 enumerable: false, // 不可遍历
14 configurable: false, // 不可修改描述符本身
15});
16// Object.defineProperty(obj, 'a', {
17// writable: true,
18// });
19obj.a = "abc";
20console.log(obj.a);
21// for (var key in obj) {
22// console.log(key);
23// }
24
25// var keys = Object.keys(obj);
26// console.log(keys);
27
28// console.log(obj);1var obj = {};
2
3Object.defineProperty(obj, "a", {
4 get: function () {
5 return 123;
6 }, // 读取器 getter
7 set: function (val) {
8 throw new Error(
9 `兄弟,你正在给a这个属性重新赋值,你所赋的值是${val},但是,这个属性是不能复制,你再考虑考虑`
10 );
11 }, // 设置器 setter
12});
13
14console.log(obj.a);
15obj.a = "abx";
16// console.log(obj.a); // console.log(get())购物车描述
1var aGoods = {
2 pic: ".",
3 title: "..",
4 desc: `...`,
5 sellNumber: 1,
6 favorRate: 2,
7 price: 3,
8};
9
10class UIGoods {
11 get totalPrice() {
12 return this.choose * this.data.price;
13 }
14
15 get isChoose() {
16 return this.choose > 0;
17 }
18
19 constructor(g) {
20 g = { ...g }; // 克隆一份
21 Object.freeze(g); // 冻结克隆对象
22 Object.defineProperty(this, "data", {
23 get: function () {
24 return g;
25 },
26 set: function () {
27 throw new Error("data 属性是只读的,不能重新赋值");
28 },
29 configurable: false,
30 });
31 var internalChooseValue = 0;
32 Object.defineProperty(this, "choose", {
33 configurable: false,
34 get: function () {
35 return internalChooseValue;
36 },
37 set: function (val) {
38 if (typeof val !== "number") {
39 throw new Error("choose属性必须是数字");
40 }
41 var temp = parseInt(val);
42 if (temp !== val) {
43 throw new Error("choose属性必须是整数");
44 }
45 if (val < 0) {
46 throw new Error("choose属性必须大于等于 0");
47 }
48 internalChooseValue = val;
49 },
50 });
51 this.a = 1;
52 Object.seal(this); // 密封自己 不能加属性了,其他属性还可以改
53 }
54}
55
56Object.freeze(UIGoods.prototype);
57
58var g = new UIGoods(aGoods);
59
60UIGoods.prototype.haha = "abc";
61// g.data.price = 100;
62
63console.log(g.haha);
64
65// 思考:能不能想到各种场景,提前预防Reflect 是一个内置的 JS 对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些 JS 底层功能 由于它类似于其他语言的反射,因此取名为 Reflect
使用 Reflect 可以实现诸如 属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在与对象中 等等功能
有一个重要的理念,在 ES5 就被提出:减少魔法、让代码更加纯粹,这种理念很大程度上是受到函数式编程的影响
ES6 进一步贯彻了这种理念,它认为,对属性内存的控制、原型链的修改、函数的调用等等,这些都属于底层实现,属于一种魔法,因此,需要将它们提取出来,形成一个正常的 API,并高度聚合到某个对象中,于是,就造就了 Reflect 对象
因此,你可以看到 Reflect 对象中有很多的 API 都可以使用过去的某种语法或其他 API 实现。
1const obj = {
2 a: 1,
3 b: 2,
4};
5// obj.a = 10;
6Reflect.set(obj, "a", 10); //给obj的a赋值为10
7console.log(Reflect.get(obj, "a"));1function method(a, b) {
2 console.log("method", a, b);
3}
4// method(3, 4);
5Reflect.apply(method, null, [3, 4]);
6
7const obj = {
8 a: 1,
9 b: 2,
10};
11// delete obj.a;
12Reflect.deleteProperty(obj, "a");
13console.log(obj);
14
15function Test(a, b) {
16 this.a = a;
17 this.b = b;
18}
19
20// const t = new Test(1, 3);
21const t = Reflect.construct(Test, [1, 3]);
22console.log(t);1const obj = {
2 a: 1,
3 b: 2,
4};
5
6// console.log("a" in obj);
7console.log(Reflect.has(obj, "a"));提供了修改底层实现的方式
1//代理一个目标对象
2//target:目标对象
3//handler:是一个普通对象,其中可以重写底层实现(可以重写反射里面所有的api)
4//返回一个代理对象
5new Proxy(target, handler);1const obj = {
2 a: 1,
3 b: 2,
4};
5
6const proxy = new Proxy(obj, {
7 set(target, propertyKey, value) {
8 // console.log(target, propertyKey, value);
9 // target[propertyKey] = value;修改属性的值。重写了底层实现
10
11 // 等价于
12 Reflect.set(target, propertyKey, value);
13 },
14 get(target, propertyKey) {
15 if (Reflect.has(target, propertyKey)) {
16 return Reflect.get(target, propertyKey);
17 } else {
18 return -1;
19 }
20 },
21 has(target, propertyKey) {
22 return false; //代理说没有就没有
23 },
24});
25// console.log(proxy);
26// proxy.a = 10;
27// console.log(proxy.a);
28
29console.log(proxy.d); //运行get
30console.log("a" in proxy);有一个对象,是观察者,它用于观察另外一个对象的属性值变化,当属性值变化后会收到一个通知,可能会做一些事。
vue2
1<div id="container"></div>
2<script>
3 //创建一个观察者
4 function observer(target) {
5 //通过观察目标元素的变化,把这些属性渲染到页面上
6 const div = document.getElementById("container");
7 const ob = {};
8 const props = Object.keys(target); // 拿到所有属性名
9 for (const prop of props) {
10 Object.defineProperty(ob, prop, {
11 get() {
12 // 获取的时候
13 return target[prop];
14 },
15 set(val) {
16 // 设置的时候
17 target[prop] = val;
18 render();
19 },
20 enumerable: true, //这两个属性默认不能被枚举
21 });
22 }
23 render();
24 function render() {
25 let html = "";
26 for (const prop of Object.keys(ob)) {
27 //拼接属性名属性值
28 html += `
29<p><span>${prop}:</span><span>${ob[prop]}</span></p>
30`;
31 }
32 div.innerHTML = html;
33 }
34 return ob;
35 }
36 const target = {
37 a: 1,
38 b: 2,
39 };
40 const obj = observer(target);
41</script>缺陷:搞出来了两个对象,占用了内存,代理不占用内存
vue3
1<div id="container"></div>
2
3<script>
4 //创建一个观察者
5 function observer(target) {
6 const div = document.getElementById("container");
7 const proxy = new Proxy(target, {
8 set(target, prop, value) {
9 // 重写底层实现
10 Reflect.set(target, prop, value);
11 render();
12 },
13 get(target, prop) {
14 return Reflect.get(target, prop);
15 },
16 });
17 render();
18 function render() {
19 let html = "";
20 for (const prop of Object.keys(target)) {
21 html += `
22<p><span>${prop}:</span><span>${target[prop]}</span></p>
23`;
24 }
25 div.innerHTML = html;
26 }
27 return proxy;
28 }
29 const target = {
30 a: 1,
31 b: 2,
32 };
33 const obj = observer(target);
34</script>恶心的类创建
1class User(){
2 constructor(firstName, lastName, age) {
3 this.firstName = firstName;
4 this.lastName = lastName;
5 this.age = age;
6 }
7}自动赋值
1class User {}
2function ConstructorProxy(Class, ...propNames) {
3 return new Proxy(Class, {
4 construct(target, argumentsList) {
5 const obj = Reflect.construct(target, argumentsList);
6 propNames.forEach((name, i) => {
7 obj[name] = argumentsList[i];
8 });
9 return obj;
10 },
11 });
12}
13const UserProxy = ConstructorProxy(User, "firstName", "lastName", "age");
14const obj = new UserProxy("付", "志强", 18);
15console.log(obj);
16class Monster {}
17const MonsterProxy = ConstructorProxy(
18 Monster,
19 "attack",
20 "defence",
21 "hp",
22 "rate",
23 "name"
24);
25const m = new MonsterProxy(10, 20, 100, 30, "怪物");
26console.log(m);1function sum(a, b) {
2 return a + b;
3}
4
5function validatorFunction(func, ...types) {
6 //并不占据内存空间,只是个代理
7 const proxy = new Proxy(func, {
8 apply(target, thisArgument, argumentsList) {
9 types.forEach((t, i) => {
10 // 进行验证
11 const arg = argumentsList[i];
12 if (typeof arg !== t) {
13 throw new TypeError(
14 `第${i + 1}个参数${argumentsList[i]}不满足类型${t}`
15 );
16 }
17 });
18 return Reflect.apply(target, thisArgument, argumentsList);
19 },
20 });
21 return proxy;
22}
23
24const sumProxy = validatorFunction(sum, "number", "number");
25console.log(sumProxy(1, 2));Proxy:如果没有我
1function sum(a, b) {
2 return a + b;
3}
4
5function validatorFunction(func, ...types) {
6 return function (...argumentsList) {
7 // 新的函数占用内存
8 types.forEach((t, i) => {
9 const arg = argumentsList[i];
10 if (typeof arg !== t) {
11 throw new TypeError(
12 `第${i + 1}个参数${argumentsList[i]}不满足类型${t}`
13 );
14 }
15 });
16 return func(...argumentsList); //运行这个函数
17 };
18 return proxy;
19}
20
21const sumProxy = validatorFunction(sum, "number", "number");
22console.log(sumProxy(1, 2));其他 proxy 资料: