vue 雙向數據綁定的實現學習(一)

前言:本系列學習筆記從如下幾個點展開

  • 什麼是雙向數據綁定
  • 雙向數據綁定的好處
  • 怎麼實現雙向數據綁定
    • 實現雙向數據數據綁定須要哪些知識點
      • 數據劫持
      • 發佈訂閱模式

 先看看咱們要實現的目標是什麼,以下動圖:html

0、什麼是雙向數據綁定

  單向數據綁定:把Model 綁定到View上,當咱們用js修改模型 Model 時候,視圖View上對應的內容也會改動,這就是 數據動,頁面動 。前端

  雙向數據綁定:簡言之 數據動 頁面動,頁面動,數據動, 典型的應用就是在作表單時候,輸入框的內容改動後,跟該輸入框的value 的值改動。vue

  

看vue 官網上的這個V-model  的演示案例:git

 

一、雙向數據綁定的好處

  要說出這個好處的時候,也只有在實際場景中才能對應的顯示出來。好比咱們須要實時顯示數據,咱們一邊說話,一邊實時顯示咱們說的話的文字內容,等等。這讓我想起了去年參加雲棲大會,臺上的大佬一邊說話,下面的字幕實時更新。(固然實現這個技術有不少技術點,咱們不討論這個內容,小編也才疏學淺,搞不懂)github

以上的都是廢話,咱們直接看看怎麼實現這個雙向數據綁定。緩存

1、實現原理

  Vue實現雙向數據綁定的原理:數據劫持 + 發佈訂閱模式(有的也稱爲觀察者模式)app

  數據劫持的核心技術: Object.defineProperty()dom

  **vue 3.0 已經用的不是這個技術了,採用是 原生的 Proxy,聽說速度可以提高100%,截張尤大的ppt,** 2018-11-21 修改本篇筆記ide

(香菇,剛研究會一點,就立馬變了,這就是前端世界),Proxy 的方式將會在本系列筆記結束後,再記錄這個技術點的使用函數

2、數據劫持的方法 Object.defineProperty()

  先上一個參照代碼,它長這個樣子:

var book = {
 _year: 2004,
 edition: 1
};
Object.defineProperty(book, "year", {
 get: function(){
   console.log('訪問year了,返回_year')   
return this._year; }, set: function(newValue){   if (newValue > 2004) {     this._year = newValue;
     console.log('從新設置_year了,並返回edition')     
this.edition += newValue - 2004;   } } }); book.year = 2005; alert(book.edition); //2

 ---摘自 JavaScript高級程序設計

  Object.defineProperty() 的具體介紹,咱們本文不作具體展開,查看我這裏的一篇文章,Object.defineProperty。 咱們這裏先要知道這麼一個事情。這個方法要傳入三個參數,傳入的數據對象data,屬性key,描述符對象。其中,描述符(descriptor)對象的屬 性必須是:configurable、enumerable、writable 和 value。設置其中的一或多個值,能夠修改 對應的特性值。咱們須要用的是這訪問器屬性。當咱們在讀取訪問器屬性時,會調用 getter 函數,這個函數負責返回有效的值;在寫入訪問器屬性時,會調用 setter 函數並傳入新值,這個函數負責決定如何處理數據。上文代碼上的 get 方法,在讀取屬性時調用的函數,set方法,在寫入屬性時調用的函數。

3、發佈訂閱者模式

  我畫了一個圖,來理解這個模式,以下圖:

  

 

代碼解釋:

//下面封裝一個單例模式,內容是發佈訂閱模式
let event = {
    eventList: [],
    listener: function (key, fn) {
        if (!this.eventList[key]) { //沒有訂閱過此類消息,建立一個緩存列表
            this.eventList[key] = [];
        }
        this.eventList[key].push(fn)
    },
    trigger: function () {
        let key = Array.prototype.shift.call(arguments); // marry
        let fns = this.eventList[key];
        if (!fns || fns.length == 0) { //沒有訂閱 則返回
            return false;
        }
        for (let i = 0, fn; fn = fns[i++];) {
            fn.apply(this, arguments)
            // 調用 event.listen 裏面的 fn 方法,經過apply將當前執行的對象指向當前的this,arguments 傳進 fn 函數
        }
    },
    remove: function (key, fn) { // 取消訂閱
        let fns = this.eventList[key];
        if (!fns) {
            return false;
        }
        if(!fn) {
            fns && (fns.length = 0)
        } else {
            for (let l = fns.length-1; l>=0; l--) {
                let _fn = fns[l];
                if( _fn === fn) {
                    fns.splice(l, 1)
                }
            }
        }
    },
    install: function (obj) {
        for (let i in this) {
            if (i === 'install') {
                return false
            }
            obj[i] = this[i];
        }
    }
}


let testMsg = {}
event.install(testMsg)

// 上面方法 就會將event的方法 淺拷貝給 testMsg, 這樣testMsg就有 event的方法和屬性
testMsg.listener('rich', fn1 = (name) => {
    console.log(`${name}知道你有錢了`)
})
testMsg.listener('borrowMoney', fn2 =  (name) => {
    console.log(`${name}想問你借錢`)
})

// listen方法將事件 放進隊列
// testMsg.remove('rich', fn1) // 取消訂閱

// trigger方法,處理事件隊列的方法,調用listen的函數的裏面的回調函數 fn
testMsg.trigger('rich', '張三')
testMsg.trigger('rich', '張三2')
testMsg.trigger('borrowMoney', '李四')
testMsg.trigger('borrowMoney', '李四2')



// 代碼總結:
// 訂閱的事件具備對應的key
    // 經過listener方法,將具體的事件隊列保存到 eventList ,能夠理解爲緩存列表也能夠是事件隊列;
    // 執行trigger 方法,將事件隊列拿出來執行調用
    // remove 方法根據對應的key值,刪除對應的訂閱事件
// 模式總結:封裝一個單例event, 執行installEvent方法,將想要event對象拷貝到某個對象中去,
// 發佈者trigger方法,監聽者listen方法 ,trigger 發佈一個東西,listen立馬知道你要發佈的東西
View Code

 

 

4、實現分解

  • 主函數入口
    •   
      function Myvue (options) {
          this.$options = options
          this.$el = document.querySelector(options.el);
          this.$data = options.data;
          Object.keys(this.$data).forEach(key => {
              this.$prop = key;
          })
          this.init()
      }
      Myvue.prototype.init = function () {
          // 監聽數據變化
          observer(this.$data);
                  // 得到值
                  // let value = this.$data[this.$prop];
                  // 不通過模板編譯直接 通知訂閱者更新dom
                  // new Watcher(this,this.$prop,value => {
                  //     console.log(`watcher ${this.$prop}的改動,要有動靜了`)
                  //     this.$el.textContent = value
                  // }) 
          //通知模板編譯來執行頁面上模板變量替換
          new Compile(this)
      }

       

  • 主函數調用
    •   
      <script>
          const vm = new Myvue({
              el: "#app",
              data: {
                  name: "vue 雙向數據綁定test1"
              }
          });
      </script>

       

  • 監聽器
  • 訂閱者
  • 模板編譯器

 未完待續,錯誤之處,敬請指出,共同進步!

下一篇 vue 雙向數據綁定的實現學習(二)-監聽器的實現

文章參考:

  http://www.javashuo.com/article/p-zupmyrcc-dw.html

  https://github.com/youngwind/blog/issues/87

  http://www.javashuo.com/article/p-rnawiolt-hw.html

後記:代碼只作基本實現,不作代碼健壯性處理,一些錯誤處理已經忽略

相關文章
相關標籤/搜索