ES6——Reflect 与 Proxy

ES6 之 Proxy 介绍
深入实践 ES6 Proxy & Reflect

1.Proxy

Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。

  • 使用
    var p = new Proxy(target, handler);
  • target 为被代理对象。handler 是一个对象,其声明了代理 target 的一些操作。p 是代理后的对象。
var target = {
   name: 'obj'
 };
 var logHandler = {
   get: function(target, key) {
     console.log(`${key} 被读取`);
     return target[key];
   },
   set: function(target, key, value) {
     console.log(`${key} 被设置为 ${value}`);
     target[key] = value;
   }
 }
 var targetWithLog = new Proxy(target, logHandler);
 targetWithLog.name; // 控制台输出:name 被读取
 targetWithLog.name = 'others'; // 控制台输出:name 被设置为 others
console.log(target.name); // 控制台输出: others
targetWithLog 读取属性的值时,实际上执行的是 logHandler.get :在控制台输出信息,并且读取被代理对象 target 的属性。
在 targetWithLog 设置属性值时,实际上执行的是 logHandler.set :在控制台输出信息,并且设置被代理对象 target 的属性的值。

  • handler没有设置任何拦截,那就等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
  • get()
    方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
var person = {
  name: "张三"
};

var proxy = new Proxy(person, {
  get: function(target, property) {
    if (property in target) {
      return target[property];
    } else {
      throw new ReferenceError("Property \"" + property + "\" does not exist.");
    }
  }
});

proxy.name // "张三"
proxy.age // 抛出一个错误

利用get拦截,实现一个生成各种 DOM 节点的通用函数dom

const dom = new Proxy({}, {
    get(target, property) {
      return function(attrs = {}, ...children) {
        const el = document.createElement(property);
        for (let prop of Object.keys(attrs)) {
          el.setAttribute(prop, attrs[prop]);
        }
        for (let child of children) {
          if (typeof child === 'string') {
            child = document.createTextNode(child);
          }
          el.appendChild(child);
        }
        return el;
      }
    }
  });
  
  const el = dom.div({},
    'Hello, my name is ',
    dom.a({href: '//example.com'}, 'Mark'),
    '. I like:',
    dom.ul({},
      dom.li({}, 'The web'),
      dom.li({}, 'Food'),
      dom.li({}, '…actually that\'s it')
    )
  );
  
console.log(el);
  • set()
    方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
    假定Person对象有一个age属性,该属性应该是一个不大于 200 的整数,那么可以使用Proxy保证age的属性值符合要求。
let validator = {
    set: function(obj, prop, value) {
      if (prop === 'age') {
        if (!Number.isInteger(value)) {
          console.log('The age is not an integer');
          return;
        }
        if (value > 200) {
            console.log('The age seems invalid');
            return;
        }else{
            obj[prop] = value;
        }
      }
    }
  };
  
  let person = new Proxy({}, validator);
  
  person.age = 100;
  person.age = 'young' // 报错
  person.age = 300 // 报错
  console.log(person.age);

防止带_的内部属性被外部读写

const handler = {
  get (target, key) {
    invariant(key, 'get');
    return target[key];
  },
  set (target, key, value) {
    invariant(key, 'set');
    target[key] = value;
    return true;
  }
};
function invariant (key, action) {
  if (key[0] === '_') {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`);
  }
}
const target = {};
const proxy = new Proxy(target, handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property
  • apply()
    apply方法拦截函数的调用、call和apply操作
    apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组
var twice = {
  apply (target, ctx, args) {
    return Reflect.apply(...arguments) * 2;
  }
};
function sum (left, right) {
  return left + right;
};
var proxy = new Proxy(sum, twice);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30
  • construct()
    construct方法用于拦截new命令,下面是拦截对象的写法
    construct方法可以接受两个参数。
    target:目标对象
    args:构造函数的参数对象
    newTarget:创造实例对象时,new命令作用的构造函数(下面例子的p)
var p = new Proxy(function () {}, {
  construct: function(target, args) {
    console.log('called: ' + args.join(', '));
    return { value: args[0] * 10 };
  }
});

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

2.Reflect

  • Reflect.get(target, name, receiver)
    Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined。
  • Reflect.set(target, name, value, receiver)
    Reflect.set方法设置target对象的name属性等于value


3.联合使用

let exam = {
    name: "Tom",
    age: 24
}
let handler = {
    get: function(target, key){
        console.log("getting "+key);
        return Reflect.get(target,key);
    },
    set: function(target, key, value){
        console.log("setting "+key+" to "+value)
        Reflect.set(target, key, value);
    }
}
let proxy = new Proxy(exam, handler)
proxy.name = "Jerry"
proxy.name
// setting name to Jerry
// getting name
// "Jerry"

对比没有使用Reflect

let target = {
    name: 'Tom',
    age: 24
}
let handler = {
    get: function(target, key) {
        console.log('getting '+key);
        return target[key]; // 不是target.key
    },
    set: function(target, key, value) {
        console.log('setting '+key);
        target[key] = value;
    }
}
let proxy = new Proxy(target, handler)
proxy.name     // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set

4. 使用 Proxy 实现观察者模式

//设置一个set结构存储执行的函数
const queuedObservers = new Set();
//将观察者函数加入set结构
const observe = function(fn){
    return queuedObservers.add(fn);
}
//返回新的对象
const observable = function(obj){
    return new Proxy(obj, {
        set:function(target, key, value, receiver) {   
            Reflect.set(target, key, value, receiver); //设置值
//论个执行函数
            queuedObservers.forEach(function(observer){
                return observer();
            });
          }
    });
}
const person = observable({
    name: '张三',
    age: 20
  });
//观察者函数1
function print() {
    console.log(`${person.name}, ${person.age}`)
}
//观察者函数2
function printTwo() {
    console.log(`${person.name}, ${person.age}`)
}
console.log(observe(print));
console.log(observe(printTwo));
person.name = '李四';


你可能感兴趣的:(ES6——Reflect 与 Proxy)