亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Vue2.x 的雙向綁定原理及實(shí)現(xiàn)

 更新時(shí)間:2021年09月27日 17:28:37   作者:九旬  
這篇文章主要介紹了Vue2.x 的雙向綁定原理,Vue 是利用的 Object.defineProperty() 方法進(jìn)行的數(shù)據(jù)劫持,利用 set、get 來(lái)檢測(cè)數(shù)據(jù)的讀寫(xiě)。需要的朋友可以參考下面文章的具體內(nèi)容

Vue 是利用的 Object.defineProperty() 方法進(jìn)行的數(shù)據(jù)劫持,利用 set、get 來(lái)檢測(cè)數(shù)據(jù)的讀寫(xiě)。

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

MVVM 框架主要包含兩個(gè)方面,數(shù)據(jù)變化更新視圖,視圖變化更新數(shù)據(jù)。

視圖變化更新數(shù)據(jù),如果是像 input 這種標(biāo)簽,可以使用 oninput 事件..

數(shù)據(jù)變化更新視圖可以使用 Object.definProperty() set 方法可以檢測(cè)數(shù)據(jù)變化,當(dāng)數(shù)據(jù)改變就會(huì)觸發(fā)這個(gè)函數(shù),然后更新視圖。

1、實(shí)現(xiàn)過(guò)程

我們知道了如何實(shí)現(xiàn)雙向綁定了,首先要對(duì)數(shù)據(jù)進(jìn)行劫持監(jiān)聽(tīng),所以我們需要設(shè)置一個(gè) Observer 函數(shù),用來(lái)監(jiān)聽(tīng)所有屬性的變化。

如果屬性發(fā)生了變化,那就要告訴訂閱者 watcher 看是否需要更新數(shù)據(jù),如果訂閱者有多個(gè),則需要一個(gè) Dep 來(lái)收集這些訂閱者,然后在監(jiān)聽(tīng)器 observer watcher 之間進(jìn)行統(tǒng)一管理。

還需要一個(gè)指令解析器 compile,對(duì)需要監(jiān)聽(tīng)的節(jié)點(diǎn)和屬性進(jìn)行掃描和解析。

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

  • 實(shí)現(xiàn)一個(gè)監(jiān)聽(tīng)器 Observer,用來(lái)劫持并監(jiān)聽(tīng)所有屬性,如果發(fā)生變動(dòng),則通知訂閱者。
  • 實(shí)現(xiàn)一個(gè)訂閱者 Watcher,當(dāng)接到屬性變化的通知時(shí),執(zhí)行對(duì)應(yīng)的函數(shù),然后更新視圖,使用 Dep 來(lái)收集這些 Watcher。
  • 實(shí)現(xiàn)一個(gè)解析器 Compile,用于掃描和解析的節(jié)點(diǎn)的相關(guān)指令,并根據(jù)初始化模板以及初始化相應(yīng)的訂閱器。

2、顯示一個(gè) Observer

Observer 是一個(gè)數(shù)據(jù)監(jiān)聽(tīng)器,核心方法是利用 Object.defineProperty() 通過(guò)遞歸的方式對(duì)所有屬性都添加 setter、getter 方法進(jìn)行監(jiān)聽(tīng)。

var library = {
  book1: {
    name: "",
  },
  book2: "",
};
observe(library);
library.book1.name = "vue權(quán)威指南"; // 屬性name已經(jīng)被監(jiān)聽(tīng)了,現(xiàn)在值為:“vue權(quán)威指南”
library.book2 = "沒(méi)有此書(shū)籍"; // 屬性book2已經(jīng)被監(jiān)聽(tīng)了,現(xiàn)在值為:“沒(méi)有此書(shū)籍”

// 為數(shù)據(jù)添加檢測(cè)
function defineReactive(data, key, val) {
  observe(val); // 遞歸遍歷所有子屬性
  let dep = new Dep(); // 新建一個(gè)dep
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function() {
      if (Dep.target) {
        // 判斷是否需要添加訂閱者,僅第一次需要添加,之后就不用了,詳細(xì)看Watcher函數(shù)
        dep.addSub(Dep.target); // 添加一個(gè)訂閱者
      }
      return val;
    },
    set: function(newVal) {
      if (val == newVal) return; // 如果值未發(fā)生改變就return
      val = newVal;
      console.log(
        "屬性" + key + "已經(jīng)被監(jiān)聽(tīng)了,現(xiàn)在值為:“" + newVal.toString() + "”"
      );
      dep.notify(); // 如果數(shù)據(jù)發(fā)生變化,就通知所有的訂閱者。
    },
  });
}

// 監(jiān)聽(tīng)對(duì)象的所有屬性
function observe(data) {
  if (!data || typeof data !== "object") {
    return; // 如果不是對(duì)象就return
  }
  Object.keys(data).forEach(function(key) {
    defineReactive(data, key, data[key]);
  });
}
// Dep 負(fù)責(zé)收集訂閱者,當(dāng)屬性發(fā)生變化時(shí),觸發(fā)更新函數(shù)。
function Dep() {
  this.subs = {};
}
Dep.prototype = {
  addSub: function(sub) {
    this.subs.push(sub);
  },
  notify: function() {
    this.subs.forEach((sub) => sub.update());
  },
};

思路分析中,需要有一個(gè)可以容納訂閱者消息訂閱器 Dep,用于收集訂閱者,在屬性發(fā)生變化時(shí)執(zhí)行對(duì)應(yīng)的更新函數(shù)。

從代碼上看,將訂閱器 Dep 添加在 getter 里,是為了讓 Watcher 初始化時(shí)觸發(fā),,因此,需要判斷是否需要訂閱者。

setter 中,如果有數(shù)據(jù)發(fā)生變化,則通知所有的訂閱者,然后訂閱者就會(huì)更新對(duì)應(yīng)的函數(shù)。

到此為止,一個(gè)比較完整的 Observer 就完成了,接下來(lái)開(kāi)始設(shè)計(jì) Watcher.

3、實(shí)現(xiàn) Watcher

訂閱者 Watcher 需要在初始化的時(shí)候?qū)⒆约禾砑拥接嗛喥?Dep 中,我們已經(jīng)知道監(jiān)聽(tīng)器 Observer 是在 get 時(shí)執(zhí)行的 Watcher 操作,所以只需要在 Watcher 初始化的時(shí)候觸發(fā)對(duì)應(yīng)的 get 函數(shù)去添加對(duì)應(yīng)的訂閱者操作即可。

那給如何觸發(fā) get 呢?因?yàn)槲覀円呀?jīng)設(shè)置了 Object.defineProperty() , 所以只需要獲取對(duì)應(yīng)的屬性值就可以觸發(fā)了。

我們只需要在訂閱者 Watcher 初始化的時(shí)候,在 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]; // 強(qiáng)制執(zhí)行監(jiān)聽(tīng)器里的get函數(shù)
    Dep.target = null; // 釋放自己
    return value;
  },
};

到此為止, 簡(jiǎn)單的額 Watcher 設(shè)計(jì)完畢,然后將 Observer Watcher 關(guān)聯(lián)起來(lái),就可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的的雙向綁定了。

因?yàn)檫€沒(méi)有設(shè)計(jì)解析器 Compile,所以可以先將模板數(shù)據(jù)寫(xiě)死。

將代碼轉(zhuǎn)化為 ES6 構(gòu)造函數(shù)的寫(xiě)法,預(yù)覽試試。

https://jsrun.net/8SIKp/embed...

這段代碼因?yàn)闆](méi)有實(shí)現(xiàn)編譯器而是直接傳入了所綁定的變量,我們只在一個(gè)節(jié)點(diǎn)上設(shè)置一個(gè)數(shù)據(jù)(name)進(jìn)行綁定,然后在頁(yè)面上進(jìn)行 new MyVue,就可以實(shí)現(xiàn)雙向綁定了。

并兩秒后進(jìn)行值得改變,可以看到,頁(yè)面也發(fā)生了變化。

// 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

4、實(shí)現(xiàn) Compile

雖然上面實(shí)現(xiàn)了雙向數(shù)據(jù)綁定,但是整個(gè)過(guò)程都沒(méi)有解析 DOM 節(jié)店,而是固定替換的,所以接下來(lái)要實(shí)現(xiàn)一個(gè)解析器來(lái)做數(shù)據(jù)的解析和綁定工作。

解析器 compile 的實(shí)現(xiàn)步驟:

  • 解析模板指令,并替換模板數(shù)據(jù),初始化視圖。
  • 將模板指定對(duì)應(yīng)的節(jié)點(diǎn)綁定對(duì)應(yīng)的更新函數(shù),初始化相應(yīng)的訂閱器。

為了解析模板,首先需要解析 DOM 數(shù)據(jù),然后對(duì)含有 DOM 元素上的對(duì)應(yīng)指令進(jìn)行處理,因此整個(gè) DOM 操作較為頻繁,可以新建一個(gè) fragment 片段,將需要的解析的 DOM 存入 fragment 片段中在進(jìn)行處理。

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

接下來(lái)需要遍歷各個(gè)節(jié)點(diǎn),對(duì)含有相關(guān)指令和模板語(yǔ)法的節(jié)點(diǎn)進(jìn)行特殊處理,先進(jìn)行最簡(jiǎn)單模板語(yǔ)法處理,使用正則解析“{{變量}}”這種形式的語(yǔ)法。

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);  // 繼續(xù)遞歸遍歷子節(jié)點(diǎn)
        }
    });
},
function compileText (node, exp) {
    var self = this;
    var initText = this.vm[exp];
    updateText(node, initText);  // 將初始化的數(shù)據(jù)初始化到視圖中
    new Watcher(this.vm, exp, function (value) {  // 生成訂閱器并綁定更新函數(shù)
        self.updateText(node, value);
    });
},
function updateText (node, value) {
    node.textContent = typeof value == 'undefined' ? '' : value;
}

獲取到最外層的節(jié)點(diǎn)后,調(diào)用 compileElement 函數(shù),對(duì)所有的子節(jié)點(diǎn)進(jìn)行判斷,如果節(jié)點(diǎn)是文本節(jié)點(diǎn)切匹配{{}}這種形式的指令,則進(jìn)行編譯處理,初始化對(duì)應(yīng)的參數(shù)。

然后需要對(duì)當(dāng)前參數(shù)生成一個(gè)對(duì)應(yīng)的更新函數(shù)訂閱器,在數(shù)據(jù)發(fā)生變化時(shí)更新對(duì)應(yīng)的 DOM。

這樣就完成了解析、初始化、編譯三個(gè)過(guò)程了。

接下來(lái)改造一個(gè) myVue 就可以使用模板變量進(jìn)行雙向數(shù)據(jù)綁定了。

https://jsrun.net/K4IKp/embed...

5、添加解析事件

添加完 compile 之后,一個(gè)數(shù)據(jù)雙向綁定就基本完成了,接下來(lái)就是在 Compile 中添加更多指令的解析編譯,比如 v-modelv-on、v-bind 等。

添加一個(gè) 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 函數(shù)是用于遍歷當(dāng)前 dom 的所有節(jié)點(diǎn)屬性,然后判斷屬性是否是指令屬性,如果是在做對(duì)應(yīng)的處理(事件就去監(jiān)聽(tīng)事件、數(shù)據(jù)就去監(jiān)聽(tīng)數(shù)據(jù)..)

6、完整版 myVue

MyVue 中添加 mounted 方法,在所有操作都做完時(shí)執(zhí)行。

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); // 所有事情處理好后執(zhí)行mounted函數(shù)
  }
  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;
      },
    });
  }
}

然后就可以測(cè)試使用了。

https://jsrun.net/Y4IKp/embed...

總結(jié)一下流程,回頭在哪看一遍這個(gè)圖,是不是清楚很多了。

可以查看的代碼地址:Vue2.x 的雙向綁定原理及實(shí)現(xiàn)

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

相關(guān)文章

  • 詳解vue中axios請(qǐng)求的封裝

    詳解vue中axios請(qǐng)求的封裝

    這篇文章主要介紹了vue中axios請(qǐng)求的封裝,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • vue3修改link標(biāo)簽?zāi)J(rèn)icon無(wú)效問(wèn)題詳解

    vue3修改link標(biāo)簽?zāi)J(rèn)icon無(wú)效問(wèn)題詳解

    這篇文章主要介紹了vue3修改link標(biāo)簽?zāi)J(rèn)icon無(wú)效問(wèn)題詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Vue實(shí)現(xiàn)DateRange選擇器的禁選功能

    Vue實(shí)現(xiàn)DateRange選擇器的禁選功能

    在基于Vue.js構(gòu)建的應(yīng)用程序中,處理日期選擇器是一個(gè)常見(jiàn)的需求,尤其是在涉及到日期范圍的選擇時(shí),Vue提供了多種方式來(lái)實(shí)現(xiàn)日期選擇器的功能,并允許我們對(duì)這些組件進(jìn)行高度定制,本文將深入探討如何在Vue應(yīng)用中實(shí)現(xiàn)DateRange選擇器的禁選功能,需要的朋友可以參考下
    2024-10-10
  • 基于Vue實(shí)現(xiàn)tab欄切換內(nèi)容不斷實(shí)時(shí)刷新數(shù)據(jù)功能

    基于Vue實(shí)現(xiàn)tab欄切換內(nèi)容不斷實(shí)時(shí)刷新數(shù)據(jù)功能

    在項(xiàng)目開(kāi)發(fā)中遇到這樣需求,就是有幾個(gè)tab欄,每個(gè)tab欄對(duì)應(yīng)的ajax請(qǐng)求不一樣,內(nèi)容區(qū)域一樣,內(nèi)容為實(shí)時(shí)刷新數(shù)據(jù),實(shí)現(xiàn)方法其實(shí)很簡(jiǎn)單的,下面小編給大家?guī)?lái)了基于Vue實(shí)現(xiàn)tab欄切換內(nèi)容不斷實(shí)時(shí)刷新數(shù)據(jù)功能,需要的朋友參考下吧
    2017-04-04
  • vue2老項(xiàng)目中node-sass更換dart-sass的操作方法

    vue2老項(xiàng)目中node-sass更換dart-sass的操作方法

    這篇文章主要介紹了vue2老項(xiàng)目中node-sass更換dart-sass的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2024-07-07
  • vue+element實(shí)現(xiàn)下拉菜單并帶本地搜索功能示例詳解

    vue+element實(shí)現(xiàn)下拉菜單并帶本地搜索功能示例詳解

    這篇文章主要介紹了vue+element實(shí)現(xiàn)下拉菜單并帶本地搜索功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • Vue組件傳值過(guò)程接收不成功的問(wèn)題及解決

    Vue組件傳值過(guò)程接收不成功的問(wèn)題及解決

    這篇文章主要介紹了Vue組件傳值過(guò)程接收不成功的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • Vue基于NUXT的SSR詳解

    Vue基于NUXT的SSR詳解

    這篇文章主要介紹了Vue基于NUXT的SSR詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-10-10
  • 基于elementUI豎向表格、和并列的案例

    基于elementUI豎向表格、和并列的案例

    這篇文章主要介紹了基于elementUI豎向表格、和并列的案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-10-10
  • Vue實(shí)現(xiàn)簡(jiǎn)單登錄界面

    Vue實(shí)現(xiàn)簡(jiǎn)單登錄界面

    這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)簡(jiǎn)單登錄界面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06

最新評(píng)論