14. Proxy 代理
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理
它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作
14.1 Object.defineProperty
响应式小功能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Object.defineProperty</title>
</head>
<body>
<div class="box"></div>
<script>
let obj = {}
// 可以获得一个对象什么时候被设置,什么时候被修改
// 通过 Object.defineProperty 对对象的某个属性(如data)进行get、set拦截
// 只要访问这个属性就会调用 get 方法,修改就会调用 set 方法
Object.defineProperty(obj, "data", {
get() {
console.log("get");
},
set() {
console.log("set");
}
})
console.log(obj);
// 响应修改的内容到 div 标签
let box = document.querySelector(".box");
let obj1 = {}
Object.defineProperty(obj1, "age", {
get() {
console.log("get");
return box.innerHTML;
},
set(value) {
console.log("set", value);
// 设置dom
box.innerHTML = value;
}
})
</script>
</body>
</html>
14.2 Proxy 的 get、set 方法
- Object.defineProperty 的缺点
- 每次只能拦截一个属性,需要 for 循环
- 只能对对象拦截,其他不行
Proxy 如其名, 它的作用是在对象和和对象的属性值之间设置一个代理,获取该对象的值或者设置该对象的值, 以及实例化等等多种操作, 都会被拦截住, 经过这一层我们可以统一处理,我们可以认为它就是“代理器”
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proxy</title>
</head>
<body>
<div class="box"></div>
<script>
let box = document.querySelector(".box");
let obj = {}
let proxy = new Proxy(obj, {
get(target, key) {
console.log("get", target, key);
// get() 需要一个返回值
return target[key];
},
// target 对应 obj 对象
set(target, key, value) {
console.log("set", target, key, value);
// dom操作
if (key == "data") {
box.innerHTML = value;
}
target[key] = value;
}
})
</script>
</body>
</html>
14.3 has 方法
用于拦截 HasProperty 操作,即在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截
此方法不判断一个属性是对象自身的属性,还是继承的属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proxy的has方法</title>
</head>
<body>
<div class="box"></div>
<script>
let box = document.querySelector(".box");
let target = {
_prop: "内部数据"
}
let proxy = new Proxy(target, {
get(target, prop) {
return target[prop]
},
set(target, prop, value) {
if (prop === "data") {
box.innerHTML = value
}
target[prop] = value;
},
// 设置满足存在条件的属性名,
// 再进行判断,就判断是否有该属性
has(target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
})
</script>
</body>
</html>
14.4 Proxy拦截不同数据结构
错误1:get方法没有做对方法的处理
使用proxy.size时可以打印get,证明拦截成功,但是使用 proxy.add 方法就报错
报错原因:get方法没有做对方法的处理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proxy拦截不同数据结构</title>
</head>
<body>
<script>
let s = new Set();
let proxy = new Proxy(s, {
get() {
console.log("get");
},
set() {
console.log("set");
}
})
// 使用proxy.size时可以打印get,证明拦截成功
// 但是使用 proxy.add 方法就报错
// 报错原因:get方法没有做对方法的处理
</script>
</body>
</html>
错误2:错误原因: this绑定指向错误
正常调用size,add都没问题,但是在使用add方法时添加元素就报错
报错原因: this绑定指向错误,this时指向Proxy代理,但是需要用this指向set结构的属性方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proxy拦截不同数据结构</title>
</head>
<body>
<script>
let s = new Set();
// let proxy = new Proxy(s, {
// get() {
// console.log("get");
// },
// set() {
// console.log("set");
// }
// })
// 使用proxy.size时可以打印get,证明拦截成功
// 但是使用 proxy.add 方法就报错
// 报错原因:get方法没有做对方法的处理
// 解决 get 方法没有做对方法的处理
let proxy = new Proxy(s, {
get(target, key) {
// console.log("get");
return target[key];
},
set() {
console.log("set");
}
})
// 正常调用size,add都没问题,
// 但是在使用add方法时添加元素就报错
// 报错原因: this绑定指向错误,this时指向Proxy代理,
// 但是需要用this指向set结构的属性方法
</script>
</body>
</html>
解决 this 绑定指向错误
遇到 Function 都手动绑定一下 this,使用 bind 绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proxy拦截不同数据结构</title>
</head>
<body>
<script>
let s = new Set();
// let proxy = new Proxy(s, {
// get() {
// console.log("get");
// },
// set() {
// console.log("set");
// }
// })
// 使用 proxy.size 时可以打印 get,证明拦截成功
// 但是使用 proxy.add 放方法就报错
// 报错原因:get 方法没有做对方法的处理
// 解决 get 方法没有做对方法的处理
// let proxy = new Proxy(s, {
// get(target, key) {
// // console.log("get");
// return target[key];
// },
// set() {
// console.log("set");
// }
// })
// 正常调用 size,add 都没问题,
// 但是在使用 add 方法时添加元素就报错
// 报错原因: this 绑定指向错误,this 时指向 Proxy 代理,
// 但是需要用 this 指向 set 结构的属性方法
// 解决 this 绑定指向错误
let proxy = new Proxy(s, {
get(target, key) {
// 判断如果是方法,就修改this指向
let value = target[key];
if (value instanceof Function) {
// call apply bind
// 遇到 Function 都手动绑定一下 this,
// 使用 bind 绑定
return value.bind(target);
//不能 是 call apply
}
return value;
},
set() {
console.log("set");
}
})
</script>
</body>
</html>
Map结构
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proxy拦截不同数据结构</title>
</head>
<body>
<script>
let s = new Set();
// let proxy = new Proxy(s, {
// get() {
// console.log("get");
// },
// set() {
// console.log("set");
// }
// })
// 使用 proxy.size 时可以打印 get,证明拦截成功
// 但是使用 proxy.add 放方法就报错
// 报错原因:get 方法没有做对方法的处理
// 解决 get 方法没有做对方法的处理
// let proxy = new Proxy(s, {
// get(target, key) {
// // console.log("get");
// return target[key];
// },
// set() {
// console.log("set");
// }
// })
// 正常调用 size,add 都没问题,
// 但是在使用 add 方法时添加元素就报错
// 报错原因: this 绑定指向错误,this 时指向 Proxy 代理,
// 但是需要用 this 指向 set 结构的属性方法
// 解决 this 绑定指向错误
let proxy = new Proxy(s, {
get(target, key) {
// 判断如果是方法,就修改this指向
let value = target[key];
if (value instanceof Function) {
// call apply bind
// 遇到 Function 都手动绑定一下 this,
// 使用 bind 绑定
return value.bind(target);
//不能 是 call apply
}
return value;
},
set() {
console.log("set");
}
})
let m = new Map();
let proxys = new Proxy(m, {
get(target, key) {
// 判断如果是方法,就修改this指向
let value = target[key];
if (value instanceof Function) {
// call apply bind
// 遇到 Function 都手动绑定一下 this,
// 使用 bind 绑定
return value.bind(target);
//不能 是 call apply
}
return value;
},
set() {
console.log("Map");
}
})
</script>
</body>
</html>
Proxy本质上属于元编程非破坏性数据劫持,在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念
15. Reflect对象
Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式
它的方法与 Proxy 是对应的
ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上(当前某些方法会同时存在于 Object 和 Reflect 对象上),未来的新方法会只部署在 Reflect 对象上
Reflect 对象对某些方法的返回结果进行了修改,使其更合理
Reflect 对象使用函数的方式实现了 Object 的命令式操作
15.1 代替Object的某些方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reflect对象代替Object的某些方法</title>
</head>
<body>
<script>
let obj = {};
// Object.defineProperties(obj,"name",{
Reflect.defineProperty(obj, 'name', {
value: 'ich',
writable: false, // 不可以修改
configurable: false // 不可以删除
});
</script>
</body>
</html>
15.2 修改某些Object方法返回结果
// 老写法
// 因为 Object 方法 出现报错后,会中断
// 因此需要 try...catch 来捕获异常
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// fail
}
// 新写法
// 用 Reflect 方法出现报错会返回 false
// 让程序更合理
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// fail
}
当 Object 的 writable、enumerable 任意一个为 false 时,重复定义属性会报错
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>修改某些Object方法返回结果</title>
</head>
<body>
<script>
// let obj = {};
// Object.defineProperty(obj, "name", {
// value: "ich",
// // writable: true,
// // enumerable: true
// writable: false,
// enumerable: false
// })
// let res = Object.defineProperty(obj, "name", {
// value: "du"
// })
// console.log(res);
// 改为 Reflect - 不会提示报错,只不过会返回 false 返回值,优点不会打断代码运行
let obj = {};
// Object.defineProperty(obj, "name", {
Reflect.defineProperty(obj, "name", {
value: "ich",
// writable: true,
// enumerable: true
writable: false,
enumerable: false
})
let res = Reflect.defineProperty(obj, "name", {
value: "du"
})
console.log(res);
</script>
</body>
</html>
15.3 命令式变为函数行为
对于一些命令式的 Object 行为,Reflect 对象可以将其变为函数式的行为
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>命令式变为函数行为</title>
</head>
<body>
<script>
let obj = {
name: "ich"
}
console.log("name" in obj);
</script>
</body>
</html>
const obj = {
name:"kerwin"
};
//老写法
console.log("name" in obj) //true
//新写法
console.log(Reflect.has(obj, 'name')) //true
//老写法
delete obj.name
//新写法
Reflect.deleteProperty(obj, "name")
15.4 配合Proxy
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>配合Proxy</title>
</head>
<body>
<script>
// let obj = {
// name: "ich"
// }
// Reflect.set(obj, "age", 100);
// console.log(Reflect.get(obj, "name"));
let s = new Set();
let proxy = new Proxy(s, {
get(target, key) {
// let value = target[key]
// 用 Reflect 获取
let value = Reflect.get(target, key)
if (value instanceof Function) {
return value.bind(target)
}
return value
},
set(target, key, value) {
// target[key] = value
// console.log("set");
// Reflect.set(target, key, value)
// 简化
Reflect.set(...arguments)
}
})
</script>
</body>
</html>
配合解决数组数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>配合Proxy解决数组</title>
</head>
<body>
<script>
let arr = [1, 2, 3]
let proxy = new Proxy(arr, {
get(target, key) {
return Reflect.get(...arguments)
},
set(target, key, value) {
console.log("set", key, value);
return Reflect.set(...arguments)
}
})
</script>
</body>
</html>
GitHub代码
gitee代码