Javascript标准内置对象Proxy

13 篇文章 0 订阅

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

语法:
const p = new Proxy(target, handler)

target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。


handler 对象的方法

handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。

  • handler.getPrototypeOf()
    Object.getPrototypeOf 方法的捕捉器。
  • handler.setPrototypeOf()
    Object.setPrototypeOf 方法的捕捉器。
  • handler.isExtensible()
    Object.isExtensible 方法的捕捉器。
  • handler.preventExtensions()
    Object.preventExtensions 方法的捕捉器。
  • handler.getOwnPropertyDescriptor()
    Object.getOwnPropertyDescriptor 方法的捕捉器。
  • handler.defineProperty()
    Object.defineProperty 方法的捕捉器。
  • handler.has()
    in 操作符的捕捉器。
  • handler.get()
    属性读取操作的捕捉器。
  • handler.set()
    属性设置操作的捕捉器。
  • handler.deleteProperty()
    delete 操作符的捕捉器。
  • handler.ownKeys()
    Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
  • handler.apply()
    函数调用操作的捕捉器。
  • handler.construct()
    new 操作符的捕捉器。

无操作转发代理:

let target = {};
let p = new Proxy(target, {});

p.a = 37;   // 操作转发到目标

console.log(target.a);    // 37. 操作已经被正确地转发

验证:

let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }


    // The default behavior to store the value
    obj[prop] = value;


    // 表示成功
    return true;
  }
};


let person = new Proxy({}, validator);


person.age = 100;


console.log(person.age);
// 100


person.age = 'young';
// 抛出异常: Uncaught TypeError: The age is not an integer


person.age = 300;
// 抛出异常: Uncaught RangeError: The age seems invalid(实际在谷歌浏览器中只卡在上一步的报错,没有展示这一句)

扩展构造函数

/** 
* 扩展构造函数extend;sup为原本的函数,base为扩充的函数
*/
function extend(sup, base) {
  base.prototype = Object.create(sup.prototype); // base继承sup的原型链
  var handler = { // handle对象里包含构造器函数和apply
    construct: function(target, args) { // 捕捉new操作符
      var obj = Object.create(base.prototype);
      // 下面这句调用的是handler的apply,target是目标函数,实际就是上面的base参数,obj是基于base原型链创建的对象,args是传入的参数
      this.apply(target, obj, args);
      return obj;
    },
    apply: function(target, that, args) { // 捕捉函数调用操作
      sup.apply(that, args);
      base.apply(that, args);
    }
  };
  var proxy = new Proxy(base, handler); // 对base对象进行代理,代理操作为handler
  return proxy;
}


var Person = function (name) {
  this.name = name
};


// 对Person进行扩展
var Boy = extend(Person, function (name, age) {
  this.age = age;
}); // Boy是一个Proxy对象,其prototype是Person对象


Boy.prototype.sex = "M";


var Peter = new Boy("Peter", 13); // 执行new Boy时,会被extend函数中handler对象的construct捕获
console.log(Peter.sex);  // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age);  // 13

值修正及附加属性

以下products代理会计算传值并根据需要转换为数组。这个代理对象同时支持一个叫做 latestBrowser的附加属性,这个属性可以同时作为 getter 和 setter。

let products = new Proxy({
  browsers: ['Internet Explorer', 'Netscape']
}, {
  get: function(obj, prop) {
    // 附加一个属性
    if (prop === 'latestBrowser') {
      return obj.browsers[obj.browsers.length - 1];
    }


    // 默认行为是返回属性值
    return obj[prop];
  },
  set: function(obj, prop, value) {
    // 附加属性
    if (prop === 'latestBrowser') {
      obj.browsers.push(value);
      return;
    }


    // 如果不是数组,则进行转换
    if (typeof value === 'string') {
      value = [value];
    }


    // 默认行为是保存属性值
    obj[prop] = value;


    // 表示成功
    return true;
  }
});


console.log(products.browsers); // ['Internet Explorer', 'Netscape']
products.browsers = 'Firefox';  // 如果不小心传入了一个字符串
console.log(products.browsers); // ['Firefox'] <- 也没问题, 得到的依旧是一个数组


products.latestBrowser = 'Chrome';
console.log(products.browsers);      // ['Firefox', 'Chrome']
console.log(products.latestBrowser); // 'Chrome'

通过属性查找数组中的特定对象

以下代理为数组扩展了一些实用工具。如你所见,通过 Proxy,我们可以灵活地“定义”属性,而不需要使用 Object.defineProperties 方法。以下例子可以用于通过单元格来查找表格中的一行。在这种情况下,target 是 table.rows。

let products = new Proxy([
  { name: 'Firefox'    , type: 'browser' },
  { name: 'SeaMonkey'  , type: 'browser' },
  { name: 'Thunderbird', type: 'mailer' }
], {
  get: function(obj, prop) {
    // 默认行为是返回属性值, prop ?通常是一个整数
    if (prop in obj) {
      return obj[prop];
    }


    // 获取 products 的 number; 它是 products.length 的别名
    if (prop === 'number') {
      return obj.length;
    }


    let result, types = {};

    // 这个for循环是为了遍历数组中的name来获取对应的product
    for (let product of obj) {
      if (product.name === prop) {
        result = product;
      }
      if (types[product.type]) {
        types[product.type].push(product);
      } else {
        types[product.type] = [product];
      }
    }


    // 通过 name 获取 product
    if (result) {
      return result;
    }


    // 通过 type 获取 products
    if (prop in types) {
      return types[prop];
    }


    // 获取 product type
    if (prop === 'types') {
      return Object.keys(types);
    }


    return undefined;
  }
});


console.log(products[0]); // { name: 'Firefox', type: 'browser' }
console.log(products['Firefox']); // { name: 'Firefox', type: 'browser' }
console.log(products['Chrome']); // undefined
console.log(products.browser); // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]
console.log(products.types); // ['browser', 'mailer']
console.log(products.number); // 3
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值