Heim  >  Artikel  >  Web-Frontend  >  Ein Artikel über das Prinzip der responsiven Implementierung von Vue

Ein Artikel über das Prinzip der responsiven Implementierung von Vue

青灯夜游
青灯夜游nach vorne
2022-09-15 19:36:431556Durchsuche

Ein Artikel über das Prinzip der responsiven Implementierung von Vue

vue ist ein benutzerfreundliches Framework mit vielen integrierten praktischen Funktionen. Das herausragendste Merkmal ist das reaktionsfähige System, das unten versteckt ist. Komponentenzustände sind alle responsive JavaScript-Objekte. Wenn Sie sie ändern, wird die Ansicht aktualisiert, wodurch die Statusverwaltung einfacher und intuitiver wird. Wie wird also das Vue-Responsive-System implementiert? Dieser Artikel basiert auch auf dem Verständnis und der Nachahmung der Implementierung nach dem Lesen des Vue-Quellcodes. Folgen Sie also den Ideen des Autors und erkunden Sie Vue von der Tiefe zur Tiefe! [Verwandte Empfehlungen: vuejs-Video-Tutorial]

Die Vue-Quellcodeversion dieses Artikels: 2.6.14 Zum besseren Verständnis ist der Code vereinfacht.

Wie Vue die Datenreaktionsfähigkeit implementiert

Wenn Sie ein gewöhnliches JavaScript-Objekt als Datenoption an eine Vue-Instanz übergeben, durchläuft Vue alle Eigenschaften dieses Objekts und verwendet Object.defineProperty, um alle diese Eigenschaften für Getter zu konvertieren /setters, und dann um Getter/Setter herumlaufen.

Um das reaktive System von Vue in einem Satz zusammenzufassen: Observer-Muster + Object.defineProperty Interception Getter/Setter

MDN ObjdefineProperty

Observer-Muster

Was ist „DefineProperty“? Die Methode

Object.defineProperty()Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

简单的说,就是通过此方式定义的 property,执行 obj.xxx 时会触发 get,执行 obj.xxx = xxx会触发 set,这便是响应式的关键。

Object.defineProperty 是 ES5 中一个无法 shim(无法通过polyfill实现) 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

响应式系统基础实现

现在,我们来基于Object.defineProperty definiert direkt eine neue Eigenschaft für ein Objekt oder ändert eine vorhandene Eigenschaft eines Objekts und gibt dieses Objekt zurück.

Einfach ausgedrückt löst die auf diese Weise definierte Eigenschaft get aus, wenn obj.xxx ausgeführt wird, und set wird ausgelöst, wenn obj.xxx = xxx ausgeführt wird. Das ist Reaktionsfähigkeit ist der Schlüssel.

Object.defineProperty ist eine Funktion in ES5, die nicht angepasst werden kann (kann nicht durch Polyfill implementiert werden), weshalb Vue IE8 und niedrigere Browser nicht unterstützt.

Grundlegende Implementierung des reaktionsfähigen Systems

Jetzt implementieren wir ein einfaches reaktionsfähiges Aktualisierungssystem basierend auf Object.defineProperty als „Vorspeise“

let data = {};
// 使用一个中间变量保存 value
let value = "hello";
// 用一个集合保存数据的响应更新函数
let fnSet = new Set();
// 在 data 上定义 text 属性
Object.defineProperty(data, "text", {
  enumerable: true,
  configurable: true,
  set(newValue) {
    value = newValue;
    // 数据变化
    fnSet.forEach((fn) => fn());
  },
  get() {
    fnSet.add(fn);
    return value;
  },
});

// 将 data.text 渲染到页面上
function fn() {
  document.body.innerText = data.text;
}
// 执行函数,触发读取 get
fn();

// 一秒后改变数据,触发 set 更新
setTimeout(() => {
  data.text = "world";
}, 1000);
Als nächstes schauen wir uns die Ausführung dieses Codes an im Server wird den gewünschten Effekt erzielen

Durch den obigen Code denke ich, dass Sie bereits ein gewisses Verständnis für das Funktionsprinzip des reaktionsfähigen Systems haben. Um diesen „Vorgeschmack“ leicht verdaulich zu machen, weist dieses einfache reaktive System immer noch viele Mängel auf. Beispielsweise sind die Daten- und Antwortaktualisierungsfunktionen durch harte Codierung stark miteinander verbunden und erreichen nur eine Eins-zu-Eins-Situation und sind nicht modular Genug. Warten Sie ... Als nächstes vervollständigen wir es nacheinander.

Entwerfen Sie ein vollständig reagierendes SystemUm ein vollständig reagierendes System zu entwerfen, müssen wir zunächst die erforderlichen Kenntnisse verstehen: Was ist das Beobachtermuster? Was ist Beobachtermuster?

Ein Artikel über das Prinzip der responsiven Implementierung von Vue

Es handelt sich um ein Verhaltensentwurfsmuster, mit dem Sie einen Abonnementmechanismus definieren können, der mehrere andere Objekte benachrichtigen kann, die das Objekt „beobachten“, wenn ein Objektereignis auftritt.

Objekte mit einigen bemerkenswerten Zuständen werden oft als

Ziele bezeichnet.

Da es andere Objekte benachrichtigen muss, wenn sich sein eigener Zustand ändert, nennen wir es auch einen

Verleger. Alle anderen Objekte, die Änderungen im Status des Herausgebers folgen möchten, werden als

Abonnenten

bezeichnet. Darüber hinaus interagiert der Herausgeber direkt mit allen Abonnenten nur über die Schnittstelle, und alle müssen über dieselbe Schnittstelle verfügen.

Zum Beispiel?:

Sie (also der Abonnent in der App) interessieren sich für das Wochenmagazin einer bestimmten Buchhandlung Sie hinterlassen Ihrem Chef (also dem Verlag in der App) eine Telefonnummer und lassen ihn Sobald er neue Informationen hat, wird er Sie anrufen, und andere Personen, die sich für diese Wochenzeitung interessieren, werden dem Chef ebenfalls ihre Telefonnummern hinterlassen. Wenn die neue Wochenzeitung erscheint, ruft der Chef einen nach dem anderen an, um die Leser aufzufordern, sie abzuholen. 🎜🎜Wenn ein Leser versehentlich eine QQ-Nummer anstelle einer Telefonnummer hinterlässt, kann die alte Version beim Anrufen nicht durchkommen und der Leser erhält keine Benachrichtigungen. Das haben wir oben gesagt, es muss die gleiche Schnittstelle haben. 🎜🎜Nachdem wir das Beobachtermuster verstanden hatten, begannen wir mit dem Entwurf eines reaktionsfähigen Systems. 🎜🎜🎜🎜Abstrakter Beobachter (Abonnent) Klasse Beobachter🎜🎜🎜

在上面的例子中,数据和响应更新函数是通过硬编码强耦合在一起的。而实际开发过程中,更新函数不一定叫fn,更有可能是一个匿名函数。所以我们需要抽像一个观察者(订阅者)类Watcher来保存并执行更新函数,同时向外提供一个update更新接口。

// Watcher 观察者可能有 n 个,我们为了区分它们,保证唯一性,增加一个 uid
let watcherId = 0;
// 当前活跃的 Watcher
let activeWatcher = null;

class Watcher {
  constructor(cb) {
    this.uid = watcherId++;
    // 更新函数
    this.cb = cb;
    // 保存 watcher 订阅的所有数据
    this.deps = [];
    // 初始化时执行更新函数
    this.get();
  }
  // 求值函数
  get() {
    // 调用更新函数时,将 activeWatcher 指向当前 watcher
    activeWatcher = this;
    this.cb();
    // 调用完重置
    activeWatcher = null;
  }
  // 数据更新时,调用该函数重新求值
  update() {
    this.get();
  }
}

抽象被观察者(发布者)类Dep

我们再想一想,实际开发过程中,data 中肯定不止一个数据,而且每个数据,都有不同的订阅者,所以说我们还需要抽象一个被观察者(发布者)Dep类来保存数据对应的观察者(Watcher),以及数据变化时通知观察者更新。

class Dep {
  constructor() {
    // 保存所有该依赖项的订阅者
    this.subs = [];
  }
  addSubs() {
    // 将 activeWatcher 作为订阅者,放到 subs 中
    // 防止重复订阅
    if(this.subs.indexOf(activeWatcher) === -1){
      this.subs.push(activeWatcher);
    }
  }
  notify() {
    // 先保存旧的依赖,便于下面遍历通知更新
    const deps = this.subs.slice()
    // 每次更新前,清除上一次收集的依赖,下次执行时,重新收集
    this.subs.length = 0;
    deps.forEach((watcher) => {
      watcher.update();
    });
  }
}

抽象 Observer

现在,WatcherDep只是两个独立的模块,我们怎么把它们关联起来呢?

答案就是Object.defineProperty,在数据被读取,触发get方法,Dep 将当前触发 get 的 Watcher 当做订阅者放到 subs中,Watcher 就与 Dep建立关系;在数据被修改,触发set方法,Dep就遍历 subs 中的订阅者,通知Watcher更新。

下面我们就来完善将数据转换为getter/setter的处理。

上面基础的响应式系统实现中,我们只定义了一个响应式数据,当 data 中有其他property时我们就处理不了了。所以,我们需要抽象一个 Observer类来完成对 data数据的遍历,并调用defineReactive转换为 getter/setter,最终完成响应式绑定。

为了简化,我们只处理data中单层数据。

class Observer {
  constructor(value) {
    this.value = value;
    this.walk(value);
  }
  // 遍历 keys,转换为 getter/setter
  walk(obj) {
    const keys = Object.keys(obj);
    for (let i = 0; i <p>这里我们通过参数 value 的闭包,来保存最新的数据,避免新增其他变量</p><pre class="brush:php;toolbar:false">function defineReactive(target, key, value) {
  // 每一个数据都是一个被观察者
  const dep = new Dep();
  Object.defineProperty(target, key, {
    enumerable: true,
    configurable: true,
    // 执行 data.xxx 时 get 触发,进行依赖收集,watcher 订阅 dep
    get() {
      if (activeWatcher) {
        // 订阅
        dep.addSubs(activeWatcher);
      }
      return value;
    },
    // 执行 data.xxx = xxx 时 set 触发,遍历订阅了该 dep 的 watchers,
    // 调用 watcher.updata 更新
    set(newValue) {
      // 如果前后值相等,没必要跟新
      if (value === newVal) {
        return;
      }
      value = newValue;
      // 派发更新
      dep.notify();
    },
  });
}

至此,响应式系统就大功告成了!!

测试

我们通过下面代码测试一下:

let data = {
  name: "张三",
  age: 18,
  address: "成都",
};
// 模拟 render
const render1 = () => {
  console.warn("-------------watcher1--------------");
  console.log("The name value is", data.name);
  console.log("The age value is", data.age);
  console.log("The address value is", data.address);
};
const render2 = () => {
  console.warn("-------------watcher2--------------");
  console.log("The name value is", data.name);
  console.log("The age value is", data.age);
};
// 先将 data 转换成响应式
new Observer(data);
// 实例观察者
new Watcher(render1);
new Watcher(render2);

在浏览器中运行这段代码,和我们期望的一样,两个render都执行了,并且在控制台上打印了结果。

Ein Artikel über das Prinzip der responsiven Implementierung von Vue

我们尝试修改 data.name = '李四 23333333',测试两个 render 都会重新执行:

Ein Artikel über das Prinzip der responsiven Implementierung von Vue

我们只修改 data.address = '北京',测试一下是否只有render 1回调都会重新执行:

Ein Artikel über das Prinzip der responsiven Implementierung von Vue

都完美通过测试!!?

总结

Ein Artikel über das Prinzip der responsiven Implementierung von Vue

Vue响应式原理的核心就是ObserverDepWatcher,三者共同构成 MVVM 中的 VM

Observer中进行数据响应式处理以及最终的WatcherDep关系绑定,在数据被读的时候,触发get方法,将 Watcher收集到 Dep中作为依赖;在数据被修改的时候,触发set方法,Dep就遍历 subs 中的订阅者,通知Watcher更新。

本篇文章属于入门篇,并非源码实现,在源码的基础上简化了很多内容,能够便于理解ObserverDepWatcher三者的作用和关系。

本文的源码,以及作者学习 Vue 源码完整的逐行注释源码地址:github.com/yue1123/vue…

(学习视频分享:web前端开发编程基础视频

Das obige ist der detaillierte Inhalt vonEin Artikel über das Prinzip der responsiven Implementierung von Vue. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen