Proxy

  • 1.get(target, propKey, receiver)

    Intercepts reading of object properties such as proxy.foo and proxy[‘foo’]

    The last parameter, Receiver, is an optional object, as shown in the reflect.get section below.

    const _obj = { name: 'veloma' };
    const obj = new Proxy(_obj, {
        get: function(target, propKey, receiver) {
            if (propKey in target) return target[propKey];
            throw new Error(`Property ${propKey} does not exist.`); }}); obj.name// 'veloma'
    obj.age // Error: Property age does not exist.
    Copy the code

    To explain this code, it throws an error if you access properties that do not exist on the target object. Without this interceptor, an access to a nonexistent property would only return undefined

    The get method can be inherited

    const proto = new Proxy({}, {
        get(target, propertyKey, receiver) {
            console.log('GET' + propertyKey);
            returntarget[propertyKey]; }});const obj = Object.create(proto);
    obj.name; // GET name
    Copy the code

    But if I do that, the get method won’t listen

    const obj = Object.create(proto);
    obj.name = 'veloma';
    obj.name; // This will execute the get method
    Copy the code

    What if we want him to do it?

    const proto = new Proxy({}, {
        get(){... },set(target, propertyKey, value, receiver) {
            target[propertyKey] = value;
            return true; }});const obj = Object.create(proto);
    obj.name = 'veloma';
    obj.name; // GET name
    Copy the code

    Here we need to modify proto by adding a set method to it.

    🌰: Use get interception, to achieve the index of the array to read negative numbers.

    function craeteArray(. elements) {
        let handler = {
            get(target, propKey, receiver) {
                let index = Number(propKey);
                if (index < 0) index = target.length + index;
                return Reflect.get(target, index, receiver); }};consttarget = []; target.push(... elements);return new Proxy(target, handler);
    }
    
    const arr = createArray('a'.'b'.'c');
    arr[-1]; // c
    Copy the code

    Get (target, propKey, receiver). If the array position is -1, it will print the last member of the inverse of the array. There is one line of code that doesn’t say reflect.get (target, propKey, receiver). All you need to know is that it returns some index in the array, which we’ll see in Reflect below.

    Using a proxy, you can turn the operation of reading a property (get) into the execution of a function, thus realizing the chain operation of the property.

    const global = {
      double: n= > n * 2.pow: n= > n * n,
      reverseInt: n= > n.toString().split(' ').reverse().join(' ') | 0
    };
    
    const pipe = (function () {
        return function (value) {
            const funcStack = [];
            const proxy = new Proxy({}, {
                get(pipeObject, fnName) {
                    if (fnName === 'get') {
                        return funcStack.reduce((val, fn) = > fn(val), value);
                    }
                    funcStack.push(global[fnName]);
                    returnproxy; }});return proxy;
        }
    }());
    
    pipe(3).double.pow.reverseInt.get; / / 63
    Copy the code

    Let’s explain this code

    1. callpipewillvalue=3Pass it in, it’s not going to executeproxy, in the end will beproxyTo return.
    2. At this timepipe(3)Is aproxyWhen the calldoubleMethod is executedproxy çš„getMethod, when executedgetWhen it comes to methods,pipeObjectHas been to{}And at the same timefnNameIt isdoubleTo determinefnNameIf it is not ‘get’, the method will bepushtofuncStackAnd then returnproxy .
    3. At this timepipe(3).doubleIs still aproxyWhen the callpowMethod will also be executedproxythegetMethod, and at this pointfnNameispow.fnNameIt’s still not ‘get’, so it’s going to bepowmethodspushtofuncStackAnd then returnproxy .
    4. At this timepipe(3).double.powIs stillproxy, and then callreverseIntMethod, execution result anddoubleMethods andpowThe same approach.
    5. At the end of the callgetMethod, willfuncStack, and return the final result. (The final result returned is notproxy).

    After setting the proxy, the code above achieves the result of chaining the function name.

    🌰: Use get interception to achieve a general-purpose function DOM that generates various DOM nodes.

    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);
                }
                returnel; }}});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')));Copy the code

    This one up here, which is a little bit easier, is essentially just writing it like this

    function createElement(property, 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 = createElement('div', {}, 
    	'Hello, my name is ',
    	createElement('a', {hreft: '//example.com'}, 'Mark'),
    	createElement('ul', {}, 
                createElement('li', {}, 'The web'),
                createElement('li', {}, 'Food'),
                createElement('li', {}, '... actually that\'s it')));Copy the code
  • 2.set(target, propKey, value, receiver)

    Intercepts object property Settings, such as proxy.foo = v or proxy[‘foo’] = v, and returns a Boolean value.

    🌰: Assuming the Person object has an age property, which should be an integer less than 150, you can use Proxy to ensure that the age property meets the requirements.

    const validator = {
        set(target, key, value) {
            if (key === 'age') {
                // The isInteger method is used to determine whether a value is an integer.
                // Note that the value must be of type number, not string
                if (!Number.isInteger(value)) {
                    throw new TypeError('The age is not an integer');
                }
                if (value > 150) {
                    throw new RangeError('The age seems invalid');
                }
            }
            target[key] = value;
            return true; }};const person = new Proxy({}, validator);
    
    person.age = 100;
    person.age; / / 100
    person.age = 'young'; // TypeError: The age is not an integer
    person.age = 200; // RangeError: The age seems invalid
    Copy the code

    To explain this code, since we set the store function set, any incorrect age attribute assignment will throw an error. Using the set method, you can also use data binding, which automatically updates the DOM when an object changes.

    We can also combine get and set methods to make private properties

    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 = { name: 'veloma'._name: 'timer' };
    const proxy = new Proxy(target, handler);
    proxy.name; // veloma
    proxy.name = 'timer';
    proxy._name; // Error: Invalid attempt to get private "_name" property
    proxy._name = 'veloma'; // Error: Invalid attempt to set private "_name" property
    Copy the code

    To explain this code, the first character of the property name that is read or written is an underscore, so as to prevent reading or writing internal properties.

  • 3.has(target, propKey)

    The has method is used to intercept HasProperty operations, that is, to determine whether an object has a property. The typical operation is the in operator (intercepting the propKey in Proxy operation and the object’s hasOwnProperty method, returning a Boolean value).

    🌰: Use the has method to hide certain properties from the in operator.

    const handler = {
        has(target, key) {
            if (key[0= = ='_') return false;
            return key intarget; }};const target = { _prop: 'foo'.prop: 'foo' };
    
    const proxy = new Proxy(target, handler);
    
    '_prop' in proxy; // false
    'prop' in proxy; // true
    Copy the code

    To explain this code, if the property name starts with underscore _, proxy.has will return false, thus undetected by the in operator.

    This is a has interception error if the original object is not configurable or the extension is disabled

    const obj = { a: 10 };
    Object.preventExtensions(obj);
    
    const p = new Proxy(obj, {
        has: (target, prop) = > false
    });
    
    'a' in p; // TypeError is thrown
    Copy the code

    In the above code, the obj object is not allowed to extend, resulting in an error using has interception.

    Note: The has method intercepts HasProperty operations, not HasOwnProperty operations. That is, the has method does not determine whether a property is a property of the object itself or an inherited property. In addition, although for… The in loop also uses the in operator, but has intercepts the for… The in loop does not take effect.

    const stu1 = { name: 'Joe'.score: 59 };
    const stu2 = { name: 'bill'.score: 99 };
    
    const handler = {
        has(target, prop) {
            if (prop === 'score' && target[prop] < 60) {
                console.log(`${target.name}Fail `);
                return false;
            }
            return prop intarget; }}const proxy1 = new Proxy(stu1, handler);
    const proxy2 = new Proxy(stu2, handler);
    
    'score' in proxy1; // False
    'score' in proxy2; // true
    
    for (let a in proxy1) {
        console.log(proxy1[a]);
    }
    / / zhang SAN
    / / 59
    
    for (let b in proxy2) {
        console.log(proxy2[b]);
    }
    / / li si
    / / 99
    Copy the code

    In the above code, the has interception is only valid for in loops, for for… The in loop does not take effect, resulting in unqualified properties not being excluded from the for… Outside of the loop.

  • 4.deleteProperty(target, propKey)

    The deleteProperty method is used to intercept delete operations. If this method throws an error or returns false, the current property cannot be deleted by the delete command (intercepting delete proxy[propKey] and returning a Boolean value).

    const handler = {
        deleteProperty(target, key) {
            invariant(key, 'delete');
            return true; }}function invariant(key, action) {
        if (key[0= = ='_') {
            throw new Error(`Invalid attempt to ${action} private "${key}" property`); }}const target = { _prop: 'foo' };
    const proxy = new Proxy(target, handler);
    	delete proxt._prop; // Invalid attempt to delete private "_prop" property
    
    Copy the code

    In the above code, the deleteProperty method intercepts the delete operator. Deleting a property whose first character is an underscore will result in an error.

  • 5.ownKeys(target)

    Interception Object. GetOwnPropertyNames (proxy), Object. GetOwnPropertySymbols (proxy), the Object. The keys (proxy), and returns an array. This method returns all the properties of the Object itself, whereas object.keys () returns only the properties that the Object can traverse.

    const target = {};
    
    const handler = {
        ownKeys(target) {
            return ['hello'.'world']; }};const proxy = new Proxy(target, handler);
    Object.keys(proxy); // ['hello', 'world']
    Copy the code

    The above code intercepts the object. keys operation on the target Object and returns a predefined array.

    The following example intercepts property names whose first character is an underscore.

    const target = {
        _bar: 'foo'._prop: 'bar'.prop: 'baz'
    };
    
    const handler = {
        ownKeys(target) {
            return Reflect.ownKeys(target).filter(key= > key[0]! = ='_'); }}const proxy = new Proxy(target, handler);
    for (let key of Object.keys(proxy)) {
        console.log(target[key]);
    }
    // baz
    Copy the code
  • 6.getOwnPropertyDescriptor(target, propKey)

    GetOwnPropertyDescriptor methods intercept Object. GetOwnPropertyDescriptor, Returns a property description Object, or undefined (interception Object. GetPropertyDescriptor (target, propKey)).

    const handler = {
        getOwnPropertyDescriptor(target, key) {
            if (key[0= = ='_') {
                return;
            }
            return Object.getOwnPropertyDescriptor(target, key); }};const target = { _foo: 'bar'.baz: 'tar' };
    const proxy = new Proxy(target, handler);
    Object.getOwnPropertyDescriptor(proxy, 'wat'); // undefined
    Object.getOwnPropertyDescriptor(proxy, '_foo'); // undefined
    Object.getOwnPropertyDescriptor(proxy, 'baz'); // { value: 'tar', writable: true, enumerable: true, configurable: true }
    Copy the code

    The above code, the handler. GetOwnPropertyDescriptor method for the first character to underline the property name will return to the undefined.

  • 7.defineProperty(target, propKey, propDesc)

    The defineProperty method intercepts the object.defineProperty (proxy, propKey, PropDesc), object.defineProperties (proxy, propDescs), returning a Boolean value).

    const handler = {
        defineProperty(target, key, descriptor) {
            return false; }}const target = {};
    const proxy = new Proxy(target, handler);
    proxy.foo = 'bar'; // TypeError: proxy defineProperty handler returned false for property '"foo"'
    Copy the code

    In the above code, the defineProperty method returns false, causing the addition of new attributes to throw an error.

  • 8.preventExtensions(target)

    PreventExtensions methods intercept Object. PreventExtensions. The method must return a Boolean value. This method has a limitation that proxy.preventExtensions will only return true if object.isextensible (proxy) is false(i.e. non-extensible). Otherwise, an error will be reported (intercept object.preventExtensions (proxy) and return a Boolean).

    const proxy = new Proxy({}, {
        preventExtensions(target) {
            return true; }});Object.preventExtensions(proxy); / / an error
    Copy the code

    In the code above, the proxy.preventExtensions method returns true, but object.isextensible (proxy) returns true, so an error is reported.

    In order to prevent this problem, usually in the proxy. PreventExtensions method, called an Object. PreventExtensions.

    const proxy = new Proxy({}, {
        preventExtensions(target) {
            console.log('called');
            Object.preventExtensions(target);
            return true; }});Object.preventExtensions(proxy); // called true
    Copy the code
  • 9.getPrototypeOf(target)

    The getPrototypeOf method is primarily used to intercept the object.getPrototypeof () operator, among other operations (intercepting object.getPrototypeof (proxy) and returning an Object).

    • Object.prototype.__proto__
    • Object.prototype.isPrototypeOf()
    • Object.getPrototypeOf()
    • Reflect.getPrototypeOf()
    • instanceThe operator

    To a 🌰 :

    const proto = {};
    const proxy = new Proxy({}, {
        getPrototypeOf(target) {
            returnproto; }});Object.getPrototypeOf(proxy) === proto; // true
    Copy the code

    In the above code, the getPrototypeOf method intercepts object.getPrototypeof (), returning the Proto Object

  • 10.isExtensible(target)

    Intercepts Object.isextensible (proxy) and returns a Boolean value.

    const proxy = new Proxy({}, {
        isExtensible(target) {
            console.log('calle');
            return true; }});Object.isExtensible(proxy); // called true
    Copy the code

    The above code sets the isExtensible method and prints called when calling object. isExtensible.

    This place has a strong constraint that will throw an error if the following conditions are not met.

    Object.isExtensible(proxy) === Object.isExtensible(target)
    Copy the code

    To a 🌰 :

    const proxy = new Proxy({}, {
        isExtensible(target) {
            return false; }});Object.isExtensible(proxy); / / an error
    Copy the code
  • 11.setPrototypeOf(target, proto)

    Intercepts object.setPrototypeof (proxy, proto) and returns a Boolean value.

    If the target object is a function, there are two additional operations that can be intercepted.

    const handler = {
        setPropertyOf(target, proto) {
            throw new Error('Changing the prototype is forbidden'); }}const proto = {};
    const target = function() {};
    const proxy = new Proxy(target, handler);
    proxy.setPrototypeOf(proxy, proto); // Error: Changing the prototype is forbidden
    Copy the code

    In the above code, whenever you modify the target prototype object, an error is reported.

  • 12.apply(target, object, args)

    The apply method intercepts function calls, calls, and apply operations for proxy instances, such as proxy(… The args), proxy. Call (object,… The args), proxy. Apply (…).

    const handler = {
        apply(target, ctx, args) {
            return Reflect.apply(...arguments);
        }
    }
    Copy the code

    The apply method can take three functions: the target object (the function itself), the context object for the target object (this), and an array of parameters for the target object

    To a 🌰 :

    const target = function () { return 'I am the veloma' };
    const handler = {
        apply: () = > 'I am the timer'
    };
    
    const p = new Proxy(target, handler);
    
    p(); // I am the timer
    Copy the code

    In the above code, the variable p is an instance of Proxy, and when it is called as a function (p()), it is intercepted by the apply method and returns a string.

    Take another example 🌰:

    const twice = {
        apply(target, ctx, args) {
            return Reflect.apply(... arguments) *2; }};function sum(left, right) {
        return left + right;
    }
    
    const proxy = new Proxy(sum, twice);
    proxy(1.2); / / 6
    proxy.call(null.5.6); / / 22
    proxy.apply(null[7.8]); / / 30
    Copy the code

    In the code above, whenever a proxy function is executed (either directly or as a call and apply call), it is intercepted by the apply method.

    Also, direct calls to the reflect.apply method are intercepted

    Reflect.apply(proxy, null[9.10]); / / 38
    Copy the code
  • 13.constructor(target, args)

    The constructor method is used to intercept new commands, (intercepting a Proxy instance as a constructor call, such as new Proxy (… args).)

    writing

    const handler = {
        constructor(target, args, newTarget) {
            return new target(...args);
        }
    }
    Copy the code

    The constructor method takes two arguments.

    • target: Target object
    • args: the argument object to the constructor

    To a 🌰 :

    const proxy = new Proxy(function() {}, {
        constructor(target, args) {
            console.log(`called ${args.join(', ')}`);
            return { value: args[0] * 10}; }});new proxy(1).value; // called: 1 10
    Copy the code

    The constructor method must return an object, otherwise an error will be reported

    const proxy = new Proxy(function() {}, {
        constructor(target, argumentsList) {
            return 1; }});new proxy(); / / an error
    Copy the code
  • 14.Proxy.revocable()

    The proxy. revocable method returns a cancelable Proxy instance

    const target = {};
    const handler = {};
    
    const { proxy, revoke } = Proxy.revocable(target, handler);
    
    proxy.foo = 123;
    proxy.foo; / / 123
    
    revoke();
    proxy.foo; // TypeError: Revoked
    Copy the code

    The proxy. revocable method returns an object whose Proxy property is a Proxy instance and revoke is a function that can cancel the Proxy instance. In the above code, when accessing the Proxy instance after executing REVOKE, an error is thrown (if the original target object is accessed).

  • This problem

    • In one sentence, the original target is not equal to the Proxy after the Proxy.
    • There are some native object internal properties that only pass correctlythisSo the Proxy cannot delegate the properties of these native objects. See 🌰 below
    const target = new Date(a);const handler = {};
    const proxy = new Proxy(target, handler);
    proxy.getDate(); // TypeError: this is not a Date object.
    Copy the code

    In this code, the getDate method can only be obtained on the Date object instance. If this is not a Date object instance, an error will be reported. In this case, this binds to the original object, which solves the problem.

    const date = new Date('2020-01-01');
    const handler = {
        get(target, prop) {
            if (prop === 'getDate') {
                return target.getDate.bind(date);
            }
            return Reflect.get(target, prop); }};const proxy = new Proxy(date, handler);
    
    proxy.getDate(); / / 1
    
    Copy the code

Reflect

The design purpose of the Reflect object

  1. willObjectObject methods that are clearly internal to the language (e.gObject.defineProperty), inReflectObject. At this stage, some methods are simultaneouslyObjectandReflectObject, future new methods will only be deployed onReflectOn the object.
  2. Modify someObjectMethod to make it more reasonable, such asObject.defineProperty(obj, name, desc)When an attribute cannot be defined, an error is thrown, andReflect.defineProperty(obj, name, desc)Will returnfalse.
  3. letObjectOperations become function behavior. Some of theObjectOperations are imperative, for examplename in obj 和 delete obj[name]And theReflect.has(obj, name) 和 Reflect.deleteProperty(obj, name)It turns them into functional behavior.
  4. ReflectObject methods andProxyObject, as long as theProxyObject methods can be used inReflectObject to find the corresponding method. This makesProxyObject can conveniently call the correspondingReflectMethod to complete the default behavior as the basis for modifying the behavior. In other words, regardlessProxyHow to modify the default behavior that you can always do inReflectGets the default behavior on

Reflect object method

Here is a list of 13 methods on the Reflect object.

  • Reflect.apply(fn, thisArg, args)

    Equal to the Function. The prototype. Apply. Call (fn, thisArg, args). In general, if you want to bind a Function of this object, you can write fn. Apply (obj, args), but if a Function defined my own way to apply, you can only write the Function. The prototype. Apply. Call (fn, obj. Args) with the Reflect object to simplify this operation. . In addition, it is important to note that Reflect the set (), Reflect the defineProperty (), Reflect the freeze (), Reflect the seal () and Reflect preventExtensions () returns a Boolean value, Indicates whether the operation succeeds. Each of their corresponding Object methods will throw an error if they fail.

    // Error thrown on failure
    Object.defineProperty(obj, name, desc);
    // Return false on failure
    Reflect.defineProperty(obj, name, desc);
    Copy the code

    In the code above, the reflect.defineProperty method does the same thing as object.defineProperty in that it defines a property for the Object. However, when the reflect.defineProperty method fails, it does not throw an error and simply returns false.

  • Reflect.construct(target, args)

    Is equivalent to new target(… Args), which provides a way to call the constructor without using new

  • Reflect.get(target, name, receiver)

    Finds and returns the name attribute of the target object, or undefined if it does not exist.

    If a read function is deployed on the name property, the this binding receiver reads the function.

    const obj = {
        get foo() { 
                return this.bar();
        },
        bar: function() { console.log('bar')}}// the following statement will make this.bar()
    // Instead call wrapper.bar()
    Reflect.get(obj, 'foo', wrapper);
    Copy the code
  • Reflect.set(target, name, value, receiver)

    Set the name property of the target object to be equal to value. If the name property sets the assignment function, then the assignment function’s this binding receiver.

  • Reflect.defineProperty(target, name, desc)

  • Reflect.deleteProperty(target, name)

    Delete obj[name]

  • Reflect.has(target, name)

    It’s the same thing as name in obj

  • Reflect.ownKeys(target)

  • Reflect.isExtensible(target)

  • Reflect.preventExtensions(target)

  • Reflect.getOwnPropertyDescriptor(target, name)

  • Reflect.getPrototypeOf(target)

    Read the __proto__ attribute of an Object, corresponding to object.setprototypeof (obj, newProto).

  • Reflect.setPrototypeOf(target, prototype)

  • Example: Implement the observer pattern with Proxy

    Observer mode is when a function automatically observes a data object and executes automatically if the object changes.

    const person = observable({
        name: 'veloma'.age: 22
    });
    
    function print() {
        console.log(`${person.name}.${person.age}`);
    }
    
    observe(print);
    person.name = 'timer'; // Li Si, 20
    Copy the code

    In the above code, the data object Person is the observation object, and the function print is the observer. Once the data object changes, print executes automatically.

    Next, write the simplest implementation of the observer pattern using a Proxy, implementing the observable and Observe functions. The idea is that the Observable function returns a Proxy for the original object, intercepts the assignment, and triggers the observer functions.

    const queuedObservers = new Set(a);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;
    }
    Copy the code

    In the above code, we define a Set to put all the observer functions into. The Observable function then returns the proxy of the original object, intercepting the assignment. In the interception Set, all observers are automatically executed.