DefineProperty is the core of vUE data hijacking. This article explains how defineProperty realizes attribute hijacking a little bit.
In fact, the general way we manipulate Object attributes, add or modify attributes, we can use Object.defineProperty.
let obj = {};
// Common action: Add/modify new properties
obj.a = 1;
// Equivalent to:
Object.defineProperty(o, "a", {
value: 1.writable: true.configurable: true.enumerable: true
});
Copy the code
Of course, in ordinary cases, we don’t play this way, it’s too long.
But defineProperty can add or modify attributes of an object more precisely.
The descriptor
Let’s start with a proper noun: descriptors.
This is the third argument to defineProperty, which is an object. This object has the following properties:
- The additional property of the descriptor can be modified without any additional control
- An enumerable property is a property that can be called by for
- Writable property: can change the value of the property, that is, can change it
obj.a = 1
- Value property: The value of the property
- The get property is a function that is automatically called when accessing the property and returns the value of the property
- Set property: a function that is automatically called when the property is modified and has one and only argument, the assigned value
Attention!!
- The value attribute writable and get attribute set are mutually exclusive. Only one attribute can exist
- The default values for all other properties are
false
And don’t want tofalse
Words, remember configuration ha, not in detail (mainly I do not use).
Talk about get and set
- The get property is a function that is automatically called when accessing the property and returns the value of the property
- Set property: a function that is automatically called when the property is modified and has one and only argument, the assigned value
Say it three times and recite it.
Let’s write an example of get and set.
This is an example that you have to learn, and once you understand it, you basically have the essence of data hijacking
let obj = {};
let value = 1;
Object.defineProperty(obj, "b", {
get() {
console.log("Read b property", value);
return value;
},
set(newValue) {
console.log("Set b property", newValue); value = newValue; }});// Trigger the get function. The return value of get is the property value
/ / 1
console.log(obj.b);
// Trigger the set function and the value becomes 2, notice!! In memory, the property value has not changed
obj.b = 2;
// However, when you want to read the property value, you must trigger the get function and the property value will change naturally, which is a really nice idea
console.log(obj.b);
Copy the code
There’s a catch: you can’t have a read in a get, or it’ll loop forever, so you’ll always need a variable to get a set
So, here, the value of the variable value is the value of the property, and if you want to change the property, you just change the value of the value.
I think that’s pretty much it.
Hijacks an attribute of an object
With that in mind, try writing down any of the properties of the hijacked object.
function observeKey(obj, key) {
let value = obj[key];
Object.defineProperty(obj, key, {
get() {
console.log("Read properties", value);
return value;
},
set(newValue) {
console.log("Set Properties", newValue); value = newValue; }}); }let obj = { a: 1 };
observeKey(obj, "a");
// Read a, trigger get function
console.log(obj.a);
// Set a to trigger the set function
obj.a = 1;
Copy the code
Hijack all attributes of an object
Try hijacking all properties of the object again
This is essentially traversal:
function observeObj(obj) {
for (let key in obj) {
// Using obj. HasOwnProperty directly will prompt an error
if (Object.prototype.hasOwnProperty.call(obj, key)) { observeKey(obj, key); }}return obj;
}
function observeKey(obj, key) {
let value = obj[key];
Object.defineProperty(obj, key, {
get() {
console.log("Read properties", value);
return value;
},
set(newValue) {
console.log("Set Properties", newValue); value = newValue; }}); }let obj = { a: 1.b: 2 };
observeObj(obj);
console.log(obj);
// Read a, trigger get function
console.log(obj.a);
// Set a to trigger the set function
obj.a = 1;
Copy the code
Hijacks all attributes of the object – including the attribute values of the object type
{a:1,c:{b:1}} {a:1,c:{b:1}}
Simple, recursive, just fill it in.
function observeObj(obj) {
// Add parameter restriction, must be the object to have hijacking, also recursive termination condition
if (typeofobj ! = ="object" || obj == null) {
return;
}
for (let key in obj) {
// Using obj. HasOwnProperty directly will prompt an error
if (Object.prototype.hasOwnProperty.call(obj, key)) {
observeKey(obj, key);
// If the object does not return it directly, it does not matterobserveObj(obj[key]); }}return obj;
}
function observeKey(obj, key) {
let value = obj[key];
Object.defineProperty(obj, key, {
get() {
console.log("Read properties", value);
return value;
},
set(newValue) {
console.log("Set Properties", newValue); value = newValue; }}); }let obj = { a: 1.b: 2.c: { name: "c"}}; observeObj(obj);console.log(obj);
// Read a, trigger get function
console.log(obj.a);
// Set a to trigger the set function
obj.a = 1;
// Trigger the set function
obj.c.name = "d";
Copy the code
Note that the function observeObj cannot hijack new attributes of an object, only existing attributes of the object.
The defect of defineProperty
- Cannot monitor object to add properties
- Object deletion properties cannot be monitored
- You cannot hijack array changes
Of course, array changes can be detected in other ways, which are implemented by hijacking the alter array method.
$set/$delete (); $set/$delete ();
let obj = { a: 1.b: [1.2]}; observeObj(obj);// Add attributes
obj.c = 3;
// The get function is not triggered
console.log(obj.c);
// The set function is not triggered
obj.b.push(3);
Copy the code
DefineProperty can also mount properties
Is access options. The data. The name can be abbreviated as options. The name, professional words, will the data on the properties of the mount on the options
Equivalent to adding a new attribute to options using defineProperty:
// Mount a single attribute first
// options.data equals source. Options equals target
function proxyKey(target, source, key) {
Object.defineProperty(target, key, {
// Where source[key] is equivalent to value, the simplest example is the core
get() {
return source[key];
},
set(newValue) {
if (newValue === source[key]) {
return; } source[key] = newValue; }}); }// Iterate through the properties and mount it
function proxyObj(target, source) {
for (let key in source) {
// Using obj. HasOwnProperty directly will prompt an error
if (Object.prototype.hasOwnProperty.call(source, key)) { proxyKey(target, source, key); }}}let options = {
data: { name: 1}}; proxyObj(options, options.data);/ / 1
console.log(options.name);
Copy the code
In other words, the core principles of vue’s attribute hijacking and mounting properties are similar to those above.
DefineProperty can also log
For example, obj has a property whose value changes frequently and you want to log all of its changing values.
let obj = { a: 1 };
let log = [obj.a];
let value = obj.a;
Object.defineProperty(obj, "a", {
get() {
return value;
},
set(newValue) {
if (newValue === value) {
return; } value = newValue; log.push(newValue); }}); obj.a =2;
obj.a = 3;
obj.a = 4;
/ / [1, 2, 3, 4]
console.log(log);
Copy the code
Generic you can isolate a class that records changes to a value
class Archiver {
constructor() {
let value = null;
this.archive = [];
Object.defineProperty(this."a", {
get() {
return value;
},
set(newValue) {
if (newValue === value) {
return;
}
value = newValue;
this.archive.push(newValue); }}); }}let archiver = new Archiver();
archiver.a = 1;
archiver.a = 2;
/ / [1, 2]
console.log(archiver.archive);
Copy the code
reference
- The MDN defineProperty