成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

響應(yīng)式數(shù)據(jù)與數(shù)據(jù)依賴基本原理

or0fun / 1060人閱讀

摘要:響應(yīng)式數(shù)據(jù)響應(yīng)式數(shù)據(jù)不是憑空出現(xiàn)的。對(duì)于對(duì)象而言,如果是之前不存在的屬性,首先可以將進(jìn)行響應(yīng)化處理比如調(diào)用,然后將對(duì)具體屬性定義監(jiān)聽比如調(diào)用函數(shù),最后再去做賦值,可能具體的處理過程千差萬別,但是內(nèi)部實(shí)現(xiàn)的原理應(yīng)該就是如此僅僅是猜測(cè)。

前言

  首先歡迎大家關(guān)注我的Github博客,也算是對(duì)我的一點(diǎn)鼓勵(lì),畢竟寫東西沒法獲得變現(xiàn),能堅(jiān)持下去也是靠的是自己的熱情和大家的鼓勵(lì)。

  國(guó)內(nèi)前端算是屬于Vue與React兩分天下,提到Vue,最令人印象深刻的就是雙向綁定了,想要深入的理解雙向綁定,最重要的就是明白響應(yīng)式數(shù)據(jù)的原理。這篇文章不會(huì)去一字一句的分析Vue中是如何實(shí)現(xiàn)響應(yīng)式數(shù)據(jù)的,我們只會(huì)從原理的角度去考量如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的響應(yīng)式模塊,希望能對(duì)你有些許的幫助。
  

響應(yīng)式數(shù)據(jù)

  響應(yīng)式數(shù)據(jù)不是憑空出現(xiàn)的。對(duì)于前端工程而言,數(shù)據(jù)模型Model都是普通的JavsScript對(duì)象。View是Model的體現(xiàn),借助JavaScript的事件響應(yīng),View對(duì)Model的修改非常容易,比如:
  

var model = {
    click: false
};

var button = document.getElementById("button");
button.addEventListener("click", function(){
    model.click = !model.click;
})

  但是想要在修改Model時(shí),View也可以對(duì)應(yīng)刷新,相對(duì)比較困難的。在這方面,React和View提供了兩個(gè)不同的解決方案,具體可以參考這篇文章。其中響應(yīng)式數(shù)據(jù)提供了一種可實(shí)現(xiàn)的思路。什么是響應(yīng)式數(shù)據(jù)?在我看來響應(yīng)式數(shù)據(jù)就是修改數(shù)據(jù)的時(shí)候,可以按照你設(shè)定的規(guī)則觸發(fā)一系列其他的操作。我們想實(shí)現(xiàn)的其實(shí)就是下面的效果:
  

var model = {
  name: "javascript"
};
// 使傳入的數(shù)據(jù)變成響應(yīng)式數(shù)據(jù)
observify(model);
//監(jiān)聽數(shù)據(jù)修改
watch(model, "name", function(newValue, oldValue){
  console.log("name newValue: ", newValue,  ", oldValue: ", oldValue);
});

model.name = "php"; // languange newValue: php, oldValue: javascript

  從上面效果中我們可以看出來,我們需要劫持修改數(shù)據(jù)的過程。好在ES5提供了描述符屬性,通過方法Object.defineProperty我們可以設(shè)置訪問器屬性。但是包括IE8在內(nèi)的低版本瀏覽器是沒有實(shí)現(xiàn)Object.defineProperty并且也不能通過polyfill實(shí)現(xiàn)(其實(shí)IE8是實(shí)現(xiàn)了該功能,只不過只能對(duì)DOM對(duì)象使用,并且非常受限),因此在低版本瀏覽器中沒法實(shí)現(xiàn)該功能。這也就是為什么Vue不支持IE8及其以下的瀏覽的原因。通過Object.defineProperty我們可以實(shí)現(xiàn):
  

Object.defineProperty(obj, "prop", {
    enumerable: true,
    configurable: true,
    set: function(value){
        //劫持修改的過程
    },
    get: function(){
        //劫持獲取的過程
    }
});
數(shù)據(jù)響應(yīng)化

  根據(jù)上面的思路我們?nèi)タ紤]如何實(shí)現(xiàn)observify函數(shù),如果我們想要將一個(gè)對(duì)象響應(yīng)化,我們則需要遍歷對(duì)象中的每個(gè)屬性,并且需要對(duì)每個(gè)屬性對(duì)應(yīng)的值同樣進(jìn)行響應(yīng)化。代碼如下:
  

// 數(shù)據(jù)響應(yīng)化
// 使用lodash
function observify(model){
  if(_.isObject(model)){
    _.each(model, function(value, key){
      defineReactive(model, key, value);
    });
  }
}

//定義對(duì)象的單個(gè)響應(yīng)式屬性
function defineReactive(obj, key, value){
  observify(value);
  Object.defineProperty(obj, key, {
    configurable: true,
    enumerable: true,
    set: function(newValue){
      var oldValue = value;
      value = newValue;
      //可以在修改數(shù)據(jù)時(shí)觸發(fā)其他的操作
      console.log("newValue: ", newValue, " oldValue: ", oldValue);
    },
    get: function(){
      return value;
    }
  });

}

  上面的函數(shù)observify就實(shí)現(xiàn)了對(duì)象的響應(yīng)化處理,例如:
  

var model = {
  name: "MrErHu",
  message: {
    languange: "javascript"
  }
};

observify(model);
model.name = "mrerhu" //newValue:  mrerhu  oldValue:  MrErHu
model.message.languange = "php" //newValue:  php  oldValue:  javascript
model.message = { db: "MySQL" } //newValue:  {db: "MySQL"} oldValue: {languange:"javascript"}

  
我們知道在JavaScript中經(jīng)常使用的不僅僅是對(duì)象,數(shù)組也是非常重要的一部分。并且中還有非常的多的方法能夠改變數(shù)組本身,那么我們?nèi)绾文軌虮O(jiān)聽到數(shù)組的方法對(duì)數(shù)組帶來的變化呢?為了解決這個(gè)問題我們能夠一種替代的方式,將原生的函數(shù)替換成我們自定義的函數(shù),并且在自定義的函數(shù)中調(diào)用原生的數(shù)組方法,就可以達(dá)到我們想要的目的。我們接著改造我們的defineReactive函數(shù)。
  

function observifyArray(array){
  //需要變異的函數(shù)名列表
  var methods = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"];
  var arrayProto = Object.create(Array.prototype);
  _.each(methods, function(method){
    arrayProto[method] = function(...args){
      // 劫持修改數(shù)據(jù)
      var ret = Array.prototype[method].apply(this, args);
      //可以在修改數(shù)據(jù)時(shí)觸發(fā)其他的操作
      console.log("newValue: ", this);
      return ret;
    }
  });
  Object.setPrototypeOf(array, arrayProto);
}

//定義對(duì)象的單個(gè)響應(yīng)式屬性
function defineReactive(obj, key, value){
  if(_.isArray(value)){
    observifyArray(value, dep);
  }else {
    observify(value);
  }
  Object.defineProperty(obj, key, {
  // 省略......
  });
}

  我們可以看到我們將數(shù)組原生的原型替換成自定義的原型,然后調(diào)用數(shù)組的變異方法時(shí)就會(huì)調(diào)用我們自定義的函數(shù)。例如:

var model = [1,2,3];
observify(model);
model.push(4); //newValue: [1, 2, 3, 4]

  到目前為止我們已經(jīng)實(shí)現(xiàn)了我們的需求,其實(shí)我寫到這里的時(shí)候,我考慮到是否需要實(shí)現(xiàn)對(duì)數(shù)組的鍵值進(jìn)行監(jiān)聽,其實(shí)作為使用過Vue的用戶一定知道,當(dāng)你利用索引直接設(shè)置一個(gè)項(xiàng)時(shí),是不會(huì)監(jiān)聽到數(shù)組的變化的。比如:
  

vm.items[indexOfItem] = newValue

  如果你想要實(shí)現(xiàn)上面的效果,可以通過下面的方式實(shí)現(xiàn):

vm.items.splice(indexOfItem, 1, newValue);

  首先考慮這個(gè)是否能實(shí)現(xiàn)。答案是顯而易見的了。當(dāng)然是可以,數(shù)組其實(shí)可以看做特殊的數(shù)組,而其實(shí)對(duì)于數(shù)組而言,數(shù)值類型的索引都會(huì)被最終解析成字符串類型,比如下面的代碼:

var array = [0,1,2];
array["0"] = 1; //array: [1,1,2]

  那要實(shí)現(xiàn)對(duì)數(shù)值索引對(duì)應(yīng)的數(shù)據(jù)進(jìn)行修改,其實(shí)也是可以通過Object.defineProperty函數(shù)去實(shí)現(xiàn),比如:

var array = [0];
Object.defineProperty(array, 0, {
    set: function(newValue){
        console.log("newValue: ", newValue);
    }
});
array[0] = 1;//newValue:  1

  可以實(shí)現(xiàn)但卻沒有實(shí)現(xiàn)該功能,想來主要原因可能就是基于性能方面的考慮(我的猜測(cè))。但是Vue提供了另一個(gè)全局的函數(shù),Vue.set可以實(shí)現(xiàn)
  

Vue.set(vm.array, indexOfItem, newValue)

  我們可以大致猜測(cè)一下Vue.set內(nèi)部怎么實(shí)現(xiàn)的,對(duì)于數(shù)組而言,只需要對(duì)newValue做響應(yīng)化處理并將其賦值到數(shù)組中,然后通知數(shù)組改變。對(duì)于對(duì)象而言,如果是之前不存在的屬性,首先可以將newValue進(jìn)行響應(yīng)化處理(比如調(diào)用observify(newValue)),然后將對(duì)具體屬性定義監(jiān)聽(比如調(diào)用函數(shù)defineReactive),最后再去做賦值,可能具體的處理過程千差萬別,但是內(nèi)部實(shí)現(xiàn)的原理應(yīng)該就是如此(僅僅是猜測(cè))。

  不僅如此,在上面的實(shí)現(xiàn)中我們可以發(fā)現(xiàn),我們并不能監(jiān)聽到對(duì)象不能檢測(cè)對(duì)象屬性的添加或刪除,因此如果如果你要監(jiān)聽某個(gè)屬性的值,而一開始這個(gè)屬性并不存在,最好是在數(shù)據(jù)初始化的時(shí)候就給其一個(gè)默認(rèn)值,從而能監(jiān)聽到該屬性的變化。

依賴收集

  上面我們講了這么多,希望大家不要被帶偏了,我們上面所做的都是希望能在數(shù)據(jù)發(fā)生變化時(shí)得到通知。回到我們最初的問題。我們希望的是,在Model層數(shù)據(jù)發(fā)生改變的時(shí)候,View層的數(shù)據(jù)相應(yīng)發(fā)生改變,我們已經(jīng)能夠監(jiān)聽到數(shù)據(jù)的改變了,接下來要考慮的就是View的改變。

  對(duì)于Vue而言,即使你使用的是Template描述View層,最終都會(huì)被編譯成render函數(shù)。比如,模板中描述了:

{{ name }}

  其實(shí)最后會(huì)被編譯成:

render: function (createElement) {
  return createElement("h1", this.name);
}

  那現(xiàn)在就存在下面這個(gè)一個(gè)問題,假如我的Model是下面這個(gè)樣子的:
  

var model = {
    name: "MrErHu",
    age: 23,
    sex: "man"
}

  事實(shí)上render函數(shù)中就只用到了屬性name,但是Model中卻存在其他的屬性,當(dāng)數(shù)據(jù)改變的時(shí)候,我們?cè)趺粗朗裁磿r(shí)候才需要重新調(diào)用render函數(shù)呢。你可能會(huì)想,哪里需要那么麻煩,每次數(shù)據(jù)改變都去刷新render函數(shù)不就行了嗎。這樣當(dāng)然可以,其實(shí)如果朝著這個(gè)思路走,我們就朝著React方向走了。事實(shí)上如果不借助虛擬DOM的前提下,如果每次屬性改變都去調(diào)用render效率必然是低下的,這時(shí)候我們就引入了依賴收集,如果我們能知道render依賴了那些屬性,那么在這些屬性修改的時(shí)候,我們?cè)倬珳?zhǔn)地調(diào)用render函數(shù),那么我們的目的不就達(dá)到了嗎?這就是我們所稱的依賴收集。

  依賴收集的原理非常的簡(jiǎn)單,在響應(yīng)式數(shù)據(jù)中我們一直利用的都是屬性描述符中的set方法,而我們知道當(dāng)調(diào)用某個(gè)對(duì)象的屬性時(shí),會(huì)觸發(fā)屬性描述符的get方法,當(dāng)get方法調(diào)用時(shí),我們將調(diào)用get的方法收集起來就能完成我們的依賴收集的任務(wù)。

  首先我們可以思考要一下,如果是自己寫一個(gè)響應(yīng)式數(shù)據(jù)帶依賴收集的模塊,我們會(huì)去怎么設(shè)計(jì)。首先我們想要達(dá)到的類似效果就是:
  

var model = {
    name: "MrErHu",
    program: {
        language: "Javascript"
    },
    favorite: ["React"]
};

//數(shù)據(jù)響應(yīng)化
observify(model);
//監(jiān)聽
watch(function(){
    return "

" + (model.name) + "

" }, function(){ console.log("name: ", model.name); }); watch(function(){ return "

" + (model.program.language) + "

" }, function(){ console.log("language: ", model.program.language); }); watch(function(){ return "

" + (model.favorite) + "

" }, function(){ console.log("favorite: ", model.favorite); }); model.name = "mrerhu"; //name: mrerhu model.program.language = "php"; //language: php model.favorite.push("Vue"); //favorite: [React, Vue]

  我們所需要實(shí)現(xiàn)的watch函數(shù)的第一個(gè)參數(shù)可以認(rèn)為是render函數(shù),通過執(zhí)行render函數(shù)我們可以收集到render函數(shù)內(nèi)部使用了那些響應(yīng)式數(shù)據(jù)屬性。然后在對(duì)應(yīng)的響應(yīng)式數(shù)據(jù)屬性改變的時(shí)候,觸發(fā)我們注冊(cè)的第二個(gè)函數(shù)。這樣看我們監(jiān)聽屬性的粒度就是響應(yīng)數(shù)據(jù)的每一個(gè)屬性。按照單一職責(zé)的概念,我們將監(jiān)聽訂閱通知發(fā)布的職責(zé)分離出去,由多帶帶的Dep類負(fù)責(zé)。由于監(jiān)聽的粒度是響應(yīng)式數(shù)據(jù)的每一個(gè)屬性,因此我們會(huì)為每一個(gè)屬性維護(hù)一個(gè)Dep。與此相對(duì)應(yīng),我們創(chuàng)建Watcher類,負(fù)責(zé)向Dep注冊(cè),并在收到通知后調(diào)用回調(diào)函數(shù)。如下圖所示:
  

  首先我們實(shí)現(xiàn)DepWatcher類:
  

//引入lodash庫(kù)
class Dep {
  constructor(){
    this.listeners = [];
  }

  // 添加Watcher
  addWatcher(watcher){
    var find = _.find(this.listeners, v => v === watcher);
    if(!find){
      //防止重復(fù)注冊(cè)
      this.listeners.push(watcher);
    }
  }
  // 移除Watcher
  removeWatcher(watcher){
    var find = _.findIndex(this.listeners, v => v === fn);
    if(find !== -1){
      this.listeners.splice(watcher, 1);
    }
  }
  // 通知
  notify(){
    _.each(this.listeners, function(watcher){
      watcher.update();
    });
  }
}

Dep.target = null;

class Watcher {
  constructor(callback){
    this.callback = callback;
  }
  //得到Dep通知調(diào)用相應(yīng)的回調(diào)函數(shù)
  update(){
    this.callback();
  }
}

  接著我們創(chuàng)建watcher函數(shù)并且改造之前響應(yīng)式相關(guān)的函數(shù):
  

// 數(shù)據(jù)響應(yīng)化
function observify(model){
  if(_.isObject(model)){
    _.each(model, function(value, key){
      defineReactive(model, key, value);
    });
  }
}

//定義對(duì)象的單個(gè)響應(yīng)式屬性
function defineReactive(obj, key, value){
  var dep = new Dep();
  if(_.isArray(value)){
    observifyArray(value, dep);
  }else {
    observify(value);
  }
  Object.defineProperty(obj, key, {
    configurable: true,
    enumerable: true,
    set: function(newValue){
      observify(value);
      var oldValue = value;
      value = newValue;
      //可以在修改數(shù)據(jù)時(shí)觸發(fā)其他的操作
      dep.notify(value);
    },
    get: function(){
      if(!_.isNull(Dep.target)){
        dep.addWatcher(Dep.target);
      }
      return value;
    }
  });
}
// 數(shù)據(jù)響應(yīng)化
function observify(model){
  if(_.isObject(model)){
    _.each(model, function(value, key){
      defineReactive(model, key, value);
    });
  }
}

//定義對(duì)象的單個(gè)響應(yīng)式屬性
function defineReactive(obj, key, value){
  var dep = new Dep();
  if(_.isArray(value)){
    observifyArray(value, dep);
  }else {
    observify(value);
  }
  Object.defineProperty(obj, key, {
    configurable: true,
    enumerable: true,
    set: function(newValue){
      observify(value);
      var oldValue = value;
      value = newValue;
      //可以在修改數(shù)據(jù)時(shí)觸發(fā)其他的操作
      dep.notify(value);
    },
    get: function(){
      if(!_.isNull(Dep.target)){
        dep.addWatcher(Dep.target);
      }
      return value;
    }
  });
}

function observifyArray(array, dep){
  //需要變異的函數(shù)名列表
  var methods = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"];
  var arrayProto = Object.create(Array.prototype);
  _.each(methods, function(method){
    arrayProto[method] = function(...args){
      var ret = Array.prototype[method].apply(this, args);
      dep.notify(this);
      return ret;
    }
  });
  Object.setPrototypeOf(array, arrayProto);
}

function watch(render, callback){
  var watcher = new Watcher(callback);
  Dep.target = watcher;
  render();
  Dep.target = null;
}

  接下來我們就可以實(shí)驗(yàn)一下我們的watch函數(shù)了:

var model = {
  name: "MrErHu",
  message: {
    languange: "javascript"
  },
  love: ["Vue"]
};

observify(model);

watch(function(){
    return "

" + (model.name) + "

" }, function(){ console.log("name: ", model.name); }); watch(function(){ return "

" + (model.message.languange) + "

" }, function(){ console.log("message: ", model.message); }); watch(function(){ return "

" + (model.love) + "

" }, function(){ console.log("love: ", model.love); }); model.name = "mrerhu"; // name: mrerhu model.message.languange = "php"; // message: { languange: "php"} model.message = { target: "javascript" }; // message: { languange: "php"} model.love.push("React"); // love: ["Vue", "React"]

  到此為止我們已經(jīng)基本實(shí)現(xiàn)了我們想要的效果,當(dāng)然上面的例子并不完備,但是也基本能展示出響應(yīng)式數(shù)據(jù)與數(shù)據(jù)依賴的基本原理。當(dāng)然上面僅僅只是采用ES5的數(shù)據(jù)描述符實(shí)現(xiàn)的,隨著ES6的普及,我們也可以用Proxy(代理)和Reflect(反射)去實(shí)現(xiàn)。作為本系列的第一篇文章,還有其他的點(diǎn)沒有一一列舉出來,大家可以關(guān)注我的Github博客繼續(xù)關(guān)注,如果有講的不準(zhǔn)確的地方,歡迎大家指正。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107790.html

相關(guān)文章

  • Vue 數(shù)據(jù)響應(yīng)原理

    摘要:接下來,我們就一起深入了解的數(shù)據(jù)響應(yīng)式原理,搞清楚響應(yīng)式的實(shí)現(xiàn)機(jī)制。回調(diào)函數(shù)只是打印出新的得到的新的值,由執(zhí)行后生成。及異步更新相信讀過前文,你應(yīng)該對(duì)響應(yīng)式原理有基本的認(rèn)識(shí)。 前言 Vue.js 的核心包括一套響應(yīng)式系統(tǒng)。 響應(yīng)式,是指當(dāng)數(shù)據(jù)改變后,Vue 會(huì)通知到使用該數(shù)據(jù)的代碼。例如,視圖渲染中使用了數(shù)據(jù),數(shù)據(jù)改變后,視圖也會(huì)自動(dòng)更新。 舉個(gè)簡(jiǎn)單的例子,對(duì)于模板: {{ name ...

    Mike617 評(píng)論0 收藏0
  • 【Vue原理】Props - 源碼版

    寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧 【Vue原理】Props - 源碼版 今天記錄 Props 源碼流程,哎,這東西,就算是研究過了,也真是會(huì)隨著時(shí)間慢慢忘記的。 幸好我做...

    light 評(píng)論0 收藏0
  • 【Vue原理依賴收集 - 源碼版之基本數(shù)據(jù)類型

    摘要:當(dāng)東西發(fā)售時(shí),就會(huì)打你的電話通知你,讓你來領(lǐng)取完成更新。其中涉及的幾個(gè)步驟,按上面的例子來轉(zhuǎn)化一下你買東西,就是你要使用數(shù)據(jù)你把電話給老板,電話就是你的,用于通知老板記下電話在電話本,就是把保存在中。剩下的步驟屬于依賴更新 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【...

    VincentFF 評(píng)論0 收藏0
  • 【前端進(jìn)階基礎(chǔ)】VUE響應(yīng)數(shù)據(jù)原理 訂閱-發(fā)布模解析

    摘要:其成員函數(shù)最主要的是和,前者用來設(shè)置某個(gè)的依賴,后者則用來通知與這個(gè)依賴相關(guān)的來運(yùn)行其回調(diào)函數(shù)。也就是進(jìn)行數(shù)據(jù)更新操作。 vue框架的兩個(gè)抽象核心:虛擬DOM和相應(yīng)式數(shù)據(jù)原理 關(guān)于虛擬DOM的核心算法,我們上一章已經(jīng)基本解析過了,詳細(xì)的見React && VUE Virtual Dom的Diff算法統(tǒng)一之路 snabbdom.js解讀 關(guān)于響應(yīng)式數(shù)據(jù)原理,我們先看張圖你 ‘ (4).p...

    陳偉 評(píng)論0 收藏0
  • 淺析Vue響應(yīng)原理(二)

    摘要:響應(yīng)式原理之之前簡(jiǎn)單介紹了和類的代碼和作用,現(xiàn)在來介紹一下類和。對(duì)于數(shù)組,響應(yīng)式的實(shí)現(xiàn)稍有不同。不存在時(shí),說明不是響應(yīng)式數(shù)據(jù),直接更新。如果對(duì)象是響應(yīng)式的,確保刪除能觸發(fā)更新視圖。 Vue響應(yīng)式原理之Observer 之前簡(jiǎn)單介紹了Dep和Watcher類的代碼和作用,現(xiàn)在來介紹一下Observer類和set/get。在Vue實(shí)例后再添加響應(yīng)式數(shù)據(jù)時(shí)需要借助Vue.set/vm.$se...

    rockswang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

or0fun

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<