vue.js中怎么實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)綁定-創(chuàng)新互聯(lián)

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)vue.js中怎么實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)綁定,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

從策劃到設(shè)計(jì)制作,每一步都追求做到細(xì)膩,制作可持續(xù)發(fā)展的企業(yè)網(wǎng)站。為客戶提供網(wǎng)站建設(shè)、做網(wǎng)站、網(wǎng)站策劃、網(wǎng)頁(yè)設(shè)計(jì)、國(guó)際域名空間、雅安服務(wù)器托管、網(wǎng)絡(luò)營(yíng)銷、VI設(shè)計(jì)、 網(wǎng)站改版、漏洞修補(bǔ)等服務(wù)。為客戶提供更好的一站式互聯(lián)網(wǎng)解決方案,以客戶的口碑塑造優(yōu)易品牌,攜手廣大客戶,共同發(fā)展進(jìn)步。

從new一個(gè)實(shí)例開始談起

網(wǎng)上的很多源碼解讀都是從 Observer 開始的,而我會(huì)從 new 一個(gè)MVVM實(shí)例開始,按照程序執(zhí)行順序去解釋或許更容易理解。先來(lái)看一個(gè)簡(jiǎn)單的例子:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>test</title>
</head>
<body>
 <div class="test">
  <p>{{user.name}}</p>
  <p>{{user.age}}</p>
 </div>

 <script type="text/javascript" src="hue.js"></script>
 <script type="text/javascript">
  let vm = new Hue({
   el: '.test',
   data: {
    user: {
     name: 'Jack',
     age: '18'
    }
   }
  });
 </script>
</body>
</html>

接下來(lái)都將以其為例來(lái)分析。下面來(lái)看一個(gè)簡(jiǎn)略的 MVVM 的實(shí)現(xiàn),在此將其命名為 hue。為了方便起見,為 data 屬性設(shè)置了一個(gè)代理,通過(guò) vm._data 來(lái)訪問 data 的屬性顯得麻煩且冗余,通過(guò)代理,可以很好地解決這個(gè)問題,在注釋中也有說(shuō)明。添加完屬性代理后,調(diào)用了一個(gè) observe 函數(shù),這一步做的就是 Observer 的屬性劫持了,這一步具體怎么實(shí)現(xiàn),暫時(shí)先不展開。先記住他為 data 的屬性添加了 getter 和 setter。

function Hue(options) {
 this.$options = options || {};
 let data = this._data = this.$options.data,
  self = this;

 Object.keys(data).forEach(function(key) {
  self._proxyData(key);
 });

 observe(data);

 self.$compile = new Compile(self, options.el || document.body);
}

// 為 data 做了一個(gè)代理,
// 訪問 vm.xxx 會(huì)觸發(fā) vm._data[xxx] 的getter,取得 vm._data[xxx] 的值,
// 為 vm.xxx 賦值則會(huì)觸發(fā) vm._data[xxx] 的setter
Hue.prototype._proxyData = function(key) {
 let self = this;
 Object.defineProperty(self, key, {
  configurable: false,
  enumerable: true,
  get: function proxyGetter() {
   return self._data[key];
  },
  set: function proxySetter(newVal) {
   self._data[key] = newVal;
  }
 });
};

再往下看,最后一步 new 了一個(gè) Compile,下面我們就來(lái)講講 Compile。

Compile

new Compile(self, options.el || document.body) 這一行代碼中,第一個(gè)參數(shù)是當(dāng)前 Hue 實(shí)例,第二個(gè)參數(shù)是綁定的元素,在上面的示例中為class為 .test 的div。

關(guān)于 Compile,這里只實(shí)現(xiàn)最簡(jiǎn)單的 textContent 的綁定。而 Compile 的代碼沒什么難點(diǎn),很輕易就能讀懂,所做的就是解析 DOM,并添加 Watcher 訂閱。關(guān)于 DOM 的解析,先將根節(jié)點(diǎn) el 轉(zhuǎn)換成文檔碎片 fragment 進(jìn)行解析編譯操作,解析完成后,再將 fragment 添加回原來(lái)的真實(shí) DOM 節(jié)點(diǎn)中。來(lái)看看這部分的代碼:

function Compile(vm, el) {
 this.$vm = vm;
 this.$el = this.isElementNode(el)
  ? el
  : document.querySelector(el);

 if (this.$el) {
  this.$fragment = this.node2Fragment(this.$el);
  this.init();
  this.$el.appendChild(this.$fragment);
 }
}

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

 // 也許有同學(xué)不太理解這一步,不妨動(dòng)手寫個(gè)小例子觀察一下他的行為
 while (child = el.firstChild) {
  fragment.appendChild(child);
 }

 return fragment;
};

Compile.prototype.init = function() {
 // 解析 fragment
 this.compileElement(this.$fragment);
};

以上面示例為例,此時(shí)若打印出 fragment,可觀察到其包含兩個(gè)p元素:

<p>{{user.name}}</p>
<p>{{user.age}}</p>

下一步就是解析 fragment,直接看代碼及注釋吧:

Compile.prototype.compileElement = function(el) {
 let childNodes = Array.from(el.childNodes),
  self = this;

 childNodes.forEach(function(node) {
  let text = node.textContent,
   reg = /\{\{(.*)\}\}/;

  // 若為 textNode 元素,且匹配 reg 正則
  // 在上例中會(huì)匹配 '{{user.name}}' 及 '{{user.age}}'
  if (self.isTextNode(node) && reg.test(text)) {
   // 解析 textContent,RegExp.$1 為匹配到的內(nèi)容,在上例中為 'user.name' 及 'user.age'
   self.compileText(node, RegExp.$1);
  }

  // 遞歸
  if (node.childNodes && node.childNodes.length) {
   self.compileElement(node);
  }
 });
};

Compile.prototype.compileText = function(node, exp) {
 // this.$vm 即為 Hue 實(shí)例,exp 為正則匹配到的內(nèi)容,即 'user.name' 或 'user.age'
 compileUtil.text(node, this.$vm, exp);
};

let compileUtil = {
 text: function(node, vm, exp) {
  this.bind(node, vm, exp, 'text');
 },

 bind: function(node, vm, exp, dir) {
  // 獲取更新視圖的回調(diào)函數(shù)
  let updaterFn = updater[dir + 'Updater'];

  // 先調(diào)用一次 updaterFn,更新視圖
  updaterFn && updaterFn(node, this._getVMVal(vm, exp));

  // 添加 Watcher 訂閱
  new Watcher(vm, exp, function(value, oldValue) {
   updaterFn && updaterFn(node, value, oldValue);
  });
 },

 // 根據(jù) exp,獲得其值,在上例中即 'vm.user.name' 或 'vm.user.age'
 _getVMVal: function(vm, exp) {
  let val = vm;
  exp = exp.trim().split('.');
  exp.forEach(function(k) {
   val = val[k];
  });
  return val;
 }
};

let updater = {
 // Watcher 訂閱的回調(diào)函數(shù)
 // 在此即更新 node.textContent,即 update view
 textUpdater: function(node, value) {
  node.textContent = typeof value === 'undefined'
   ? ''
   : value;
 }
};

正如代碼中所看到的,Compile 在解析到 {{xxx}} 后便添加了 xxx 屬性的訂閱,即 new Watcher(vm, exp, callback)。理解了這一步后,接下來(lái)就需要了解怎么實(shí)現(xiàn)相關(guān)屬性的訂閱了。先從 Observer 開始談起。

Observer

從最簡(jiǎn)單的情況來(lái)考慮,即不考慮數(shù)組元素的變化。暫時(shí)先不考慮 Dep 與 Observer 的聯(lián)系。先看看 Observer 構(gòu)造函數(shù):

function Observer(data) {
 this.data = data;
 this.walk(data);
}

Observer.prototype.walk = function(data) {
 const keys = Object.keys(data);
 // 遍歷 data 的所有屬性
 for (let i = 0; i < keys.length; i++) {
  // 調(diào)用 defineReactive 添加 getter 和 setter
  defineReactive(data, keys[i], data[keys[i]]);
 }
};

接下來(lái)通過(guò) Object.defineProperty 方法給所有屬性添加 getter 和 setter,就達(dá)到了我們的目的。屬性有可能也是對(duì)象,因此需要對(duì)屬性值進(jìn)行遞歸調(diào)用。

function defineReactive(obj, key, val) {
 // 對(duì)屬性值遞歸,對(duì)應(yīng)屬性值為對(duì)象的情況
 let childObj = observe(val);

 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function() {
   // 直接返回屬性值
   return val;
  },
  set: function(newVal) {
   if (newVal === val) {
    return;
   }
   // 值發(fā)生變化時(shí)修改閉包中的 val,
   // 保證在觸發(fā) getter 時(shí)返回正確的值
   val = newVal;

   // 對(duì)新賦的值進(jìn)行遞歸,防止賦的值為對(duì)象的情況
   childObj = observe(newVal);
  }
 });
}

最后補(bǔ)充上 observe 函數(shù),也即 Hue 構(gòu)造函數(shù)中調(diào)用的 observe 函數(shù):

function observe(val) {
 // 若 val 是對(duì)象且非數(shù)組,則 new 一個(gè) Observer 實(shí)例,val 作為參數(shù)
 // 簡(jiǎn)單點(diǎn)說(shuō):是對(duì)象就繼續(xù)。
 if (!Array.isArray(val) && typeof val === "object") {
  return new Observer(val);
 }
}

這樣一來(lái)就對(duì) data 的所有子孫屬性(不知有沒有這種說(shuō)法。。)都進(jìn)行了“劫持”。顯然到目前為止,這并沒什么用,或者說(shuō)如果只做到這里,那么和什么都不做沒差別。于是 Dep 上場(chǎng)了。我認(rèn)為理解 Dep 與 Observer 和 Watcher 之間的聯(lián)系是最重要的,先來(lái)談?wù)?Dep 在 Observer 里做了什么。

Observer & Dep

在每一次 defineReactive 函數(shù)被調(diào)用之后,都會(huì)在閉包中新建一個(gè) Dep 實(shí)例,即 let dep = new Dep()。Dep 提供了一些方法,先來(lái)說(shuō)說(shuō) notify 這個(gè)方法,它做了什么事?就是在屬性值發(fā)生變化的時(shí)候通知 Dep,那么我們的代碼可以增加如下:

function defineReactive(obj, key, val) {
 let childObj = observe(val);
 const dep = new Dep();

 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function() {
   return val;
  },
  set: function(newVal) {
   if (newVal === val) {
    return;
   }

   val = newVal;
   childObj = observe(newVal);

   // 發(fā)生變動(dòng)
   dep.notify();
  }
 });
}

如果僅考慮 Observer 與 Dep 的聯(lián)系,即有變動(dòng)時(shí)通知 Dep,那么這里就算完了,然而在 vue.js 的源碼中,我們還可以看到一段增加在 getter 中的代碼:

// ...
get: function() {
 if (Dep.target) {
  dep.depend();
 }
 return val;
}
// ...

這個(gè) depend 方法呢,它又做了啥?答案是為閉包中的 Dep 實(shí)例添加了一個(gè) Watcher 的訂閱,而 Dep.target 又是啥?他其實(shí)是一個(gè) Watcher 實(shí)例,???一臉懵逼,先記住就好,先看一部份的 Dep 源碼:

// 標(biāo)識(shí)符,在 Watcher 中有用到,先不用管
let uid = 0;

function Dep() {
 this.id = uid++;
 this.subs = [];
}

Dep.prototype.depend = function() {
 // 這一步相當(dāng)于做了這么一件事:this.subs.push(Dep.target)
 // 即添加了 Watcher 訂閱,addDep 是 Watcher 的方法
 Dep.target.addDep(this);
};

// 通知更新
Dep.prototype.notify = function() {
 // this.subs 的每一項(xiàng)都為一個(gè) Watcher 實(shí)例
 this.subs.forEach(function(sub) {
  // update 為 Watcher 的一個(gè)方法,更新視圖
  // 沒錯(cuò),實(shí)際上這個(gè)方法最終會(huì)調(diào)用到 Compile 中的 updaterFn,
  // 也即 new Watcher(vm, exp, callback) 中的 callback
  sub.update();
 });
};

// 在 Watcher 中調(diào)用
Dep.prototype.addSub = function(sub) {
 this.subs.push(sub);
};

// 初始時(shí)引用為空
Dep.target = null;

也許看到這還是一臉懵逼,沒關(guān)系,接著往下。大概有同學(xué)會(huì)疑惑,為什么要把添加 Watcher 訂閱放在 getter 中,接下來(lái)我們來(lái)說(shuō)說(shuō)這 Watcher 和 Dep 的故事。

Watcher & Dep

先讓我們回顧一下 Compile 做的事,解析 fragment,然后給相應(yīng)屬性添加訂閱:new Watcher(vm, exp, cb)。new 了這個(gè) Watcher 之后,Watcher 怎么辦呢,就有了下面這樣的對(duì)話:

Watcher:hey Dep,我需要訂閱 exp 屬性的變動(dòng)。

Dep:這我可做不到,你得去找 exp 屬性中的 dep,他能做到這件事。

Watcher:可是他在閉包中啊,我無(wú)法和他聯(lián)系。

Dep:你拿到了整個(gè) Hue 實(shí)例 vm,又知道屬性 exp,你可以觸發(fā)他的 getter 啊,你在 getter 里動(dòng)些手腳不就行了。

Watcher:有道理,可是我得讓 dep 知道是我訂閱的啊,不然他通知不到我。

Dep:這個(gè)簡(jiǎn)單,我?guī)湍?,你每次觸發(fā) getter 前,把你的引用告訴 Dep.target 就行了。記得辦完事后給 Dep.target 置空。

于是就有了上面 getter 中的代碼:

// ...
get: function() {
 // 是否是 Watcher 觸發(fā)的
 if (Dep.target) {
  // 是就添加進(jìn)來(lái)
  dep.depend();
 }
 return val;
}
// ...

現(xiàn)在再回頭看看 Dep 部分的代碼,是不是好理解些了。如此一來(lái), Watcher 需要做的事情就簡(jiǎn)單明了了:

function Watcher(vm, exp, cb) {
 this.$vm = vm;
 this.cb = cb;
 this.exp = exp;
 this.depIds = new Set();

 // 返回一個(gè)用于獲取相應(yīng)屬性值的函數(shù)
 this.getter = parseGetter(exp.trim());

 // 調(diào)用 get 方法,觸發(fā) getter
 this.value = this.get();
}

Watcher.prototype.get = function() {
 const vm = this.$vm;
 // 將 Dep.target 指向當(dāng)前 Watcher 實(shí)例
 Dep.target = this;
 // 觸發(fā) getter
 let value = this.getter.call(vm, vm);
 // Dep.target 置空
 Dep.target = null;
 return value;
};

Watcher.prototype.addDep = function(dep) {
 const id = dep.id;
 if (!this.depIds.has(id)) {
  // 添加訂閱,相當(dāng)于 dep.subs.push(this)
  dep.addSub(this);
  this.depIds.add(id);
 }
};

function parseGetter(exp) {
 if (/[^\w.$]/.test(exp)) {
  return;
 }

 let exps = exp.split(".");

 return function(obj) {
  for (let i = 0; i < exps.length; i++) {
   if (!obj)
    return;
   obj = obj[exps[i]];
  }
  return obj;
 };
}

最后還差一部分,即 Dep 通知變化后,Watcher 的處理,具體的函數(shù)調(diào)用流程是這樣的:dep.notify() -> sub.update(),直接上代碼:

Watcher.prototype.update = function() {
 this.run();
};

Watcher.prototype.run = function() {
 let value = this.get();
 let oldVal = this.value;

 if (value !== oldVal) {
  this.value = value;
  // 調(diào)用回調(diào)函數(shù)更新視圖
  this.cb.call(this.$vm, value, oldVal);
 }
};

上述就是小編為大家分享的vue.js中怎么實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)綁定了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)網(wǎng)站建設(shè)公司行業(yè)資訊頻道。

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)建站www.muchs.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。

網(wǎng)站標(biāo)題:vue.js中怎么實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)綁定-創(chuàng)新互聯(lián)
當(dāng)前鏈接:http://www.muchs.cn/article38/djhhsp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供商城網(wǎng)站、網(wǎng)站制作、微信公眾號(hào)、網(wǎng)站收錄、網(wǎng)站策劃企業(yè)網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

綿陽(yáng)服務(wù)器托管