目錄
Vue 資料雙向綁定原則
實作過程
顯示一個Observer
實作Watcher
實作Compile
添加解析事件
完整版 myVue
首頁 web前端 Vue.js vuejs實作雙向綁定的原理是什麼

vuejs實作雙向綁定的原理是什麼

Sep 28, 2021 pm 02:06 PM
vuejs 雙向綁定

vuejs實現雙向綁定的原則:利用資料劫持和發布訂閱模式,透過「Object.defineProperty()」來劫持各個屬性的setter、getter,在資料變動時發布訊息給訂閱者,觸發對應的監聽回調,進而對檢視進行更新。

vuejs實作雙向綁定的原理是什麼

本教學操作環境:windows7系統、vue2.9.6版,DELL G3電腦。

Vue 資料雙向綁定原則

Vue實作資料雙向綁定主要利用的是: 資料劫持和發佈訂閱模式,利用的Object.defineProperty() 方法進行的資料劫持,然後通知發布者(主題對象)去通知所有觀察者,觀察者收到通知後,就會對視圖進行更新。

https://jsrun.net/RMIKp/embedded/all/light

MVVM 框架主要包含兩個方面,資料變更更新視圖,視圖變更更新資料。

視圖變更更新數據,如果是像input 這種標籤,可以使用oninput 事件..

資料變更更新視圖可以使用Object.definProperty() 的set方法可以偵測資料變化,當資料改變就會觸發這個函數,然後更新視圖。

實作過程

我們知道如何實現雙向綁定了,首先要對資料進行劫持監聽,所以我們需要設定一個 Observer 函數,用來監聽所有屬性的變化。

如果屬性發生了變化,那就要告訴訂閱者watcher 看是否需要更新數據,如果訂閱者有多個,則需要一個Dep 來收集這些訂閱者,然後在監聽器observer 和watcher 之間進行統一管理。

還需要一個指令解析器 compile,對需要監聽的節點和屬性進行掃描和解析。

因此,流程大概是這樣的:

  • 實作一個監聽器 Observer,用來劫持並監聽所有屬性,如果發生變動,則通知訂閱者。

  • 實作一個訂閱者 Watcher,當接到屬性變化的通知時,執行對應的函數,然後更新視圖,使用 Dep 來收集這些 Watcher。

  • 實作一個解析器 Compile,用於掃描和解析的節點的相關指令,並根據初始化模板以及初始化對應的訂閱器。

vuejs實作雙向綁定的原理是什麼

顯示一個Observer

#Observer 是一個資料監聽器,核心方法是利用Object.defineProperty() 透過遞歸的方式對所有屬性都添加setter、getter 方法進行監聽。

var library = {
  book1: {
    name: "",
  },
  book2: "",
};
observe(library);
library.book1.name = "vue权威指南"; // 属性name已经被监听了,现在值为:“vue权威指南”
library.book2 = "没有此书籍"; // 属性book2已经被监听了,现在值为:“没有此书籍”

// 为数据添加检测
function defineReactive(data, key, val) {
  observe(val); // 递归遍历所有子属性
  let dep = new Dep(); // 新建一个dep
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function() {
      if (Dep.target) {
        // 判断是否需要添加订阅者,仅第一次需要添加,之后就不用了,详细看Watcher函数
        dep.addSub(Dep.target); // 添加一个订阅者
      }
      return val;
    },
    set: function(newVal) {
      if (val == newVal) return; // 如果值未发生改变就return
      val = newVal;
      console.log(
        "属性" + key + "已经被监听了,现在值为:“" + newVal.toString() + "”"
      );
      dep.notify(); // 如果数据发生变化,就通知所有的订阅者。
    },
  });
}

// 监听对象的所有属性
function observe(data) {
  if (!data || typeof data !== "object") {
    return; // 如果不是对象就return
  }
  Object.keys(data).forEach(function(key) {
    defineReactive(data, key, data[key]);
  });
}
// Dep 负责收集订阅者,当属性发生变化时,触发更新函数。
function Dep() {
  this.subs = {};
}
Dep.prototype = {
  addSub: function(sub) {
    this.subs.push(sub);
  },
  notify: function() {
    this.subs.forEach((sub) => sub.update());
  },
};
登入後複製

思路分析中,需要有一個可以容納訂閱者訊息訂閱器​​ Dep,用於收集訂閱者,在屬性發生變化時執行對應的更新函數。

從程式碼上看,將訂閱器 Dep 加入在 getter 裡,是為了讓 Watcher 初始化時觸發,,因此,需要判斷是否需要訂閱者。

在 setter 中,如果有資料發生變化,則通知所有的訂閱者,然後訂閱者就會更新對應的函數。

到此為止,一個比較完整的Observer 就完成了,接下來開始設計Watcher.

實作Watcher

訂閱者Watcher 需要在初始化的時候將自己添加到訂閱器Dep 中,我們已經知道監聽器Observer 是在get 時執行的Watcher 操作,所以只需要在Watcher 初始化的時候觸發對應的get 函數去添加對應的訂閱者操作即可。

那給如何觸發 get 呢?因為我們已經設定了 Object.defineProperty(),所以只需要取得對應的屬性值就可以觸發了。

我們只需要在訂閱者 Watcher 初始化的時候,在 Dep.target 上快取下訂閱者,添加成功之後在將其去掉就可以了。

function Watcher(vm, exp, cb) {
  this.cb = cb;
  this.vm = vm;
  this.exp = exp;
  this.value = this.get(); // 将自己添加到订阅器的操作
}

Watcher.prototype = {
  update: function() {
    this.run();
  },
  run: function() {
    var value = this.vm.data[this.exp];
    var oldVal = this.value;
    if (value !== oldVal) {
      this.value = value;
      this.cb.call(this.vm, value, oldVal);
    }
  },
  get: function() {
    Dep.target = this; // 缓存自己,用于判断是否添加watcher。
    var value = this.vm.data[this.exp]; // 强制执行监听器里的get函数
    Dep.target = null; // 释放自己
    return value;
  },
};
登入後複製

到此為止, 簡單的額 Watcher 設計完畢,然後將 Observer 和 Watcher 關聯起來,就可以實現一個簡單的的雙向綁定了。

因為還沒有設計解析器 Compile,所以可以先將模板資料寫死。

將程式碼轉換為 ES6 建構子的寫法,預覽試試。

https://jsrun.net/8SIKp/embedded/all/light

#這段程式碼因為沒有實作編譯器而是直接傳入了所綁定的變量,我們只在一個節點上設定一個資料(name)進行綁定,然後在頁面上進行new MyVue,就可以實現雙向綁定了。

並兩秒後進行值得改變,可以看到,頁面也發生了變化。

// MyVue
proxyKeys(key) {
    var self = this;
    Object.defineProperty(this, key, {
        enumerable: false,
        configurable: true,
        get: function proxyGetter() {
            return self.data[key];
        },
        set: function proxySetter(newVal) {
            self.data[key] = newVal;
        }
    });
}
登入後複製

上面這段程式碼的功能是將 this.data 的 key 代理到 this 上,使得我可以方便的使用 this.xx 就可以取到 this.data.xx。

實作Compile

雖然上面實作了雙向資料綁定,但是整個過程都沒有解析DOM 節店,而是固定替換的,所以接下來要實作一個解析器來做數據的解析和綁定工作。

解析器 compile 的實作步驟:

  • 解析模板指令,並取代模板數據,初始化視圖。

  • 将模板指定对应的节点绑定对应的更新函数,初始化相应的订阅器。

为了解析模板,首先需要解析 DOM 数据,然后对含有 DOM 元素上的对应指令进行处理,因此整个 DOM 操作较为频繁,可以新建一个 fragment 片段,将需要的解析的 DOM 存入 fragment 片段中在进行处理。

function nodeToFragment(el) {
  var fragment = document.createDocumentFragment();
  var child = el.firstChild;
  while (child) {
    // 将Dom元素移入fragment中
    fragment.appendChild(child);
    child = el.firstChild;
  }
  return fragment;
}
登入後複製

接下来需要遍历各个节点,对含有相关指令和模板语法的节点进行特殊处理,先进行最简单模板语法处理,使用正则解析“{{变量}}”这种形式的语法。

function compileElement (el) {
    var childNodes = el.childNodes;
    var self = this;
    [].slice.call(childNodes).forEach(function(node) {
        var reg = /\{\{(.*)\}\}/; // 匹配{{xx}}
        var text = node.textContent;
        if (self.isTextNode(node) && reg.test(text)) {  // 判断是否是符合这种形式{{}}的指令
            self.compileText(node, reg.exec(text)[1]);
        }
        if (node.childNodes && node.childNodes.length) {
            self.compileElement(node);  // 继续递归遍历子节点
        }
    });
},
function compileText (node, exp) {
    var self = this;
    var initText = this.vm[exp];
    updateText(node, initText);  // 将初始化的数据初始化到视图中
    new Watcher(this.vm, exp, function (value) {  // 生成订阅器并绑定更新函数
        self.updateText(node, value);
    });
},
function updateText (node, value) {
    node.textContent = typeof value == 'undefined' ? '' : value;
}
登入後複製

获取到最外层的节点后,调用 compileElement 函数,对所有的子节点进行判断,如果节点是文本节点切匹配{{}}这种形式的指令,则进行编译处理,初始化对应的参数。

然后需要对当前参数生成一个对应的更新函数订阅器,在数据发生变化时更新对应的 DOM。

这样就完成了解析、初始化、编译三个过程了。

接下来改造一个 myVue 就可以使用模板变量进行双向数据绑定了。

https://jsrun.net/K4IKp/embedded/all/light

添加解析事件

添加完 compile 之后,一个数据双向绑定就基本完成了,接下来就是在 Compile 中添加更多指令的解析编译,比如 v-model、v-on、v-bind 等。

添加一个 v-model 和 v-on 解析:

function compile(node) {
  var nodeAttrs = node.attributes;
  var self = this;
  Array.prototype.forEach.call(nodeAttrs, function(attr) {
    var attrName = attr.name;
    if (isDirective(attrName)) {
      var exp = attr.value;
      var dir = attrName.substring(2);
      if (isEventDirective(dir)) {
        // 事件指令
        self.compileEvent(node, self.vm, exp, dir);
      } else {
        // v-model 指令
        self.compileModel(node, self.vm, exp, dir);
      }
      node.removeAttribute(attrName); // 解析完毕,移除属性
    }
  });
}
// v-指令解析
function isDirective(attr) {
  return attr.indexOf("v-") == 0;
}
// on: 指令解析
function isEventDirective(dir) {
  return dir.indexOf("on:") === 0;
}
登入後複製

上面的 compile 函数是用于遍历当前 dom 的所有节点属性,然后判断属性是否是指令属性,如果是在做对应的处理(事件就去监听事件、数据就去监听数据..)

完整版 myVue

在 MyVue 中添加 mounted 方法,在所有操作都做完时执行。

class MyVue {
  constructor(options) {
    var self = this;
    this.data = options.data;
    this.methods = options.methods;
    Object.keys(this.data).forEach(function(key) {
      self.proxyKeys(key);
    });
    observe(this.data);
    new Compile(options.el, this);
    options.mounted.call(this); // 所有事情处理好后执行mounted函数
  }
  proxyKeys(key) {
    // 将this.data属性代理到this上
    var self = this;
    Object.defineProperty(this, key, {
      enumerable: false,
      configurable: true,
      get: function getter() {
        return self.data[key];
      },
      set: function setter(newVal) {
        self.data[key] = newVal;
      },
    });
  }
}
登入後複製

然后就可以测试使用了。

https://jsrun.net/Y4IKp/embedded/all/light

总结一下流程,回头在哪看一遍这个图,是不是清楚很多了。

vuejs實作雙向綁定的原理是什麼

可以查看的代码地址:Vue2.x 的双向绑定原理及实现

相关推荐:《vue.js教程

以上是vuejs實作雙向綁定的原理是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

使用Vue.js和Kotlin語言開發安卓應用程式的一些技巧 使用Vue.js和Kotlin語言開發安卓應用程式的一些技巧 Jul 31, 2023 pm 02:17 PM

使用Vue.js和Kotlin語言開發安卓應用程式的一些技巧隨著行動應用的普及和用戶需求的不斷增長,安卓應用的開發越來越受到開發者的關注。在開發安卓應用程式時,選擇合適的技術堆疊至關重要。近年來,Vue.js和Kotlin語言逐漸成為安卓應用程式開發的熱門選擇。本文將介紹使用Vue.js和Kotlin語言開發安卓應用程式的一些技巧,並給出對應的程式碼範例。一、搭建開發環境在開始

使用Vue.js和Python開發資料視覺化應用的一些技巧 使用Vue.js和Python開發資料視覺化應用的一些技巧 Jul 31, 2023 pm 07:53 PM

使用Vue.js和Python開發資料視覺化應用的一些技巧引言:隨著大數據時代的到來,資料視覺化成為了一個重要的解決方案。而在資料視覺化應用的開發中,Vue.js和Python的組合能夠提供靈活性和強大的功能。本文將分享一些使用Vue.js和Python開發資料視覺化應用的技巧,並附上對應的程式碼範例。一、Vue.js簡介Vue.js是一款輕量級的JavaSc

如何使用Vue實現仿QQ聊天氣泡特效 如何使用Vue實現仿QQ聊天氣泡特效 Sep 20, 2023 pm 02:27 PM

如何使用Vue實現仿QQ聊天氣泡特效在現今的社交時代,聊天功能已成為了手機應用程式和網頁應用程式的核心功能之一。而聊天介面中最常見的元素之一就是聊天氣泡,它可以清楚地將發送者和接收者的訊息區分開來,有效地提高了訊息的可讀性。本文將介紹如何使用Vue實現仿QQ聊天氣泡特效,以及提供具體的程式碼範例。首先,我們需要建立一個Vue元件來表示聊天氣泡。組件包含兩個主要部分

Vue.js與Lua語言的融合,建構遊戲開發的前端引擎的最佳實踐和經驗分享 Vue.js與Lua語言的融合,建構遊戲開發的前端引擎的最佳實踐和經驗分享 Aug 01, 2023 pm 08:14 PM

Vue.js與Lua語言的融合,建構遊戲開發的前端引擎的最佳實踐和經驗分享引言:隨著遊戲開發的不斷發展,遊戲前端引擎的選擇成為了一個重要的決策。在這些選擇中,Vue.js框架和Lua語言都成為了眾多開發者的關注點。 Vue.js作為一款受歡迎的前端框架具有豐富的生態系統和便捷的開發方式,而Lua語言則因其輕量級和高效性能在遊戲開發中得到廣泛應用。本文將探討如何將

如何使用PHP和Vue.js實現圖表上的資料篩選和排序功能 如何使用PHP和Vue.js實現圖表上的資料篩選和排序功能 Aug 27, 2023 am 11:51 AM

如何使用PHP和Vue.js實現圖表上的資料篩選和排序功能在網頁開發中,圖表是一種非常常見的資料展示方式。使用PHP和Vue.js可以輕鬆實現圖表上的資料篩選和排序功能,使用戶能夠自訂查看圖表上的數據,提高資料的視覺化效果和使用者體驗。首先,我們需要準備一組資料供圖表使用。假設我們有一個資料表格,包含姓名、年齡和成績三列,資料如下:姓名年齡成績張三1890李

Vue.js與Objective-C語言的集成,開發可靠的Mac應用的技巧和建議 Vue.js與Objective-C語言的集成,開發可靠的Mac應用的技巧和建議 Jul 30, 2023 pm 03:01 PM

Vue.js與Objective-C語言的集成,開發可靠的Mac應用的技巧和建議近年來,隨著Vue.js在前端開發中的普及和Objective-C在Mac應用開發中的穩定性,開發者們開始嘗試將這兩者結合起來,以開發出更可靠和高效的Mac應用程式。本文將介紹一些技巧和建議,幫助開發者正確整合Vue.js和Objective-C,並開發出高品質的Mac應用。一

使用Vue.js和Perl語言開發高效的網路爬蟲和資料抓取工具 使用Vue.js和Perl語言開發高效的網路爬蟲和資料抓取工具 Jul 31, 2023 pm 06:43 PM

使用Vue.js和Perl語言開發高效的網路爬蟲和資料抓取工具近年來,隨著網路的快速發展和資料的日益重要,網路爬蟲和資料抓取工具的需求也越來越大。在這個背景下,結合Vue.js和Perl語言開發高效率的網路爬蟲和資料抓取工具是個不錯的選擇。本文將介紹如何使用Vue.js和Perl語言開發這樣一個工具,並附上對應的程式碼範例。一、Vue.js和Perl語言的介

Vue.js與Dart語言的集成,建構跨平台行動應用的思路 Vue.js與Dart語言的集成,建構跨平台行動應用的思路 Jul 30, 2023 pm 10:33 PM

Vue.js與Dart語言的集成,建構跨平台行動應用的思維在行動應用開發領域,跨平台的開發框架得到了越來越多的關注。 Vue.js是一種用於建立使用者介面的JavaScript框架,而Dart語言是由Google開發的一種用於建立跨平台應用的語言。本文將探討如何將Vue.js與Dart語言集成,以建立跨平台行動應用程式。一、Vue.js簡介Vue.js被認為是一種輕

See all articles