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

Vue2.x 的雙向綁定原理及實現

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

Vue 是利用的 Object.defineProperty() 方法進行的數據劫持,利用 set、get 來檢測數據的讀寫。

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

MVVM 框架主要包含兩個方面,數據變化更新視圖,視圖變化更新數據。

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

數據變化更新視圖可以使用 Object.definProperty() set 方法可以檢測數據變化,當數據改變就會觸發(fā)這個函數,然后更新視圖。

1、實現過程

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

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

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

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

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

2、顯示一個 Observer

Observer 是一個數據監(jiān)聽器,核心方法是利用 Object.defineProperty() 通過遞歸的方式對所有屬性都添加 setter、getter 方法進行監(jiān)聽。

var library = {
  book1: {
    name: "",
  },
  book2: "",
};
observe(library);
library.book1.name = "vue權威指南"; // 屬性name已經被監(jiān)聽了,現在值為:“vue權威指南”
library.book2 = "沒有此書籍"; // 屬性book2已經被監(jiān)聽了,現在值為:“沒有此書籍”

// 為數據添加檢測
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; // 如果值未發(fā)生改變就return
      val = newVal;
      console.log(
        "屬性" + key + "已經被監(jiān)聽了,現在值為:“" + newVal.toString() + "”"
      );
      dep.notify(); // 如果數據發(fā)生變化,就通知所有的訂閱者。
    },
  });
}

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

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

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

setter 中,如果有數據發(fā)生變化,則通知所有的訂閱者,然后訂閱者就會更新對應的函數。

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

3、實現 Watcher

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

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

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

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

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

將代碼轉化為 ES6 構造函數的寫法,預覽試試。

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

這段代碼因為沒有實現編譯器而是直接傳入了所綁定的變量,我們只在一個節(jié)點上設置一個數據(name)進行綁定,然后在頁面上進行 new MyVue,就可以實現雙向綁定了。

并兩秒后進行值得改變,可以看到,頁面也發(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、實現 Compile

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

解析器 compile 的實現步驟:

  • 解析模板指令,并替換模板數據,初始化視圖。
  • 將模板指定對應的節(jié)點綁定對應的更新函數,初始化相應的訂閱器。

為了解析模板,首先需要解析 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;
}

接下來需要遍歷各個節(jié)點,對含有相關指令和模板語法的節(jié)點進行特殊處理,先進行最簡單模板語法處理,使用正則解析“{{變量}}”這種形式的語法。

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é)點
        }
    });
},
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;
}

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

然后需要對當前參數生成一個對應的更新函數訂閱器,在數據發(fā)生變化時更新對應的 DOM。

這樣就完成了解析、初始化、編譯三個過程了。

接下來改造一個 myVue 就可以使用模板變量進行雙向數據綁定了。

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

5、添加解析事件

添加完 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 的所有節(jié)點屬性,然后判斷屬性是否是指令屬性,如果是在做對應的處理(事件就去監(jiān)聽事件、數據就去監(jiān)聽數據..)

6、完整版 myVue

MyVue 中添加 mounted 方法,在所有操作都做完時執(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函數
  }
  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/embed...

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

可以查看的代碼地址:Vue2.x 的雙向綁定原理及實現

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

相關文章

  • 詳解vue中axios請求的封裝

    詳解vue中axios請求的封裝

    這篇文章主要介紹了vue中axios請求的封裝,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-04-04
  • vue3修改link標簽默認icon無效問題詳解

    vue3修改link標簽默認icon無效問題詳解

    這篇文章主要介紹了vue3修改link標簽默認icon無效問題詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-10-10
  • Vue實現DateRange選擇器的禁選功能

    Vue實現DateRange選擇器的禁選功能

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

    基于Vue實現tab欄切換內容不斷實時刷新數據功能

    在項目開發(fā)中遇到這樣需求,就是有幾個tab欄,每個tab欄對應的ajax請求不一樣,內容區(qū)域一樣,內容為實時刷新數據,實現方法其實很簡單的,下面小編給大家?guī)砹嘶赩ue實現tab欄切換內容不斷實時刷新數據功能,需要的朋友參考下吧
    2017-04-04
  • vue2老項目中node-sass更換dart-sass的操作方法

    vue2老項目中node-sass更換dart-sass的操作方法

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

    vue+element實現下拉菜單并帶本地搜索功能示例詳解

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

    Vue組件傳值過程接收不成功的問題及解決

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

    Vue基于NUXT的SSR詳解

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

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

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

    Vue實現簡單登錄界面

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

最新評論