天天看點

Vue的雙向綁定原理詳解Vue的雙向綁定原理

Vue的雙向綁定原理

Vue采用資料劫持結合釋出者-訂閱者模式的方法,通過Object.defineProperty()來劫持各個屬性的setter,getter屬性,在資料變動話,通知訂閱者,觸發更新回調函數,重新渲染視圖

關鍵元素
observer 實作對vue各個屬性進行監聽
function observer(obj, vm){
      Object.keys(obj).forEach(function(key){
          defineReactive(vm, key, obj[key])
  })
}
// Object.defineProperty改寫各個屬性
function defineReactive( obj, key, val ) {
    // 每個屬性建立個依賴收集對象,get中收集依賴,set中觸發依賴,調用更新函數 
    var dep = new Dep();
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function() {
          // 收集依賴  Dep.target标志
          Dep.target && dep.addSub(Dep.target)
          return val
        },
        set: function(newVal){
            if(newVal === val) return
            // 觸發依賴
            dep.notify()
            val = newVal
      }
  })
}
           
-Dep實作
function Dep(){
    this.subs = []
}
Dep.prototype = {
    constructor: Dep,
    addSub: function(sub){
        this.subs.push(sub)
    },
    notify: function(){
        this.subs.forEach(function(sub){
            sub.update() // 調用的Watcher的update方法
      })
    }
}
           
compiler 實作對vue各個指令模闆的解析器,生成AST抽象文法樹,編譯成Virtual Dom,渲染視圖
function compiler(node, vm){
    var reg = /\{\{(.*)\}\}/;
    // 節點類型為元素
    if(node.nodeType ===1){
        var attr = node.attributes;
        // 解析屬性
        for(var i=0; i< attr.length;i++){
                if(attr[i].nodeName == 'v-model'){
                var  _value = attr[i].nodeValue
                 node.addEventListener('input', function(e){
                    //給相應的data屬性指派,觸發修改屬性的setter
                    vm[_value] = e.target.value
                })
                node.value = vm[_value] // 将data的值指派給node
                node.removeAttribute('v-model')
            }
        }
        new Watcher(vm,node,_value,'input')
    }
    // 節點類型為text
    if(node.nodeType ===3){
       if(reg.test(node.nodeValue)){
           var name = RegExp.$1; 
            name = name.trim()
            new Watcher(vm,node,name,'input')
       }
    }
}
           
Watcher 連接配接observer和compiler,接受每個屬性變動的通知,綁定更新函數,更新視圖
function Watcher(vm,node,name, nodeType){
    Dep.target = this; // this為watcher執行個體
    this.name = name
    this.node = node 
    this.vm = vm
    this.nodeType = nodeType
    this.update() // 綁定更新函數
    Dep.target = null //綁定完後登出 标志
}
Watcher.prototype = {
    get: function(){
        this.value = this.vm[this.name] //觸發observer中的getter監聽
  },
   update: function(){
      this.get()
      if(this.nodeType == 'text'){
        this.node.nodeValue = this.value
      }   
      if(this.nodeType == 'input') {
          this.node.value = this.value
    }
  }
}
           

完整實作

function Vue(options){
    this.date = options.data
    var data = this.data
    observer(data, this) // 監測
    var id = options.el
    var dom = nodeToFragment(document.getElmentById(id),this) //生成Virtual Dom
  // 編譯完成後,生成視圖
    document.getElementById(id).appendChild(dom)    
 }
function nodeToFragment(node, vm){
    var flag = document.createDocumentFragment()
    var child
    while(child = node.firstChild){
        compiler(cild, vm)
        flag.appendChild(child)
  }
  return flag
}

// 調用
  var vm = new Vue({
    el: "app",
    data: {
        msg: "hello word"
  }
})