亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue監聽數據對象變化源碼

發布時間:2020-09-12 22:42:15 來源:腳本之家 閱讀:121 作者:duiel 欄目:web開發

監聽數據對象變化,最容易想到的是建立一個需要監視對象的表,定時掃描其值,有變化,則執行相應操作,不過這種實現方式,性能是個問題,如果需要監視的數據量大的話,每掃描一次全部的對象,需要的時間很長。當然,有些框架是采用的這種方式,不過他們用非常巧妙的算法提升性能,這不在我們的討論范圍之類。

Vue 中數據對象的監視,是通過設置 ES5 的新特性(ES7 都快出來了,ES5 的東西倒也真稱不得新)Object.defineProperty() 中的 set、get 來實現的。

目標

與官方文檔第一個例子相似,不過也有簡化,因為這篇只是介紹下數據對象的監聽,不涉及文本解析,所以文本解析相關的直接舍棄了:

<div id="app"></div>
var app = new Vue({
 el: 'app',
 data: {
 message: 'Hello Vue!'
 }
});

瀏覽器顯示:

Hello Vue!

在控制臺輸入諸如:

app.message = 'Changed!'

之類的命令,瀏覽器顯示內容會跟著修改。

Object.defineProperty

引用 MDN 上的定義:

Object.defineProperty()方法會直接在一個對象上定義一個新屬性,或者修改一個已經存在的屬性, 并返回這個對象。
與此相生相伴的還有一個 Object.getOwnPropertyDescriptor():

Object.getOwnPropertyDescriptor() 返回指定對象上一個自有屬性對應的屬性描述符。(自有屬性指的是直接賦予該對象的屬性,不需要從原型鏈上進行查找的屬性)

下面的例子用一種比較簡單、直觀的方式來設置 setter、getter:

var dep = [];

function defineReactive(obj, key, val) {
 // 有自定義的 property,則用自定義的 property
 var property = Object.getOwnPropertyDescriptor(obj, key);
 if(property && property.configurable === false) {
 return;
 }

 var getter = property && property.get;
 var setter = property && property.set;

 Object.defineProperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: function() {
  var value = getter ? getter.call(obj) : val;
  dep.push(value);
  return value;
 },
 set: function(newVal) {
  var value = getter ? getter.call(obj) : val;
  // set 值與原值相同,則不更新
  if(newVal === value) {
  return;
  }
  if(setter) {
  setter.call(obj, newVal);
  } else {
  val = newVal;
  }
  console.log(dep);
 }
 });
}
var a = {};
defineReactive(a, 'a', 12);
// 調用 getter,12 被壓入 dep,此時 dep 值為 [12]
a.a;
// 調用 setter,輸出 dep ([12])
a.a = 24;
// 調用 getter,24 被壓入 dep,此時 dep 值為 [12, 24]
a.a;

Observer

簡單說過 Object.defineProperty 之后,就要開始扯 Observer 了。observer,中文解釋為“觀察者”,觀察什么東西呢?觀察對象屬性值的變化。故此,所謂 observer,就是給對象的所有屬性加上 getter、setter,如果對象的屬性還有屬性,比如說 {a: {a: {a: 'a'}}},則通過遞歸給其屬性的屬性也加上 getter、setter:

function Observer(value) {
 this.value = value;
 this.walk(value);
}
Observer.prototype.walk = function(obj) {
 var keys = Object.keys(obj);
 for(var i = 0; i < keys.length; i++) {
 // 給所有屬性添加 getter、setter
 defineReactive(obj, keys[i], obj[keys[i]]);
 }
};

var dep = [];

function defineReactive(obj, key, val) {
 // 有自定義的 property,則用自定義的 property
 var property = Object.getOwnPropertyDescriptor(obj, key);
 if(property && property.configurable === false) {
 return;
 }

 var getter = property && property.get;
 var setter = property && property.set;

 // 遞歸的方式實現給屬性的屬性添加 getter、setter
 var childOb = observe(val);
 Object.defineProperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: function() {
  var value = getter ? getter.call(obj) : val;
  dep.push(value);
  return value;
 },
 set: function(newVal) {
  var value = getter ? getter.call(obj) : val;
  // set 值與原值相同,則不更新
  if(newVal === value) {
  return;
  }
  if(setter) {
  setter.call(obj, newVal);
  } else {
  val = newVal;
  }
  // 給新賦值的屬性值的屬性添加 getter、setter
  childOb = observe(newVal);
  console.log(dep);
 }
 });
}

function observe(value) {
 if(!value || typeof value !== 'object') {
 return;
 }
 return new Observer(value);
}

Watcher

Observer 通過設置數據對象的 getter、setter 來達到監聽數據變化的目的。數據被獲取,被設置、被修改,都能監聽到,且能做出相應的動作。

現在還有一個問題就是,誰讓你監聽的?

這個發出指令的就是 Watcher,只有 Watcher 獲取數據才觸發相應的操作;同樣,修改數據時,也只執行 Watcher 相關操作。

那如何講 Observer、Watcher 兩者關聯起來呢?全局變量!這個全局變量,只有 Watcher 才做修改,Observer 只是讀取判斷,根據這個全局變量的值不同而判斷是否 Watcher 對數據進行讀取,這個全局變量可以附加在 dep 上:

dep.target = null;

根據以上所述,簡單整理下,代碼如下:

function Watcher(data, exp, cb) {
 this.data = data;
 this.exp = exp;
 this.cb = cb;
 this.value = this.get();
}
Watcher.prototype.get = function() {
 // 給 dep.target 置值,告訴 Observer 這是 Watcher 調用的 getter
 dep.target = this;
 // 調用 getter,觸發相應響應
 var value = this.data[this.exp];
 // dep.target 還原
 dep.target = null;
 return value;
};
Watcher.prototype.update = function() {
 this.cb();
};
function Observer(value) {
 this.value = value;
 this.walk(value);
}
Observer.prototype.walk = function(obj) {
 var keys = Object.keys(obj);
 for(var i = 0; i < keys.length; i++) {
 // 給所有屬性添加 getter、setter
 defineReactive(obj, keys[i], obj[keys[i]]);
 }
};

var dep = [];
dep.target = null;

function defineReactive(obj, key, val) {
 // 有自定義的 property,則用自定義的 property
 var property = Object.getOwnPropertyDescriptor(obj, key);
 if(property && property.configurable === false) {
 return;
 }

 var getter = property && property.get;
 var setter = property && property.set;

 // 遞歸的方式實現給屬性的屬性添加 getter、setter
 var childOb = observe(val);
 Object.defineProperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: function() {
  var value = getter ? getter.call(obj) : val;
  // 如果是 Watcher 監聽的,就把 Watcher 對象壓入 dep
  if(dep.target) {
  dep.push(dep.target);
  }
  return value;
 },
 set: function(newVal) {
  var value = getter ? getter.call(obj) : val;
  // set 值與原值相同,則不更新
  if(newVal === value) {
  return;
  }
  if(setter) {
  setter.call(obj, newVal);
  } else {
  val = newVal;
  }
  // 給新賦值的屬性值的屬性添加 getter、setter
  childOb = observe(newVal);
  // 按序執行 dep 中元素的 update 方法
  for(var i = 0; i < dep.length; i++) {
  dep[i].update(); 
  }
 }
 });
}

function observe(value) {
 if(!value || typeof value !== 'object') {
 return;
 }
 return new Observer(value);
}

var data = {a: 1};
new Observer(data);
new Watcher(data, 'a', function(){console.log('it works')});
data.a =12;
data.a =14;

上面基本實現了數據的監聽,bug 肯定有不少,不過只是一個粗糙的 demo,只是想展示一個大概的流程,沒有扣到非常細致。

Dep

上面幾個例子,dep 是個全局的數組,但凡 new 一個 Watcher,dep 中就要多一個 Watcher 實例,這時候不管哪個 data 更新,所有的 Watcher 實例的 update 都會執行,這是不可接受的。

Dep 抽象出來,單獨搞一個構造函數,不放在全局,就能解決了:

function Dep() {
 this.subs = [];
}
Dep.prototype.addSub = function(sub) {
 this.subs.push(sub);
};
Dep.prototype.notify = function() {
 var subs = this.subs.slice();
 for(var i = 0; i < subs.length; i++) {
 subs[i].update();
 }
}

利用 Dep 將上面的代碼改寫下就好了(當然,此處的 Dep 代碼也不完全,只是一個大概的意思罷了)。

Vue 實例代理 data 對象

官方文檔中有這么一句話:

每個 Vue 實例都會代理其 data 對象里所有的屬性。

var data = { a: 1 };
var vm = new Vue({data: data});

vm.a === data.a // -> true

// 設置屬性也會影響到原始數據
vm.a = 2
data.a // -> 2

// ... 反之亦然
data.a = 3
vm.a // -> 3

這種代理看起來很麻煩,其實也是可以通過 Object.defineProperty 來實現的:

function Vue(options) {
 var data = this.data = options.data;

 var keys = Object.keys(data);
 var i = keys.length;
 while(i--) {
 proxy(this, keys[i];
 }
}
function proxy(vm, key) {
 Object.defineProperty(vm, key, {
 configurable: true,
 enumerable: true,
 // 直接獲取 vm.data[key] 的值
 get: function() {
  return vm.data[key];
 },
 // 設置值的時候直接設置 vm.data[key] 的值
 set: function(val) {
  vm.data[key] = val;
 }
 };
}

捏出一個 Vue,實現最初目標

var Vue = (function() {
 var Watcher = function Watcher(vm, exp, cb) {
  this.vm = vm;
  this.exp = exp;
  this.cb = cb;
  this.value = this.get();
 };
 Watcher.prototype.get = function get() {
  Dep.target = this;
  var value = this.vm._data[this.exp];
  Dep.target = null;
  return value;
 };
 Watcher.prototype.addDep = function addDep(dep) {
  dep.addSub(this);
 };
 Watcher.prototype.update = function update() {
  this.run();
 };
 Watcher.prototype.run = function run() {
  this.cb.call(this.vm);
 }

 var Dep = function Dep() {
  this.subs = [];
 };
 Dep.prototype.addSub = function addSub(sub) {
  this.subs.push(sub);
 };
 Dep.prototype.depend = function depend() {
  if(Dep.target) {
   Dep.target.addDep(this);
  }
 };
 Dep.prototype.notify = function notify() {
  var subs = this.subs.slice();
  for(var i = 0; i < subs.length; i++) {
   subs[i].update();
  }
 };

 Dep.target = null;

 var Observer = function Observer(value) {
  this.value = value;
  this.dep = new Dep();

  this.walk(value);
 };
 Observer.prototype.walk = function walk(obj) {
  var keys = Object.keys(obj);

  for(var i = 0; i < keys.length; i++) {
   defineReactive(obj, keys[i], obj[keys[i]]);
  }
 };

 function defineReactive(obj, key, val) {
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj, key);
  if(property && property.configurable === false) {
   return;
  }

  var getter = property && property.get;
  var setter = property && property.set;

  var childOb = observe(val);
  Object.defineProperty(obj, key, {
   enumerable: true,
   configurable: true,
   get: function reactiveGetter() {
    var value = getter ? getter.call(obj) : val;

    if(Dep.target) {
     dep.depend();
     if(childOb) {
      childOb.dep.depend();
     }
    }
    return value;
   },
   set: function reactiveSetter(newVal) {
    var value = getter ? getter.call(obj) : val;
    if(newVal === value) {
     return;
    }
    if(setter) {
     setter.call(obj, newVal);
    } else {
     val = newVal;
    }
    childOb = observe(newVal);
    dep.notify();
   }
  });
 }
 function observe(value) {
  if(!value || typeof value !== 'object') {
   return;
  }
  return new Observer(value);
 }

 function Vue(options) {
  var vm = this;
  this._el = options.el;
  var data = this._data = options.data;

  var keys = Object.keys(data);
  var i = keys.length;
  while(i--) {
   proxy(this, keys[i]);
  }
  observe(data);

  var elem = document.getElementById(this._el);
  elem.innerHTML = vm.message;

  new Watcher(this, 'message', function() {
   elem.innerHTML = vm.message;
  });

 }
 function proxy(vm, key) {
  Object.defineProperty(vm, key, {
   configurable: true,
   enumerable: true,
   get: function proxyGetter() {
    return vm._data[key];
   },
   set: function proxySetter(val) {
    vm._data[key] = val;
   }
  });
 }
 return Vue;
})();

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Document</title>
 <script type="text/javascript" src="vue.js"></script>
</head>
<body>
 <div id="app"></div>
 <script type="text/javascript">
  var app = new Vue({
   el: 'app',
   data: {
    message: 'aaaaaaaaaaaaa'
   }
  });
 </script>
</body>
</html>


參考資料:

vue 源碼分析之如何實現 observer 和 watcher
vue早期源碼學習系列之一:如何監聽一個對象的變化

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

泰州市| 长春市| 平乐县| 泗洪县| 菏泽市| 泗水县| 杨浦区| 手游| 通城县| 沂源县| 阜康市| 长岛县| 陕西省| 镇江市| 邵东县| 盐津县| 台东县| 枣强县| 福建省| 黄冈市| 株洲市| 慈利县| 横峰县| 左贡县| 高州市| 循化| 容城县| 绥棱县| 介休市| 嫩江县| 柳江县| 白城市| 三台县| 萝北县| 城步| 紫金县| 喀喇| 靖安县| 札达县| 虹口区| 威远县|