ES6 Proxy&Reflect

1. Introduction to Proxy

1 Overview

Proxy is used to modify the default behavior of certain operations, which is equivalent to making changes at the language level, so it belongs to a kind of "meta programming" (meta programming), that is, programming a programming language.

Proxy can intercept operations such as reading the target object and function calls, and then perform operation processing. It does not directly manipulate the object, but like a proxy mode, it operates through the proxy object of the object. When performing these operations, you can add some additional operations that you need.

Two, Proxy usage

1. Basic usage

A Proxy object consists of two parts: target and handler. These two parameters need to be provided when generating an instance object through the Proxy constructor. The target is the target object, and the handler is an object that declares the specified behavior of the proxy 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 instance method

  • get(target, propKey, receiver): intercept the reading of object properties, such as proxy.foo and 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): intercept the setting of object properties, such as proxy.foo = v or proxy['foo'] = v, return a Boolean value
//限制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): intercept the operation of propKey in proxy and return a Boolean value
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): intercept the operation of delete proxy[propKey] and return a Boolean value
  • ownKeys(target): intercept Object.getOwnPropertyNames(proxy), Object.getOwnPropertySymbols(proxy), Object.keys(proxy), for...in loop and return an array. This method returns the property names of all its own properties of the target object, and the return result of Object.keys() only includes the traversable properties of the target object itself
  • getOwnPropertyDescriptor(target, propKey): intercept Object.getOwnPropertyDescriptor(proxy, propKey), return the description object of the property
  • defineProperty(target, propKey, propDesc): intercept Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDescs), return a Boolean value
  • preventExtensions(target): intercept Object.preventExtensions(proxy), return a Boolean value
  • getPrototypeOf(target): intercept Object.getPrototypeOf(proxy) and return an object
  • isExtensible(target): intercept Object.isExtensible(proxy) and return a Boolean value
  • setPrototypeOf(target, proto): intercept Object.setPrototypeOf(proxy, proto) and return a Boolean value. If the target object is a function, then there are two additional operations that can be intercepted
  • apply(target, object, args): intercept Proxy instance as a function call operation, such as 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): intercept the operation called by the Proxy instance as the constructor, such as new proxy(...args)

Three, Reflect introduction

1 Overview

The Reflect object, like the Proxy object, is also a new API provided by ES6 to manipulate objects

2. The design purpose of the Reflect object

  • Put some methods (such as Object.defineProperty) of the Object object that are obviously internal to the language on the Reflect object
  • Modify the return results of some Object methods to make them more reasonable.
  • Let Object operations become functional behaviors.
  • The methods of the Reflect object have a one-to-one correspondence with the methods of the Proxy object. As long as it is a method of the Proxy object, the corresponding method can be found on the Reflect object.

Four, Reflect usage

1. Static method

The Reflect object has 13 static methods in total.

  • Reflect.get(target, name, receiver): Find and return the name attribute of the target object.
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): Set the name attribute of the target object to value. The return value is boolean, true means the modification is successful, false means failure.
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): used to find whether the name attribute exists in the obj object.
let exam = {
    
    
    name: "Tom",
    age: 24
}
Reflect.has(exam, 'name'); // true
  • Reflect.defineProperty(target, name, desc): Used to define properties for the target object. If target is not an object, an error will be thrown.
const student = {
    
    };
Reflect.defineProperty(student, "name", {
    
    value: "Mike"}); // true
student.name; // "Mike"
  • Reflect.deleteProperty(target, name): used to delete the property property of the obj object, the return value is 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): used to read the proto property of obj .
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): used to set the prototype of the target object.
let obj ={
    
    }
Reflect.setPrototypeOf(obj, Array.prototype); // true
  • Reflect.apply(target, thisArg, args): equivalent to Function.prototype.apply.call(func, thisArg, args). func represents the target function; thisArg represents the this object bound by the target function; args represents the parameter list passed in when the target function is called, which can be an array or an array-like object.
Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1]); // 5
  • Reflect.construct(target, args): equivalent to new target(...args)
function exam(name){
    
    
    this.name = name;
}
Reflect.construct(exam, ['Tom']); // exam {name: "Tom"}
  • Reflect.ownKeys(target): used to return all the properties of the target object, equivalent to the sum of Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
var exam = {
    
    
  name: 1,
  [Symbol.for('age')]: 4
}
Reflect.ownKeys(exam) // ["name", Symbol(age)]
  • Reflect.isExtensible(target): used to determine whether the target object is extensible. The return value is boolean.
let exam = {
    
    }
Reflect.isExtensible(exam) // true
  • Reflect.preventExtensions(target): Used to make the target object non-extensible.
let exam = {
    
    }
Reflect.preventExtensions(exam) // true
  • Reflect.getOwnPropertyDescriptor(target, name): used to get the description object of the propertyKey property of the target object.
var exam = {
    
    }
Reflect.defineProperty(exam, 'name', {
    
    
  value: true,
  enumerable: false,
})
Reflect.getOwnPropertyDescriptor(exam, 'name')
// { configurable: false, enumerable: false, value: true, writable:
// false}

Five, Proxy&Reflect combined use

1. Basic example

There is a one-to-one correspondence between the methods of the Reflect object and the methods of the Proxy object. So the methods of the Proxy object can get the default behavior by calling the methods of the Reflect object, and then perform additional operations.

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. Implement the observer mode

Observer mode refers to the function that automatically observes the data object. Once the object changes, the function will automatically execute.

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

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

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

Next, use Proxy to write the simplest implementation of the observer pattern, which implements the two functions of observable and observe. The idea is that the observable function returns a Proxy proxy of the original object, intercepts assignment operations, and triggers various functions that act as observers.

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

Guess you like

Origin blog.csdn.net/qq_41466440/article/details/112302108