In Vue3.0, why abandon Object.defineProperty and use Proxy to implement data hijacking?

In Vue3.0, why abandon Object.defineProperty and use Proxy to implement data hijacking?

foreword

Recently, I am reviewing Vue, and compare the learning records~

text

Before explaining the problem, let's review the knowledge of Proxy and Object.defineProperty

1.1 Proxy

What is Proxy?

The Proxy object is used to create a proxy for an object, enabling interception and customization of basic operations (such as property lookup, assignment, enumeration, function invocation, etc.). It can be understood that a layer of "interception" is set up before the target object, and the access to the object from the outside world must pass through this layer of interception first. Therefore, a mechanism is provided to filter and rewrite the access of the outside world.

grammar:

const p = new Proxy(target, handler)
  • target: The target object to wrap with Proxy (can be any type of object, including native arrays, functions, or even another proxy).
  • handler: An object that usually has functions as attributes, and the functions in each attribute define the behavior of the proxy p when performing various operations .

Note: Proxy.revocable(target, handler);Used to create a revocable proxy object that returns a revocable Proxy object containing the proxy object itself and its revocation method revoke.

Once a proxy object is revoked, it will become almost completely uncallable, and performing any proxy operation on it will throw a TypeError exception (note that there are 14 types of proxy operations, and operations other than these 14 operations are performed will not throw an exception). Once revoked, the proxy object cannot be directly restored to its original state, and the target object and processor object associated with it may be garbage collected .

var revocable = Proxy.revocable({
    
    }, {
    
    
  get(target, name) {
    
    
    return "[[" + name + "]]";
  }
});
var proxy = revocable.proxy;
proxy.foo;              // "[[foo]]"

revocable.revoke();

console.log(proxy.foo); // 抛出 TypeError
proxy.foo = 1           // 还是 TypeError
delete proxy.foo;       // 又是 TypeError
typeof proxy            // "object",因为 typeof 不属于可代理操作

handler

get()
is used to intercept the read property operation of the object.

get: function(target, property, receiver) {
    
    
  }

This method intercepts the following operations on the target object:

  • access attribute: proxy[foo]andproxy.bar
  • Access properties on the prototype chain:Object.create(proxy)[foo]
  • Reflect.get()
var p = new Proxy({
    
    }, {
    
    
  get: function(target, prop, receiver) {
    
    
    console.log("called: " + prop);
    return 10;
  }
});

console.log(p.a); // "called: a"
                  // 10

set()
catcher for setting property value operations

set: function(target, property, value, receiver) {
    
    
  }

This method intercepts the following operations on the target object:

  • Specify property values: proxy[foo] = bar and proxy.foo = bar
  • Specify the property value of the inheritor: Object.create(proxy)[foo] = bar
  • Reflect.set()
var p = new Proxy({
    
    }, {
    
    
  set: function(target, prop, value, receiver) {
    
    
    target[prop] = value;
    console.log('property set: ' + prop + ' = ' + value);
    return true;
  }
})

console.log('a' in p);  // false

p.a = 10;               // "property set: a = 10"
console.log('a' in p);  // true
console.log(p.a);       // 10

Setting up a layer of interception on an empty object is actually like overloading the dot operator, that is, overwriting the original definition of the language with your own definition.

var obj = new Proxy({
    
    },{
    
    
    get(target,prop){
    
    
        console.log(`读取了${
      
      prop}`);
        return Reflect.get(target, prop);
    },
    set(target,prop,value){
    
    
        console.log(`更新了${
      
      prop}`);
        return Reflect.set(target, prop, value);
    }
})
obj.a=1; //更新了a
console.log(obj.a); //读取了a
                    // 1

注意: To make Proxy work, you must operate on the Proxy instance (the proxy object in the above example), not the target object (the empty object in the above example).


Other methods of handler objects:

  • applyMethods intercept function calls, call and apply operations.
  • has()The method is used to intercept the HasProperty operation, that is, when it is judged whether the object has a certain property, this method will take effect. A typical operation is the in operator. Although the in operator is also used in the for...in loop, the has() interception does not work for the for...in loop.
  • 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) and return the description object of the property.
    defineProperty(target, propKey, propDesc): Intercept Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDescs) and return a boolean value.
  • preventExtensions(target): Intercept Object.preventExtensions(proxy) and 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, there are two additional operations that can be intercepted.
  • construct(target, args): Intercept the operation of the Proxy instance as a constructor call, such as new proxy(...args).
  • defineProperty(): Used to intercept Object.defineProperty() operations on objects.

1.2 Object.defineProperty()

Definition and Syntax

What does the Object.defineProperty() method do?

Object.defineProperty()The method defines a new property directly on an object, or modifies an existing property of an object, and returns the object.

grammar:

Object.defineProperty(obj, prop, descriptor)
  • obj: The object on which to define properties.
  • prop: The name or Symbol of the property to define or modify.
  • descriptor: The property descriptor to define or modify. There are two main forms of attribute descriptors: data descriptors and access descriptors . A data descriptor is an attribute with a value, which may or may not be writable. Access descriptors are properties described by getter and setter functions.

Descriptor

  • valueIndicates the value corresponding to this property. Default is undefined
  • When the writableproperty is set to false, the property is said to be "not writable". It cannot be reassigned. Attempting to write to a non-writable property will not change it and will not raise an error . Default value is false
  • enumerableDefines whether the properties of an object can be enumerated in for...in loops and Object.keys(). Default value is false
  • configurableAttributes indicate whether an object's properties can be deleted, and whether other attributes other than the value and writable attributes can be modified. Default value is false

存取描述符Also has the following optional key values:

  • get: The property's getter function, or undefined if there is no getter. This function is called when the property is accessed . No parameters are passed in during execution, but the this object will be passed in (due to the inheritance relationship, the this here is not necessarily the object that defines the property). The return value of this function is used as the property's value. Defaults to undefined.
  • set: The property's setter function, or undefined if there is no setter. This function is called when the property value is modified . This method accepts one parameter (that is, the new value to be assigned), and will pass in the this object at the time of assignment. Defaults to undefined.

For 数据描述符and 存取描述符, the difference is as follows:
insert image description here

use

If the specified property does not exist in the object, Object.defineProperty() will create the property. When certain fields are omitted from the descriptor, those fields will use their default values. If the property already exists, Object.defineProperty() will attempt to modify the property based on the value in the descriptor and the object's current configuration.

var o = {
    
    }; // 创建一个新对象

// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(o, "a", {
    
    
  value : 37,
  writable : true,
  enumerable : true,
  configurable : true
});

// 对象 o 拥有了属性 a,值为 37

// 在对象中添加一个设置了存取描述符属性的示例
var bValue = 38;
Object.defineProperty(o, "b", {
    
    
  // 使用了方法名称缩写(ES2015 特性)
  // 下面两个缩写等价于:
  // get : function() { return bValue; },
  // set : function(newValue) { bValue = newValue; },
  get() {
    
     return bValue; },
  set(newValue) {
    
     bValue = newValue; },
  enumerable : true,
  configurable : true
});
console.log(o.a); // 37
console.log(o.b); // 38

o.a=100
bValue=9;
console.log(o.a); // 100
console.log(o.b); // 9

注意: If you return ob directly in the get function, the ob here will also call the get function once, so it will fall into an infinite loop; the same is true for the set function, so we use a third-party variable bValue to prevent the infinite loop.
insert image description here

When assigning a property to an object using the dot operator and Object.defineProperty(), the default value of the property in the data descriptor is different

var o = {
    
    };

o.a = 1;
// 等同于:
Object.defineProperty(o, "a", {
    
    
  value: 1,
  writable: true,
  configurable: true,
  enumerable: true
});


// 另一方面,
Object.defineProperty(o, "a", {
    
     value : 1 });
// 等同于:
Object.defineProperty(o, "a", {
    
    
  value: 1,
  writable: false,
  configurable: false,
  enumerable: false
});

If the visitor's property is inherited, its get and set methods will be called when the child object's property is accessed or modified. If these methods use a variable to store a value, that value will be shared by all objects.

function myclass() {
    
    
}

var value;
Object.defineProperty(myclass.prototype, "x", {
    
    
  get() {
    
    
    return value;
  },
  set(x) {
    
    
    value = x;
  }
});

var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // 1

This can be solved by storing the value in another property. In get and set methods, this points to an object whose properties are being accessed and modified.

function myclass() {
    
    
}

Object.defineProperty(myclass.prototype, "x", {
    
    
  get() {
    
    
    return this.stored_x;
  },
  set(x) {
    
    
    this.stored_x = x;
  }
});

var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // undefined

1.3 Analysis

in Vue2_

//源数据
let person = {
    
    
	name:'张三',
	age:18
}

let p = {
    
    }
Object.defineProperty(p,'name',{
    
    
	configurable:true,
	get(){
    
     //有人读取name时调用
		return person.name
	},
	set(value){
    
     //有人修改name时调用
		console.log('有人修改了name属性,我发现了,我要去更新界面!')
		person.name = value
	}
})
  • For object properties, we use Object.defineProperty to intercept read, write and modify properties (data hijacking)
  • For array types, interception is implemented by overriding a series of methods that update the array

The disadvantages of doing this are:

  • Although Object.defineProperty can hijack the properties of the object, it needs to traverse and hijack each property of the object;

  • Direct additions or deletions to object properties cannot be detected unless they are added using the following command

// 监测不到
this.person.sex=delete this.person.name

// 监测得到
this.$set(this.person,'sex',)
// Vue.set(this.person,'sex',女)
this.$delete(this.person.'name')
//Vue.delete(this.person.'name')
  • Unable to detect changes in array elements, need to rewrite array methods
// 监测不到
this.person.hobby[0]='学习'

// 监测得到
this.$set(this.person.hobby,0,'逛街')
this.person.hobby.splice(0,1,splice)

in Vue3_

//源数据
let person = {
    
    
	name:'张三',
	age:18
}

const p = new Proxy(person,{
    
    
	//有人读取p的某个属性时调用
	get(target,propName){
    
    
		console.log(`有人读取了p身上的${
      
      propName}属性`)
		return Reflect.get(target,propName)
	},
	//有人修改p的某个属性、或给p追加某个属性时调用
	set(target,propName,value){
    
    
		console.log(`有人修改了p身上的${
      
      propName}属性,我要去更新界面了!`)
		Reflect.set(target,propName,value)
	},
	//有人删除p的某个属性时调用
	deleteProperty(target,propName){
    
    
		console.log(`有人删除了p身上的${
      
      propName}属性,我要去更新界面了!`)
		return Reflect.deleteProperty(target,propName)
	}
})

The advantages of using Proxy to compare Object.defineProperty for data hijacking are as follows:

  • Compared with Object.defineProperty hijacking a property, Proxy is more thorough, not limiting a property, but directly proxying the entire object.
  • Proxy can monitor the addition and deletion of object attributes.
// 检查得到
person.sex=delete person.name
  • Whether it is the change of the array subscript or the length of the array, or through function calls, Proxy can monitor changes very well; and in addition to our commonly used get and set, Proxy supports 13 interception operations.
// 可检测
person.hobby[0]='学习'

This article ends everywhere, if you think it is helpful to you, remember to bookmark~

Guess you like

Origin blog.csdn.net/qq_45890970/article/details/123992485