携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 27 天,点击查看活动详情
start
-
今天学习一下 ES6的
Proxy
-
学习网站:
开始
解释:
MDN官网的解释:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
ECMAScript 6 入门的解释:Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
1. 基础用法:
// 1. 基础的使用
var proxy = new Proxy(target, handler);
target 参数表示所要拦截的目标对象,handler 参数也是一个对象,用来定制拦截行为。 (Proxy的用法,主要的不同是 handler 参数的写法)
2. handler 参数有哪些
-
handler.get()
-
handler.set()
-
handler.deleteProperty()
-
handler.apply()
-
handler.construct()
-
handler.getPrototypeOf()
-
handler.setPrototypeOf()
-
handler.isExtensible()
-
handler.preventExtensions()
-
handler.getOwnPropertyDescriptor()
-
handler.defineProperty()
-
handler.has()
-
handler.ownKeys()
可以看到截止目前有 13 个可以设置的配置项。
3. 需要注意的事项
-
代理对象是通过
new Proxy()
来实现的;new Proxy()
两个参数都必填,可为空对象; -
代理对象
proxy
和目标对象target
不相等;var target = {} var proxy = new Proxy(target, {}) console.log(target === proxy) // false
-
如果
handler
没有设置任何拦截,那就等同于直接通向原对象;但是需要注意一下 this 指向var target = { name: '你好' } var proxy = new Proxy(target, {}) console.log(target, proxy) // { name: '你好' } { name: '你好' } proxy.name = '修改代理对象' console.log(target, proxy) // { name: '你好' } { name: '你好' }
handler中的配置项
1. get() 拦截属性的读取
》 可以接受三个参数,依次为目标对象、属性名和 操作行为所针对的对象
操作行为所针对的对象: handler 的 get 方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是 proxy 本身)。
var obj = {
name: 'tomato',
}
var proxy = new Proxy(obj, {
get: function () {
console.log('读取属性', ...arguments)
/* 可以接受三个参数,依次为目标对象、属性名和 操作行为所针对的对象 */
console.log('obj === arguments[0]', obj === arguments[0]) // true
console.log('proxy === arguments[2]', proxy === arguments[2]) // true
},
})
console.log(proxy.name)
// 读取属性 { name: 'tomato' } name { name: 'tomato' }
// undefined
》get方法的返回值就是访问到的数据
var obj = {
name: 'tomato',
}
var proxy = new Proxy(obj, {
get: function (target, prop) {
if (prop === 'tomato') {
return 'cool'
}
return target[prop]
},
})
console.log(proxy.name) // tomato
console.log(proxy.tomato) // cool
》get
方法可以继承
var obj = {
name: 'tomato',
}
var proxy = new Proxy(obj, {
get: function (target, prop) {
if (prop === 'tomato') {
return 'cool'
}
return target[prop]
},
})
var son = Object.create(proxy)
console.log(son) // {}
console.log(son.name) // tomato
console.log(son.tomato) // cool
console.log(son.lazy) // undefined
总结:
-
可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象);
-
get方法的返回值就是访问到的数据;
-
get
方法可以继承;
2. set() 拦截属性的设置
》可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身
var obj = {
name: 'tomato',
}
var proxy = new Proxy(obj, {
set: function () {
console.log('设置属性', ...arguments)
/* 可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身 */
console.log('obj === arguments[0]', obj === arguments[0]) // true
console.log('proxy === arguments[3]', proxy === arguments[3]) // true
},
})
proxy.name = 'lazy'
// { name: 'tomato' } name lazy { name: 'tomato' }
》新增属性(能拦截到新增的属性,实际设置属性还是要通过 target[propKey] = prop
实现)
var obj = {
name: 'tomato',
}
var proxy = new Proxy(obj, {
set: function (target, propKey, prop, receiver) {
console.log('设置属性', arguments[1])
target[propKey] = prop
},
})
proxy.age = 18
// 设置属性 age
console.log(obj, proxy) // { name: 'tomato', age: 18 } { name: 'tomato', age: 18 }
》可以直接通过数组索引修改
var arr = [1, 2, 3, 4, 5]
var proxy = new Proxy(arr, {
set: function (target, propKey, prop, receiver) {
console.log('设置属性', arguments[1])
target[propKey] = prop
},
})
proxy[2] = '通过索引设置值'
// 设置属性 2
console.log(arr, proxy) // [ 1, 2, '通过索引设置值', 4, 5 ] [ 1, 2, '通过索引设置值', 4, 5 ]
》set
代理应当返回一个布尔值。严格模式下,set
代理如果没有返回true
,就会报错。
'use strict'
var obj = {
name: 'tomato',
}
var proxy = new Proxy(obj, {
set: function () {
console.log('设置属性', ...arguments)
},
})
proxy.name = 'lazy'
/*
proxy.name = 'lazy'
^
TypeError: 'set' on proxy: trap returned falsish for property 'name'
*/
总结:
- 可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身;
- 新增属性(能拦截到新增的属性,实际设置属性还是要通过
target[propKey] = prop
实现); - 可以直接通过数组索引修改;
set
代理应当返回一个布尔值。严格模式下,set
代理如果没有返回true
,就会报错。
3. apply() 拦截函数的调用
》可以接受三个参数,分别是目标对象、目标对象的上下文对象(this
)和目标对象的参数数组。
function say() {
console.log(this, '你好呀番茄', ...arguments)
}
var p = new Proxy(say, {
apply: function () {
/* 可以接受三个参数,分别是目标对象、目标对象的上下文对象(`this`)和目标对象的参数数组。 */
console.log('拦截函数的执行', ...arguments)
},
})
p(1, 2, 3)
// 拦截函数的执行 [Function: say] undefined [1,2,3]
》函数直接调用,call,apply,bind生成的函数都会被 handler.apply
拦截,new 不会;
function say() {
console.log(this, '你好呀番茄', ...arguments)
}
var p = new Proxy(say, {
apply: function () {
/* 可以接受三个参数,分别是目标对象、目标对象的上下文对象(`this`)和目标对象的参数数组。 */
console.log('拦截函数的执行', ...arguments)
},
})
p(1, 2, 3)
// 拦截函数的执行 [Function: say] undefined [1,2,3]
p.call({ name: 'call会被拦截' })
// 拦截函数的执行 [Function: say] { name: 'call会被拦截' } []
p.apply({ name: 'apply会被拦截' })
// 拦截函数的执行 [Function: say] { name: 'apply会被拦截' } []
p.bind({ name: 'bind生成的函数会被拦截嘛' })()
// 拦截函数的执行 [Function: say] { name: 'bind生成的函数会被拦截嘛' } []
new p()
// say {} 你好呀番茄
总结:
- 可以接受三个参数,分别是目标对象、目标对象的上下文对象(
this
)和目标对象的参数数组。 - 函数直接调用,call,apply,bind生成的函数都会被
handler.apply
拦截,new 不会;
4. deleteProperty() 用于拦截 delete 操作
》如果这个方法抛出错误或者返回false
,当前属性就无法被delete
命令删除。
》可以接受两个参数,分别是目标对象,删除的属性名
var obj = {
name: 'tomato',
age: 18,
}
var proxy = new Proxy(obj, {
deleteProperty: function () {
console.log('删除属性', ...arguments)
},
})
delete proxy.age
console.log(obj, proxy)
/* 必须返回 true 才会删除对应的属性 */
// 删除属性 { name: 'tomato', age: 18 } age
// { name: 'tomato', age: 18 } { name: 'tomato', age: 18 }
总结:
-
如果这个方法抛出错误或者返回
false
,当前属性就无法被delete
命令删除。 -
可以接受两个参数,分别是目标对象,删除的属性名
Proxy 这里就比 Object.defineProperty 多了对 delete 的拦截
5. construct() 用于拦截new
命令
》 construct 返回值必须是对象,返回的对象,就是 new 代理对象
的返回值;
function fn() {
console.log('fn', ...arguments)
}
var proxyFn = new Proxy(fn, {
construct: function() {
console.log('construct的参数', ...arguments)
}
})
new proxyFn(1, 2, 3)
// TypeError: 'construct' on proxy: trap returned non-object ('undefined')
》construct()
方法可以接受三个参数。target
:目标对象。args
:构造函数的参数数组。newTarget
:new
命令作用的构造函数
function fn() {
console.log('fn', ...arguments)
}
var proxyFn = new Proxy(fn, {
construct: function() {
console.log('construct的参数', ...arguments)
console.log(fn === arguments[0]) // true
console.log(proxyFn === arguments[2]) // true
return { name: '随意的参数' }
}
})
var son = new proxyFn(1, 2, 3)
// construct的参数 [Function: fn] [ 1, 2, 3 ] [Function: fn]
console.log(son)
// { name: '随意的参数' }
》目标对象必须是函数
var proxyFn = new Proxy(
{ name: '对象' },
{
construct: function() {
console.log('construct的参数', ...arguments)
return { name: '随意的参数' }
}
}
)
/* TypeError: proxyFn is not a constructor */
》construct中的 this 指向 handler
function fn() {
console.log('fn')
}
var handler = {
construct: function() {
console.log('construct的参数', this, this === handler)
return { name: '随意的参数' }
}
}
var proxyFn = new Proxy(fn, handler)
new proxyFn()
/* construct的参数 { construct: [Function: construct] } true */
其他
其他的我个人暂时用的不多,这里就整体列一下
-
handler.getPrototypeOf()
getPrototypeOf()
方法主要用来拦截获取对象原型
-
handler.setPrototypeOf()
- 用来拦截
Object.setPrototypeOf()
方法。
- 用来拦截
-
handler.isExtensible()
- 拦截
Object.isExtensible()
操作。
- 拦截
-
handler.preventExtensions()
- 拦截
Object.preventExtensions()
。 - **
Object.preventExtensions()
**方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。
- 拦截
-
handler.getOwnPropertyDescriptor()
- 拦截
Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor()
方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
- 拦截
-
handler.defineProperty()
- 拦截
Object.defineProperty
- 拦截
-
handler.has()
has()
方法用来拦截HasProperty
操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in
运算符。
-
handler.ownKeys()
ownKeys()
方法用来拦截对象自身属性的读取操作
Proxy.revocable()
Proxy.revocable()
方法返回一个可取消的 Proxy 实例;
revocable(英文释义:可撤销的)
let target = {}
let handler = {}
let { proxy, revoke } = Proxy.revocable(target, handler)
proxy.foo = 123
console.log(target, proxy)
/* { foo: 123 } { foo: 123 } */
revoke()
console.log(target, proxy)
/* {foo: 123} {} */
this 问题
虽然说:如果 handler
没有设置任何拦截,那就等同于直接通向原对象;但是两者是两个不同的对象,这就导致会有一些差异需要注意。
代理对象 p 的this指向 Proxy
let target = new Date()
var p = new Proxy(target, {})
console.log(target.getTime())
/* 1661323388976 */
console.log(p.getTime())
/* TypeError: this is not a Date object. */
拦截配置函数的 this 指向 handle
var target = {}
var handle = {
get: function() {
console.log(this === handle)
}
}
var p = new Proxy(target, handle)
console.log(p.name)
// true
// undefined
end
总结一下收获:
-
熟悉了 Proxy的基本用法;
-
知道了可以使用 new Proxy的方法代理对象,通过常见的 get set deleteProperty拦截对象的操作;
-
可以通过 Proxy.revocable() 创建可取消的代理对象;
-
使用 proxy 的实例的时候,需要注意一下 this指向的问题。
加油