目录
- 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)
