Was ist Reaktionsfähigkeit? Wie erreicht Vue Reaktionsfähigkeit? Der folgende Artikel vermittelt Ihnen ein detailliertes Verständnis des Reaktionsfähigkeitsprinzips von Vue3. Ich hoffe, er wird Ihnen hilfreich sein!
Ich glaube, dass jeder mit dem Vue-Framework vertraut ist. Wenn es um Vue geht, ist eine der ersten Fragen, die der Interviewer stellen wird, die, wie das Responsivitätsprinzip von Vue umgesetzt wird Vorheriger Artikel zur Reaktionsfähigkeit von Vue2. Lassen Sie uns heute über den Reaktionsmechanismus von Vue3 sprechen. [Verwandte Empfehlungen: vuejs-Video-Tutorial, Web-Front-End-Entwicklung]
Variablen in JavaScript verfügen nicht über das Konzept der Reaktionsfähigkeit. Die Ausführungslogik des Codes erfolgt von oben nach unten. Im Vue-Framework ist Reaktionsfähigkeit eine der vorgestellten Funktionen. Schauen wir uns zunächst ein Beispiel an
let num = 1; let double = num * 2; console.log(double); // 2 num = 2; console.log(double); // 2
Es ist deutlich zu erkennen, dass die Beziehung zwischen der Variablen double und der Variablen num nicht reagiert. Wenn wir die Logik der Berechnung von double in eine Funktion kapseln, ändert sich der Wert der Variablen num. Wir führen diese Funktion erneut aus, sodass sich der Wert von double ändert, wenn sich num ändert, was wir üblicherweise als reaktionsfähig bezeichnen.
let num = 1; // 将计算过程封装成一个函数 let getDouble = (n) => n * 2; let double = getDouble(num); console.log(double); // 2 num = 2; // 重新计算double,这里当然也没有实现响应式,只是说明响应式实现的时候这个函数应该再执行一次 double = getDouble(num); console.log(double); // 4
Obwohl der eigentliche Entwicklungsprozess viel komplizierter sein wird als die aktuelle einfache Situation, kann er in eine zu implementierende Funktion gekapselt werden. Das Problem besteht nun darin, wie wir den Wert von double entsprechend der Änderung der Num-Variablen neu berechnen lassen ?
Wenn der Wert der Num-Variablen jedes Mal geändert wird, kann die Funktion getDouble die Änderung der Num-Variablen erkennen und ausführen. Dies ist ein Prototyp der Reaktionsfähigkeit.
In Vue wurden drei reaktionsfähige Lösungen verwendet, nämlich defineProperty, Proxy und value setter. Die defineProperty API wird im vorherigen Artikel ausführlich beschrieben. Wenn Sie mehr über die Reaktionsfähigkeit von Vue2 erfahren möchten, klicken Sie hier ---> vue-Reaktionsfähigkeitsprinzip | API
let num = 1; let detDouble = (n) => n * 2; let obj = {} let double = getDouble(num) Object.defineProperty(obj,'num',{ get() { return num; } set(val){ num = val; double = getDouble(val) } }) console.log(double); // 2 obj.num = 2; console.log(double); // 4
: Wenn wir das obj.num-Attribut löschen, wird die Set-Funktion nicht ausgeführt, daher benötigen wir in Vue2 eine
eine spezielle Funktion zum Löschen von Daten. Und Attribute, die im obj-Objekt nicht vorhanden sind, können nicht gekapert werden, und das Ändern des Längenattributs im Array ist ebenfalls ungültig.Proxy$delete
var proxy = new Proxy(target, handler);
Alle Verwendungen von Proxy-Objekten erfolgen in der oben genannten Form. Der einzige Unterschied besteht in der Art und Weise, wie die
handler-Parameter geschrieben werden. Darunter bedeutet
new Proxy() das Generieren einer Proxy-Instanz, der target-Parameter stellt das abzufangende Zielobjekt dar und der handler-Parameter ist auch ein Objekt, das zum Anpassen des Abfangverhaltens verwendet wird. 在ES6中官方新定义了 Reflect 对象,在ES6之前对象上的所有的方法都是直接挂载在对象这个构造函数的原型身上,而未来对象可能还会有很多方法,如果全部挂载在原型上会显得比较臃肿,而 Reflect 对象就是为了分担 Object的压力。 (1) 将 (2) 修改某些 (3) 让 (4) 所以我们在这里会使用到 Proxy 和 Reflect 对象的方与 Proxy 一一对应这一特性,来实现Vue3的响应式原理。 在Vue3中响应式的核心方法是 根据我们前面所做的铺垫,所以我们会使用 判断是否为对象(方法不唯一,有多种方法) 尽可能采用函数式编程,让每一个函数只做一件事,逻辑更加清晰。 但是这样会有一个问题,如果我需要代理的对象是深层嵌套的对象呢?我们先看看效果 当我们深层代理时,我们直接修改深层对象中的属性并不会触发 Proxy 对象中的 set 方法,那为什么我们可以修改呢?其实就是直接访问原对象中深层对象的值并修改了,那我们如何优化这个问题呢? 那也需要用到递归操作,判断深层对象是否被代理了,如果没有再执行reactive将内部未被代理的对象代理。 那么我们在 get 方法内部就不能直接将映射之后的 res 返回出去了 这样我们就实现了对象的深层代理,并且只有当我们访问到内部嵌套的对象时我们才
会去递归调用reactive ,这样不仅可以实现深层代理,并且节约了性能,但是其实我们还没有彻底完善,我们来看看下面这段代码 这样是不是合法的,当然是合法的,但是没有必要也没有意义,所以为了避免被代理过的对象,再次被代理,太浪费性能,所以我们需要将被代理的对象打上标记,这样当带被代理过的对象访问到时,直接将被代理过的对象返回,不需要再次代理。 在 Vue3 中,使用了hash表做映射,来记录是否已经被代理了。 与 Vue2 的数据劫持相比,Vue3 中的 Proxy 可以直接修改数组的长度,但是这样我们需要在 set 方法中判断我们是要在代理对象身上添加属性还是修改属性。 因为更新视图的函数会在set函数中调用,我们向数组中进行操作会触发两次更新视图,所以我们需要做一些优化。 避免多次更新视图,比如修改的值与原来一致就不更新视图,在上面两个判断条件中添加更新视图的函数,就不会多次更新视图。 在IE11以下的浏览器都不兼容,所以如果使用 Vue3 开发一个单页应用的项目,需要考虑到兼容性问题,需要我们做额外的很多操作,才能使得IE11 以下的版本能够兼容。 Das obige ist der detaillierte Inhalt vonEine eingehende Untersuchung des Reaktionsmechanismus von Vue3. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
proxy.foo
和proxy['foo']
。proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。propKey in proxy
的操作,返回一个布尔值。delete proxy[propKey]
的操作,返回一个布尔值。Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。Object.preventExtensions(proxy)
,返回一个布尔值。Object.getPrototypeOf(proxy)
,返回一个对象。Object.isExtensible(proxy)
,返回一个布尔值。Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。new proxy(...args)
。Reflect
Object
对象的一
些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上现阶段,某些方法同时在Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。也就是说,从Reflect
对象上可以拿到语言内部的方法。Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。// 老写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。// 老写法
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true
Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。Proxy(target, {
set: function(target, name, value, receiver) {
var success = Reflect.set(target, name, value, receiver);
if (success) {
console.log('property ' + name + ' on ' + target + ' set to ' + value);
}
return success;
}
});
三、Vue3响应式原理的实现
function reactive (target){
// 返回一个响应式对象
return createReactiveObject(target);
}
Proxy
代理我们所需要的相应的对象,同时使用 Reflect
对象来映射。所以我们先初步实现一下,再慢慢优化,尽可能全面。function isObject(val){
return typeof val === 'object' && val !== null
}
初步实现
function createReactiveObject (target) {
// 首先由于Proxy所代理的是对象,所以我们需要判断target,若是原始值直接返回
if(!isObject(target)) {
return target;
}
let handler = {
get(target, key, receiver) {
let res = Reflect.get(target, key, receiver); // 使用Reflect对象做映射,不修改原对象
console.log('获取');
return res;
},
set(target, key, value, receiver) {
let res = Reflect.set(target, key, value, receiver);
console.log('修改');
return res
},
deleteProperty(target, key) {
let res = Reflect.deleteProperty(target, key)
console.log('删除');
return res;
}
}
let ProxyObj = new Proxy(target,handler); // 被代理过的对象
return ProxyObj;
}
解决代理对象内部有嵌套对象
get(target, key, receiver) {
let res = Reflect.get(target, key, receiver); // 使用Reflect对象做映射,不修改原对象
console.log('获取');
// 判断代理之后的对象是否内部含有对象,如果有的话就递归一次
return isObject(res) ? reactive(res) : res;
}
解决对象重复代理(多次代理、多层代理)
let proxy = reactive({name: '寒月十九', message: { like: 'coding' }});
reactive(proxy);
reactive(proxy);
reactive(proxy);
// WeakMap-弱引用对象,一旦弱引用对象未被使用,会被垃圾回收机制回收
let toProxy = new WeakMap(); // 存放形式 { 原对象(key): 代理过的对象(value)}
let toRow = new WeakMap(); // 存放形式 { 代理过的对象(key): 原对象(value)}
let ProxyObj = new Proxy(target,handler); // 被代理过的对象
toProxy.set(target,ProxyObj);
toRow.set(ProxyObj.target);
return ProxyObj;
let ByProxy = toProxy.get(target);
// 防止多次代理
if(ByProxy) { // 如果在WeakMap中可以取到值,则说明已经被代理过了,直接返回被代理过的对象
return ByProxy;
}
// 防止多层代理
if(toRow.get(target)) {
return target
}
// 为了防止下面这种写法(多层代理)
// let proxy2 = reactive(proxy);
// let proxy3 = reactive(proxy2);
// 其实本质上与下面这种写法没有区别(多次代理)
// reactive(proxy);
// reactive(proxy);
// reactive(proxy);
数组相应问题
let arr = [1 ,2 ,3 ,4];
let proxy = reactive(arr);
proxy.push(5);
// 在set内部其实会干两件事,首先会将5这个值添加到数组下标4的地方,并且会修改length的值
// 判断属性是否原本存在
function hasOwn(target,key) {
return target.hasOwnProperty(key);
}
set(target, key, value, receiver) {
let res = Reflect.set(target, key, value, receiver);
// 判断是新增属性还是修改属性
let hadKey = hasOwn(target,key);
let oldValue = target[key];
if(!hadKey) { // 新增属性
console.log('新增属性');
}else if(oldValue !== value){
console.log('修改属性');
}
return res
},
完整版代码
function isObject(val) {
return typeof val === 'object' && val !== null
}
function reactive(target) {
// 返回一个响应式对象
return createReactiveObject(target);
}
// 判断属性是否原本存在
function hasOwn(target, key) {
return target.hasOwnProperty(key);
}
// WeakMap-弱引用对象,一旦弱引用对象未被使用,会被垃圾回收机制回收
let toProxy = new WeakMap(); // 存放形式 { 原对象(key): 代理过的对象(value)}
let toRow = new WeakMap(); // 存放形式 { 代理过的对象(key): 原对象(value)}
function createReactiveObject(target) {
// 首先由于Proxy所代理的是对象,所以我们需要判断target,若是原始值直接返回
if (!isObject(target)) {
return target;
}
let ByProxy = toProxy.get(target);
// 防止多次代理
if (ByProxy) { // 如果在WeakMap中可以取到值,则说明已经被代理过了,直接返回被代理过的对象
return ByProxy;
}
// 防止多层代理
if (toRow.get(target)) {
return target
}
let handler = {
get(target, key, receiver) {
let res = Reflect.get(target, key, receiver); // 使用Reflect对象做映射,不修改原对象
console.log('获取');
return isObject(res) ? reactive(res) : res;
},
set(target, key, value, receiver) {
let res = Reflect.set(target, key, value, receiver);
// 判断是新增属性还是修改属性
let hadKey = hasOwn(target, key);
let oldValue = target[key];
if (!hadKey) { // 新增属性
console.log('新增属性');
} else if (oldValue !== value) {
console.log('修改属性');
}
return res
},
deleteProperty(target, key) {
let res = Reflect.deleteProperty(target, key)
console.log('删除');
return res;
}
}
let ProxyObj = new Proxy(target, handler); // 被代理过的对象
return ProxyObj;
}
// let proxy = reactive({name: '寒月十九'});
// proxy.name = '十九';
// console.log(proxy.name);
// delete proxy.name;
// console.log(proxy.name);
// let proxy = reactive({name: '寒月十九', message: { like: 'coding' }});
// proxy.message.like = 'writing';
// console.log('====================================');
// console.log(proxy.message.like);
// console.log('====================================');
let arr = [1, 2, 3, 4];
let proxy = reactive(arr);
proxy.push(5)
Proxy的缺陷