代理与反射
属性描述符
Property Descriptor
Property Descriptor 属性描述符 是一个普通对象,用于描述一个属性的相关信息 通过Object.getOwnPropertyDescriptor(对象, 属性名)
可以得到一个对象的某个属性的属性描述符
const desc = Object.getOwnPropertyDescriptor(obj, "a");
console.log(desc);
const desc = Object.getOwnPropertyDescriptor(obj, "a");
console.log(desc);
- value:属性值
- configurable:该属性的描述符是否可以修改
- enumerable:该属性是否可以被枚举
- writable:该属性是否可以被重新赋值
Object.getOwnPropertyDescriptors(对象)
可以得到某个对象的所有属性描述符
如果需要为某个对象添加属性时 或 修改属性时, 配置其属性描述符,可以使用下面的代码: Object.defineProperty(对象, 属性名, 描述符);
const obj = {
a: 1,
b: 2,
};
Object.defineProperty(obj, "a", {
value: 3,
configurable: false, //描述符不可以修改
enumerable: false, //不可枚举
writable: false, //不可写
});
console.log(obj);
for (const prop in obj) {
console.log(prop); //只有b,没有a了
}
obj.a = 10; //不可写
console.log(obj);
const props = Object.keys(obj); //得到对象的所有属性
console.log(props);
const values = Object.values(obj); //得到对象的所有值
console.log(values);
const obj = {
a: 1,
b: 2,
};
Object.defineProperty(obj, "a", {
value: 3,
configurable: false, //描述符不可以修改
enumerable: false, //不可枚举
writable: false, //不可写
});
console.log(obj);
for (const prop in obj) {
console.log(prop); //只有b,没有a了
}
obj.a = 10; //不可写
console.log(obj);
const props = Object.keys(obj); //得到对象的所有属性
console.log(props);
const values = Object.values(obj); //得到对象的所有值
console.log(values);
Object.defineProperties(对象, 多个属性的描述符)
Object.defineProperties(obj, {
a: {
value: 3,
configurable: false,
enumerable: false,
writable: false,
},
});
Object.defineProperties(obj, {
a: {
value: 3,
configurable: false,
enumerable: false,
writable: false,
},
});
存取器属性
属性描述符中,如果配置了 get 和 set 中的任何一个,则该属性,不再是一个普通属性,而变成了存取器属性。 get 和 set 配置均为函数,如果一个属性是存取器属性,则读取该属性时,会运行 get 方法,将 get 方法得到的返回值作为属性值;如果给该属性赋值,则会运行 set 方法。 属性已经不再内存里面了,而是 get 和 set
const obj = {
b: 2,
};
Object.defineProperty(obj, "a", {
get() {
console.log("运行了属性a的get函数");
},
set(val) {
console.log("运行了属性a的set函数", val);
},
});
// obj.a的时候运行get函数
// obj.a赋值的时候运行set函数
// 所以console.log(obj.a)//undefined,因为get函数没有返回结果
obj.a = 1; //相当于set(20)
console.log(obj.a); // 相当于console.log(get())
// 运行set
// 运行get
// undefined
const obj = {
b: 2,
};
Object.defineProperty(obj, "a", {
get() {
console.log("运行了属性a的get函数");
},
set(val) {
console.log("运行了属性a的set函数", val);
},
});
// obj.a的时候运行get函数
// obj.a赋值的时候运行set函数
// 所以console.log(obj.a)//undefined,因为get函数没有返回结果
obj.a = 1; //相当于set(20)
console.log(obj.a); // 相当于console.log(get())
// 运行set
// 运行get
// undefined
// 演示1
obj.a = 20 + 10; // set(20 + 10) 相当于运行set()
console.log(obj.a); // console.log(get()) 读取属性a,相当于运行get()
// 演示2
obj.a = obj.a + 1; // set(obj.a + 1) 相当于读取 set(get() + 1) undefined+1=NaN
console.log(obj.a); //读取属性一定运行get undefined
// 演示1
obj.a = 20 + 10; // set(20 + 10) 相当于运行set()
console.log(obj.a); // console.log(get()) 读取属性a,相当于运行get()
// 演示2
obj.a = obj.a + 1; // set(obj.a + 1) 相当于读取 set(get() + 1) undefined+1=NaN
console.log(obj.a); //读取属性一定运行get undefined
const obj = {
b: 2,
};
Object.defineProperty(obj, "a", {
get() {
console.log("运行了属性a的get函数");
return obj._a;
// 错误的思路:return obj.a
// 这里就成了无限递归,无限读取,导致浏览器卡死,同理set
},
set(val) {
console.log("运行了属性a的set函数", val);
obj._a = val;
},
});
obj.a = 10;
console.log(obj.a);
const obj = {
b: 2,
};
Object.defineProperty(obj, "a", {
get() {
console.log("运行了属性a的get函数");
return obj._a;
// 错误的思路:return obj.a
// 这里就成了无限递归,无限读取,导致浏览器卡死,同理set
},
set(val) {
console.log("运行了属性a的set函数", val);
obj._a = val;
},
});
obj.a = 10;
console.log(obj.a);
存取器属性最大的意义,在于可以控制属性的读取和赋值
obj = {
name: "adsf",
};
Object.defineProperty(obj, "age", {
get() {
return obj._age;
},
set(val) {
if (typeof val !== "number") {
throw new TypeError("年龄必须是一个数字");
}
if (val < 0) {
val = 0;
} else if (val > 200) {
val = 200;
}
obj._age = val;
},
});
obj.age = "Asdfasasdf";
console.log(obj.age);
obj = {
name: "adsf",
};
Object.defineProperty(obj, "age", {
get() {
return obj._age;
},
set(val) {
if (typeof val !== "number") {
throw new TypeError("年龄必须是一个数字");
}
if (val < 0) {
val = 0;
} else if (val > 200) {
val = 200;
}
obj._age = val;
},
});
obj.age = "Asdfasasdf";
console.log(obj.age);
应用:将对象的属性和元素关联
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<p>
<span>姓名:</span>
<span id="name"></span>
</p>
<p>
<span>年龄:</span>
<span id="age"></span>
</p>
<script>
const spanName = document.getElementById("name");
const spanAge = document.getElementById("age");
const user = {};
Object.defineProperties(user, {
name: {
get() {
return spanName.innerText;
//获取页面窗口中对应的元素内容
},
set(val) {
spanName.innerText = val;
},
},
age: {
get() {
return +spanAge.innerText;
},
set(val) {
if (typeof val !== "number") {
throw new TypeError("年龄必须是一个数字");
}
if (val < 0) {
val = 0;
} else if (val > 200) {
val = 200;
}
spanAge.innerText = val;
},
},
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<p>
<span>姓名:</span>
<span id="name"></span>
</p>
<p>
<span>年龄:</span>
<span id="age"></span>
</p>
<script>
const spanName = document.getElementById("name");
const spanAge = document.getElementById("age");
const user = {};
Object.defineProperties(user, {
name: {
get() {
return spanName.innerText;
//获取页面窗口中对应的元素内容
},
set(val) {
spanName.innerText = val;
},
},
age: {
get() {
return +spanAge.innerText;
},
set(val) {
if (typeof val !== "number") {
throw new TypeError("年龄必须是一个数字");
}
if (val < 0) {
val = 0;
} else if (val > 200) {
val = 200;
}
spanAge.innerText = val;
},
},
});
</script>
</body>
</html>
属性描述符应用
var obj = {
b: 2,
};
// 得到属性描述符
// var desc = Object.getOwnPropertyDescriptor(obj, 'a');
// console.log(desc);
// 设置属性描述符
Object.defineProperty(obj, "a", {
value: 10,
writable: false, // 不可重写
enumerable: false, // 不可遍历
configurable: false, // 不可修改描述符本身
});
// Object.defineProperty(obj, 'a', {
// writable: true,
// });
obj.a = "abc";
console.log(obj.a);
// for (var key in obj) {
// console.log(key);
// }
// var keys = Object.keys(obj);
// console.log(keys);
// console.log(obj);
var obj = {
b: 2,
};
// 得到属性描述符
// var desc = Object.getOwnPropertyDescriptor(obj, 'a');
// console.log(desc);
// 设置属性描述符
Object.defineProperty(obj, "a", {
value: 10,
writable: false, // 不可重写
enumerable: false, // 不可遍历
configurable: false, // 不可修改描述符本身
});
// Object.defineProperty(obj, 'a', {
// writable: true,
// });
obj.a = "abc";
console.log(obj.a);
// for (var key in obj) {
// console.log(key);
// }
// var keys = Object.keys(obj);
// console.log(keys);
// console.log(obj);
var obj = {};
Object.defineProperty(obj, "a", {
get: function () {
return 123;
}, // 读取器 getter
set: function (val) {
throw new Error(
`兄弟,你正在给a这个属性重新赋值,你所赋的值是${val},但是,这个属性是不能复制,你再考虑考虑`
);
}, // 设置器 setter
});
console.log(obj.a);
obj.a = "abx";
// console.log(obj.a); // console.log(get())
var obj = {};
Object.defineProperty(obj, "a", {
get: function () {
return 123;
}, // 读取器 getter
set: function (val) {
throw new Error(
`兄弟,你正在给a这个属性重新赋值,你所赋的值是${val},但是,这个属性是不能复制,你再考虑考虑`
);
}, // 设置器 setter
});
console.log(obj.a);
obj.a = "abx";
// console.log(obj.a); // console.log(get())
购物车描述
var aGoods = {
pic: ".",
title: "..",
desc: `...`,
sellNumber: 1,
favorRate: 2,
price: 3,
};
class UIGoods {
get totalPrice() {
return this.choose * this.data.price;
}
get isChoose() {
return this.choose > 0;
}
constructor(g) {
g = { ...g }; // 克隆一份
Object.freeze(g); // 冻结克隆对象
Object.defineProperty(this, "data", {
get: function () {
return g;
},
set: function () {
throw new Error("data 属性是只读的,不能重新赋值");
},
configurable: false,
});
var internalChooseValue = 0;
Object.defineProperty(this, "choose", {
configurable: false,
get: function () {
return internalChooseValue;
},
set: function (val) {
if (typeof val !== "number") {
throw new Error("choose属性必须是数字");
}
var temp = parseInt(val);
if (temp !== val) {
throw new Error("choose属性必须是整数");
}
if (val < 0) {
throw new Error("choose属性必须大于等于 0");
}
internalChooseValue = val;
},
});
this.a = 1;
Object.seal(this); // 密封自己 不能加属性了,其他属性还可以改
}
}
Object.freeze(UIGoods.prototype);
var g = new UIGoods(aGoods);
UIGoods.prototype.haha = "abc";
// g.data.price = 100;
console.log(g.haha);
// 思考:能不能想到各种场景,提前预防
var aGoods = {
pic: ".",
title: "..",
desc: `...`,
sellNumber: 1,
favorRate: 2,
price: 3,
};
class UIGoods {
get totalPrice() {
return this.choose * this.data.price;
}
get isChoose() {
return this.choose > 0;
}
constructor(g) {
g = { ...g }; // 克隆一份
Object.freeze(g); // 冻结克隆对象
Object.defineProperty(this, "data", {
get: function () {
return g;
},
set: function () {
throw new Error("data 属性是只读的,不能重新赋值");
},
configurable: false,
});
var internalChooseValue = 0;
Object.defineProperty(this, "choose", {
configurable: false,
get: function () {
return internalChooseValue;
},
set: function (val) {
if (typeof val !== "number") {
throw new Error("choose属性必须是数字");
}
var temp = parseInt(val);
if (temp !== val) {
throw new Error("choose属性必须是整数");
}
if (val < 0) {
throw new Error("choose属性必须大于等于 0");
}
internalChooseValue = val;
},
});
this.a = 1;
Object.seal(this); // 密封自己 不能加属性了,其他属性还可以改
}
}
Object.freeze(UIGoods.prototype);
var g = new UIGoods(aGoods);
UIGoods.prototype.haha = "abc";
// g.data.price = 100;
console.log(g.haha);
// 思考:能不能想到各种场景,提前预防
Reflect
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 = 10;
Reflect.set(obj, "a", 10); //给obj的a赋值为10
console.log(Reflect.get(obj, "a"));
const obj = {
a: 1,
b: 2,
};
// obj.a = 10;
Reflect.set(obj, "a", 10); //给obj的a赋值为10
console.log(Reflect.get(obj, "a"));
- Reflect.get(target, propertyKey): 读取对象 target 的属性 propertyKey,等同于读取对象的属性值
- Reflect.apply(target, thisArgument, argumentsList):调用一个指定的函数,并绑定 this 和参数列表。等同于函数调用
- Reflect.deleteProperty(target, propertyKey):删除一个对象的属性
- Reflect.defineProperty(target, propertyKey, attributes):类似于 Object.defineProperty,不同的是如果配置出现问题,返回 false 而不是报错
- Reflect.construct(target, argumentsList):用构造函数的方式创建一个对象
- Reflect.has(target, propertyKey): 判断一个对象是否拥有一个属性
- 其他 API
function method(a, b) {
console.log("method", a, b);
}
// method(3, 4);
Reflect.apply(method, null, [3, 4]);
const obj = {
a: 1,
b: 2,
};
// delete obj.a;
Reflect.deleteProperty(obj, "a");
console.log(obj);
function Test(a, b) {
this.a = a;
this.b = b;
}
// const t = new Test(1, 3);
const t = Reflect.construct(Test, [1, 3]);
console.log(t);
function method(a, b) {
console.log("method", a, b);
}
// method(3, 4);
Reflect.apply(method, null, [3, 4]);
const obj = {
a: 1,
b: 2,
};
// delete obj.a;
Reflect.deleteProperty(obj, "a");
console.log(obj);
function Test(a, b) {
this.a = a;
this.b = b;
}
// const t = new Test(1, 3);
const t = Reflect.construct(Test, [1, 3]);
console.log(t);
const obj = {
a: 1,
b: 2,
};
// console.log("a" in obj);
console.log(Reflect.has(obj, "a"));
const obj = {
a: 1,
b: 2,
};
// console.log("a" in obj);
console.log(Reflect.has(obj, "a"));
Proxy
提供了修改底层实现的方式
//代理一个目标对象
//target:目标对象
//handler:是一个普通对象,其中可以重写底层实现(可以重写反射里面所有的api)
//返回一个代理对象
new Proxy(target, handler);
//代理一个目标对象
//target:目标对象
//handler:是一个普通对象,其中可以重写底层实现(可以重写反射里面所有的api)
//返回一个代理对象
new Proxy(target, handler);
const obj = {
a: 1,
b: 2,
};
const proxy = new Proxy(obj, {
set(target, propertyKey, value) {
// console.log(target, propertyKey, value);
// 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) {
return false; //代理说没有就没有
},
});
// console.log(proxy);
// proxy.a = 10;
// console.log(proxy.a);
console.log(proxy.d); //运行get
console.log("a" in proxy);
const obj = {
a: 1,
b: 2,
};
const proxy = new Proxy(obj, {
set(target, propertyKey, value) {
// console.log(target, propertyKey, value);
// 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) {
return false; //代理说没有就没有
},
});
// console.log(proxy);
// proxy.a = 10;
// console.log(proxy.a);
console.log(proxy.d); //运行get
console.log("a" in proxy);
应用
观察者模式
有一个对象,是观察者,它用于观察另外一个对象的属性值变化,当属性值变化后会收到一个通知,可能会做一些事。
vue2
<div id="container"></div>
<script>
//创建一个观察者
function observer(target) {
//通过观察目标元素的变化,把这些属性渲染到页面上
const div = document.getElementById("container");
const ob = {};
const props = Object.keys(target); // 拿到所有属性名
for (const prop of props) {
Object.defineProperty(ob, prop, {
get() {
// 获取的时候
return target[prop];
},
set(val) {
// 设置的时候
target[prop] = val;
render();
},
enumerable: true, //这两个属性默认不能被枚举
});
}
render();
function render() {
let html = "";
for (const prop of Object.keys(ob)) {
//拼接属性名属性值
html += `
<p><span>${prop}:</span><span>${ob[prop]}</span></p>
`;
}
div.innerHTML = html;
}
return ob;
}
const target = {
a: 1,
b: 2,
};
const obj = observer(target);
</script>
<div id="container"></div>
<script>
//创建一个观察者
function observer(target) {
//通过观察目标元素的变化,把这些属性渲染到页面上
const div = document.getElementById("container");
const ob = {};
const props = Object.keys(target); // 拿到所有属性名
for (const prop of props) {
Object.defineProperty(ob, prop, {
get() {
// 获取的时候
return target[prop];
},
set(val) {
// 设置的时候
target[prop] = val;
render();
},
enumerable: true, //这两个属性默认不能被枚举
});
}
render();
function render() {
let html = "";
for (const prop of Object.keys(ob)) {
//拼接属性名属性值
html += `
<p><span>${prop}:</span><span>${ob[prop]}</span></p>
`;
}
div.innerHTML = html;
}
return ob;
}
const target = {
a: 1,
b: 2,
};
const obj = observer(target);
</script>
缺陷:搞出来了两个对象,占用了内存,代理不占用内存
vue3
<div id="container"></div>
<script>
//创建一个观察者
function observer(target) {
const div = document.getElementById("container");
const proxy = new Proxy(target, {
set(target, prop, value) {
// 重写底层实现
Reflect.set(target, prop, value);
render();
},
get(target, prop) {
return Reflect.get(target, prop);
},
});
render();
function render() {
let html = "";
for (const prop of Object.keys(target)) {
html += `
<p><span>${prop}:</span><span>${target[prop]}</span></p>
`;
}
div.innerHTML = html;
}
return proxy;
}
const target = {
a: 1,
b: 2,
};
const obj = observer(target);
</script>
<div id="container"></div>
<script>
//创建一个观察者
function observer(target) {
const div = document.getElementById("container");
const proxy = new Proxy(target, {
set(target, prop, value) {
// 重写底层实现
Reflect.set(target, prop, value);
render();
},
get(target, prop) {
return Reflect.get(target, prop);
},
});
render();
function render() {
let html = "";
for (const prop of Object.keys(target)) {
html += `
<p><span>${prop}:</span><span>${target[prop]}</span></p>
`;
}
div.innerHTML = html;
}
return proxy;
}
const target = {
a: 1,
b: 2,
};
const obj = observer(target);
</script>
偷懒的构造函数
恶心的类创建
class User(){
constructor(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
}
class User(){
constructor(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
}
自动赋值
class User {}
function ConstructorProxy(Class, ...propNames) {
return new Proxy(Class, {
construct(target, argumentsList) {
const obj = Reflect.construct(target, argumentsList);
propNames.forEach((name, i) => {
obj[name] = argumentsList[i];
});
return obj;
},
});
}
const UserProxy = ConstructorProxy(User, "firstName", "lastName", "age");
const obj = new UserProxy("付", "志强", 18);
console.log(obj);
class Monster {}
const MonsterProxy = ConstructorProxy(
Monster,
"attack",
"defence",
"hp",
"rate",
"name"
);
const m = new MonsterProxy(10, 20, 100, 30, "怪物");
console.log(m);
class User {}
function ConstructorProxy(Class, ...propNames) {
return new Proxy(Class, {
construct(target, argumentsList) {
const obj = Reflect.construct(target, argumentsList);
propNames.forEach((name, i) => {
obj[name] = argumentsList[i];
});
return obj;
},
});
}
const UserProxy = ConstructorProxy(User, "firstName", "lastName", "age");
const obj = new UserProxy("付", "志强", 18);
console.log(obj);
class Monster {}
const MonsterProxy = ConstructorProxy(
Monster,
"attack",
"defence",
"hp",
"rate",
"name"
);
const m = new MonsterProxy(10, 20, 100, 30, "怪物");
console.log(m);
可验证的函数参数
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}`
);
}
});
return Reflect.apply(target, thisArgument, argumentsList);
},
});
return proxy;
}
const sumProxy = validatorFunction(sum, "number", "number");
console.log(sumProxy(1, 2));
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}`
);
}
});
return Reflect.apply(target, thisArgument, argumentsList);
},
});
return proxy;
}
const sumProxy = validatorFunction(sum, "number", "number");
console.log(sumProxy(1, 2));
Proxy:如果没有我
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); //运行这个函数
};
return proxy;
}
const sumProxy = validatorFunction(sum, "number", "number");
console.log(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); //运行这个函数
};
return proxy;
}
const sumProxy = validatorFunction(sum, "number", "number");
console.log(sumProxy(1, 2));
其他 proxy 资料: