成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專(zhuān)欄INFORMATION COLUMN

Vue雙向綁定原理,教你一步一步實(shí)現(xiàn)雙向綁定

Labradors / 1296人閱讀

摘要:儲(chǔ)存訂閱器因?yàn)閷傩员槐O(jiān)聽(tīng),這一步會(huì)執(zhí)行監(jiān)聽(tīng)器里的方法這一步我們把也給弄了出來(lái),到這一步我們已經(jīng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的雙向綁定了,我們可以嘗試把兩者結(jié)合起來(lái)看下效果??偨Y(jié)本文主要是對(duì)雙向綁定原理的學(xué)習(xí)與實(shí)現(xiàn)。

當(dāng)今前端天下以 Angular、React、vue 三足鼎立的局面,你不選擇一個(gè)陣營(yíng)基本上無(wú)法立足于前端,甚至是兩個(gè)或者三個(gè)陣營(yíng)都要選擇,大勢(shì)所趨。

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

最近在學(xué)習(xí) Vue,一直以來(lái)對(duì)它的雙向綁定只能算了解并不深入,最近幾天打算深入學(xué)習(xí)下,通過(guò)幾天的學(xué)習(xí)查閱資料,算是對(duì)它的原理有所認(rèn)識(shí),所以自己動(dòng)手寫(xiě)了一個(gè)雙向綁定的例子,下面我們一步步看如何實(shí)現(xiàn)的。

看完這篇文章之后我相信你會(huì)對(duì) Vue 的雙向綁定原理有一個(gè)清楚的認(rèn)識(shí)。也能幫助我們更好的認(rèn)識(shí) Vue。

先看效果圖

//代碼:

{{name}}

數(shù)據(jù)綁定

在正式開(kāi)始之前我們先來(lái)說(shuō)說(shuō)數(shù)據(jù)綁定的事情,數(shù)據(jù)綁定我的理解就是讓數(shù)據(jù)M(model)展示到 視圖V(view)上。我們常見(jiàn)的架構(gòu)模式有 MVC、MVP、MVVM模式,目前前端框架基本上都是采用 MVVM 模式實(shí)現(xiàn)雙向綁定,Vue 自然也不例外。但是各個(gè)框架實(shí)現(xiàn)雙向綁定的方法略有所不同,目前大概有三種實(shí)現(xiàn)方式。

發(fā)布訂閱模式

Angular 的臟查機(jī)制

數(shù)據(jù)劫持

而 Vue 則采用的是數(shù)據(jù)劫持與發(fā)布訂閱相結(jié)合的方式實(shí)現(xiàn)雙向綁定,數(shù)據(jù)劫持主要通過(guò) Object.defineProperty 來(lái)實(shí)現(xiàn)。

Object.defineProperty

這篇文章我們不詳細(xì)討論 Object.defineProperty 的用法,我們主要看看它的存儲(chǔ)屬性 get 與 set。我們來(lái)看看通過(guò)它設(shè)置的對(duì)象屬性之后有何變化。

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

上述代碼就是普通的獲取/設(shè)置對(duì)象的屬性,看不到什么奇怪的變化。

var modeng = {}
var age;
Object.defineProperty(modeng, "age", {
  get: function () {
    console.log("獲取年齡");
    return age;
  },
  set: function (newVal) {
    console.log("設(shè)置年齡");
    age = newVal;
  }
});
modeng.age = 18;
console.log(modeng.age);

你會(huì)發(fā)現(xiàn)通過(guò)上述操作之后,我們?cè)L問(wèn) age 屬性時(shí)會(huì)自動(dòng)執(zhí)行 get 函數(shù),設(shè)置 age 屬性時(shí),會(huì)自動(dòng)執(zhí)行 set 函數(shù),這就給我們的雙向綁定提供了非常大的方便。

分析

我們知道 MVVM 模式在于數(shù)據(jù)與視圖的保持同步,意思是說(shuō)數(shù)據(jù)改變時(shí)會(huì)自動(dòng)更新視圖,視圖發(fā)生變化時(shí)會(huì)更新數(shù)據(jù)。

所以我們需要做的就是如何檢測(cè)到數(shù)據(jù)的變化然后通知我們?nèi)ジ乱晥D,如何檢測(cè)到視圖的變化然后去更新數(shù)據(jù)。檢測(cè)視圖這個(gè)比較簡(jiǎn)單,無(wú)非就是我們利用事件的監(jiān)聽(tīng)即可。

那么如何才能知道數(shù)據(jù)屬性發(fā)生變化呢?這個(gè)就是利用我們上面說(shuō)到的 Object.defineProperty 當(dāng)我們的屬性發(fā)生變化時(shí),它會(huì)自動(dòng)觸發(fā) set 函數(shù)從而能夠通知我們?nèi)ジ乱晥D。

實(shí)現(xiàn)

通過(guò)上面的描述與分析我們知道 Vue 是通過(guò)數(shù)據(jù)劫持結(jié)合發(fā)布訂閱模式來(lái)實(shí)現(xiàn)雙向綁定的。我們也知道數(shù)據(jù)劫持是通過(guò) Object.defineProperty 方法,當(dāng)我們知道這些之后,我們就需要一個(gè)監(jiān)聽(tīng)器 Observer 來(lái)監(jiān)聽(tīng)屬性的變化。得知屬性發(fā)生變化之后我們需要一個(gè) Watcher 訂閱者來(lái)更新視圖,我們還需要一個(gè) compile 指令解析器,用于解析我們的節(jié)點(diǎn)元素的指令與初始化視圖。所以我們需要如下:

Observer 監(jiān)聽(tīng)器:用來(lái)監(jiān)聽(tīng)屬性的變化通知訂閱者

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

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

順著這條思路我們一步一步去實(shí)現(xiàn)。

監(jiān)聽(tīng)器 Observer

監(jiān)聽(tīng)器的作用就是去監(jiān)聽(tīng)數(shù)據(jù)的每一個(gè)屬性,我們上面也說(shuō)了使用 Object.defineProperty 方法,當(dāng)我們監(jiān)聽(tīng)到屬性發(fā)生變化之后我們需要通知 Watcher 訂閱者執(zhí)行更新函數(shù)去更新視圖,在這個(gè)過(guò)程中我們可能會(huì)有很多個(gè)訂閱者 Watcher 所以我們要?jiǎng)?chuàng)建一個(gè)容器 Dep 去做一個(gè)統(tǒng)一的管理。

function defineReactive(data, key, value) {
  //遞歸調(diào)用,監(jiān)聽(tīng)所有屬性
  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 執(zhí)行更新視圖函數(shù)");
  this.subs.forEach(sub => {
    sub.update();
  })
}
Dep.target = null;

以上我們就創(chuàng)建了一個(gè)監(jiān)聽(tīng)器 Observer,我們現(xiàn)在可以嘗試一下給一個(gè)對(duì)象添加監(jiān)聽(tīng)然后改變屬性會(huì)有何變化。

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

我們可以看到瀏覽器控制臺(tái)打印出 “屬性變化通知 Watcher 執(zhí)行更新視圖函數(shù)” 說(shuō)明我們實(shí)現(xiàn)的監(jiān)聽(tīng)器沒(méi)毛病,既然監(jiān)聽(tīng)器有了,我們就可以通知屬性變化了,那肯定是需要 Watcher 的時(shí)候了。

訂閱者 Watcher

Watcher 主要是接受屬性變化的通知,然后去執(zhí)行更新函數(shù)去更新視圖,所以我們做的主要是有兩步:

把 Watcher 添加到 Dep 容器中,這里我們用到了 監(jiān)聽(tīng)器的 get 函數(shù)

接收到通知,執(zhí)行更新函數(shù)。

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; //儲(chǔ)存訂閱器
    const value = this.vm.$data[this.prop]; //因?yàn)閷傩员槐O(jiān)聽(tīng),這一步會(huì)執(zhí)行監(jiān)聽(tīng)器里的 get方法
    Dep.target = null;
    return value;
  }
}

這一步我們把 Watcher 也給弄了出來(lái),到這一步我們已經(jīng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的雙向綁定了,我們可以嘗試把兩者結(jié)合起來(lái)看下效果。

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;
    });
}

這里我們嘗試?yán)靡粋€(gè)實(shí)例來(lái)把數(shù)據(jù)與需要監(jiān)聽(tīng)的屬性傳遞進(jìn)來(lái),通過(guò)監(jiān)聽(tīng)器監(jiān)聽(tīng)數(shù)據(jù),然后添加屬性訂閱,綁定更新函數(shù)。

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

我們可以看到數(shù)據(jù)已經(jīng)正常的顯示在頁(yè)面上,那么我們?cè)谕ㄟ^(guò)控制臺(tái)去修改數(shù)據(jù),發(fā)生變化后視圖也會(huì)跟著修改。

到這一步我們我們基本上已經(jīng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的雙向綁定,但是不難發(fā)現(xiàn)我們這里的屬性都是寫(xiě)死的,也沒(méi)有指令模板的解析,所以下一步我們來(lái)實(shí)現(xiàn)一個(gè)模板解析器。

Compile 解析器

Compile 的主要作用一個(gè)是用來(lái)解析指令初始化模板,一個(gè)是用來(lái)添加添加訂閱者,綁定更新函數(shù)。

因?yàn)樵诮馕?DOM 節(jié)點(diǎn)的過(guò)程中我們會(huì)頻繁的操作 DOM, 所以我們利用文檔片段(DocumentFragment)來(lái)幫助我們?nèi)ソ馕?DOM 優(yōu)化性能。

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;
    //將子節(jié)點(diǎn),全部移動(dòng)文檔片段里
    while (child) {
      fragment.appendChild(child);
      child = el.firstChild;
    }
    return fragment;
  }
}

然后我們就需要對(duì)整個(gè)節(jié)點(diǎn)和指令進(jìn)行處理編譯,根據(jù)不同的節(jié)點(diǎn)去調(diào)用不同的渲染函數(shù),綁定更新函數(shù),編譯完成之后,再把 DOM 片段添加到頁(yè)面中。

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); //渲染{{}} 模板
      }

      //遞歸編譯子節(jié)點(diǎn)
      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);
      }
    });
  },
  //省略。。。
}

因?yàn)榇a比較長(zhǎng)如果全部貼出來(lái)會(huì)影響閱讀,我們主要是講整個(gè)過(guò)程實(shí)現(xiàn)的思路,文章結(jié)束我會(huì)把源碼發(fā)出來(lái),有興趣的可以去查看全部代碼。

到這里我們的整個(gè)的模板編譯也已經(jīng)完成,不過(guò)這里我們并沒(méi)有實(shí)現(xiàn)過(guò)多的指令,我們只是簡(jiǎn)單的實(shí)現(xiàn)了 v-model 指令,本意是通過(guò)這篇文章讓大家熟悉與認(rèn)識(shí) Vue 的雙向綁定原理,并不是去創(chuàng)造一個(gè)新的 MVVM 實(shí)例。所以并沒(méi)有考慮很多細(xì)節(jié)與設(shè)計(jì)。

現(xiàn)在我們實(shí)現(xiàn)了 Observer、Watcher、Compile,接下來(lái)就是把三者給組織起來(lái),成為一個(gè)完整的 MVVM。

創(chuàng)建 Mvue

這里我們創(chuàng)建一個(gè) Mvue 的類(lèi)(構(gòu)造函數(shù))用來(lái)承載 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);
}

然后我們就去測(cè)試一下結(jié)果,看看我們實(shí)現(xiàn)的 Mvue 是不是真的可以運(yùn)行。

{{name}}

我們嘗試去修改數(shù)據(jù),也完全沒(méi)問(wèn)題,但是有個(gè)問(wèn)題就是我們修改數(shù)據(jù)時(shí)時(shí)通過(guò) vm.$data.name 去修改數(shù)據(jù),而不是想 Vue 中直接用 vm.name 就可以去修改,那這個(gè)是怎么做到的呢?其實(shí)很簡(jiǎn)單,Vue 做了一步數(shù)據(jù)代理操作。

數(shù)據(jù)代理

我們來(lái)改造下 Mvue 添加數(shù)據(jù)代理功能,我們也是利用 Object.defineProperty 方法進(jìn)行一步中間的轉(zhuǎn)換操作,間接的去訪問(wèn)。

function Mvue(options) {
  this.$options = options;
  this.$data = options.data;
  this.$el = document.querySelector(options.el);
  //數(shù)據(jù)代理
  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 一樣去修改我們的屬性了,非常完美。完全自己動(dòng)手實(shí)現(xiàn),你也來(lái)試試把,體驗(yàn)下自己動(dòng)手寫(xiě)代碼的樂(lè)趣。

總結(jié)

本文主要是對(duì) Vue 雙向綁定原理的學(xué)習(xí)與實(shí)現(xiàn)。

主要是對(duì)整個(gè)思路的學(xué)習(xí),并沒(méi)有考慮到太多的實(shí)現(xiàn)與設(shè)計(jì)的細(xì)節(jié),所以還存在很多問(wèn)題,并不完美。

源碼地址,整個(gè)過(guò)程的全部代碼,希望對(duì)你有所幫助。

如果你覺(jué)得本文對(duì)你有幫助,歡迎轉(zhuǎn)發(fā),點(diǎn)贊。

關(guān)注微信公眾號(hào):六小登登。領(lǐng)取全套學(xué)習(xí)資源

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/98674.html

相關(guān)文章

  • 基于Object.defineProperty實(shí)現(xiàn)雙向數(shù)據(jù)綁定

    摘要:雙向數(shù)據(jù)綁定可算是前端領(lǐng)域經(jīng)久不衰的熱詞,不管是前端開(kāi)發(fā)還是面試都會(huì)有所涉及。因此,中的挺身而出,拯救了中對(duì)數(shù)組數(shù)據(jù)處理的不足。有興趣的朋友請(qǐng)期待筆者的下一篇博客,討論下用實(shí)現(xiàn)雙向數(shù)據(jù)綁定。 雙向數(shù)據(jù)綁定可算是前端領(lǐng)域經(jīng)久不衰的熱詞,不管是前端開(kāi)發(fā)還是面試都會(huì)有所涉及。而且不同的框架也想盡一切辦法去實(shí)現(xiàn)這一特性,比如:Knockout / Backbone --- 發(fā)布-訂閱模式Ang...

    fredshare 評(píng)論0 收藏0
  • Vue簡(jiǎn)單實(shí)現(xiàn)原理

    摘要:需要指令解析器,對(duì)每個(gè)元素節(jié)點(diǎn)的指令進(jìn)行掃描和解析,根據(jù)指令模板替換數(shù)據(jù),以及綁定相應(yīng)的更新函數(shù)。然后將模版中的變量替換成數(shù)據(jù),渲染,將每個(gè)指令對(duì)應(yīng)的節(jié)點(diǎn)綁定更新函數(shù),添加監(jiān)聽(tīng)數(shù)據(jù)的訂閱者,一旦數(shù)據(jù)發(fā)生變動(dòng),收到通知,更新視圖。 用了Vue也有兩年時(shí)間了,一直以來(lái)都是只知其然,不知其所以然,為了能更好的使用Vue不被Vue所奴役,學(xué)習(xí)一下Vue底層的基本原理。 Vue官網(wǎng)有一段這樣的介...

    luqiuwen 評(píng)論0 收藏0
  • 【教學(xué)向】150行代碼教你實(shí)現(xiàn)一個(gè)低配版的MVVM庫(kù)(1)- 原理

    摘要:模塊則負(fù)責(zé)維護(hù),以及各個(gè)模塊間的調(diào)度思考題了解了的實(shí)現(xiàn)機(jī)制,你能否自己動(dòng)手也試著用百來(lái)行代碼實(shí)現(xiàn)一個(gè)庫(kù)呢好了本教程第一部分設(shè)計(jì)篇就寫(xiě)到這里,具體請(qǐng)移步下一篇教學(xué)向行代碼教你實(shí)現(xiàn)一個(gè)低配版的庫(kù)代碼篇我會(huì)用給出一版實(shí)現(xiàn)。 適讀人群 本文適合對(duì)MVVM有一定了解(如有主流框架ng,vue等使用經(jīng)驗(yàn)配合本文服用則效果更佳),雖然會(huì)用這類(lèi)框架,但是對(duì)框架底層核心實(shí)現(xiàn)又不太清楚,或者能說(shuō)出個(gè)所以然...

    selfimpr 評(píng)論0 收藏0
  • Vue 雙向數(shù)據(jù)綁定原理分析

    摘要:關(guān)于雙向數(shù)據(jù)綁定當(dāng)我們?cè)谇岸碎_(kāi)發(fā)中采用的模式時(shí),,指的是模型,也就是數(shù)據(jù),,指的是視圖,也就是頁(yè)面展現(xiàn)的部分。參考沉思錄一數(shù)據(jù)綁定雙向數(shù)據(jù)綁定實(shí)現(xiàn)數(shù)據(jù)與視圖的綁定與同步,最終體現(xiàn)在對(duì)數(shù)據(jù)的讀寫(xiě)處理過(guò)程中,也就是定義的數(shù)據(jù)函數(shù)中。 關(guān)于雙向數(shù)據(jù)綁定 當(dāng)我們?cè)谇岸碎_(kāi)發(fā)中采用MV*的模式時(shí),M - model,指的是模型,也就是數(shù)據(jù),V - view,指的是視圖,也就是頁(yè)面展現(xiàn)的部分。通常,...

    nanfeiyan 評(píng)論0 收藏0
  • javascript知識(shí)點(diǎn)

    摘要:模塊化是隨著前端技術(shù)的發(fā)展,前端代碼爆炸式增長(zhǎng)后,工程化所采取的必然措施。目前模塊化的思想分為和。特別指出,事件不等同于異步,回調(diào)也不等同于異步。將會(huì)討論安全的類(lèi)型檢測(cè)惰性載入函數(shù)凍結(jié)對(duì)象定時(shí)器等話題。 Vue.js 前后端同構(gòu)方案之準(zhǔn)備篇——代碼優(yōu)化 目前 Vue.js 的火爆不亞于當(dāng)初的 React,本人對(duì)寫(xiě)代碼有潔癖,代碼也是藝術(shù)。此篇是準(zhǔn)備篇,工欲善其事,必先利其器。我們先在代...

    Karrdy 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<