(JS基础)Proxy 对象
栏目: JavaScript · 发布时间: 5年前
阅读 6
(JS基础)Proxy 对象
Proxy 意为" 代理器 ",用于修改目标 对象的成员 之前进行"拦截",外界对该对象的成员的访问,都必须先通过这层拦截。其中,成员包括未定义的,即访问或设置不存在的属性,也会触发相应的代理函数。
初步认识
创建
通过构造函数可以创建 Proxy 对象( new Proxy()
),需要依次传入 两个参数
:
- 被代理的 目标对象 。可以是任意类型的对象,包括数组、函数等,甚至是另一个 Proxy 对象。
- 控制器对象 。控制器是一个对象,内部定义零个或多个代理函数。若控制器为空对象,等同于直接访问目标对象。
下面给出简单的例子:
let obj = { a: 1 } let proxy = new Proxy(obj, { get(obj, prop) { return 'proxyReturn' } }); console.log(proxy.a); // 'proxyReturn' console.log(proxy.b); // 'proxyReturn'复制代码
例子中, proxy
对 obj
对象拦截,访问其属性时,永远返回的是 'proxyReturn'
,即使属性是不存在。而 get
函数是众多代理函数中的一种,下面会分别介绍其他的代理函数。
作为 prototype 的情况
有一种情况,Proxy 对象作为某个对象的原型。看下面例子:
let proxy = new Proxy({}, { get(obj, prop) { return 'proxyReturn' } }); let obj = { a: 1 } Object.setPrototypeOf(obj, proxy); console.log(obj.a); // 1 console.log(obj.b); // 'proxyReturn'复制代码
当访问 obj.b
时,由于 b
在 obj
对象内不存在,因此会到访问原型是否存在该属性,即访问了 proxy
对象,从而触发了代理函数。
代理对象必须与被代理的对象具有相同特性(这里说的特性指的是,对象是否冻结,是否密封,是否可扩展),即被代理的对象不能修改,使用代理对象的 set
方法也会报错。再例如,被代理的对象被冻结, get
方法不能返回与原值不同的值; getOwnPropertyDescriptor
必须返回描述符对象。总之一句, 被代理对象不能改的,代理对象同样不能改;代理对象的方法的返回值类型必须与被代理对象一致
。
控制器对象内方法的 this
控制器对象内方法的 this
指向控制器对象
本身,看如下例子:
let handler = { get() { return this } } let proxy = new Proxy({}, handler); console.log(proxy.a === handler); // true复制代码
控制器对象
上面提到,控制器对象可以包含零个或多个的代理函数。像 get
这样的代理函数,一共有 13 个。简单介绍下面出现的参数名意义:
- target :表示 被代理的对象 。
- propKey :表示 属性名 (或方法名)。
- receiver :表示该方法所在的 代理对象 。
get(target, propKey, receiver)
get
方法用于 拦截
某个 成员的读取
操作(包括方法),上面已经简单给出了例子。下面给出代理数组的例子,使其可以用负数索引。
let arr = [1, 2, 3, 4, 5]; let proxy = new Proxy(arr, { get(target, prop) { let length = target.length, index = Number.parseInt(prop); if (index < 0) { return target[length + index] } if (index >= length) { return target[length - 1] } return target[index] } }); console.log(proxy[-1]); // 5 console.log(proxy[10]); // 5 console.log(proxy[2]); // 3复制代码
set(target, propKey, value, receiver)
set
方法用于 拦截
对象 属性的设置
。其中, value
表示属性的 目标值
。比较实用的用法是,与 DOM 的数据绑定。
需要 注意
的是!当存在二级属性时,如 [{}, {}]
或 { a: {}, b: {} }
, 对二级或以上的属性修改并不会触发
set
方法
。
如下例子:
let listData = ['one', 'two', 'three']; let proxy = new Proxy(listData, { set(target, prop, value) { let dom = document.getElementsByClassName('list-item')[prop] dom ? dom.innerText = value : null; target[prop] = value; } }) proxy[0] = 'changed-one';复制代码
has(target, propKey)
has
方法用来 拦截
HasProperty
操作
,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是 in
运算符
。
注意, hasOwnProperty()
和 for...in
都不能触发 has
方法。
注意, has
方法 只能返回布尔值
, return
非布尔值会自动转成布尔值。
let arr = [1, 2, 3, 4]; let proxyArr = new Proxy(arr, { has(target, prop) { return 'a' } }) console.log('x' in proxyArr); // true复制代码
deleteProperty(target, propKey)
deleteProperty
方法用于 拦截
delete
操作
,只能返回布尔值,非布尔值会被转成布尔值,代表属性是否被删除成功,省略默认返回 false
。
let obj = { a: 1, b: 2 } let proxyDel = new Proxy(obj, { deleteProperty(target, prop) { delete target[prop] } }); console.log(delete proxyDel.a); // false console.log(obj); // { b: 2 }复制代码
getOwnPropertyDescriptor(target, propKey)
getOwnPropertyDescriptor
方法 拦截
Object.getOwnPropertyDescriptor()
或 Object.getOwnPropertyDescriptors()
,返回一个属性描述对象或者 undefined
。
注意,
若返回值不是 undefined
,则返回值必须是包含
[[configurable]]
为
true
的对象
,该值为 false
或省略会导致报错;若其他描述符未定义,以默认值填充;若返回对象包含除描述符以外的属性,则该属性被忽略。
// undefined let proxy1 = new Proxy({}, { getOwnPropertyDescriptor(target, prop) { return undefined } }); console.log(Object.getOwnPropertyDescriptor(proxy1, 'a')); // { value: undefined, writable: false, enumerable: false, configurable: true } let proxy2 = new Proxy({}, { getOwnPropertyDescriptor(target, prop) { return { configurable: true } } }); console.log(Object.getOwnPropertyDescriptor(proxy2, 'a')); // 报错 let proxy3 = new Proxy({}, { getOwnPropertyDescriptor(target, prop) { return { configurable: false } } }); console.log(Object.getOwnPropertyDescriptor(proxy3, 'a'));复制代码
defineProperty(target, propKey, propDesc)
defineProperty
方法 拦截
Object.defineProperty()
、 Object.defineProperties()
、 obj.prop = 'value'
等 修改/添加对象的属性
的操作。该方法只能返回布尔值,非布尔值会转成布尔值,表示是否成功定义属性,返回 fasle
会导致报错。其中, propDesc
表示属性的描述符。
注意,如果对象被冻结( Object.freeze()
)或对象被密封( Object.seal()
)或对象不可扩展( Object.preventExtensions()
),甚至是某个属性被密封( [[configurable]]
特性为 false
),对代理对象使用 Object.defineProperty()
都可能会报错。总之,和原对象的特性一致,不能被修改的被代理后同样不能被修改。
let obj = { d: 4 } let proxyObj = new Proxy(obj, { defineProperty() { console.log('proxy-defineProperty'); return true } }) // 以下4种方法都会触发 defineProperty 方法 Object.defineProperty(proxyObj, 'a', {}); Object.defineProperties(proxyObj, { b: {} }); proxyObj.c = 3; proxyObj.d = 5;复制代码
apply(target, object, args)
apply
方法 拦截函数的调用
、 call
、 apply
、 bind
操作。其中, object
表示函数 上下文对象
,
args
表示函数的 参数
。
let obj = { a: 10 } function fn() { } let proxyFn = new Proxy(fn, { apply(target, object, args) { console.log('proxyFn-apply'); } }) // 直接执行函数 会触发 proxyFn(); // apply 和 call 会触发 proxyFn.apply(obj); proxyFn.call(obj); // bind 定义时不会触发 执行时触发 let p = proxyFn.bind(obj); p();复制代码
construct(target, args, receiver)
construct
方法用于 拦截
new
命令
。
注意, 被代理 的函数 不能是箭头函数 ,否则也会报错。
注意, construct
方法 必须要
return
对象
(包括函数、数组等), 否则会报错
。
// 错误例子1 let P1 = new Proxy(()=>{},{ construct(){ return {} } }); let p1 = new P1(); // 报错,被代理函数不能是箭头函数 // 错误例子2 let P2 = new Proxy(function(){},{ construct(){} }); let p2 = new P2(); // 报错,construct必须返回对象复制代码
getPrototypeOf(target)
getPrototypeOf
方法主要用来 拦截获取对象原型
,返回值必须是 null
或对象。具体来说,拦截下面这些操作。
-
Object.prototype.__proto__
// 只能在浏览器环境下执行 -
Object.prototype.isPrototypeOf()
// 代理对象作为参数 -
Object.getPrototypeOf()
// 代理对象作为参数 -
Reflect.getPrototypeOf()
// 代理对象作为参数 -
instanceof
// 代理对象在运算符的左边
下面给出简单例子:
let proxy = new Proxy({},{ getPrototypeOf(){ console.log('proxy-getPrototypeOf'); return null } }); // 都会打印 'proxy-getPrototypeOf' let x1 = proxy.__proto__; // 只能在浏览器下执行 let x2 = {}.isPrototypeOf(proxy); let x3 = Object.getPrototypeOf(proxy); let x4 = Reflect.getPrototypeOf(proxy); let x5 = (proxy instanceof function(){});复制代码
setPrototypeOf(target, proto)
setPrototypeOf
方法主要用来 拦截
Object.setPrototypeOf()
方法
。和 Object.setPrototypeOf()
方法一样,必须返回被设置的对象,否则报错。其中, proto
表示被设为原型的对象。
注意,浏览器环境下可以通过 __proto__
设置原型对象,同样能触发 setPrototypeOf
方法。
let proxy = new Proxy({}, { setPrototypeOf(target, proto) { console.log('proxy-setPrototypeOf') return target; } }); // 都打印 'proxy-setPrototypeOf' Object.setPrototypeOf(proxy, {}); proxy.__proto__ = {} // 非浏览器环境下使用会报错复制代码
isExtensible(target)
isExtensible
方法 拦截
Object.isExtensible()
方法
,必须返回布尔值,非布尔值会被转成布尔值,且 返回值必须与源对象
所对应的值 相同
,否则报错。
// 错误示范 let proxy = new Proxy({}, { isExtensible() { return false } }); console.log(Object.isExtensible(proxy));复制代码
preventExtensions(target)
preventExtensions
方法 拦截
Object.preventExtensions()
方法
,只能返回被设为不可扩展的被代理对象,否则报错。如下:
let proxy = new Proxy({}, { preventExtensions(target) { console.log('proxy-preventExtensions') return Object.preventExtensions(target); } }); Object.preventExtensions(proxy); // proxy-preventExtensions // 错误示范 let proxyErr = new Proxy({}, { preventExtensions(target) { return {} } }); Object.preventExtensions(proxyErr); // 报错复制代码
ownKeys(target)
ownKeys
方法用来 拦截
对象自身 所有属性的读取
操作,必须返回数组或对象,否则报错。具体来说,拦截以下操作。
-
Object.getOwnPropertyNames()
。 -
Object.getOwnPropertySymbols()
。 -
getOwnPropertyDescriptors()
。 -
Object.entries()
、Object.keys()
、Object.values()
。 -
for...in
循环。
注意,虽然可以返回非数组的对象,对于如 Object.keys
这种默认返回数组的方法,得到的结果会是一个空数组。
注意,诸如 Object.entries
方法返回的数组不会含有 Symbol
属性名、不存在的属性名、不可遍历( enumerable
)的属性名,与被代理对象的行为一致。 Object.getOwnPropertySymbols
及其他方法同理,与被代理对象一致。
看下面简单例子:
let proxy = new Proxy({ a: 1, b: 2 }, { ownKeys() { console.log('proxy-ownKeys'); return ['a', 'c'] } }); // 都会打印 'proxy-ownKeys' let x1 = Object.entries(proxy); let x2 = Object.keys(proxy); let x3 = Object.values(proxy); for (let x4 in proxy) { } let x5 = Object.getOwnPropertyNames(proxy); let x6 = Object.getOwnPropertyDescriptors(proxy); let x7 = Object.getOwnPropertySymbols(proxy); // entries、keys、values 都会过滤不可能显示的属性,跟被代理对象一致 console.log(x1); // [ [ 'a', 1 ] ] 复制代码
Proxy.revocable()
Proxy.revocable()
方法与 new Proxy()
用法类似,都是依次传入被代理对象和控制器对象,
返回一个包含 proxy
对象和 revoke
方法的对象
。其中, proxy
对象和上面描述的一致, revoke
方法用于解除 proxy
对象的代理,一旦取消将无法继续使用。
let proxyR = Proxy.revocable({ a: 1 }, {}); console.dir(proxyR); // { proxy: { a: 1 }, revoke: [Function] } console.log(proxyR.proxy); // { a: 1 } proxyR.revoke(); // console.log(proxyR); // 报错复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Python基础入门_5面向对象基础
- JavaScript基础学习——面向对象(对象创建之工厂模式)
- 【重温基础】7.时间对象
- 面向Python,面向对象(基础)
- 面向Python,面向对象(基础3)
- 【重温基础】15.JS对象介绍
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Effective JavaScript
赫尔曼 (David Herman) / 黄博文、喻杨 / 机械工业出版社 / 2014-1-1 / CNY 49.00
Effective 系列丛书经典著作,亚马逊五星级畅销书,Ecma 的JavaScript 标准化委员会著名专家撰写,JavaScript 语言之父、Mozilla CTO —— Brendan Eich 作序鼎力推荐!作者凭借多年标准化委员会工作和实践经验,深刻辨析JavaScript 的内部运作机制、特性、陷阱和编程最佳实践,将它们高度浓缩为极具实践指导意义的 68 条精华建议。 本书共......一起来看看 《Effective JavaScript》 这本书的介绍吧!