碼迷,www.tparu.icu
吉利平特名人堂 > 其他好文 > 詳細

神童平特一肖彩图109:vue(原理)_數據綁定

時間:2019-05-23 18:25:22      閱讀:11      評論:0      收藏:0      [點我收藏+]

吉利平特名人堂 www.tparu.icu 標簽:組成   gif   綁定   總結   相關   自動調用   ret   完成   http   

一、前言                                                                         

                       1、數據綁定原理

                               2、在數據綁定中四個重要的對象

                               3、具體實現

                                                  3.1初始化階段

                                                  3.2建立Dep和watcher的聯系階段

                                                  3.3更新階段

 

 

 

二、主要內容                                                                  

1、數據綁定原理

  (1)概念:一旦更新了某個數據,該節點上所有直接使用或者間接使用的節點都會更新

  (2)導致頁面更新的操作

    •   方式一:原生js實現: 先獲取節點對象,操作節點對象,頁面發生改變
    •   方式二:vue中只需要更新data中的數據,界面中用{{msg}},或者間接使用計算屬性都會導致頁面發生變化

  (3)基本思想

    第一步:給data中所有的屬性添加set和get方法

    第二步:用數據劫持技術實現數據綁定。思想:definedProtype去監視數據是否變化,一旦變化就去更新界面。

    第三步:舉例(

        首先給vm實例中data添加xxx屬性,然后會給這個實例中添加set/get方法

        然后會給data中的屬性添加set/get方法

        如果用this.xxxx=xxxx去改變屬性的改變,首先vm中的set先知道,然后會通知data中數據改變

        一旦data中數據改變,data中對應屬性的set方法就會監視到數據改變,然后就會去更新頁面

         ?。?/span>

 

2、在數據綁定中四個重要的對象

Observer(在數據劫持中創建)

(1)是一個對data中所有屬性進行劫持的構造函數

(2)給data中的所有屬性添加set/get方法(重新定義)

(3)為data中所有的屬性重新創建Dep(依賴)對象

 
Dep(Depend)

(1)data中的每個屬性(所有層次)都對應一個dep對象

(2)創建的時機:

*在初始化定義data中的屬性的時候創建對應的dep對象

*在data中的某個屬性被設置為新對象的時候

(3)Dep對象的結構:

{

     id,//每個dep對應唯一的id

     subs//包含n個對應的watcher的數組

}

(4)subs屬性

*當watcher被創建是,內部將當前watcher對象添加到對應的Dep對象的subs里面去

*當data屬性的值發生改變的時候,subs中所有的watcher都會收到更新的通知

從而更新頁面

 
Compiler

(1)用來解析模板頁面的對象的構造函數

(2)利用compile對象解析模板頁面

(3)每解析一個表達式(非事件指令)都會創建對應的watcher對象,并建立watcher和dep之間的聯系

(4)complie與watcher:一對多(一個屬性可能被多次使用)

 
Watcher

(1)模板中每個非事件指令或者表達式都對應一個Watcher對象

(2)監視當前表達式數據的變化

(3)創建時機:在初始化編譯模板時

(4)對象組成:

{

vm,

exp,

cb, //當表達式的數據發生改變時的回調函數

value,

depIds//表達式中各級屬性所對應的dep對象的集合對象

 

}

 

(5)總結:dep與watcher的關系:多對多

       a. data中的一個屬性對應一個dep, 一個dep中可能包含多個Watcher(模板中有幾個表達式使用到了同一個屬性)

       b.模板中一個非事件表達式對應一個watcher, 一個watcher中可能包含多個dep(表達式是多層)

       c.使用到數據劫持技術和消息訂閱與發布技術

 

 

3、具體實現

 3.1初始化階段

(1)數據劫持實現:

技術圖片

 

 數據劫持:

技術圖片

 

 幾個重要的點:

                            ①defineReactive:進行響應式數據劫持的時候就會創建Dep對象

                            ②definedReactive里面又重新定義了data里面的對象,目的是給里面的屬性添加set()/get()方法

                      ③修改的值也為對象,需要對新值進行監視

 

 

(2)模板編譯:模板編譯完成后會創建watcher對象

                        技術圖片

 

(3)此時頁面已經有如下幾個對象:

技術圖片

 

 

 3.2建立Dep和watcher的聯系階段

 

(1)如下圖所示

技術圖片

 

 

3.3更新階段

技術圖片

 

此時更新階段完成:

                        技術圖片

 4、測試代碼

技術圖片
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>06_數據劫持-數據綁定</title>
  <!--
    1. 數據綁定
      * 初始化顯示: 頁面(表達式/指令)能從data讀取數據顯示 (編譯/解析)
      * 更新顯示: 更新data中的屬性數據==>頁面更新
  -->
</head>
<body>

<div id="test">
  <p>{{name}}</p>
  <p v-text="name"></p>
  <p v-text="wife.name"></p>
  <button v-on:click="update">更新</button>
</div>

<!--
dep
  與data中的屬性一一對應  (4)
watcher
  與模板中一般指令/大括號表達式一一對應 (3)

1. 什么時候一個dep中關聯多個watcher?
  多個指令或表達式用到了當前同一個屬性  {{name}} {{name}}
2. 什么時候一個watcher中關聯多個dep?
  多層表達式的watcher對應多個dep    {{a.b.c}}
-->


<script type="text/javascript" src="js/mvvm/compile.js"></script>
<script type="text/javascript" src="js/mvvm/mvvm.js"></script>
<script type="text/javascript" src="js/mvvm/observer.js"></script>
<script type="text/javascript" src="js/mvvm/watcher.js"></script>
<script type="text/javascript">
  new MVVM({
    el: #test,
    data: {
      name: sadamu,  // dep0
      wife: { // dep1
        name: binbin, // dep2
        age: 18 // dep3
      }
    },
    methods: {
      update () {
        this.name = avatar
      }
    }
  })
</script>
</body>

</html>
數據綁定.html
技術圖片
function Compile(el, vm) {
  // 保存vm
  this.$vm = vm;
  // 保存el元素
  this.$el = this.isElementNode(el) ? el : document.querySelector(el);
  // 如果el元素存在
  if (this.$el) {
    // 1. 取出el中所有子節點, 封裝在一個framgment對象中
    this.$fragment = this.node2Fragment(this.$el);
    // 2. 編譯fragment中所有層次子節點
    this.init();
    // 3. 將fragment添加到el中
    this.$el.appendChild(this.$fragment);
  }
}

Compile.prototype = {
  node2Fragment: function (el) {
    var fragment = document.createDocumentFragment(),
      child;

    // 將原生節點拷貝到fragment
    while (child = el.firstChild) {
      fragment.appendChild(child);
    }

    return fragment;
  },

  init: function () {
    // 編譯fragment
    this.compileElement(this.$fragment);
  },

  compileElement: function (el) {
    // 得到所有子節點
    var childNodes = el.childNodes,
      // 保存compile對象
      me = this;
    // 遍歷所有子節點
    [].slice.call(childNodes).forEach(function (node) {
      // 得到節點的文本內容
      var text = node.textContent;
      // 正則對象(匹配大括號表達式)
      var reg = /\{\{(.*)\}\}/;  // {{name}}
      // 如果是元素節點
      if (me.isElementNode(node)) {
        // 編譯元素節點的指令屬性
        me.compile(node);
        // 如果是一個大括號表達式格式的文本節點
      } else if (me.isTextNode(node) && reg.test(text)) {
        // 編譯大括號表達式格式的文本節點
        me.compileText(node, RegExp.$1); // RegExp.$1: 表達式   name
      }
      // 如果子節點還有子節點
      if (node.childNodes && node.childNodes.length) {
        // 遞歸調用實現所有層次節點的編譯
        me.compileElement(node);
      }
    });
  },

  compile: function (node) {
    // 得到所有標簽屬性節點
    var nodeAttrs = node.attributes,
      me = this;
    // 遍歷所有屬性
    [].slice.call(nodeAttrs).forEach(function (attr) {
      // 得到屬性名: v-on:click
      var attrName = attr.name;
      // 判斷是否是指令屬性
      if (me.isDirective(attrName)) {
        // 得到表達式(屬性值): test
        var exp = attr.value;
        // 得到指令名: on:click
        var dir = attrName.substring(2);
        // 事件指令
        if (me.isEventDirective(dir)) {
          // 解析事件指令
          compileUtil.eventHandler(node, me.$vm, exp, dir);
        // 普通指令
        } else {
          // 解析普通指令
          compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
        }

        // 移除指令屬性
        node.removeAttribute(attrName);
      }
    });
  },

  compileText: function (node, exp) {
    // 調用編譯工具對象解析
    compileUtil.text(node, this.$vm, exp);
  },

  isDirective: function (attr) {
    return attr.indexOf(‘v-‘) == 0;
  },

  isEventDirective: function (dir) {
    return dir.indexOf(‘on‘) === 0;
  },

  isElementNode: function (node) {
    return node.nodeType == 1;
  },

  isTextNode: function (node) {
    return node.nodeType == 3;
  }
};

// 指令處理集合
var compileUtil = {
  // 解析: v-text/{{}}
  text: function (node, vm, exp) {
    this.bind(node, vm, exp, ‘text‘);
  },
  // 解析: v-html
  html: function (node, vm, exp) {
    this.bind(node, vm, exp, ‘html‘);
  },

  // 解析: v-model
  model: function (node, vm, exp) {
    this.bind(node, vm, exp, ‘model‘);

    var me = this,
      val = this._getVMVal(vm, exp);
    node.addEventListener(‘input‘, function (e) {
      var newValue = e.target.value;
      if (val === newValue) {
        return;
      }

      me._setVMVal(vm, exp, newValue);
      val = newValue;
    });
  },

  // 解析: v-class
  class: function (node, vm, exp) {
    this.bind(node, vm, exp, ‘class‘);
  },

  // 真正用于解析指令的方法
  bind: function (node, vm, exp, dir) {
    /*實現初始化顯示*/
    // 根據指令名(text)得到對應的更新節點函數
    var updaterFn = updater[dir + ‘Updater‘];
    // 如果存在調用來更新節點
    updaterFn && updaterFn(node, this._getVMVal(vm, exp));

    // 創建表達式對應的watcher對象
    new Watcher(vm, exp, function (value, oldValue) {/*更新界面*/
      // 當對應的屬性值發生了變化時, 自動調用, 更新對應的節點
      updaterFn && updaterFn(node, value, oldValue);
    });
  },

  // 事件處理
  eventHandler: function (node, vm, exp, dir) {
    // 得到事件名/類型: click
    var eventType = dir.split(‘:‘)[1],
      // 根據表達式得到事件處理函數(從methods中): test(){}
      fn = vm.$options.methods && vm.$options.methods[exp];
    // 如果都存在
    if (eventType && fn) {
      // 綁定指定事件名和回調函數的DOM事件監聽, 將回調函數中的this強制綁定為vm
      node.addEventListener(eventType, fn.bind(vm), false);
    }
  },

  // 得到表達式對應的value
  _getVMVal: function (vm, exp) {
    var val = vm._data;
    exp = exp.split(‘.‘);
    exp.forEach(function (k) {
      val = val[k];
    });
    return val;
  },

  _setVMVal: function (vm, exp, value) {
    var val = vm._data;
    exp = exp.split(‘.‘);
    exp.forEach(function (k, i) {
      // 非最后一個key,更新val的值
      if (i < exp.length - 1) {
        val = val[k];
      } else {
        val[k] = value;
      }
    });
  }
};

// 包含多個用于更新節點方法的對象
var updater = {
  // 更新節點的textContent
  textUpdater: function (node, value) {
    node.textContent = typeof value == ‘undefined‘ ? ‘‘ : value;
  },

  // 更新節點的innerHTML
  htmlUpdater: function (node, value) {
    node.innerHTML = typeof value == ‘undefined‘ ? ‘‘ : value;
  },

  // 更新節點的className
  classUpdater: function (node, value, oldValue) {
    var className = node.className;
    className = className.replace(oldValue, ‘‘).replace(/\s$/, ‘‘);

    var space = className && String(value) ? ‘ ‘ : ‘‘;

    node.className = className + space + value;
  },

  // 更新節點的value
  modelUpdater: function (node, value, oldValue) {
    node.value = typeof value == ‘undefined‘ ? ‘‘ : value;
  }
};
complie.js
技術圖片
/*
相關于Vue的構造函數
 */
function MVVM(options) {
  // 將選項對象保存到vm
  this.$options = options;
  // 將data對象保存到vm和datq變量中
  var data = this._data = this.$options.data;
  //將vm保存在me變量中
  var me = this;
  // 遍歷data中所有屬性
  Object.keys(data).forEach(function (key) { // 屬性名: name
    // 對指定屬性實現代理
    me._proxy(key);
  });

  // 對data進行監視
  observe(data, this);

  // 創建一個用來編譯模板的compile對象
  this.$compile = new Compile(options.el || document.body, this)
}

MVVM.prototype = {
  $watch: function (key, cb, options) {
    new Watcher(this, key, cb);
  },

  // 對指定屬性實現代理
  _proxy: function (key) {
    // 保存vm
    var me = this;
    // 給vm添加指定屬性名的屬性(使用屬性描述)
    Object.defineProperty(me, key, {
      configurable: false, // 不能再重新定義
      enumerable: true, // 可以枚舉
      // 當通過vm.name讀取屬性值時自動調用
      get: function proxyGetter() {
        // 讀取data中對應屬性值返回(實現代理讀操作)
        return me._data[key];
      },
      // 當通過vm.name = ‘xxx‘時自動調用
      set: function proxySetter(newVal) {
        // 將最新的值保存到data中對應的屬性上(實現代理寫操作)
        me._data[key] = newVal;
      }
    });
  }
};
mvvm.js
技術圖片
function Observer(data) {
    // 保存data對象
    this.data = data;
    // 走起
    this.walk(data);
}

Observer.prototype = {
    walk: function(data) {
        var me = this;
        // 遍歷data中所有屬性
        Object.keys(data).forEach(function(key) {
            // 針對指定屬性進行處理
            me.convert(key, data[key]);
        });
    },
    convert: function(key, val) {
        // 對指定屬性實現響應式數據綁定
        this.defineReactive(this.data, key, val);
    },

    defineReactive: function(data, key, val) {
        // 創建與當前屬性對應的dep對象
        var dep = new Dep();
        // 間接遞歸調用實現對data中所有層次屬性的劫持
        var childObj = observe(val);
        // 給data重新定義屬性(添加set/get)
        Object.defineProperty(data, key, {
            enumerable: true, // 可枚舉
            configurable: false, // 不能再define
            get: function() {
                // 建立dep與watcher的關系
                if (Dep.target) {
                    dep.depend();
                }
                // 返回屬性值
                return val;
            },
            set: function(newVal) {
                if (newVal === val) {
                    return;
                }
                val = newVal;
                // 新的值是object的話,進行監聽
                childObj = observe(newVal);
                // 通過dep
                dep.notify();
            }
        });
    }
};

function observe(value, vm) {
    // value必須是對象, 因為監視的是對象內部的屬性
    if (!value || typeof value !== ‘object‘) {
        return;
    }
    // 創建一個對應的觀察都對象
    return new Observer(value);
};


var uid = 0;

function Dep() {
    // 標識屬性
    this.id = uid++;
    // 相關的所有watcher的數組
    this.subs = [];
}

Dep.prototype = {
    addSub: function(sub) {
        this.subs.push(sub);
    },

    depend: function() {
        Dep.target.addDep(this);
    },

    removeSub: function(sub) {
        var index = this.subs.indexOf(sub);
        if (index != -1) {
            this.subs.splice(index, 1);
        }
    },

    notify: function() {
        // 通知所有相關的watcher(一個訂閱者)
        this.subs.forEach(function(sub) {
            sub.update();
        });
    }
};

Dep.target = null;
observer.js
技術圖片
function Watcher(vm, exp, cb) {
  this.cb = cb;  // callback
  this.vm = vm;
  this.exp = exp;
  this.depIds = {};  // {0: d0, 1: d1, 2: d2}
  this.value = this.get();
}

Watcher.prototype = {
  update: function () {
    this.run();
  },
  run: function () {
    // 得到最新的值
    var value = this.get();
    // 得到舊值
    var oldVal = this.value;
    // 如果不相同
    if (value !== oldVal) {
      this.value = value;
      // 調用回調函數更新對應的界面
      this.cb.call(this.vm, value, oldVal);
    }
  },
  addDep: function (dep) {
    if (!this.depIds.hasOwnProperty(dep.id)) {
      // 建立dep到watcher
      dep.addSub(this);
      // 建立watcher到dep的關系
      this.depIds[dep.id] = dep;
    }
  },
  get: function () {
    Dep.target = this;
    // 獲取當前表達式的值, 內部會導致屬性的get()調用
    var value = this.getVMVal();

    Dep.target = null;
    return value;
  },

  getVMVal: function () {
    var exp = this.exp.split(‘.‘);
    var val = this.vm._data;
    exp.forEach(function (k) {
      val = val[k];
    });
    return val;
  }
};
/*

const obj1 = {id: 1}
const obj12 = {id: 2}
const obj13 = {id: 3}
const obj14 = {id: 4}

const obj2 = {}
const obj22 = {}
const obj23 = {}
// 雙向1對1
// obj1.o2 = obj2
// obj2.o1 = obj1

// obj1: 1:n
obj1.o2s = [obj2, obj22, obj23]

// obj2: 1:n
obj2.o1s = {
  1: obj1,
  2: obj12,
  3: obj13
}
*/
watcher.js

 

三、總結                                                                         

 綜上:

1) 被觀察的必須為一個對象,觀察對象里面的屬性

2) 創建一個觀察者,

3) Observer中進行數據劫持的,開始對data的監視

4) Walk保存observer對象,遍歷data中所有屬性,對對應的屬性進行劫持defineRective對對應屬性進行劫持

5) defineRective:實現響應式數據綁定;先創建屬性對應的dep(dependency);

通過間接遞歸調用,實現對DATA中所有層次屬性的數據劫持,

給data重新定義屬性,為了添加set(監視data中key屬性的變化,通知dep,ge更新界面,)和get方法(返回當前值,建立depend與watcher之間的關系,)

新的值如果是object的話需要監視,然后通知所有相關的訂閱者。

6) 訂閱者里面遍歷所有dep跟新

添加watcher到dep中,去建立dep與watcher之間的關系

Update:

        watcher里面有包含相關的dep的容器對象,得到表達式的初始值保存

 

         Run:調用回調函數,更新去 set導致run , set是由

 

7) addDep判斷dep與watcher的關系是否已經建立。將watcher添加dep中,將dep添加到watcher中,

vue(原理)_數據綁定

標簽:組成   gif   綁定   總結   相關   自動調用   ret   完成   http   

原文地址:https://www.cnblogs.com/xxm980617/p/10913509.html

(0)
(0)
   
舉報
評論 一句話評論(0
0條  
登錄后才能評論!
? 2014 吉利平特名人堂 版權所有 京ICP備13008772號-2
迷上了代碼!
pk10赛车直播网址 大乐透单式投注怎么投 双色球中奖 北京pk10有赚钱的人 麻将二八杠游戏下载 买彩票稳赚不赔计划 七乐彩走势图带连线 七乐彩走势图表近30期 pk10宝宝计划软件下载 pk10冷热号实战经验 重庆时时彩官方网站 捕鱼达人2旧版本 二人斗地主棋牌平台 北京pk10最稳定玩法 pk10苹果手机软件 时时规律口诀