32、proxy

参考阮一峰博客:https://es6.ruanyifeng.com/#docs/proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

1、生成Proxy实例

1)ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例

var proxy = new Proxy(target, handler);

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法

  • new Proxy()表示生成一个Proxy实例;
  • target参数表示所要拦截的目标对象;
  • handler参数也是一个对象,用来定制拦截行为

2)实例proxy

  • 调用new Proxy(),可以创建一个代理proxy用来替代拦截后的目标对象target。代理允许拦截目标对象上的底层操作(比如读取或获取target对象的属性值)
  • 没有拦截行为时代理对象p会将所有应用到它的操作转发到目标对象target上。
let target = {};
let p = new Proxy(target, {});

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

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

2、Proxy 支持的部分拦截操作。即handler参数

  1. get(target, propKey, receiver):拦截对象属性的读取。如proxy.foo
  2. set(target, propKey, value, receiver):拦截对象属性的设置,如proxy.foo = v,返回一个布尔值。
  3. apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  4. construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)
  5. has(target, propKey)拦截HasProperty操作,即判断对象是否具有某个属性。
  6. ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。

2.1 get

get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。

1)作用

  1. 可以创造一些本来没有的属性
  2. 可以自定义一些语法糖操作
  3. get返回一个函数的话可以把一个属性转换成方法
  4. 可以在取数据时对数据进行验证和转换

2)针对作用,举例

// 定义要拦截的目标对象
var base = {
  a : 520,
  small : "pink cc!!"
}

// 1. 生成proxy实例。base是所要拦截的目标对象
var proxy = new Proxy( base, {
  //target: 目标对象(如base); property:目标对象的属性名(如a)。
  get(target, property, receiver){ 

    //(1) 创建本来不存在的属性
    if(property === "ghost"){
      return "ghostValue"
    }

    //(2) 自定义语法糖
    if(property.includes("_")){  // 如果属性名包含_,则根据_后面的值进行相应操作
      const active = property.split("_")[1]; //操作
      const prop = property.split("_")[0]; //属性
      switch(active){
        case "Big":
          return receiver[prop].toLocaleUpperCase(); //将属性prop的值转为大写
        default:
          break;
      }
    }

    //(3) 将属性转换为方法。返回函数
    if(property === "fn"){ //若访问属性名fn,返回函数
      return function(value){
        console.log(value)
      }
    }
    
    //(4) 验证属性值
    if(!(property in target)){ // 对象target没有属性property
      throw new ReferenceError("属性"+property+"不存在");
    }

    // 未经操作的属性,则直接返回对象target中对应的属性值。
    return target[property]; //由于property是字符串,只能用[]访问
  }
})

// 1) 创建不存在的属性ghost。
console.log(proxy.ghost);  // ghostValue

// 2) 自定义语法糖,将属性值转化为大写。
console.log(proxy.small_Big); // PINK CC!!

// 3) 将属性转换为方法。返回函数 
console.log(proxy.fn);  // ƒ (value){console.log(value)}
// 执行返回的函数
console.log(proxy.fn("将属性转化为方法")); //将属性转化为方法

// 4) 验证属性值。属性hh不存在,抛出错误:test.html:63 Uncaught ReferenceError: 属性hh不存在
console.log(proxy.hh);

// 访问存在的属性。520
console.log(proxy.a); // 520

2.2 set

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值Proxy 实例本身,其中最后一个参数可选。

注意:set代理应当返回一个布尔值严格模式下,set代理如果没有返回true,就会报错

 1)作用:

  • 可以用来监听数据更改事件
  • 可以用来更改数据格式
  • 防止这些内部属性被外部读写比如以"_"开头的私有变量
  • 可以用来验证属性是否符合要求

2)针对作用,举例:

 const target = {
    age: 18,
    _money : "一千万"
  };

  const proxy = new Proxy(target, {
    set(obj, prop, value, receiver){

      // 1) 屏蔽对私有变量的读写操作。比如以"_"开头的私有变量
      if(prop[0] === "_"){ //私有变量
        throw new Error("禁止读写内部属性:"+ prop);
      }

      // 2) 验证属性值是否符合要求。对年龄属性验证
      if(prop === "age"){
        if(!Number.isInteger(value)){ // 不是数字
          throw new TypeError("年龄不是数字");
        }
        if(value > 200){ // 年龄超过范围
          throw new RangeError("年龄值无效");
        }
      }

      obj[prop] = value; // 对于满足条件的属性直接保存
      return true;
    }
    

  });
  // 1) 验证属性值是否符合要求
  proxy.age = 20; //设置有效的年龄
  console.log(proxy.age); //20

  proxy.age = 201; //设置无效的年龄, 抛出错误 Uncaught RangeError: 年龄值无效

  //2) 屏蔽对私有变量的读写操作
 proxy._money = "一毛"; // 抛出错误 Uncaught Error: 禁止读写内部属性:_money

2.3 apply

apply方法拦截函数的调用、callapply操作。

可以接受三个参数,分别是目标对象、目标对象的上下文对象(this目标对象的参数数组

 举个栗子:
变量p是 Proxy 的实例,当它作为函数调用时p()),就会apply方法拦截,返回一个字符串

  // 目标对象
  var target = function () { 
    return 'I am the target'; 
  };

  // 代理
  var p = new Proxy(target, {
    // apply操作。拦截target函数的调用
    apply: function () {
      return 'I am the proxy';
    }
  });

  // 拦截了target函数的调用。执行了apply中的函数
  console.log(p()) // "I am the proxy"

 2.4 construct

  1. 用于拦截new命令。
  2. construct()方法返回的必须是一个对象,否则会报错。
  3. 另外,由于construct()拦截的是构造函数,所以它的目标对象必须是函数,否则就会报错。
  4. construct()方法中的this指向的是handler,而不是实例对象
  5. 可以接受三个参数
  • target:目标对象。
  • args:构造函数的参数数组
  • newTarget:创造实例对象时,new命令作用的构造函数(下面例子的p

 1)举个栗子:

const p = new Proxy(function () {}, {
  // target:目标对象; args: new对象时传入的参数
  construct: function(target, args) {
    console.log('called: ' + args.join(', ')); // 打印输入的参数
    return { value: args[0] * 10 }; //目标对象的value属性值
  }
});

(new p(1)).value // 10
/*
  "called: 1"
*/ 

3、代理对象proxy 和 拦截操作中的this指向问题

1)代理对象proxy的this指向

一旦目标对象target被代理之后,他的this就指向了代理对象proxy

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true。目标函数的this指向proxy

2)拦截操作中的this指向

  • handler定义的拦截操作中this指向 handler(get()set()拦截函数内部的this,指向的都是handler对象
  • receiver指向的是代理对象proxy
const target = {
    m: 100
  };
  const handler = {
    get(target, property, receiver) {
      console.log(this === handler) // true。 拦截操作中的this指向 handler
      console.log(receiver === proxy) // true。receiver 指向 代理对象proxy
      return target[property]
    }
  };

  const proxy = new Proxy(target, handler);
  console.log(proxy.m)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值