欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

手動(dòng)實(shí)現(xiàn)vue2.0的雙向數(shù)據(jù)綁定原理詳解

 更新時(shí)間:2021年02月06日 14:25:04   作者:我還是我…  
這篇文章主要給大家介紹了關(guān)于手動(dòng)實(shí)現(xiàn)vue2.0的雙向數(shù)據(jù)綁定原理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

一句話概括:數(shù)據(jù)劫持(Object.defineProperty)+發(fā)布訂閱模式

雙向數(shù)據(jù)綁定有三大核心模塊(dep 、observer、watcher),它們之間是怎么連接的,下面來(lái)一一介紹。

為了大家更好的理解雙向數(shù)據(jù)綁定原理以及它們之間是如何實(shí)現(xiàn)關(guān)聯(lián)的,先帶領(lǐng)大家復(fù)習(xí)一下發(fā)布訂閱模式。

一.首先了解什么是發(fā)布訂閱模式

直接上代碼:

一個(gè)簡(jiǎn)單的發(fā)布訂閱模式,幫助大家更好的理解雙向數(shù)據(jù)綁定原理

//發(fā)布訂閱模式
function Dep() {
  this.subs = []//收集依賴(也就是手機(jī)watcher實(shí)例),
}
Dep.prototype.addSub = function (sub) { //添加訂閱者
  this.subs.push(sub); //實(shí)際上添加的是watcher這個(gè)實(shí)例
}
Dep.prototype.notify = function (sub) { //發(fā)布,這個(gè)方法的作用是遍歷數(shù)組,讓每個(gè)訂閱者的update方法去執(zhí)行
  this.subs.forEach((sub) => sub.update())
}

function Watcher(fn) {
  this.fn = fn;
}
Watcher.prototype.update = function () { //添加一個(gè)update屬性讓每一個(gè)實(shí)例都可以繼承這個(gè)方法
  this.fn();
}
let watcher = new Watcher(function () {
  alert(1)
});//訂閱
let dep = new Dep();
dep.addSub(watcher);//添加依賴,添加訂閱者
dep.notify();//發(fā)布,讓每個(gè)訂閱者的update方法執(zhí)行

二.new Vue()的時(shí)候做了什么?

只是針對(duì)雙向數(shù)據(jù)綁定做說(shuō)明

<template>
   <div id="app">
    <div>obj.text的值:{{obj.text}}</div>
    <p>word的值:{{word}}</p>
    <input type="text" v-model="word">
  </div>
</template>
<script>
  new Vue({
    el: "#app",
    data: {
      obj: {
        text: "向上",
      },
      word: "學(xué)習(xí)"
    },
    methods:{
    // ...
    }
  })
</script>

Vue構(gòu)造函數(shù)都干什么了?

function Vue(options = {}) {
  this.$options = options;//接收參數(shù)
  var data = this._data = this.$options.data;
  observer(data);//對(duì)data中的數(shù)據(jù)進(jìn)型循環(huán)遞歸綁定
  for (let key in data) {
    let val = data[key];
    observer(val);
    Object.defineProperty(this, key, {
      enumerable: true,
      get() {
        return this._data[key];
      },
      set(newVal) {
        this._data[key] = newVal;
      }
    })
  }
  new Compile(options.el, this)
};

在new Vue({…})構(gòu)造函數(shù)時(shí),首先獲取參數(shù)options,然后把參數(shù)中的data數(shù)據(jù)賦值給當(dāng)前實(shí)例的_data屬性上(this._data = this.$options.data),重點(diǎn)來(lái)了,那下面的遍歷是為什么呢?首先我們?cè)诓僮鲾?shù)據(jù)的時(shí)候是this.word獲取,而不是this._data.word,所以是做了一個(gè)映射,在獲取數(shù)據(jù)的時(shí)候this.word,其實(shí)是獲取的this._data.word的值,大家可以在自己項(xiàng)目中輸出this查看一下

1.接下來(lái)看看observer方法干了什么

function observer(data) {
  if (typeof data !== "object") return;
  return new Observer(data);//返回一個(gè)實(shí)例
}
function Observer(data) {
  let dep = new Dep();//創(chuàng)建一個(gè)dep實(shí)例
  for (let key in data) {//對(duì)數(shù)據(jù)進(jìn)行循環(huán)遞歸綁定
    let val = data[key];
    observer(val);
    Object.defineProperty(data, key, {
      enumerable: true,
      get() {
        Dep.target && dep.depend(Dep.target);//Dep.target就是Watcher的一個(gè)實(shí)例
        return val;
      },
      set(newVal) {
        if (newVal === val) {
          return;
        }
        val = newVal;
        observer(newVal);
        dep.notify() //讓所有方法執(zhí)行
      }
    })
  }
}

Observer構(gòu)造函數(shù),首先let dep=new Dep(),作為之后的觸發(fā)數(shù)據(jù)劫持的get方法和set方法時(shí),去收集依賴和發(fā)布時(shí)調(diào)用,主要的操作就是通過(guò)Object.defineProperty對(duì)data數(shù)據(jù)進(jìn)行循環(huán)遞歸綁定,使用getter/setter修改其默認(rèn)讀寫,用于收集依賴和發(fā)布更新。

2.再來(lái)看看Compile具體干了那些事情

function Compile(el, vm) {
  vm.$el = document.querySelector(el);
  let fragment = document.createDocumentFragment(); //創(chuàng)建文檔碎片,是object類型
  while (child = vm.$el.firstChild) {
    fragment.appendChild(child);
  };//用while循環(huán)把所有節(jié)點(diǎn)都添加到文檔碎片中,之后都是對(duì)文檔碎片的操作,最后再把文檔碎片添加到頁(yè)面中,這里有一個(gè)很重要的特性是,如果使用appendChid方法將原dom樹(shù)中的節(jié)點(diǎn)添加到fragment中時(shí),會(huì)刪除原來(lái)的節(jié)點(diǎn)。
  replace(fragment);

  function replace(fragment) {
    Array.from(fragment.childNodes).forEach((node) => {//循環(huán)所有的節(jié)點(diǎn)
      let text = node.textContent;
      let reg = /\{\{(.*)\}\}/;
      if (node.nodeType === 3 && reg.test(text)) {//判斷當(dāng)前節(jié)點(diǎn)是不是文本節(jié)點(diǎn)且符不符合{{obj.text}}的輸出方式,如果滿足條件說(shuō)明它是雙向的數(shù)據(jù)綁定,要添加訂閱者(watcher)
        console.log(RegExp.$1); //obj.text
        let arr = RegExp.$1.split("."); //轉(zhuǎn)換成數(shù)組的方式[obj,text],方便取值
        let val = vm;
        arr.forEach((key) => { //實(shí)現(xiàn)取值this.obj.text
          val = val[key];
        });
        new Watcher(vm, RegExp.$1, function (newVal) {
          node.textContent = text.replace(/\{\{(.*)\}\}/, newVal)
        });
        node.textContent = text.replace(/\{\{(.*)\}\}/, val); //對(duì)節(jié)點(diǎn)內(nèi)容進(jìn)行初始化的賦值
      }
      if (node.nodeType === 1) { //說(shuō)明是元素節(jié)點(diǎn)
        let nodeAttrs = node.attributes;
        Array.from(nodeAttrs).forEach((item) => {
          if (item.name.indexOf("v-") >= 0) {//判斷是不是v-model這種指令
            node.value = vm[item.value]//對(duì)節(jié)點(diǎn)賦值操作
          }
          //添加訂閱者
          new Watcher(vm, item.value, function (newVal) {
            node.value = vm[item.value]
          });
          node.addEventListener("input", function (e) {
            let newVal = e.target.value;
            vm[item.value] = newVal;
          })
        })
      }
      if (node.childNodes) { //這個(gè)節(jié)點(diǎn)里還有子元素,再遞歸
        replace(node);
      }
    })
  }

  //這是頁(yè)面中的文檔已經(jīng)沒(méi)有了,所以還要把文檔碎片放到頁(yè)面中
  vm.$el.appendChild(fragment);

}

Compile(編譯方法)

首先解釋一下DocuemntFragment(文檔碎片)它是一個(gè)dom節(jié)點(diǎn)收容器,當(dāng)你創(chuàng)造了多個(gè)節(jié)點(diǎn),當(dāng)每個(gè)節(jié)點(diǎn)都插入到文檔當(dāng)中都會(huì)引發(fā)一次回流,也就是說(shuō)瀏覽器要回流多次,十分耗性能,而使用文檔碎片就是把多個(gè)節(jié)點(diǎn)都先放入到一個(gè)容器中,最后再把整個(gè)容器直接插入就可以了,瀏覽器只回流了1次。

Compile方法首先遍歷文檔碎片的所有節(jié)點(diǎn),1.判斷是否是文本節(jié)點(diǎn)且符不符合{{obj.text}}的雙大括號(hào)的輸出方式,如果滿足條件說(shuō)明它是雙向的數(shù)據(jù)綁定,要添加訂閱者(watcher),new Watcher(vm,動(dòng)態(tài)綁定的變量,回調(diào)函數(shù)fn) 2.判斷是否是元素節(jié)點(diǎn)且屬性中是否含有v-model這種指令,如果滿足條件說(shuō)明它是雙向的數(shù)據(jù)綁定,要添加訂閱者(watcher),new Watcher(vm,動(dòng)態(tài)綁定的變量,回調(diào)函數(shù)fn) ,直至遍歷完成。

最后別忘了把文檔碎片放到頁(yè)面中

3.Dep構(gòu)造函數(shù)(怎么收集依賴的)

var uid=0;
//發(fā)布訂閱
function Dep() {
  this.id=uid++;
  this.subs = [];
}
Dep.prototype.addSub = function (sub) { //訂閱
  this.subs.push(sub); //實(shí)際上添加的是watcher這個(gè)實(shí)例
}
Dep.prototype.depend = function () { // 訂閱管理器
  if(Dep.target){//只有Dep.target存在時(shí)采取添加
    Dep.target.addDep(this);
  }
}
Dep.prototype.notify = function (sub) { //發(fā)布,遍歷數(shù)組讓每個(gè)訂閱者的update方法去執(zhí)行
  this.subs.forEach((sub) => sub.update())
}

Dep構(gòu)造函數(shù)內(nèi)部有一個(gè)id和一個(gè)subs,id=uid++ ,id用于作為dep對(duì)象的唯一標(biāo)識(shí),subs就是保存watcher的數(shù)組。depend方法就是一個(gè)訂閱的管理器,會(huì)調(diào)用當(dāng)前watcher的addDep方法添加訂閱者,當(dāng)觸發(fā)數(shù)據(jù)劫持(Object.defineProperty)的get方法時(shí)會(huì)調(diào)用Dep.target && dep.depend(Dep.target)添加訂閱者,當(dāng)數(shù)據(jù)改變時(shí)觸發(fā)數(shù)據(jù)劫持(Object.defineProperty)的set方法時(shí)會(huì)調(diào)用dep.notify方法更新操作。

4.Watcher構(gòu)造函數(shù)干了什么

function Watcher(vm, exp, fn) {
  this.fn = fn;
  this.vm = vm;
  this.exp = exp //
  this.newDeps = [];
  this.depIds = new Set();
  this.newDepIds = new Set();
  Dep.target = this; //this是指向當(dāng)前(Watcher)的一個(gè)實(shí)例
  let val = vm;
  let arr = exp.split(".");
  arr.forEach((k) => { //取值this.obj.text
    val = val[k] //取值this.obj.text,就會(huì)觸發(fā)數(shù)據(jù)劫持的get方法,把當(dāng)前的訂閱者(watcher實(shí)例)添加到依賴中
  });
  Dep.target = null;
}
Watcher.prototype.addDep = function (dep) {
  var id=dep.id;
  if(!this.newDepIds.has(id)){
    this.newDepIds.add(id);
    this.newDeps.push(dep);
    if(!this.depIds.has(id)){
      dep.addSub(this);
    }
  }
 
}
Watcher.prototype.update = function () { //這就是每個(gè)綁定的方法都添加一個(gè)update屬性
  let val = this.vm;
  let arr = this.exp.split(".");
  arr.forEach((k) => { 
    val = val[k] //取值this.obj.text,傳給fn更新操作
  });
  this.fn(val); //傳一個(gè)新值
}

Watcher構(gòu)造函數(shù)干了什么

1 接收參數(shù),定義了幾個(gè)私有屬性( this.newDep ,this.depIds
,this.newDepIds)

2. Dep.target = this,通過(guò)參數(shù)進(jìn)行data取值操作,這就會(huì)觸發(fā)Object.defineProperty的get方法,它會(huì)通過(guò)訂閱者管理器(dep.depend())添加訂閱者,添加完之后再將Dep.target=null置為空;

3.原型上的addDep是通過(guò)id這個(gè)唯一標(biāo)識(shí),和幾個(gè)私有屬性的判斷防止訂閱者被多次重復(fù)添加

4.update方法就是當(dāng)數(shù)據(jù)更新時(shí),dep.notify()執(zhí)行,觸發(fā)訂閱者的update這個(gè)方法, 執(zhí)行發(fā)布更新操作。

總結(jié)一下

vue2.0中雙向數(shù)據(jù)綁定,簡(jiǎn)單來(lái)說(shuō)就是Observer、Watcher、Dep三大部分;

1.首先用Object.defineProperty()循環(huán)遞歸實(shí)現(xiàn)數(shù)據(jù)劫持,為每個(gè)屬性分配一個(gè)訂閱者集合的管理數(shù)組dep;

2.在編譯的時(shí)候,創(chuàng)建文檔碎片,把所有節(jié)點(diǎn)添加到文檔碎片中,遍歷文檔碎片的所有結(jié)點(diǎn),如果是{{}},v-model這種,new Watcher()實(shí)例并向dep的subs數(shù)組中添加該實(shí)例

3.最后修改值就會(huì)觸發(fā)Object.defineProperty()的set方法,在set方法中會(huì)執(zhí)行dep.notify(),然后循環(huán)調(diào)用所有訂閱者的update方法更新視圖。

到此這篇關(guān)于手動(dòng)實(shí)現(xiàn)vue2.0的雙向數(shù)據(jù)綁定原理的文章就介紹到這了,更多相關(guān)vue2.0雙向數(shù)據(jù)綁定內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue-hook-form使用詳解

    vue-hook-form使用詳解

    這篇文章主要為大家詳細(xì)介紹了vue-hook-form的使用方法,以及如何安裝vue-hook-form,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-04-04
  • Vue腳手架的簡(jiǎn)單使用實(shí)例

    Vue腳手架的簡(jiǎn)單使用實(shí)例

    這篇文章主要介紹了Vue腳手架的簡(jiǎn)單使用實(shí)例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-07-07
  • SSM VUE Axios詳解

    SSM VUE Axios詳解

    Axios是在前端開(kāi)發(fā)中常用的一個(gè)發(fā)送 HTTP 請(qǐng)求的庫(kù),用過(guò)的都知道,篇文章主要給大家介紹了關(guān)于vue的相關(guān)資料,需要的朋友可以參考下
    2021-10-10
  • element組件el-date-picker禁用當(dāng)前時(shí)分秒之前的日期時(shí)間選擇

    element組件el-date-picker禁用當(dāng)前時(shí)分秒之前的日期時(shí)間選擇

    本文主要介紹了element組件el-date-picker禁用當(dāng)前時(shí)分秒之前的日期時(shí)間選擇,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • vue axios sessionID每次請(qǐng)求都不同的原因以及修改方式

    vue axios sessionID每次請(qǐng)求都不同的原因以及修改方式

    這篇文章主要介紹了vue axios sessionID每次請(qǐng)求都不同的原因以及修改方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Vue腳手架編寫試卷頁(yè)面功能

    Vue腳手架編寫試卷頁(yè)面功能

    腳手架是一種用于快速開(kāi)發(fā)Vue項(xiàng)目的系統(tǒng)架構(gòu),這篇文章主要介紹了Vue腳手架實(shí)現(xiàn)試卷頁(yè)面功能,通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03
  • Vue中Table組件Select的勾選和取消勾選事件詳解

    Vue中Table組件Select的勾選和取消勾選事件詳解

    這篇文章主要為大家詳細(xì)介紹了Vue中Table組件Select的勾選和取消勾選事件詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-03-03
  • Vue?項(xiàng)目的成功發(fā)布和部署的實(shí)現(xiàn)

    Vue?項(xiàng)目的成功發(fā)布和部署的實(shí)現(xiàn)

    本文主要介紹了Vue?項(xiàng)目的成功發(fā)布和部署的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • vue使用高德地圖點(diǎn)擊下鉆上浮效果的實(shí)現(xiàn)思路

    vue使用高德地圖點(diǎn)擊下鉆上浮效果的實(shí)現(xiàn)思路

    這篇文章主要介紹了vue使用高德地圖點(diǎn)擊下鉆 上浮效果的實(shí)現(xiàn)思路,本文以浙江省為例通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2019-10-10
  • vue用FileSaverJs導(dǎo)出文件

    vue用FileSaverJs導(dǎo)出文件

    FileSaver.js?是在客戶端保存文件的解決方案,非常適合?在客戶端上生成文件的?Web?應(yīng)用,它是?HTML5?版本的?saveAs()?FileSaver?實(shí)現(xiàn),這篇文章主要介紹了vue用FileSaverJs導(dǎo)出文件,需要的朋友可以參考下
    2023-09-09

最新評(píng)論