目录
- Proxy
- 对象的基本操作
- 常见捕获器
- get
- set
- 函数相关拦截器
- Reflect
- Reflect与基本语法的区别
- Reflect常见方法
- Proxy和Reflect
在
ES6
之前,如果我们对对象进行
新增或者删除属性
时,仅靠原有的
Object.defineProperty
是无法
监听
的,基于此,在
ES6
中就推出了
Proxy
和
Reflect
,简单地说,
Proxy
和
Reflect
只做了一件事,就是将
对象原有的基本操作暴露出来或者拦截原有操作
来供我们使用
Proxy
如果我们想监听一个对象的所有操作,我们就可以创建一个Proxy
对象用于代理原对象
,之后对原对象所有的操作都将会由代理对象执行
在实例化一个Proxy
的时候需要传入两个参数,一个为需要监听的对象
,一个处理对象handler
let obj = {name: '张三',age: 18
}
let objProxy = new Proxy(obj, {})
很显然我们需要再handler
中写一些东西,那么应该写一些什么呢
我们需要在handler
中定义捕获器
对象的基本操作
在了解什么是捕获器
之前,我们需要先知道对象的基本操作
事实上,无论是对对象的读取还是赋值,都会触发对象的内部方法
[[GET]]
、[[SET]]
,这些方法我们平时无法在外部访问
到
对象一共有11
种基本操作,如果对象是函数
的话还会额外再多出两种
基本操作,所以在JavaScript
中对象一共有13
种基本操作,Proxy
中有13种捕获器
,每种捕获器都与一个基本操作相对应,我们可以定义不同的捕获器来拦截对象原本的基本操作
常见捕获器
这些捕获器
我们都定义在handler
中
当用户访问代理对象的属性时,代理对象的基本操作被get
捕获器拦截,在拦截器
中我们定义针对于原对象的操作
,基于此我们才能实现对对象的全面监听
以下是一些常见的捕获器
get
get
对应的是基本操作里的[[GET]]
,定义如下
let obj = {name: '张三',age: 18
}
let objProxy = new Proxy(obj, {get: function (target, key, receiver) {console.log(`getting ${key}!`)return target[key]}
})
console.log(objProxy.name)
get
函数有三个参数,target
为被监听的对象,key
为访问的属性名,receiver
是被调用的代理对象
set
set
对应基本操作里的[[SET]]
,它的基本定义如下
let obj = {name: '张三',age: 18
}
let objProxy = new Proxy(obj, {get: function (target, key, receiver) {console.log(`getting ${key}!`)return target[key]},set: function (target, key, value, receiver) {console.log(`setting ${key}!`)target[key] = value}
})
console.log(objProxy.name)
objProxy.age = 20
objProxy.sex = "男"
set
函数有四个参数,target
为原对象,key
为设置的属性,value
为新的值,receiver
为代理对象
可以看到无论是设置已有的属性还是设置未拥有的属性
,set
都能监听到
函数相关拦截器
函数
相关的拦截器一共有两个
,以下是它的简单定义
let func = function (num) {console.log(1 + num)
}
let funcProxy = new Proxy(func, {apply: function (target, ctx, args) {console.log(target, ctx, args)return target(...args)}
})
funcProxy(2)
let Func = function (num) {this.num = num
}
let FuncProxy = new Proxy(Func, {construct: function (target, args) {console.log(target, args)return new target(...args)}
})
let obj = new FuncProxy(2)
console.log(obj.num)
Reflect
如果说Proxy
可以拦截对象的基本操作,那么Reflect
就可以将对象的基本操作暴露出来
直接使用
Reflect与基本语法的区别
我们先看下面两段代码
let obj = {name: "张三",age: 18
}
Reflect.set(obj, "age", 20)
let obj = {name: "张三",age: 18
}
obj.age = 20
这两种写法是在结果上相同的,区别在于Reflect
是直接调用了[[SET]]
方法,而第二种只是间接调用
这么说可能并不能说明Reflect
与基本语法
的区别,让我们来看下一个例子
let obj = {name: "张三",age: 18,get info() {return `姓名:${this.name},年龄:${this.age}`}
}
console.log(obj.info)
这段代码会执行两个步骤:
- 寻找
this
,info
是一个函数,它由obj
来调用,所以this
指向了obj
- 调用
[[GET]]
,传入原对象,属性名,以及this
,得到返回结果
而如果是Reflect
的话则会不同
let obj = {name: "张三",age: 18,get info() {return `姓名:${this.name},年龄:${this.age}`}
}
Reflect.get(obj, "info", obj)
Reflect
的get
方法所需的参数与[[GET]]
方法一致,所以只会执行一个步骤
- 因为
this
已经指定,所以直接调用[[GET]]
方法
因为Reflect
可以指定this
,所以我们可以这么写
let obj = {name: "张三",age: 18,get info() {return `姓名:${this.name},年龄:${this.age}`}
}
Reflect.get(obj, "info", { name: "李四", age: 30 })
这种写法是无法在基本语法
层面里展现的
Reflect常见方法
因为对象有13种基本操作
,所以Reflect也有13个方法
以下是一些常用方法
Reflect.get(target, key [, receiver])
获取target上某个值Reflect.set(target, key, value [, receiver])
设置target上某个属性的值Reflect.has(target, key)
判断一个target上是否有key属性
Proxy和Reflect
很多时候我们都是将Proxy
和Reflect
合在一起来使用
let obj = {name: "张三",age: 18,get info() {return `姓名:${this.name},年龄:${this.age}`}
}
let objProxy = new Proxy(obj, {get(target, key) {console.log(`getting ${key}`)return target[key]}
})
console.log(objProxy.info)
我们有这么一段代码,它的运行结果如下
看起来似乎没什么问题,但仔细想想好像有哪里不对,我们在访问info
时同时访问了name
和age
,但他们并没有在控制台输出getting
,但是偏偏info
的getting
打印出来了,我们对obj
设置了get
的拦截器,那所有的get
操作应该都会被拦截
才对,这是为什么呢
因为this
的指向错了,我们在控制台打印一下this
,现在把代码稍微修改一下
let obj = {name: "张三",age: 18,get info() {console.log(this)return `姓名:${this.name},年龄:${this.age}`}
}
真相出来了,我们在info
内的this
指向了原始对象
,没有经过proxy
,自然没有触发拦截器
,解决这个问题很简单,使用Reflect
即可
let obj = {name: "张三",age: 18,get info() {console.log(this)return `姓名:${this.name},年龄:${this.age}`}
}
let objProxy = new Proxy(obj, {get(target, key, recevier) {console.log(`getting ${key}`)return Reflect.get(target, key, recevier)}
})
console.log(objProxy.info)