(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'复制代码

例子中, proxyobj 对象拦截,访问其属性时,永远返回的是 '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 时,由于 bobj 对象内不存在,因此会到访问原型是否存在该属性,即访问了 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 运算符

注意, has​OwnProperty()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 方法 拦截函数的调用callapplybind 操作。其中, 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);  // 报错复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Effective JavaScript

Effective JavaScript

赫尔曼 (David Herman) / 黄博文、喻杨 / 机械工业出版社 / 2014-1-1 / CNY 49.00

Effective 系列丛书经典著作,亚马逊五星级畅销书,Ecma 的JavaScript 标准化委员会著名专家撰写,JavaScript 语言之父、Mozilla CTO —— Brendan Eich 作序鼎力推荐!作者凭借多年标准化委员会工作和实践经验,深刻辨析JavaScript 的内部运作机制、特性、陷阱和编程最佳实践,将它们高度浓缩为极具实践指导意义的 68 条精华建议。 本书共......一起来看看 《Effective JavaScript》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器