preface
Although MY main technology stack is React, the interviewer will almost ask you to tell me the difference between React and Vue in every interview. When talking about two-way data binding, the interviewer will subconsciously ask you to tell me the principle of two-way data binding of Vue. In Vue3.0, Proxy replaces Object. DefineProperty and becomes the underlying principle of bidirectional binding. At this time, Proxy becomes particularly important.
This article introduces object.defineProperty, then explains Proxy, and finally compares the pros and cons of the two.
1, Object. DefineProperty data hijacking
The object.defineProperty () method directly defines a new property on an Object, or modifies an existing property on an Object, and returns the Object.
This method takes three arguments. The first argument is obj: the object whose property is to be defined, the second argument is Prop: the name or Symbol of the property to be defined or modified, the third argument is descriptor: the property descriptor to be defined or modified.
const obj = {};
Object.defineProperty(obj, "property", {
value: 18.
});
console.log(obj.property); / / 18
Copy the code
While we can add properties and values directly, we can do a lot more configuration this way.
The property descriptors represented by the third parameter descriptor of the function come in two forms: data descriptors and access descriptors. A data descriptor is an attribute that has a value, which can be writable or unwritable. Access descriptors are properties described by getter and setter functions. A descriptor can only be one of these two; It can’t be both.
These two have both of the following keys:
- The description of a different property can be changed and deleted from the corresponding object only when the value of the different key is set to true. The default is false.
- Enumerable: This property appears in the enumeration property of an object if and only if its Enumerable key value is true. The default is false.
const obj = { property: 24 };
Object.defineProperty(obj, "property", {
configurable: true.
});
delete obj["property"]; // true
obj; / / {}
// Change the state
const obj = { property: 24 };
Object.defineProperty(obj, "property", {
configurable: false.
});
delete obj["property"]; // false
obj; // {'property': 24}
Copy the code
const obj = {
property1: 24.
property2: 34.
property3: 54.
};
Object.defineProperty(obj, "property1", {
enumerable: true.
});
for (i in obj) {
console.log(i);
}
// property1
// property2
// property3
/ / state
Object.defineProperty(obj, "property1", {
enumerable: false.
});
for (i in obj) {
console.log(i);
}
// property2
// property3
Copy the code
The data descriptor also has the following optional key values:
- Value: indicates the value of the attribute. It can be any valid JavaScript value (value, object, function, etc.). The default is undefined.
- Writable: The value of the attribute, value above, can be changed by the assignment operator if and only if the attribute’s writable key is true. The default is false.
const obj = {};
Object.defineProperty(obj, "property1", {
value: 18.
});
obj; // {'property1': 18}
Copy the code
const obj = {};
Object.defineProperty(obj, "property1", {
value: 18.
writable: false.
});
obj.property1 = 24;
obj; // {'property1': 18}
// Change the state
const obj = {};
Object.defineProperty(obj, "property1", {
value: 18.
writable: true.
});
obj.property1 = 24;
obj; // {'property1': 24}
Copy the code
The access descriptor also has the following optional key values:
- Get: The getter function for the property, or undefined if there is no getter. This function is called when the property is accessed. No arguments are passed, but the this object is passed (because of inheritance, this is not necessarily the object that defines the property). The return value of this function is used as the value of the property. The default is undefined.
- Set: the setter function for the property, or undefined if there is no setter. This function is called when the property value is modified. This method takes a parameter (that is, the new value assigned) and passes the this object that was assigned. The default is undefined.
const obj = {};
Object.defineProperty(obj, "property1", {
get(value) {
return value;
},
set(newValue) {
value = newValue;
},
});
Copy the code
2. Proxy data interception
Object.defineproperty only redefines the behavior of getting and setting, while Proxy acts as an upgrade and redefines more behavior, which we’ll explore in more detail next.
First, Proxy is a constructor, and you can create an instance of it with new. It takes two arguments. One is target: the target object to wrap with Proxy (which can be any type of object, including native arrays, functions, or even another Proxy). The other is handler: an object that usually has functions as properties, each of which defines the behavior of the proxy instance when performing various operations.
let p = new Proxy(target, handler);
Copy the code
Handler object methods
All methods in handler are optional, and if no method is defined, the default behavior of the original object is left.
1, the get ()
A read operation used to intercept an object. This method takes three parameters: target: the target object, property: the name of the property to be obtained, and receiver: Proxy or an object that inherits from Proxy.
let person = {
name: "Jack".
};
let p = new Proxy(person, {
get(target, property) {
if (property in target) {
return target[property];
} else {
throw Error("This property does not exist.");
}
},
});
p.name; // Jack
p.age; // Uncaught Error: the attribute does not exist
Copy the code
Look at the third property
let person = {
name: "Jack".
};
let p = new Proxy(person, {
get(target, property, receiver) {
return receiver;
},
});
p.name === p; // true
Copy the code
In the above code, the name property of the p object is provided by the P object, so receiver points to the proxy object.
2, the set ()
The catcher used to set the property value operation. This method takes four parameters: target: the target object, property: the name of the property to be set or Symbol, Value: the value of the new property, and Receiver: the object that was originally called. It is usually the proxy itself, but it is possible that the handler’s set method may be called indirectly on the prototype chain or in other ways (and therefore not necessarily the proxy itself).
let person = {};
let p = new Proxy(person, {
set(target, property, value, receiver) {
target[property] = value;
},
});
p.name = 2;
Copy the code
The set() method is called when properties are added to a Proxy instance.
Now let’s take a look at the fourth parameter of the set() method, which is usually referred to the proxy itself.
let person = {};
let p = new Proxy(person, {
set(target, property, value, receiver) {
target[property] = receiver;
},
});
p.name = 2;
p.name === p; // true
Copy the code
But there are other cases. Let’s use the example mentioned in Ruan Yifeng: ECMAScript 6 Starter -Proxy:
const handler = {
set: function (obj, prop, value, receiver) {
obj[prop] = receiver;
},
};
const proxy = new Proxy({}, handler);
const myObj = {};
Object.setPrototypeOf(myObj, proxy);
myObj.foo = "bar";
myObj.foo === myObj; // true
Copy the code
In the above code, when setting the value of myobj. foo, myObj does not have the foo property, so the engine will look for the foo property in myObj’s prototype chain. MyObj’s prototype object, proxy, is a proxy instance, and setting its foo property triggers the set method. At this point, the fourth parameter, receiver, refers to the object myObj where the original assignment occurred.
It is important to note that when the object is not configurable or writable, then the set() method will not work.
const obj = {};
Object.defineProperty(obj, "foo", {
value: 18.
writable: false.
});
let p = new Proxy(obj, {
set(target, property, value, receiver) {
target[property] = value;
},
});
p.foo = 28;
p.foo; / / 18
Copy the code
3, the apply ()
Used to intercept calls to functions. This method takes three arguments, target: the target object (function). ThisArg: The context object when called. ArgumentsList: The array of arguments to be called.
This method is executed when an instance of Prxoy is used as a function.
let p = new Proxy(function () {}, {
apply(target, thisArg, argumentsList) {
console.log("Hello Word");
},
});
p(); // Hello Word
Copy the code
4, from the ()
Is a proxy method for the in operator. This method takes two arguments, target: the target object. Prop: A property that needs to be checked for existence.
let p = new Proxy(
{},
{
has(target, prop) {
console.log(target, prop); // {} 'a'
},
}
);
console.log("a" in p); // false
Copy the code
The has() method works only for the in operator, for the for… in… It’s not really useful.
let p = new Proxy(
{ value: 18 },
{
has(target, prop) {
if (prop === "value" && target[prop] < 20) {
console.log("Value less than 20");
return false;
}
return prop in target;
},
}
);
"value" in p; // Value less than 20 false
for (let a in p) {
console.log(p[a]); / / 18
}
Copy the code
As you can see from the example above, the has method intercepts only for the in operator, and for… in… There is no interception effect.
5, the construct
Used to intercept the new operator. In order for the new operator to take effect on the generated Proxy object, the target object used to initialize the Proxy itself must have the [[Construct]] internal method (that is, the new Target must be valid). This method takes three arguments, target: the target object. ArgumentsList: Argument list for constructor. NewTarget: The constructor that was originally called.
let p = new Proxy(function () {}, {
construct(target, argumentsList, newTarget) {
return { value: argumentsList[0]};
},
}) (new p(1)).value; / / 1
Copy the code
The construct() method must return an object or it will not report an error.
let p = new Proxy(function () {}, {
construct(target, argumentsList, newTarget) {
console.log("Hello Word");
},
});
new p(); // Uncaught TypeError: 'construct' on proxy: trap returned non-object ('undefined')
Copy the code
6, deleteProperty
Used to intercept DELETE operations on object properties. This method takes two parameters. Target: Indicates the target object. Property: indicates the name of the property to be deleted.
let p = new Proxy(
{},
{
deleteProperty(target, property) {
console.log("called: " + property);
return true;
},
}
);
delete p.a; // "called: a"
Copy the code
If this method throws an error or returns false, the current property cannot be deleted by the delete command. Note that the properties of the target object that are not configurable and works without additional information cannot be deleted by the deleteProperty method. Otherwise, errors will be reported.
7, defineProperty ()
Used to intercept an object.defineProperty () operation on an Object. This method takes three arguments, target: the target object. Property: The name of the property whose description is to be retrieved. Descriptor: descriptor of the property to be defined or modified.
let p = new Proxy(
{},
{
defineProperty: function (target, prop, descriptor) {
console.log("called: " + prop);
return true;
},
}
);
let desc = { configurable: true.enumerable: true.value: 10 };
Object.defineProperty(p, "a", desc); // "called: a"
Copy the code
The defineProperty() method returns false if there is no action inside, so adding new attributes is always invalid. Note that false is only used to indicate that the operation has failed, and does not in itself prevent the addition of new properties.
Note that defineProperty() cannot add attributes that do not exist on the target object if the target object is non-extensible, otherwise an error will be reported. In addition, the defineProperty() method cannot change any of the properties of the target object that are not writable or configurable.
8, defineProperty ()
Usage is intercepted Object. GetOwnPropertyDescriptor (), must return an Object, or undefined. This method takes two arguments, target: the target object. Prop: Returns a description of the property name.
let p = new Proxy(
{ a: 20 },
{
getOwnPropertyDescriptor: function (target, prop) {
console.log("called: " + prop);
return { configurable: true.enumerable: true.value: 10 };
},
}
);
console.log(Object.getOwnPropertyDescriptor(p, "a").value); // "called: a"
/ / 10
Copy the code
9, getPrototypeOf ()
Is a Proxy method that is called when the prototype of the Proxy object is read. This method takes only one argument, target: the target object to be represented. The return value must be an object or null. There are five conditions that trigger this method:
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- __proto__
- Object.prototype.isPrototypeOf()
- instanceof
let proto = {};
let p = new Proxy(
{},
{
getPrototypeOf(target) {
return proto;
},
}
);
Object.getPrototypeOf(p) === proto; // true
Copy the code
10, isExtensible ()
Object.isextensible () is used to intercept objects. This method takes one parameter. Target: Indicates the target object. The return value must return a Boolean value or a value that can be converted to Boolean.
let p = new Proxy(
{},
{
isExtensible: function (target) {
console.log("called");
return true;
},
}
);
console.log(Object.isExtensible(p)); // "called"
// true
Copy the code
Note that this method has an enforced constraint that object.isextensible (proxy) must return the same value as object.isextensible (target). That is, it must return true or a value that is true. False and false will both return an error.
let p = new Proxy(
{},
{
isExtensible: function (target) {
return false;
},
}
);
Object.isExtensible(p); // Uncaught TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')
Copy the code
11, ownKeys ()
A read operation that intercepts the properties of an object itself. This method takes one argument, target: the target object.
Specific intercepts are as follows:
- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
- for… In circulation
This method has several constraints:
- The result of ownKeys must be an array
- The element type of an array is either a String or a Symbol
- The result list must contain all keys of the target object that are freely configurable or own
- If the target object is not extensible, then the resulting list must contain keys for all the own properties of the target object and no other values
let target = {
a: 1.
b: 2.
c: 3.
};
let p = new Proxy(target, {
ownKeys(target) {
return ["a"];
},
});
Object.keys(p); // ['a']
Copy the code
12, preventExtensions ()
Use to set intercepting on object.preventExtensions (). This method takes one argument, target: the target object to intercept. This method returns a Boolean value. The limitation of this method is that it can only return false if the target object is extensible.
let p = new Proxy(
{},
{
preventExtensions: function (target) {
return true;
},
}
);
Object.preventExtensions(p); // Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible
Copy the code
In the code above, the proxy.preventExtensions() method returns true, but object.isextensible (proxy) returns true, so an error is reported.
To prevent this, it is common to call object.preventExtensions () once in the proxy.preventExtensions() method.
let p = new Proxy(
{},
{
preventExtensions: function (target) {
console.log("called");
Object.preventExtensions(target);
return true;
},
}
);
console.log(Object.preventExtensions(p)); // "called"
// false
Copy the code
13, setPrototypeOf ()
Used to intercept object.setPrototypeof (). This method takes two arguments, target: the target object to intercept. Prototype: New prototype or null for the object. The setPrototypeOf method returns true if the [[Prototype]] change was successful, and false otherwise.
var handler = {
setPrototypeOf(target, proto) {
throw new Error("Changing the prototype is forbidden");
},
};
var proto = {};
var target = function () {};
var proxy = new Proxy(target, handler);
Object.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.
Note that the setPrototypeOf() method cannot change the stereotype of the target object if it is non-extensible.
Object. DefineProperty and Proxy
The main difference is actually applied to Vue two-way data binding, in fact, because of Vue two-way data binding, let these two methods more and more into people’s vision.
We won’t go into the depth of bidirectional data binding here, so let’s implement a simple version of bidirectional data binding with each method to see the difference.
Version 3.1, Object. DefineProperty
const obj = {};
Object.defineProperty(obj, "text", {
get: function () {
console.log("get val");
},
set: function (newVal) {
console.log("set val:" + newVal);
document.getElementById("input").value = newVal;
document.getElementById("span").innerHTML = newVal;
},
});
const input = document.getElementById("input");
input.addEventListener("keyup".function (e) {
obj.text = e.target.value;
});
Copy the code
This simple version of object.defineProperty has the obvious disadvantage that you can only listen on one property of an Object. If you want to listen on all properties of an Object, you have to iterate over it. There is also the problem that you can’t listen on arrays. This is why Vue2.0 preferred object.defineProperty.
Version 3.2, the Proxy
const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};
const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}! `);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key === 'text') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
},
});
input.addEventListener('keyup'.function(e) {
newObj.text = e.target.value;
});
Copy the code
From the above, we can see that Proxy can intercept the whole object, and it can return a new object, in addition to it can intercept an array. And the most intuitive is that there are 13 interceptions. However, the most fatal problem was that it was not compatible and could not be polished with polyfill, so You stated that you would have to wait until the next major release (3.0) to rewrite it with Proxy.
After the language
Related articles:
- Take you relearn ES6 | var, let and the difference between const
- Take you relearn ES6 | Promsie
- Take you relearn ES6 | Generator
- Take you relearn ES6 | Async and Await
- Take you relearn ES6 | Set and Map
- Take you relearn ES6 | Symbol (more than just a new data type)
- Take you relearn ES6 | Export (keep in mind that the output is variable)
- Take you relearn ES6 | proxy and defineProperty
I think it’s ok, please give me a thumbs up when I leave, and we can study and discuss together!
You can also follow my blog and hope to give me a Start on Github, you will find a problem, almost all my user names are related to tomatoes, because I really like tomatoes ❤️!!
The guy who wants to follow the car without getting lost also hopes to follow the public account or scan the qr code below 👇👇👇.
I am a primary school student in the field of programming, your encouragement is the motivation for me to keep moving forward, 😄 hope to refueling together.
This article was typeset using MDNICE