Heim > Web-Frontend > View.js > Eine eingehende Untersuchung des Reaktionsmechanismus von Vue3

Eine eingehende Untersuchung des Reaktionsmechanismus von Vue3

青灯夜游
Freigeben: 2022-12-20 21:43:39
nach vorne
2995 Leute haben es durchsucht

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!

Eine eingehende Untersuchung des Reaktionsmechanismus von Vue3

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]

1. Was ist Reaktionsfähigkeit?

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
Nach dem Login kopieren

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
Nach dem Login kopieren

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.

2. Reaktionsprinzip

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

Der Kernteil von Vue2 ist die Datenhijacking-API defineProperty. Wenn wir ein Objekt obj definieren, verwenden wir defineProperty als Proxy für das Num-Attribut. Beim Lesen des Num-Attributs wird die Get-Funktion ausgeführt und wenn das Num-Attribut geändert wird Wenn die Set-Funktion ausgeführt wird, müssen wir nur die Logik zur Berechnung von Double in die Set-Funktion schreiben, sodass bei jeder Änderung von Num das Double entsprechend zugewiesen wird, was reagiert.
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
Nach dem Login kopieren

defineProperty-Defekt

: 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

Nur aus dem Namen Proxy können wir ersehen, dass er Proxy bedeutet und die wichtige Bedeutung von Proxy darin besteht, die Mängel der Vue2-Reaktionsfähigkeit zu beheben. Proxy-Verwendung:

var proxy = new Proxy(target, handler);
Nach dem Login kopieren

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.

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35
Nach dem Login kopieren
Unterstützt 13 Arten von benutzerdefiniertem Abfangen auf Proxy
  • get(target, propKey, receiver) :拦截对象属性的读取,比如proxy.fooproxy['foo']
  • set(target, propKey, value, receiver) :拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
  • has(target, propKey) :拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey) :拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target) :拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey) :拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc) :拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target) :拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target) :拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target) :拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto) :拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args) :拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • construct(target, args) :拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

Reflect

在ES6中官方新定义了 Reflect 对象,在ES6之前对象上的所有的方法都是直接挂载在对象这个构造函数的原型身上,而未来对象可能还会有很多方法,如果全部挂载在原型上会显得比较臃肿,而 Reflect 对象就是为了分担 Object的压力。

(1) 将Object对象的一 些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。

(2) 修改某些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
}
Nach dem Login kopieren

(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true
Nach dem Login kopieren

(4)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;
  }
});
Nach dem Login kopieren

所以我们在这里会使用到 Proxy 和 Reflect 对象的方与 Proxy 一一对应这一特性,来实现Vue3的响应式原理。

三、Vue3响应式原理的实现

在Vue3中响应式的核心方法是

function reactive (target){
    // 返回一个响应式对象
    return createReactiveObject(target); 
}
Nach dem Login kopieren

根据我们前面所做的铺垫,所以我们会使用 Proxy 代理我们所需要的相应的对象,同时使用 Reflect 对象来映射。所以我们先初步实现一下,再慢慢优化,尽可能全面。

判断是否为对象(方法不唯一,有多种方法)

function isObject(val){
    return typeof val === 'object' && val !== null
}
Nach dem Login kopieren

尽可能采用函数式编程,让每一个函数只做一件事,逻辑更加清晰。

初步实现

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;
}
Nach dem Login kopieren

Eine eingehende Untersuchung des Reaktionsmechanismus von Vue3

但是这样会有一个问题,如果我需要代理的对象是深层嵌套的对象呢?我们先看看效果

Eine eingehende Untersuchung des Reaktionsmechanismus von Vue3

当我们深层代理时,我们直接修改深层对象中的属性并不会触发 Proxy 对象中的 set 方法,那为什么我们可以修改呢?其实就是直接访问原对象中深层对象的值并修改了,那我们如何优化这个问题呢?

那也需要用到递归操作,判断深层对象是否被代理了,如果没有再执行reactive将内部未被代理的对象代理。

那么我们在 get 方法内部就不能直接将映射之后的 res 返回出去了

解决代理对象内部有嵌套对象

get(target, key, receiver) {
            let res = Reflect.get(target, key, receiver); // 使用Reflect对象做映射,不修改原对象
            console.log('获取');
            // 判断代理之后的对象是否内部含有对象,如果有的话就递归一次
            return isObject(res) ? reactive(res) : res;
        }
Nach dem Login kopieren

解决对象重复代理(多次代理、多层代理)

这样我们就实现了对象的深层代理,并且只有当我们访问到内部嵌套的对象时我们才 会去递归调用reactive ,这样不仅可以实现深层代理,并且节约了性能,但是其实我们还没有彻底完善,我们来看看下面这段代码

let proxy = reactive({name: '寒月十九', message: { like: 'coding' }});
reactive(proxy);
reactive(proxy);
reactive(proxy);
Nach dem Login kopieren

这样是不是合法的,当然是合法的,但是没有必要也没有意义,所以为了避免被代理过的对象,再次被代理,太浪费性能,所以我们需要将被代理的对象打上标记,这样当带被代理过的对象访问到时,直接将被代理过的对象返回,不需要再次代理。

在 Vue3 中,使用了hash表做映射,来记录是否已经被代理了。

// WeakMap-弱引用对象,一旦弱引用对象未被使用,会被垃圾回收机制回收
let toProxy = new WeakMap();  // 存放形式 { 原对象(key): 代理过的对象(value)}
let toRow = new WeakMap();  // 存放形式 { 代理过的对象(key): 原对象(value)}
Nach dem Login kopieren
let ProxyObj = new Proxy(target,handler); // 被代理过的对象
toProxy.set(target,ProxyObj);
toRow.set(ProxyObj.target);
return ProxyObj;
Nach dem Login kopieren
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);
Nach dem Login kopieren

数组相应问题

let arr = [1 ,2 ,3 ,4];
let proxy = reactive(arr);
proxy.push(5);
// 在set内部其实会干两件事,首先会将5这个值添加到数组下标4的地方,并且会修改length的值
Nach dem Login kopieren

Eine eingehende Untersuchung des Reaktionsmechanismus von Vue3

与 Vue2 的数据劫持相比,Vue3 中的 Proxy 可以直接修改数组的长度,但是这样我们需要在 set 方法中判断我们是要在代理对象身上添加属性还是修改属性。

因为更新视图的函数会在set函数中调用,我们向数组中进行操作会触发两次更新视图,所以我们需要做一些优化。

// 判断属性是否原本存在
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
 },
Nach dem Login kopieren

避免多次更新视图,比如修改的值与原来一致就不更新视图,在上面两个判断条件中添加更新视图的函数,就不会多次更新视图。

完整版代码

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)
Nach dem Login kopieren

Proxy的缺陷

在IE11以下的浏览器都不兼容,所以如果使用 Vue3 开发一个单页应用的项目,需要考虑到兼容性问题,需要我们做额外的很多操作,才能使得IE11 以下的版本能够兼容。

(学习视频分享:vuejs入门教程编程基础视频

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!

Verwandte Etiketten:
Quelle:juejin.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage