preface

At the Recent VueConf TO 2018 conference in Toronto from November 14 TO 16, You gave a keynote called Vue3.0 Updates, which detailed the plans and direction for the update (check out the full POWERPOINT for those interested). Object.defineproperty has been abandoned in favor of using the faster native Proxy!!

This will remove many of the limitations of the previous object.defineProperty-based implementation in vue2. x: inability to listen for attribute additions and deletions, array indexes and length changes, and support for Map, Set, WeakMap, and WeakSet!

As a “front-end engineer”, it is necessary to amway wave Proxy!!

What is a Proxy?

This is how it is described in MDN — Proxy objects are used to define the custom behavior of basic operations (such as property lookup, assignment, enumeration, function call, etc.).

The official description is so succinct that it’s hard to understand…

In fact, before the operation of the target object to provide interception, you can filter and rewrite the operation of the outside world, modify the default behavior of some operations, so that we can not directly operate the object itself, but through the operation of the object’s proxy object to indirectly operate the object, to achieve the desired purpose ~

What? Not clear? Let’s take a look at an example, at a glance ~

    let obj = {
    	a : 1
    }
    let proxyObj = new Proxy(obj,{
        get : function (target,prop) {
            return prop in target ? target[prop] : 0
        },
        set : function (target,prop,value) {
            target[prop] = 888;
        }
    })
    
    console.log(proxyObj.a);        // 1
    console.log(proxyObj.b);        // 0

    proxyObj.a = 666;
    console.log(proxyObj.a)         // 888

Copy the code

In the example above, we define an object obj, generate a proxyObj object through the Proxy constructor, and modify its set(write) and get (read) behavior.

If we attempt to access a non-existent attribute, 0 will be returned. That is, when we access proxyObj. A, the original object has a attribute, so 1 will be returned. When we attempt to access b, the non-existent attribute, Instead of returning undefined, we return 0. When we try to set a new property value, we always return 888. Therefore, even if we assign proxyobj. a 666, it doesn’t work, it still returns 888!

grammar

The Proxy syntax provided by ES6 natively is simple and can be used as follows:

let proxy = new Proxy(target, handler);

The target argument is a target object wrapped in a Proxy (it can be any type of object, including a native array, a function, or even another Proxy), and the handler argument is an object whose property is a function that defines the behavior of the Proxy when an operation is performed, that is, custom behavior.

Basic usage of the Proxy as above, so different is the difference between the handler object, handler can be an empty object {}, said the Proxy is the target object target operation, namely:

    let obj = {}
    
    let proxyObj = new Proxy(obj,{})
    
    proxyObj.a = 1;
    proxyObj.fn = function () {
        console.log('it is a function')
    }

    console.log(proxyObj.a); // 1
    console.log(obj.a);      // 1
    console.log(obj.fn())    // it is a function
Copy the code

Handler Cannot be set to null. Cannot create proxy with a non-object as target or handler!

In order for Proxy to work, we cannot operate on the object of the original object, that is, the target object (obj object in the example above), we must operate on the Proxy instance (proxyObj object in the example above), otherwise the expected effect will not be achieved, as shown in the initial example. After setting the get method, we try to read a nonexistent property b from obj, and return undefined:

    console.log(proxyObj.b);     // 0
    console.log(obj.b);         // undefined
Copy the code

For an operation that can be set but does not have interception, the result of the proxy object is also applied to the original target object. Again, we redefined the set method so that all property Settings return 888. There is no special interception or processing for a particular property (in this case, obj’s A property). ProxyObj. A = 666 will also apply to the target object (obj object), so the value of obj object a will also change to 888!

proxyObj.a = 666; console.log( proxyObj.a); // 888 console.log( obj.a); / / 888Copy the code

API

ES6 Proxy currently provides 13 kinds of Proxy operations. Below, I will summarize and sort out several commonly used apis. Students who want to know other methods can go to the official website for reference:

–handler.get(target,property,receiver)

Target refers to the target object, Property refers to the acquired attribute name, and Receiver is Proxy or an object inherited from Proxy. In general, it is a Proxy instance.

let proxy = new Proxy({},{
    get : function (target,prop) {
        console.log(`get ${prop}`);
        return 10;
    }
})
    
console.log(proxy.a)    // get a
                        // 10
Copy the code

We intercepted the read get operation on an empty object. When we get the property inside it, we print get ${prop} and return 10.

let proxy = new Proxy({},{
    get : function (target,prop,receiver) {
            return receiver;
        }
    })

console.log(proxy.a)    // Proxy{}
console.log(proxy.a === proxy)  //true
Copy the code

The a attribute of the above proxy object is provided by the proxy object, so the receiver refers to the proxy object, so proxy.a === proxy returns true.

Note that if the target property to be accessed is not writable and configurable, the value returned must be the same as that of the target property, that is, it cannot be modified, otherwise an exception ~ will be thrown

let obj = {};
Object.defineProperty(obj, "a", {
	configurable: false,
	enumerable: false,
	value: 10,
	writable: false
});

let proxy = new Proxy(obj,{
    get : function (target,prop) {
        return 20;
    }
})

console.log(proxy.a)    // Uncaught TypeError

Copy the code

The a attribute in the obj object mentioned above is not writable and configurable. We created a Proxy instance through Proxy and intercepted its GET operation. When we output proxy.a, we will throw an exception. That is 10, which eliminates the exception ~

–handler.set(target, property, value, receiver)

Use to intercept an operation that sets the value of a property. The value of the property to be set is ~

In strict mode, the set method returns a Boolean value, true if the property was set successfully, or false if the property failed, and a TypeError is raised.

let proxy = new Proxy({},{
    set : function (target,prop,value) {
        if( prop === 'count') {if( typeof value === 'number'){
                console.log('success')
            	target[prop] = value;
            }else{
            	throw new Error('The variable is not an integer')
            }
        }
    }
})
    
 proxy.count = '10';    // The variable is not an integer
 
 proxy.count = 10;      // success
Copy the code

If The variable is not an INTEGER, an error will be returned. If The variable is not an integer, an error will be returned. The first time we assign count to the string ’10’, which throws an exception, the second time we assign the number 10, which prints successfully, so we can use the set method to do some data verification!

Similarly, if the target attribute is unwritable and unconfigurable, its value cannot be changed, i.e. the assignment is invalid, as follows:

let obj = {};
Object.defineProperty(obj, "count", {
    configurable: false,
    enumerable: false,
    value: 10,
    writable: false
});

let proxy = new Proxy(obj,{
    set : function (target,prop,value) {
        target[prop] = 20;
    }
})

proxy.count = 20 ;
console.log(proxy.count)   // 10
Copy the code

The count property in the obj object above is set to unmodifiable, and the default value is set to 10, so even if it is assigned to 20, the result will still be the same!

–handler.apply(target, thisArg, argumentsList)

This method is used to intercept a call to a function with three arguments: the target object (function), the called context object thisArg, and the called argument array argumentsList. This method can return any value.

Target must be a function object, otherwise a TypeError will be raised;

function sum(a, b) {
	return a + b;
}

const handler = {
    apply: function(target, thisArg, argumentsList) {
    	console.log(`Calculate sum: ${argumentsList}`); 
    	returntarget(argumentsList[0], argumentsList[1]) * 2; }};letproxy = new Proxy(sum, handler); console.log(sum(1, 2)); // 3 console.log(proxy(1, 2)); Calculate sum: 1,2 // 6Copy the code

In practice, apply also intercepts the function.prototype.apply () and function.prototype.call () of the target object, as well as the reflect.apply () operation, as follows:

console.log(proxy.call(null, 3, 4)); Calculate sum: 3,4 // 14 console.log(Reflect. Apply (proxy, null, [5, 6])); Calculate sum: 5,6 // 22Copy the code

–handler.construct(target, argumentsList, newTarget)

Construct intercepts the new operator. In order for the new operator to work on the generated Proxy object, the target object used to initialize the Proxy must itself have [[construct]] internal methods. It takes three arguments, the target object, the constructor argument list argumentsList, and the constructor used by the new command when the object was first instantiated, p in the following example.

let p = new Proxy(function() {}, {
    construct: function(target, argumentsList, newTarget) {
    	console.log(newTarget === p );                          // true
    	console.log('called: ' + argumentsList.join(', ')); / / called: 1, 2return{ value: ( argumentsList[0] + argumentsList[1] )* 10 }; }}); The console. The log (new p (1, 2). The value). / / 30Copy the code

Also, the method must return an object or an exception will be thrown!

var p = new Proxy(function() {}, {
    construct: function(target, argumentsList, newTarget) {
    	return2}}); The console. The log (new p (1, 2)); // Uncaught TypeErrorCopy the code

–handler.has(target,prop)

The has method can be thought of as a hook for the in operation, which takes effect when we determine whether an object has a property, typically in. The method takes two arguments, target and the property to check, prop, and returns a Boolean value.

let p = new Proxy({}, {
    has: function(target, prop) {
    	if( prop[0] === '_' ) {
    		console.log('it is a private property')
    		return false;
    	}
    	return true; }}); console.log('a' in p);      // true
console.log('_a' in p )     // it is a private property
                            // false

Copy the code

In the example above, we use the has method to hide private attributes that begin with underscore _, so that we return false on judgment and are not detected by the IN operator ~

Note that a property of the target object cannot be hidden by the proxy if it is not itself configurable, and that property cannot be hidden by the proxy if the target object is non-extensible, otherwise TypeError will be raised.

letobj = { a : 1 }; Object.preventExtensions(obj); // Make an object unextensible, that is, you can never add new attributeslet p = new Proxy(obj, {
	has: function(target, prop) {
		return false; }}); console.log('a' in p); // TypeError is thrown
Copy the code

Data binding

With all that said, we can use proxies to manually implement a very simple two-way binding of data (see object.defineProperty () at the end of my last article) ~

Mainly look at the realization of the function, so the layout I readily waved ~

The page structure is as follows:

<! --html--> <div id="app">
    <h3 id="paragraph"></h3>
    <input type="text" id="input"/>
</div>
Copy the code

It comes down to the logic:

Const paragraph = document.getelementById ('paragraph'); Const input = document.getelementById (const input = document.getelementById ('input'); Const data = {text:'hello world'} const handler = {// Monitors changes in the text property of dataset: function (target, prop, value) {
    	if ( prop === 'text') {// Update target[prop] = value; // Update the view text.innerhtml = value; input.value = value;return true;
    	} else {
    		return false; }} // Add input listener input.addeventListener ('input'.function(e) { myText.text = e.target.value; // Update myText},falseConst myText = new proxy (data,handler); // Initialize mytext.text = data.text;Copy the code

Above, we created myText instance through Proxy, and updated view changes by intercepting the text attribute set method in myText, and realized a very simple two-way data binding ~

conclusion

Say so many, I finally got my Proxy is an introduction to, although it’s grammar is very simple, but if you want to play its actual value, is not easy, coupled with its own Proxy compatibility problems, so we used in the actual application development scenario is not many, but does not mean it is not practical, in my opinion, It can be used for data processing, data validity check, and even the function of the proxy, more useful value waiting for you to develop it ~

Besides, Vue3.0 is ready to be released. Are you going to learn it?

Come on!