ES6 Proxy&Reflect

一、Proxy简介

1.概述

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

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

二、Proxy用法

1.基本用法

一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。

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
// getting name
// setting age
// 25
 
// target 可以为空对象
let targetEpt = {
    
    }
let proxyEpt = new Proxy(targetEpt, handler)
// 调用 get 方法,此时目标对象为空,没有 name 属性
proxyEpt.name // getting name
// 调用 set 方法,向目标对象中添加了 name 属性
proxyEpt.name = 'Tom'
// setting name
// "Tom"
// 再次调用 get ,此时已经存在 name 属性
proxyEpt.name
// getting name
// "Tom"
 
// 通过构造函数新建实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相
// 影响
targetEpt)
// {name: "Tom"}
 
// handler 对象也可以为空,相当于不设置拦截操作,直接访问目标对象
let targetEmpty = {
    
    }
let proxyEmpty = new Proxy(targetEmpty,{
    
    })
proxyEmpty.name = "Tom"
targetEmpty) // {name: "Tom"}

2.Proxy实例方法

  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]
//如果访问目标对象不存在的属性,会抛出一个错误
var person = {
    
    
  name: "张三"
};

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

proxy.name // "张三"
proxy.age // 抛出一个错误
//get方法可以继承
let proto = new Proxy({
    
    }, {
    
    
  get(target, propertyKey, receiver) {
    
    
    console.log('GET ' + propertyKey);
    return target[propertyKey];
  }
});

let obj = Object.create(proto);
obj.foo // "GET foo"
  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值
//限制age属性,该属性应该是一个不大于 200 的整数
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');
      }
    }
    // 对于满足条件的 age 属性以及其他属性,直接保存
    obj[prop] = value;
  }
};

let person = new Proxy({
    
    }, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错
//属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合get和set方法,就可以做到防止这些内部属性被外部读写。
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
  • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值
let handler = {
    
    
    has: function(target, propKey){
    
    
        console.log("handle has");
        return propKey in target;
    }
}
let exam = {
    
    name: "Tom"}
let proxy = new Proxy(exam, handler)
'name' in proxy
// handle has
// true
  • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值
  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值
  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象
  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截
  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)
function sub(a, b){
    
    
    return a - b;
}
let handler = {
    
    
    apply: function(target, ctx, args){
    
    
        console.log('handle apply');
        return Reflect.apply(...arguments);
    }
}
let proxy = new Proxy(sub, handler)
proxy(2, 1) 
// handle apply
// 1
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)

三、Reflect简介

1.概述

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API

2.Reflect对象的设计目的

  • 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上
  • 修改某些Object方法的返回结果,让其变得更合理。
  • 让Object操作都变成函数行为。
  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。

四、Reflect用法

1.静态方法

Reflect对象一共有 13 个静态方法。

  • Reflect.get(target, name, receiver):查找并返回target对象的name属性。
let exam = {
    
    
    name: "Tom",
    age: 24,
    get info(){
    
    
        return this.name + this.age;
    }
}
Reflect.get(exam, 'name'); // "Tom"
 
// 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定 // receiver
let receiver = {
    
    
    name: "Jerry",
    age: 20
}
Reflect.get(exam, 'info', receiver); // Jerry20
 
// 当 name 为不存在于 target 对象的属性时,返回 undefined
Reflect.get(exam, 'birth'); // undefined
 
// 当 target 不是对象时,会报错
Reflect.get(1, 'name'); // TypeError
  • Reflect.set(target, name, value, receiver):设置target对象的name属性等于value。返回值为 boolean ,true 表示修改成功,false 表示失败。
let exam = {
    
    
    name: "Tom",
    age: 24,
    set info(value){
    
    
        return this.age = value;
    }
}
exam.age; // 24
Reflect.set(exam, 'age', 25); // true
exam.age; // 25
 
// value 为空时会将 name 属性清除
Reflect.set(exam, 'age', ); // true
exam.age; // undefined
 
// 当 target 对象中存在 name 属性 setter 方法时,setter 方法中的 this 会绑定 // receiver , 所以修改的实际上是 receiver 的属性,
let receiver = {
    
    
    age: 18
}
Reflect.set(exam, 'info', 1, receiver); // true
receiver.age; // 1
 
let receiver1 = {
    
    
    name: 'oppps'
}
Reflect.set(exam, 'info', 1, receiver1);
receiver1.age; // 1
  • Reflect.has(target, name):用于查找 name 属性在 obj 对象中是否存在。
let exam = {
    
    
    name: "Tom",
    age: 24
}
Reflect.has(exam, 'name'); // true
  • Reflect.defineProperty(target, name, desc):用于为目标对象定义属性。如果 target 不是对象,会抛出错误。
const student = {
    
    };
Reflect.defineProperty(student, "name", {
    
    value: "Mike"}); // true
student.name; // "Mike"
  • Reflect.deleteProperty(target, name):用于删除 obj 对象的 property 属性,返回值为 boolean
let exam = {
    
    
    name: "Tom",
    age: 24
}
Reflect.deleteProperty(exam , 'name'); // true
exam // {age: 24} 
// property 不存在时,也会返回 true
Reflect.deleteProperty(exam , 'name'); // true
  • Reflect.getPrototypeOf(obj):用于读取 obj 的 proto 属性。
class Exam{
    
    }
let obj = new Exam()
Reflect.getPrototypeOf(obj) === Exam.prototype // true

//扩展: _proto_与prototype
对象的_proto_属性,和创建这个对象的构造函数的prototype是一个东西。
function  Fn(){
    
    }
var o=new Fn();
console.log(o._proto_===Fn.prototype);//true
  • Reflect.setPrototypeOf(obj, newProto):用于设置目标对象的 prototype。
let obj ={
    
    }
Reflect.setPrototypeOf(obj, Array.prototype); // true
  • Reflect.apply(target, thisArg, args):等同于 Function.prototype.apply.call(func, thisArg, args) 。func 表示目标函数;thisArg 表示目标函数绑定的 this 对象;args 表示目标函数调用时传入的参数列表,可以是数组或类似数组的对象。
Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1]); // 5
  • Reflect.construct(target, args):等同于 new target(…args)
function exam(name){
    
    
    this.name = name;
}
Reflect.construct(exam, ['Tom']); // exam {name: "Tom"}
  • Reflect.ownKeys(target):用于返回 target 对象的所有属性,等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和。
var exam = {
    
    
  name: 1,
  [Symbol.for('age')]: 4
}
Reflect.ownKeys(exam) // ["name", Symbol(age)]
  • Reflect.isExtensible(target):用于判断 target 对象是否可扩展。返回值为 boolean 。
let exam = {
    
    }
Reflect.isExtensible(exam) // true
  • Reflect.preventExtensions(target):用于让 target 对象变为不可扩展。
let exam = {
    
    }
Reflect.preventExtensions(exam) // true
  • Reflect.getOwnPropertyDescriptor(target, name):用于得到 target 对象的 propertyKey 属性的描述对象。
var exam = {
    
    }
Reflect.defineProperty(exam, 'name', {
    
    
  value: true,
  enumerable: false,
})
Reflect.getOwnPropertyDescriptor(exam, 'name')
// { configurable: false, enumerable: false, value: true, writable:
// false}

五、Proxy&Reflect组合使用

1.基础样例

Reflect 对象的方法与 Proxy 对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。

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"

2.实现观察者模式

观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。

//数据对象person是观察目标,函数print是观察者。
const person = observable({
    
    
  name: '张三',
  age: 20
});

function print() {
    
    
  console.log(`${
      
      person.name}, ${
      
      person.age}`)
}

observe(print);
person.name = '李四';
// 自动输出:李四, 20

下面,使用 Proxy 写一个观察者模式的最简单实现,即实现observable和observe这两个函数。思路是observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数。

const queuedObservers = new Set();

const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {
    
    set});

function set(target, key, value, receiver) {
    
    
  const result = Reflect.set(target, key, value, receiver);
  queuedObservers.forEach(observer => observer());
  return result;
}

https://es6.ruanyifeng.com/#docs/proxy
https://www.runoob.com/w3cnote/es6-reflect-proxy.html

猜你喜欢

转载自blog.csdn.net/qq_41466440/article/details/112302108
今日推荐