只要有“代理”的诉求都可以考虑使用 Proxy 来实现。
基本语法
语法
let p = new Proxy(target, handler)
解释
参数 | 含义 | 必选 |
---|---|---|
target | 用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理) | Y |
handler | 一个对象,其属性是当执行一个操作时定义代理的行为的函数 | Y |
MDN 给出的解释偏官方,通俗的讲第一个参数 target 就是用来代理的“对象”,被代理之后它是不能直接被访问的,而 handler 就是实现代理的过程。
拦截操作场景
如果读取一个对象的 key-value
let o = {
name: 'xiaoming',
age: 20
}
console.log(o.name) // xiaoming
console.log(o.age) // 20
console.log(o.from) // undefined
当读取 from 的时候返回的是 undefined,因为 o 这个对象中没有这个 key-value。想想看我们在读取数据的时候,这个数据经常是聚合的,当大家没有按照规范来的时候或者数据缺失的情况下,经常会出现现象。
如果不想在调用 key 的时候返回 undefined
console.log(o.from || '')
- ES6 的 Proxy解决对象中没有 key-value的情况
let o = {
name: 'xiaoming',
age: 20
}
let handler = {
get(obj, key) {
//如果对象有这个key,就返回这个key,否则就返回空字符串
return Reflect.has(obj, key) ? obj[key] : ''
}
}
let p = new Proxy(o, handler)
console.log(p.from) //''
console.log(p.name) //xiaoming
console.log(p.age) //20
get
let arr = [7, 8, 9]
arr = new Proxy(arr, {
get(target, prop) {
// console.log(target, prop)
return prop in target ? target[prop] : 'error'
}
})
console.log(arr[1])
console.log(arr[10])
扩展:
Reflect:反映;反射
Reflect.has()
Reflect.has 用于检查一个对象是否拥有某个属性, 相当于in 操作符,语法:Reflect.has(target, propertyKey),比如:Reflect.has(Object, 'assign') // true window下面的Object属性是否包含assign
- set操作给对象设置值
set
let arr = []
arr = new Proxy(arr, {
set(target, prop, val) {
if (typeof val === 'number') {
target[prop] = val
return true
} else {
return false
}
}
})
arr.push(5)
arr.push(6)
console.log(arr[0], arr[1], arr.length)
上面代码:对数据的“读操作”进行了拦截,接下来我们描述下“写操作”进行拦截。
- 从服务端获取的数据希望是只读,不允许在任何一个环节被修改
// result.data.userinfo是 JSON 格式的数据,来自服务端的响应
// 在 ES5 中只能通过遍历把所有的属性设置为只读
for (let [key] of Object.entries(result.data.userinfo)) {
Object.defineProperty(result.data.userinfo, key, {
writable: false
})
}
let response = {
name: 'xiaoming',
age: 20
}
for (let [key] of Object.entries(response)) {
Object.defineProperty(response, key, {
writable: false
})
}
// Object.getOwnPropertyDescriptor(object, propertyname)
// 获取指定对象的自身属性描述符。自身属性描述符是指直接在对象上定义(而非从对象的原型继承)
console.log('response',Object.getOwnPropertyDescriptor( response, "name" ))
response.name='xiaowu';
console.log('name:',response.name) //xiaoming
// defineProperty
var myObject = {};
Object.defineProperty( myObject, "a", {
value: 2,
writable: true,
enumerable: true,
configurable: true
});
myObject.a; // 2
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
Object.defineProperty(obj, prop, descriptor) obj要定义属性的对象,prop要定义或修改的属性的名称或 Symbol,descriptor要定义或修改的属性描述符
Object.entries(obj) 可以返回其可枚举属性的键值对的对象。
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。
对象的属性分为可枚举和不可枚举之分,它们是由属性的enumerable值决定的。可枚举性决定了这个属性能否被for…in查找遍历到。
- 传统的校验做法是将校验写在了业务逻辑里,导致代码耦合度较高
has判断当前值是否在对象范围内
// Validator.js
export default (obj, key, value) => {
if (Reflect.has(key) && value > 20) {
obj[key] = value
}
}
import Validator from './Validator'
let data = new Proxy(response.data, {
set: Validator
})
let range = {
start: 1,
end: 5
}
range = new Proxy(range, {
has(target, prop){
return prop >= target.start && prop <= target.end
}
})
console.log(2 in range)
console.log(9 in range)
- 如果对读写进行监控,可以这样写:
let validator = {
set(target, key, value) {
if (key === 'age') {
if (typeof value !== 'number' || Number.isNaN(value)) {
throw new TypeError('Age must be a number')
}
if (value <= 0) {
throw new TypeError('Age must be a positive number')
}
}
return true
}
}
const person = {
age: 27
}
const proxy = new Proxy(person, validator)
proxy.age = 'foo'
// <- TypeError: Age must be a number
proxy.age = NaN
// <- TypeError: Age must be a number
proxy.age = 0
// <- TypeError: Age must be a positive number
proxy.age = 28
console.log(person.age)
// <- 28
// 添加监控
window.addEventListener(
'error',
e => {
console.log(e.message) // Uncaught TypeError: Age must be a number
},
true
)
addEventListener() 方法,事件监听
element.addEventListener(event, function, useCapture);
第一个参数是事件的类型 (如 "click" 或 "mousedown").
第二个参数是事件触发后调用的函数。
第三个参数是个布尔值用于描述事件是冒泡还是捕获。该参数是可选的。
注意:不要使用 "on" 前缀。 例如,使用 "click" ,而不是使用 "onclick"。
你可以使用 removeEventListener() 方法来移除事件的监听。
- 实例一个对象,每个对象都有一个自己的 id 而且只读
class Component {
constructor() {
this.proxy = new Proxy({
id: Math.random().toString(36).slice(-8)
})
}
get id() {
return this.proxy.id
}
}
常用拦截操作
- get
拦截对象属性的读取,比如proxy.foo和proxy['foo']
let arr = [7, 8, 9]
arr = new Proxy(arr, {
get(target, prop) {
// console.log(target, prop)
return prop in target ? target[prop] : 'error'
}
})
console.log(arr[1])
console.log(arr[10])
let dict = {
'hello': '你好',
'world': '世界'
}
dict = new Proxy(dict, {
get(target, prop) {
return prop in target ? target[prop] : prop
}
})
console.log(dict['world'])
console.log(dict['xiaowu'])
- set
拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值
let arr = []
arr = new Proxy(arr, {
set(target, prop, val) {
if (typeof val === 'number') {
target[prop] = val
return true
} else {
return false
}
}
})
arr.push(5)
arr.push(6)
console.log(arr[0], arr[1], arr.length)
- has
拦截propKey in proxy的操作,返回一个布尔值。
let range = {
start: 1,
end: 5
}
range = new Proxy(range, {
has(target, prop) {
return prop >= target.start && prop <= target.end
}
})
console.log(2 in range)
console.log(9 in range)
- ownKeys
拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性
let obj = {
name: 'xiaowu',
[Symbol('es')]: 'es6'
}
console.log(Object.getOwnPropertyNames(obj))
console.log(Object.getOwnPropertySymbols(obj))
console.log(Object.keys(obj))
for (let key in obj) {
console.log(key)
}
let userinfo = {
username: 'xiecheng',
age: 34,
_password: '***'
}
userinfo = new Proxy(userinfo, {
ownKeys(target) {
return Object.keys(target).filter(key => !key.startsWith('_'))
}
})
// for (let key in userinfo) {
// console.log(key)
// }
console.log(Object.keys(userinfo)) //["username", "age"]
Object.keys(obj)
Object.keys 返回一个所有元素为字符串的数组,其元素来自于从给定的object上面可直接枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致。
返回字符串下标数组或者对象的key
try...catch 的作用是测试代码中的错误。
try{
//在此运行代码
}catch(err){
//在此处理错误
}
- apply
拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)
let sum = (...args) => {
let num = 0
args.forEach(item => {
num += item
})
return num
}
sum = new Proxy(sum, {
apply(target, ctx, args) {
return target(...args) * 2
}
})
console.log(sum(1, 2)) //6
console.log(sum.call(null, 1, 2, 3)) //12
console.log(sum.apply(null, [1, 2, 3])) //12
- construct
拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)
let User = class {
constructor(name) {
this.name = name
}
}
User = new Proxy(User, {
construct(target, args, newTarget) {
return new target(...args)
}
})
console.log(new User('xiaowu')) //{name: "imooc"}