搜尋
首頁web前端js教程帶你詳細實作vue雙向綁定

帶你詳細實作vue雙向綁定

Oct 24, 2018 pm 05:08 PM
javascript

這篇文章帶給大家的內容是關於帶你詳細實現vue雙向綁定,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

當今前端天下以 Angular、React、vue 三足鼎立的局面,你不選擇一個陣營基本上無法立足於前端,甚至是兩個或者三個陣營都要選擇,大勢所趨。

所以我們要時時保持好奇心,擁抱變化,只有在不斷的變化中你才能利於不敗之地,保守只能等死。

最近在學習Vue,一直以來對它的雙向綁定只能算了解並不深入,最近幾天打算深入學習下,透過幾天的學習查閱資料,算是對它的原理有所認識,所以自己動手寫了一個雙向綁定的例子,下面我們一步步看如何實現的。

看完這篇文章後我相信你會對 Vue 的雙向綁定原理有一個清楚的認識。也能幫助我們更好的認識 Vue。

先看效果圖帶你詳細實作vue雙向綁定

//代码:
<div>
    <input>
    <h1 id="name">{{name}}</h1>
</div>
<script></script>
<script></script>
<script></script>
<script></script>
<script>
const vm = new Mvue({
    el: "#app",
    data: {
        name: "我是摩登"
    }
});
</script>

資料綁定

在正式開始之前我們先說資料綁定的事情,資料綁定我的理解就是讓資料M(model)展示到視圖V(view)上。我們常見的架構模式有 MVC、MVP、MVVM模式,目前前端框架基本上都是採用 MVVM 模式實現雙向綁定,Vue 自然也不例外。但是各個框架實現雙向綁定的方法略有不同,目前大概有三種實作方式。

  • 發布訂閱模式

  • Angular 的髒查機制

  • 資料劫持

而Vue 則採用的是資料劫持與發布訂閱結合的方式實現雙向綁定,而資料劫持主要透過Object.defineProperty 來實現。

Object.defineProperty

這篇文章我們不詳細討論 Object.defineProperty 的用法,我們主要看看它的儲存屬性 get 與 set。讓我們來看看透過它設定的物件屬性之後有何變化。

var people = {
    name: "Modeng",
    age: 18
}
people.age; //18
people.age = 20;

上述程式碼就是普通的取得/設定物件的屬性,看不到什麼奇怪的變化。

var modeng = {}
var age;
Object.defineProperty(modeng, 'age', {
  get: function () {
    console.log("获取年龄");
    return age;
  },
  set: function (newVal) {
    console.log("设置年龄");
    age = newVal;
  }
});
modeng.age = 18;
console.log(modeng.age);

你會發現透過上述操作之後,我們存取age 屬性時會自動執行get 函數,設定age 屬性時,會自動執行set 函數,這就給我們的雙向綁定提供了非常大的方便。

分析

我們知道 MVVM 模式在於資料與視圖的保持同步,意思是說資料改變時會自動更新視圖,視圖發生變化時會更新資料。

所以我們需要做的就是如何偵測資料的變化然後通知我們去更新視圖,如何偵測到視圖的變化然後去更新資料。偵測視圖這個比較簡單,無非就是我們利用事件的監聽即可。

那麼如何知道資料屬性改變呢?這就是利用我們上面說到的 Object.defineProperty 當我們的屬性改變時,它會自動觸發 set 函數以便能夠通知我們去更新視圖。

帶你詳細實作vue雙向綁定

實作

透過上面的描述與分析我們知道Vue 是透過資料劫持結合發布訂閱模式來實現雙向綁定的。我們也知道資料劫持是透過 Object.defineProperty 方法,當我們知道這些之後,我們就需要一個監聽器 Observer 來監聽屬性的變化。得知屬性改變之後我們需要一個 Watcher 訂閱者來更新視圖,我們還需要一個 compile 指令解析器,用於解析我們的節點元素的指令與初始化視圖。所以我們需要如下:

  • Observer 監聽器:用來監聽屬性的變化通知訂閱者

  • Watcher 訂閱者:收到屬性的變化,然後更新視圖

  • Compile 解析器:解析指令,初始化模版,綁定訂閱者

帶你詳細實作vue雙向綁定

##順著這條思路我們一步一步去實現。

監聽器Observer

監聽器的作用就是去監聽資料的每一個屬性,我們上面也說了使用

Object.defineProperty 方法,當我們監聽到屬性改變之後我們需要通知Watcher 訂閱者執行更新函數去更新視圖,在這個過程中我們可能會有很多個訂閱者Watcher 所以我們要建立一個容器Dep 去做一個統一的管理。

function defineReactive(data, key, value) {
  //递归调用,监听所有属性
  observer(value);
  var dep = new Dep();
  Object.defineProperty(data, key, {
    get: function () {
      if (Dep.target) {
        dep.addSub(Dep.target);
      }
      return value;
    },
    set: function (newVal) {
      if (value !== newVal) {
        value = newVal;
        dep.notify(); //通知订阅器
      }
    }
  });
}

function observer(data) {
  if (!data || typeof data !== "object") {
    return;
  }
  Object.keys(data).forEach(key => {
    defineReactive(data, key, data[key]);
  });
}

function Dep() {
  this.subs = [];
}
Dep.prototype.addSub = function (sub) {
  this.subs.push(sub);
}
Dep.prototype.notify = function () {
  console.log('属性变化通知 Watcher 执行更新视图函数');
  this.subs.forEach(sub => {
    sub.update();
  })
}
Dep.target = null;
以上我們就創建了一個監聽器 Observer,我們現在可以嘗試一下給一個物件添加監聽然後改變屬性會有何變化。

var modeng = {
  age: 18
}
observer(modeng);
modeng.age = 20;

我们可以看到浏览器控制台打印出 “属性变化通知 Watcher 执行更新视图函数” 说明我们实现的监听器没毛病,既然监听器有了,我们就可以通知属性变化了,那肯定是需要 Watcher 的时候了。

订阅者 Watcher

Watcher 主要是接受属性变化的通知,然后去执行更新函数去更新视图,所以我们做的主要是有两步:

  1. 把 Watcher 添加到 Dep 容器中,这里我们用到了 监听器的 get 函数

  2. 接收到通知,执行更新函数。

function Watcher(vm, prop, callback) {
  this.vm = vm;
  this.prop = prop;
  this.callback = callback;
  this.value = this.get();
}
Watcher.prototype = {
  update: function () {
    const value = this.vm.$data[this.prop];
    const oldVal = this.value;
    if (value !== oldVal) {
      this.value = value;
      this.callback(value);
    }
  },
  get: function () {
    Dep.target = this; //储存订阅器
    const value = this.vm.$data[this.prop]; //因为属性被监听,这一步会执行监听器里的 get方法
    Dep.target = null;
    return value;
  }
}

这一步我们把 Watcher 也给弄了出来,到这一步我们已经实现了一个简单的双向绑定了,我们可以尝试把两者结合起来看下效果。

function Mvue(options, prop) {
    this.$options = options;
    this.$data = options.data;
    this.$prop = prop;
    this.$el = document.querySelector(options.el);
    this.init();
}
Mvue.prototype.init = function () {
    observer(this.$data);
    this.$el.textContent = this.$data[this.$prop];
    new Watcher(this, this.$prop, value => {
        this.$el.textContent = value;
    });
}

这里我们尝试利用一个实例来把数据与需要监听的属性传递进来,通过监听器监听数据,然后添加属性订阅,绑定更新函数。

<p>{{name}}</p>
const vm = new Mvue({
    el: "#app",
    data: {
        name: "我是摩登"
    }
}, "name");

我们可以看到数据已经正常的显示在页面上,那么我们在通过控制台去修改数据,发生变化后视图也会跟着修改。

帶你詳細實作vue雙向綁定

到这一步我们我们基本上已经实现了一个简单的双向绑定,但是不难发现我们这里的属性都是写死的,也没有指令模板的解析,所以下一步我们来实现一个模板解析器。

Compile 解析器

Compile 的主要作用一个是用来解析指令初始化模板,一个是用来添加添加订阅者,绑定更新函数。

因为在解析 DOM 节点的过程中我们会频繁的操作 DOM, 所以我们利用文档片段(DocumentFragment)来帮助我们去解析 DOM 优化性能。

function Compile(vm) {
  this.vm = vm;
  this.el = vm.$el;
  this.fragment = null;
  this.init();
}
Compile.prototype = {
  init: function () {
    this.fragment = this.nodeFragment(this.el);
  },
  nodeFragment: function (el) {
    const fragment = document.createDocumentFragment();
    let child = el.firstChild;
    //将子节点,全部移动文档片段里
    while (child) {
      fragment.appendChild(child);
      child = el.firstChild;
    }
    return fragment;
  }
}

然后我们就需要对整个节点和指令进行处理编译,根据不同的节点去调用不同的渲染函数,绑定更新函数,编译完成之后,再把 DOM 片段添加到页面中。

Compile.prototype = {
  compileNode: function (fragment) {
    let childNodes = fragment.childNodes;
    [...childNodes].forEach(node => {
      let reg = /\{\{(.*)\}\}/;
      let text = node.textContent;
      if (this.isElementNode(node)) {
        this.compile(node); //渲染指令模板
      } else if (this.isTextNode(node) && reg.test(text)) {
        let prop = RegExp.$1;
        this.compileText(node, prop); //渲染{{}} 模板
      }

      //递归编译子节点
      if (node.childNodes && node.childNodes.length) {
        this.compileNode(node);
      }
    });
  },
  compile: function (node) {
    let nodeAttrs = node.attributes;
    [...nodeAttrs].forEach(attr => {
      let name = attr.name;
      if (this.isDirective(name)) {
        let value = attr.value;
        if (name === "v-model") {
          this.compileModel(node, value);
        }
        node.removeAttribute(name);
      }
    });
  },
  //省略。。。
}

因为代码比较长如果全部贴出来会影响阅读,我们主要是讲整个过程实现的思路,文章结束我会把源码发出来,有兴趣的可以去查看全部代码。

到这里我们的整个的模板编译也已经完成,不过这里我们并没有实现过多的指令,我们只是简单的实现了 v-model 指令,本意是通过这篇文章让大家熟悉与认识 Vue 的双向绑定原理,并不是去创造一个新的 MVVM 实例。所以并没有考虑很多细节与设计。

现在我们实现了 Observer、Watcher、Compile,接下来就是把三者给组织起来,成为一个完整的 MVVM。

创建 Mvue

这里我们创建一个 Mvue 的类(构造函数)用来承载 Observer、Watcher、Compile 三者。

function Mvue(options) {
  this.$options = options;
  this.$data = options.data;
  this.$el = document.querySelector(options.el);
  this.init();
}
Mvue.prototype.init = function () {
  observer(this.$data);
  new Compile(this);
}

然后我们就去测试一下结果,看看我们实现的 Mvue 是不是真的可以运行。

<p>
    </p><h1 id="name">{{name}}</h1>

<script></script>
<script></script>
<script></script>
<script></script>
<script>
    const vm = new Mvue({
        el: "#app",
        data: {
            name: "完全没问题,看起来是不是很酷!"
        }
    });
</script>

帶你詳細實作vue雙向綁定

我们尝试去修改数据,也完全没问题,但是有个问题就是我们修改数据时时通过 vm.$data.name 去修改数据,而不是想 Vue 中直接用 vm.name 就可以去修改,那这个是怎么做到的呢?其实很简单,Vue 做了一步数据代理操作。

帶你詳細實作vue雙向綁定

数据代理

我们来改造下 Mvue 添加数据代理功能,我们也是利用 Object.defineProperty 方法进行一步中间的转换操作,间接的去访问。

function Mvue(options) {
  this.$options = options;
  this.$data = options.data;
  this.$el = document.querySelector(options.el);
  //数据代理
  Object.keys(this.$data).forEach(key => {
    this.proxyData(key);
  });

  this.init();
}
Mvue.prototype.init = function () {
  observer(this.$data);
  new Compile(this);
}
Mvue.prototype.proxyData = function (key) {
  Object.defineProperty(this, key, {
    get: function () {
      return this.$data[key]
    },
    set: function (value) {
      this.$data[key] = value;
    }
  });
}

帶你詳細實作vue雙向綁定

到这里我们就可以像 Vue 一样去修改我们的属性了,非常完美。完全自己动手实现,你也来试试把,体验下自己动手写代码的乐趣。

总结

  1. 本文主要是对 Vue 双向绑定原理的学习与实现。

  2. 主要是对整个思路的学习,并没有考虑到太多的实现与设计的细节,所以还存在很多问题,并不完美。

  3. 源码地址,整个过程的全部代码,希望对你有所帮助。


以上是帶你詳細實作vue雙向綁定的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:segmentfault思否。如有侵權,請聯絡admin@php.cn刪除
Python vs. JavaScript:開發環境和工具Python vs. JavaScript:開發環境和工具Apr 26, 2025 am 12:09 AM

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。

JavaScript是用C編寫的嗎?檢查證據JavaScript是用C編寫的嗎?檢查證據Apr 25, 2025 am 12:15 AM

是的,JavaScript的引擎核心是用C語言編寫的。 1)C語言提供了高效性能和底層控制,適合JavaScript引擎的開發。 2)以V8引擎為例,其核心用C 編寫,結合了C的效率和麵向對象特性。 3)JavaScript引擎的工作原理包括解析、編譯和執行,C語言在這些過程中發揮關鍵作用。

JavaScript的角色:使網絡交互和動態JavaScript的角色:使網絡交互和動態Apr 24, 2025 am 12:12 AM

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C和JavaScript:連接解釋C和JavaScript:連接解釋Apr 23, 2025 am 12:07 AM

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

從網站到應用程序:JavaScript的不同應用從網站到應用程序:JavaScript的不同應用Apr 22, 2025 am 12:02 AM

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python vs. JavaScript:比較用例和應用程序Python vs. JavaScript:比較用例和應用程序Apr 21, 2025 am 12:01 AM

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C/C在JavaScript口譯員和編譯器中的作用C/C在JavaScript口譯員和編譯器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在行動中:現實世界中的示例和項目JavaScript在行動中:現實世界中的示例和項目Apr 19, 2025 am 12:13 AM

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

See all articles

熱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

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

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器