麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 編程 > JavaScript > 正文

Vue監(jiān)聽數據對象變化源碼

2019-11-19 17:13:30
字體:
來源:轉載
供稿:網友

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

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

目標

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

<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 來達到監(jiān)聽數據變化的目的。數據被獲取,被設置、被修改,都能監(jiān)聽到,且能做出相應的動作。

現在還有一個問題就是,誰讓你監(jiān)聽的?

這個發(fā)出指令的就是 Watcher,只有 Watcher 獲取數據才觸發(fā)相應的操作;同樣,修改數據時,也只執(zhí)行 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,觸發(fā)相應響應 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 監(jiān)聽的,就把 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);  // 按序執(zhí)行 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;

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

Dep

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

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 = 2data.a // -> 2// ... 反之亦然data.a = 3vm.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早期源碼學習系列之一:如何監(jiān)聽一個對象的變化

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

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 亚洲精品xxx | 香蕉久久久久久 | 一区二区三区欧美在线观看 | 色诱亚洲精品久久久久久 | 免费a级观看 | 成人免费一区二区三区视频网站 | 日韩精品免费一区二区三区 | 日日鲁夜夜视频热线播放 | 羞羞电影在线观看 | 欧美成人午夜一区二区三区 | 久久久国产精品网站 | 亚a在线| 久久精品亚洲一区二区 | 成人精品一区二区三区中文字幕 | 91美女福利视频 | 在线成人看片 | 日韩视频一区在线 | 羞羞色网站 | 狠狠久久伊人中文字幕 | 国产成人自拍av | 日日噜噜噜夜夜狠狠久久蜜桃 | 一级黄色影片在线观看 | 欧美激情天堂 | 黄网站色成年大片免费高 | 久久千人斩 | 久久影院一区二区三区 | 欧美一级淫片a免费播放口 91九色蝌蚪国产 | 欧美电影在线观看 | 美国av片在线观看 | 羞羞答答www网站进入 | 成人一级黄色片 | 国产小视频在线观看 | 久久综合精品视频 | 国产中文av在线 | 看个毛片 | 国产精品入口夜色视频大尺度 | 国产手机在线视频 | 亚洲欧洲日产v特级毛片 | 国产成年人在线观看 | 91精品国产91久久久久久 | 欧美一级淫片a免费播放口 91九色蝌蚪国产 |