欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

稍微學(xué)一下Vue的數(shù)據(jù)響應(yīng)式(Vue2及Vue3區(qū)別)

 更新時(shí)間:2019年11月21日 08:35:06   作者:ST_Pace  
這篇文章主要介紹了稍微學(xué)一下 Vue 的數(shù)據(jù)響應(yīng)式(Vue2 及 Vue3),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

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

從一開(kāi)始使用 Vue 時(shí),對(duì)于之前的 jq 開(kāi)發(fā)而言,一個(gè)很大的區(qū)別就是基本不用手動(dòng)操作 dom,data 中聲明的數(shù)據(jù)狀態(tài)改變后會(huì)自動(dòng)重新渲染相關(guān)的 dom。
換句話(huà)說(shuō)就是 Vue 自己知道哪個(gè)數(shù)據(jù)狀態(tài)發(fā)生了變化及哪里有用到這個(gè)數(shù)據(jù)需要隨之修改。

因此實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式有兩個(gè)重點(diǎn)問(wèn)題:

  1. 如何知道數(shù)據(jù)發(fā)生了變化?
  2. 如何知道數(shù)據(jù)變化后哪里需要修改?

對(duì)于第一個(gè)問(wèn)題,如何知道數(shù)據(jù)發(fā)生了變化,Vue3 之前使用了 ES5 的一個(gè) API Object.defineProperty Vue3 中使用了 ES6 的 Proxy,都是對(duì)需要偵測(cè)的數(shù)據(jù)進(jìn)行 變化偵測(cè) ,添加 getter 和 setter ,這樣就可以知道數(shù)據(jù)何時(shí)被讀取和修改。

第二個(gè)問(wèn)題,如何知道數(shù)據(jù)變化后哪里需要修改,Vue 對(duì)于每個(gè)數(shù)據(jù)都收集了與之相關(guān)的 依賴(lài) ,這里的依賴(lài)其實(shí)就是一個(gè)對(duì)象,保存有該數(shù)據(jù)的舊值及數(shù)據(jù)變化后需要執(zhí)行的函數(shù)。每個(gè)響應(yīng)式的數(shù)據(jù)變化時(shí)會(huì)遍歷通知其對(duì)應(yīng)的每個(gè)依賴(lài),依賴(lài)收到通知后會(huì)判斷一下新舊值有沒(méi)有發(fā)生變化,如果變化則執(zhí)行回調(diào)函數(shù)響應(yīng)數(shù)據(jù)變化(比如修改 dom)。

下面詳細(xì)分別介紹 Vue2 及 Vue3 的數(shù)據(jù)變化偵測(cè)及依賴(lài)收集。

Vue2

變化偵測(cè)

Object 的變化偵測(cè)

轉(zhuǎn)化響應(yīng)式數(shù)據(jù)需要將 Vue 實(shí)例上 data 屬性中定義的數(shù)據(jù)通過(guò)遞歸將所有屬性都轉(zhuǎn)化為 getter/setter 的形式,Vue 中定義了一個(gè) Observer 類(lèi)來(lái)做這個(gè)事情。

function def(obj, key, val, enumerable) {
 Object.defineProperty(obj, key, {
  value: val,
  enumerable: !!enumerable,
  writable: true,
  configurable: true
 })
}

class Observer {
 constructor(value) {
  this.value = value;
  def(value, '__ob__', this);
  if (!Array.isArray(value)) {
   this.walk(value);
  }
 }
 walk(obj) {
  for (const [key, value] of Object.entries(obj)) {
   defineReactive(obj, key, value);
  }
 }
}

直接將一個(gè)對(duì)象傳入 new Observer() 后就對(duì)每項(xiàng)屬性都調(diào)用 defineReactive 函數(shù)添加變化偵測(cè),下面定義這個(gè)函數(shù):

function defineReactive(data, key, val) {
 let childOb = observe(val);
 Object.defineProperty(data, key, {
  enumerable: true,
  configurable: true,
  get: function () {
   // 讀取 data[key] 時(shí)觸發(fā)
   console.log('getter', val);
   return val;
  },
  set: function (newVal) {
   // 修改 data[key] 時(shí)觸發(fā)
   console.log('setter', newVal);
   if (val === newVal) {
    return;
   }
   val = newVal;
  }
 })
}

function observe(value, asRootData) {
 if (typeof val !== 'object') {
  return;
 }
 let ob;
 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  ob = value.__ob__;
 } else {
  ob = new Observer(val);
 }
 return ob;
}

函數(shù)中判斷如果是對(duì)象則遞歸調(diào)用 Observer 來(lái)實(shí)現(xiàn)所有屬性的變化偵測(cè),根據(jù) __ob__ 屬性判斷是否已處理過(guò),防止多次重復(fù)處理,Observer 處理過(guò)后會(huì)給數(shù)據(jù)添加這個(gè)屬性,下面寫(xiě)一個(gè)對(duì)象試一下:

const people = {
 name: 'c',
 age: 12,
 parents: {
  dad: 'a',
  mom: 'b'
 },
 mates: ['d', 'e']
};
new Observer(people);
people.name; // getter c
people.age++; // getter 12 setter 13
people.parents.dad; // getter {} getter a

打印 people 可以看到所有屬性添加了 getter/setter 方法,讀取 name 屬性時(shí)打印了 people.age++ 修改 age 時(shí)打印了 getter 12 setter 13 說(shuō)明 people 的屬性已經(jīng)被全部成功代理監(jiān)聽(tīng)。

Array 的變化偵測(cè)

可以看到前面 Observer 中僅對(duì) Object 類(lèi)型個(gè)數(shù)據(jù)做了處理,為每個(gè)屬性添加了 getter/setter,處理后如果屬性值中有數(shù)組,通過(guò) 屬性名 + 索引 的方式(如:this.people.mates[0])獲取也是會(huì)觸發(fā) getter 的。但是如果通過(guò)數(shù)組原型方法修改數(shù)組的值,如 this.people.mates.push('f'),這樣是無(wú)法通過(guò) setter 偵測(cè)到的,因此,在 Observer 中需要對(duì) Object 和 Array 分別進(jìn)行單獨(dú)的處理。

為偵測(cè)到數(shù)組原型方法的操作,Vue 中是通過(guò)創(chuàng)建一個(gè)攔截器 arrayMethods,并將攔截器重新掛載到數(shù)組的原型對(duì)象上。

下面是攔截器的定義:

const ArrayProto = Array.prototype;
const arrayMethods = Object.create(ArrayProto);
;[
 'push',
 'pop',
 'shift',
 'unshift',
 'splice',
 'sort',
 'reverse'
].forEach(method => {
 const original = ArrayProto[method];
 Object.defineProperty(arrayMethods, method, {
  value: function mutator(...args) {
   console.log('mutator:', this, args);
   return original.apply(this, args);
  },
  enumerable: false,
  writable: true,
  configurable: true
 })
})

這里 arrayMethods 繼承了 Array 的原型對(duì)象 Array.prototype,并給它添加了 push pop shift unshift splice sort reverse 這些方法,因?yàn)閿?shù)組是可以通過(guò)這些方法進(jìn)行修改的。添加的 push pop... 方法中重新調(diào)用 original(緩存的數(shù)組原型方法),這樣就不會(huì)影響數(shù)組本身的操作。

最后給 Observer 中添加數(shù)組的修改:直接將攔截器掛載到數(shù)組原型對(duì)象上

class Observer {
 constructor(value) {
  this.value = value;
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
   value.__proto__ = arrayMethods;
  } else {
   this.walk(value);
  }
 }
 walk(obj) {
  for (const [key, value] of Object.entries(obj)) {
   defineReactive(obj, key, value);
  }
 }
}

再來(lái)驗(yàn)證一下:

const people = {
 name: 'c',
 age: 12,
 parents: {
  dad: 'a',
  mom: 'b'
 },
 mates: ['d', 'e']
};
new Observer(people);
people.mates[0]; // getter (2) ["d", "e"]
people.mates.push('f'); // mutator: (2) ["d", "e"] ["f"]

現(xiàn)在數(shù)組的修改也能被偵測(cè)到了。

依賴(lài)收集

目前已經(jīng)可以對(duì) Object 及 Array 數(shù)據(jù)的變化進(jìn)行截獲,那么開(kāi)始考慮一開(kāi)始提到的 Vue 響應(yīng)式數(shù)據(jù)的第二個(gè)問(wèn)題:如何知道數(shù)據(jù)變化后哪里需要修改?

最開(kāi)始已經(jīng)說(shuō)過(guò),Vue 中每個(gè)數(shù)據(jù)都需要收集與之相關(guān)的依賴(lài),用來(lái)表示該數(shù)據(jù)變化時(shí)需要進(jìn)行的操作行為。

通過(guò)數(shù)據(jù)的變化偵測(cè)我們可以知道數(shù)據(jù)何時(shí)被讀取或修改,因此可以在數(shù)據(jù)讀取時(shí)收集依賴(lài),修改時(shí)通知依賴(lài)更新,這樣就可以實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式了。

依賴(lài)收集在哪

為每個(gè)數(shù)據(jù)都創(chuàng)建一個(gè)收集依賴(lài)的對(duì)象 dep,對(duì)外暴露 depend(收集依賴(lài))、notify(通知依賴(lài)更新)的兩個(gè)方法,內(nèi)部維護(hù)了一個(gè)數(shù)組用來(lái)保存該數(shù)據(jù)的每項(xiàng)依賴(lài)。

對(duì)于 Object,可以在 getter 中收集,setter 中通知更新,對(duì) defineReactive 函數(shù)修改如下:

function defineReactive(data, key, val) {
 let childOb = observe(val);
 // 處理每個(gè)響應(yīng)式數(shù)據(jù)時(shí)都創(chuàng)建一個(gè)對(duì)象用來(lái)收集依賴(lài)
 let dep = new Dep();
 Object.defineProperty(data, key, {
  enumerable: true,
  configurable: true,
  get: function () {
   // 收集依賴(lài)
   dep.depend();
   return val;
  },
  set: function (newVal) {
   if (val === newVal) {
    return;
   }
   val = newVal;
   // 通知依賴(lài)更新
   dep.notify();
  }
 })
}

上面代碼中依賴(lài)是收集在一個(gè) Dep 實(shí)例對(duì)象上的,下面看一下 Dep 這個(gè)類(lèi)。

class Dep {
 constructor() {
  this.subs = [];
 }
 addSub(sub) {
  this.subs.push(sub);
 }
 removeSub(sub) {
  if (this.subs.length) {
   const index = this.subs.indexOf(sub);
   this.subs.splice(index, 1);
  }
 }
 depend() {
  if (window.target) {
   this.addSub(window.target);
  }
 }
 notify() {
  const subs = this.subs.slice();
  for (let i = 0; i < subs.length; i++) {
   subs[i].update();
  }
 }
}

Dep 的每個(gè)實(shí)例都有一個(gè)保存依賴(lài)的數(shù)組 subs,收集依賴(lài)時(shí)是從全局的一個(gè)變量上獲取到并插入 subs,通知依賴(lài)時(shí)就遍歷所有 subs 成員并調(diào)用其 update 方法。

Object 的依賴(lài)收集和觸發(fā)都是在 defineProperty 中進(jìn)行的,因此 Dep 實(shí)例定義在 defineReactive 函數(shù)中就可以讓 getter 和 setter 都拿到。

而對(duì)于 Array 來(lái)說(shuō),依賴(lài)可以在 getter 中收集,但觸發(fā)卻是在攔截器中,為了保證 getter 和 攔截器中都能訪問(wèn)到 Dep 實(shí)例,Vue 中給 Observer 實(shí)例上添加了 dep 屬性。

class Observer {
 constructor(value) {
  this.value = value;
  this.dep = new Dep();
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
   value.__proto__ = arrayMethods;
  } else {
   this.walk(value);
  }
 }
 walk(obj) {
  for (const [key, value] of Object.entries(obj)) {
   defineReactive(obj, key, value);
  }
 }
}

Observer 在處理數(shù)據(jù)響應(yīng)式時(shí)也將自身實(shí)例添加到了數(shù)據(jù)的 __ob__ 屬性上,因此在 getter 和攔截器中都能通過(guò)響應(yīng)式數(shù)據(jù)本身的  __ob__.dep 拿到其對(duì)應(yīng)的依賴(lài)。修改 defineReactive 和 攔截器如下:

function defineReactive(data, key, val) {
 let childOb = observe(val);
 let dep = new Dep();
 Object.defineProperty(data, key, {
  enumerable: true,
  configurable: true,
  get: function () {
   dep.depend();
   // 給 Observer 實(shí)例上的 dep 屬性收集依賴(lài)
   if (childOb) {
    childOb.dep.depend();
   }
   return val;
  },
  ...
 })
}

;[
 'push',
 'pop',
 'shift',
 'unshift',
 'splice',
 'sort',
 'reverse'
].forEach(method => {
 const original = ArrayProto[method];
 def(arrayMethods, method, (...args) => {
  const result = original.apply(this, args);
  const ob = this.__ob__;
  ob.dep.notify();
  return result;
 })
})

依賴(lài)長(zhǎng)什么樣

現(xiàn)在已經(jīng)知道了依賴(lài)保存在每個(gè)響應(yīng)式數(shù)據(jù)對(duì)應(yīng)的 Dep 實(shí)例中的 subs 中,通過(guò)上面 Dep 的代碼可以知道,收集的依賴(lài)是一個(gè)全局對(duì)象,且該對(duì)象對(duì)外暴露了一個(gè) update 方法,記錄了數(shù)據(jù)變化時(shí)需要進(jìn)行的更新操作(如修改 dom 或 Vue 的 Watch)。

首先這個(gè)依賴(lài)對(duì)象的功能主要有兩點(diǎn):

  1. 需要主動(dòng)將自己收集到對(duì)應(yīng)響應(yīng)式數(shù)據(jù)的 Dep 實(shí)例中;
  2. 保存數(shù)據(jù)變化時(shí)要進(jìn)行的操作并在 update 方法中調(diào)用;

其實(shí)就是一個(gè)中介角色,Vue 中起名為 Watcher。

class Watcher {
 constructor(vm, expOrFn, cb) {
  this.vm = vm;
  // 保存通過(guò)表達(dá)式獲取數(shù)據(jù)的方法
  this.getter = parsePath(expOrFn);
  this.cb = cb;
  this.value = this.get();
 }
 get() {
  // 將自身 Watcher 實(shí)例掛到全局對(duì)象上
  window.target = this;
  // 獲取表達(dá)式對(duì)應(yīng)的數(shù)據(jù)
  // 會(huì)自動(dòng)觸發(fā)該數(shù)據(jù)的 getter
  // getter 中收集依賴(lài)時(shí)從全局對(duì)象上拿到這個(gè) Watcher 實(shí)例
  let value = this.getter.call(this.vm, this.vm);
  window.target = undefined;
  return value;
 }
 update() {
  const oldValue = this.value;
  this.value = this.get();
  // 將舊值與新值傳遞給回調(diào)函數(shù)
  this.cb.call(this.vm, this.value, oldValue);
 }
}

對(duì)于第一點(diǎn),主動(dòng)將自己收集到 Dep 實(shí)例中,Watcher 中設(shè)計(jì)的非常巧妙,在 get 中將自身 Watcher 實(shí)例掛到全局對(duì)象上,然后通過(guò)獲取數(shù)據(jù)觸發(fā) getter 來(lái)實(shí)現(xiàn)依賴(lài)收集。

第二點(diǎn)實(shí)現(xiàn)很簡(jiǎn)單,只需要將構(gòu)造函數(shù)參數(shù)中的回調(diào)函數(shù)保存并在 update 方法中調(diào)用即可。

構(gòu)造函數(shù)中的 parsePath 方法就是從 Vue 實(shí)例的 data 上通過(guò)表達(dá)式獲取數(shù)據(jù),比如表達(dá)式為 "user.name" 則需要解析該字符串然后獲取 data.user.name 數(shù)據(jù)。

總結(jié)

  • 數(shù)據(jù)先通過(guò)調(diào)用 new Observer() 為每項(xiàng)屬性添加變化偵測(cè),并創(chuàng)建一個(gè) Dep 實(shí)例用來(lái)保存相關(guān)依賴(lài)。在讀取屬性值時(shí)保存依賴(lài),修改屬性值時(shí)通知依賴(lài);
  • Dep 實(shí)例的 subs 屬性為一個(gè)數(shù)組,保存依賴(lài)是向數(shù)組中添加,通知依賴(lài)時(shí)遍歷數(shù)組一次調(diào)用依賴(lài)的 update 方法;
  • 依賴(lài)是一個(gè) Watcher 實(shí)例,保存了數(shù)據(jù)變化時(shí)需要進(jìn)行的操作,并將實(shí)例自身放到全局的一個(gè)位置,然后讀取數(shù)據(jù)觸發(fā)數(shù)據(jù)的 getter,getter 中從全局指定的位置獲取到該 Watcher 實(shí)例并收集在 Dep 實(shí)例中。

以上就是 Vue2 中的響應(yīng)式原理,在 Observer 處理完后,外界只需要通過(guò)創(chuàng)建 Watcher 傳入需要監(jiān)聽(tīng)的數(shù)據(jù)及數(shù)據(jù)變化時(shí)的響應(yīng)回調(diào)函數(shù)即可。

Vue3

Vue3 中每個(gè)功能單獨(dú)為一個(gè)模塊,并可以單獨(dú)打包使用,本文僅簡(jiǎn)單討論 Vue3 中與數(shù)據(jù)響應(yīng)式相關(guān)的 Reactive 模塊,了解其內(nèi)部原理,與 Vue2 相比又有何不同。

因?yàn)樵撃K可以單獨(dú)使用,先來(lái)看一下這個(gè)模塊的用法示例:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>vue3 demo</title>
</head>
<body>
 <div id="app">
  <div id="count"></div>
  <button id="btn">+1</button>
 </div>
 <script src="./vue3.js"></script>
 <script>
  const countEl = document.querySelector('#count')
  const btnEl = document.querySelector('#btn')
  // 定義響應(yīng)式數(shù)據(jù)
  const state = reactive({
   count: 0,
   man: {
    name: 'pan'
   }
  })
  // 定義計(jì)算屬性
  let double = computed(() => {
   return state.count * 2
  })
  // 回調(diào)函數(shù)立即執(zhí)行一次,內(nèi)部使用到的數(shù)據(jù)更新時(shí)會(huì)重新執(zhí)行回調(diào)函數(shù)
  effect(() => {
   countEl.innerHTML = `count is ${state.count}, double is ${double.value}, man's name is ${state.man.name}`
  })
  // 修改響應(yīng)式數(shù)據(jù)觸發(fā)更新
  btnEl.addEventListener('click', () => {
   state.count++
  }, false)
 </script>
</body>
</html>

通過(guò)示例可以看到實(shí)現(xiàn) Vue3 這個(gè)數(shù)據(jù)響應(yīng)式需要有 reactive、computed、effect 這幾個(gè)函數(shù),下面仍然通過(guò)從變化偵測(cè)及依賴(lài)收集兩個(gè)方面介紹,簡(jiǎn)單實(shí)現(xiàn)這幾個(gè)函數(shù)。

變化偵測(cè)

示例中的 reactive 函數(shù)是對(duì)數(shù)據(jù)進(jìn)行響應(yīng)式化的,因此該函數(shù)的功能就類(lèi)似于 Vue2 中的 defineReactive 函數(shù)的 getter/setter 處理,處理后能夠?qū)?shù)據(jù)的獲取及修改操作進(jìn)行捕獲。

const toProxy = new WeakMap()
const toRaw = new WeakMap()

const baseHandler = {
 get(target, key) {
  console.log('Get', target, key)
  const res = Reflect.get(target, key)
  // 遞歸尋找
  return typeof res == 'object' ? reactive(res) : res
 },
 set(target, key, val) {
  console.log('Set', target, key, val)
  const res = Reflect.set(target, key, val)
  return res
 }
}
function reactive(target) {
 console.log('reactive', target)
 // 查詢(xún)緩存
 let observed = toProxy.get(target)
 if (observed) {
  return observed
 }
 if (toRaw.get(target)) {
  return target
 }
 observed = new Proxy(target, baseHandler)
 // 設(shè)置緩存
 toProxy.set(target, observed)
 toRaw.set(observed, target)
 return observed
}

reactive 中使用 Proxy 對(duì)目標(biāo)進(jìn)行代理,代理的行為是 baseHander ,然后對(duì)目標(biāo)對(duì)象及代理后的對(duì)象進(jìn)行緩存,防止多次代理。

baseHandler 中就是對(duì)數(shù)據(jù)的獲取及修改進(jìn)行攔截,并通過(guò) Reflect 執(zhí)行 get/set 的原本操作,并在獲取值為 Object 時(shí)遞歸進(jìn)行響應(yīng)式處理。很簡(jiǎn)單地就完成了數(shù)據(jù)的響應(yīng)式處理。

依賴(lài)收集

依賴(lài)收集與 Vue2 類(lèi)似,在 getter 中收集依賴(lài),setter 中觸發(fā)依賴(lài),修改 baseHandler 如下:

const baseHandler = {
 get(target, key) {
  const res = Reflect.get(target, key)
  // 收集依賴(lài)
  track(target, key)
  return typeof res == 'object' ? reactive(res) : res
 },
 set(target, key, val) {
  const info = {
   oldValue: target[key],
   newValue: val
  }
  const res = Reflect.set(target, key, val)
  // 觸發(fā)更新
  trigger(target, key, info)
  return res
 }
}

track 函數(shù)收集依賴(lài),trigger 函數(shù)觸發(fā)依賴(lài)更新。

首先需要兩個(gè)全局變量,用于保存當(dāng)前待收集的依賴(lài)對(duì)象的 effectStack 及一個(gè)記錄所有數(shù)據(jù)及其對(duì)應(yīng)依賴(lài)的表 targetMap 。

const effectStack = []
const targetMap = new WeakMap()

接下來(lái)定義這收集依賴(lài)及觸發(fā)依賴(lài)更新這兩個(gè)函數(shù):

function track(target, key) {
 // 從棧中拿到待收集的依賴(lài)對(duì)象
 let effect = effectStack[effectStack.length - 1]
 if (effect) {
  // 通過(guò) target 及 key 從依賴(lài)映射表中拿到對(duì)應(yīng)的依賴(lài)列表(Set類(lèi)型)
  // 首次需要對(duì)依賴(lài)映射表初始化
  let depsMap = targetMap.get(target)
  if (depsMap === undefined) {
   depsMap = new Map()
   targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (dep === undefined) {
   dep = new Set()
   depsMap.set(key, dep)
  }
  // 若 target.key 對(duì)應(yīng)的依賴(lài)列表中不存在該依賴(lài)則收集
  if (!dep.has(effect)) {
   dep.add(effect)
  }
 }
}
function trigger(target, key, info) {
 // 依賴(lài)映射表中取出 target 相關(guān)數(shù)據(jù)
 const depsMap = targetMap.get(target)
 if (depsMap === undefined) {
  return
 }
 // 普通依賴(lài)對(duì)象的列表
 const effects = new Set()
 // 計(jì)算屬性依賴(lài)對(duì)象的列表
 const computedRunners = new Set()
 if (key) {
  // 取出 key 相關(guān)的依賴(lài)列表遍歷分類(lèi)存入 effects 及 computedRunners
  let deps = depsMap.get(key)
  deps.forEach(effect => {
   if (effect.computed) {
    computedRunners.add(effect)
   } else {
    effects.add(effect)
   }
  })
 }
 // 遍歷執(zhí)行所有依賴(lài)對(duì)象
 const run = effect=> effect()
 effects.forEach(run)
 computedRunners.forEach(run)
}

track 及 trigger 的大致代碼也很簡(jiǎn)單,track 是拿到待收集的依賴(lài)對(duì)象 effect 后收集到 effectStack,trigger 是從 effectStack 拿到對(duì)應(yīng)的依賴(lài)列表遍歷執(zhí)行。

到現(xiàn)在就差這個(gè)依賴(lài)對(duì)象了,根據(jù)上面 trigger 函數(shù)可以知道,這個(gè)依賴(lài) effect 首先是個(gè)函數(shù)可以執(zhí)行,并且還有自身屬性,如 computed 表示其為一個(gè)計(jì)算屬性的依賴(lài),有時(shí)會(huì)根據(jù)該標(biāo)識(shí)進(jìn)行寫(xiě)特殊處理。

下面開(kāi)始介紹這個(gè)依賴(lài)對(duì)象是如何產(chǎn)生的:

// 創(chuàng)建依賴(lài)對(duì)象
function createReactiveEffect(fn, options) {
 const effect = function effect(...args) {
  return run(effect, fn, args)
 }
 effect.computed = options.computed
 effect.lazy = options.lazy
 return effect
}

function run(effect, fn, args) {
 if (!effectStack.includes(effect)) {
  try {
   effectStack.push(effect)
   return fn(...args)
  } finally {
   effectStack.pop()
  }
 }
}

createReactiveEffect 是一個(gè)高階函數(shù),內(nèi)部創(chuàng)建了一個(gè)名為 effect 的函數(shù),函數(shù)內(nèi)部返回的是一個(gè) run 函數(shù),run 函數(shù)中將依賴(lài) effect 對(duì)象存入全局的待收集依賴(lài)棧 effectStack 中,并執(zhí)行傳入的回調(diào)函數(shù),該回調(diào)函數(shù)其實(shí)就是一開(kāi)始示例中 effect 函數(shù)傳入的修改 Dom 的函數(shù)。也就是說(shuō)依賴(lài)對(duì)象作為函數(shù)直接執(zhí)行就會(huì)添加依賴(lài)到全局棧并執(zhí)行回調(diào)函數(shù)。

回調(diào)函數(shù)中如果有讀取了響應(yīng)式數(shù)據(jù)的話(huà)則會(huì)觸發(fā) proxy 的 get 收集依賴(lài),這時(shí)就能從 effectStack 上拿到該依賴(lài)對(duì)象了。

然后給 effect 增加了 computed lazy 屬性后返回。

最后就是對(duì)外暴露的 effect 及 computed 函數(shù)了:

// 創(chuàng)建依賴(lài)對(duì)象并判斷非計(jì)算屬性則立即執(zhí)行
function effect(fn, options = {}) {
 let e = createReactiveEffect(fn, options)
 if (!options.lazy) {
  e()
 }
 return e
}

// computed 內(nèi)部調(diào)用 effect 并添加計(jì)算屬性相關(guān)的 options
function computed(fn) {
 const runner = effect(fn, {
  computed: true,
  lazy: true
 })
 return {
  effect: runner,
  get value() {
   return runner()
  }
 }
}

computed 就不多說(shuō)了,effect 就是將傳入的回調(diào)函數(shù)傳給 createReactiveEffect 創(chuàng)建依賴(lài)對(duì)象,然后執(zhí)行依賴(lài)對(duì)象就會(huì)執(zhí)行回調(diào)函數(shù)并收集該依賴(lài)對(duì)象。

總結(jié)

  • reactive 將傳入的數(shù)據(jù)對(duì)象使用 proxy 包裝,通過(guò) proxy 的 get set 攔截?cái)?shù)據(jù)的獲取及修改,與 Vue2 的 defineProperty 一樣,在 get 中收集依賴(lài),在 set 中觸發(fā)依賴(lài);
  • effect 函數(shù)接受一個(gè)回調(diào)函數(shù)作為參數(shù),將回調(diào)函數(shù)包裝一下作為依賴(lài)對(duì)象后執(zhí)行回調(diào)函數(shù),回調(diào)函數(shù)執(zhí)行時(shí)觸發(fā)相關(guān)數(shù)據(jù)的 get 后進(jìn)行依賴(lài)收集;

到此 Vue2 及 Vue3 中的數(shù)據(jù)響應(yīng)式原理都分析完了。

Vue2 及 Vue3 數(shù)據(jù)響應(yīng)式的對(duì)比

本次 Vue 對(duì)于數(shù)據(jù)響應(yīng)式的升級(jí)主要在變化偵測(cè)部分。

Vue2 中的變化偵測(cè)實(shí)現(xiàn)對(duì) Object 及 Array 分別進(jìn)行了不同的處理,Objcet 使用了
Object.defineProperty API ,Array 使用了攔截器對(duì) Array 原型上的能夠改變數(shù)據(jù)的方法進(jìn)行攔截。雖然也實(shí)現(xiàn)了數(shù)據(jù)的變化偵測(cè),但存在很多局限 ,比如對(duì)象新增屬性無(wú)法被偵測(cè),以及通過(guò)數(shù)組下邊修改數(shù)組內(nèi)容,也因此在 Vue2 中經(jīng)常會(huì)使用到 $set 這個(gè)方法對(duì)數(shù)據(jù)修改,以保證依賴(lài)更新。

Vue3 中使用了 es6 的 Proxy API 對(duì)數(shù)據(jù)代理,沒(méi)有像 Vue2 中對(duì)原數(shù)據(jù)進(jìn)行修改,只是加了代理包裝,因此首先性能上會(huì)有所改善。其次解決了 Vue2 中變化偵測(cè)的局限性,可以不使用 $set 新增的對(duì)象屬性及通過(guò)下標(biāo)修改數(shù)組都能被偵測(cè)到。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 詳解vue前后臺(tái)數(shù)據(jù)交互vue-resource文檔

    詳解vue前后臺(tái)數(shù)據(jù)交互vue-resource文檔

    本篇文章主要介紹了vue前后臺(tái)數(shù)據(jù)交互vue-resource文檔,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • VUE多層路由嵌套實(shí)現(xiàn)代碼

    VUE多層路由嵌套實(shí)現(xiàn)代碼

    這篇文章主要為大家詳細(xì)介紹了VUE多層路由嵌套的實(shí)現(xiàn)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • 如何把vuejs打包出來(lái)的文件整合到springboot里

    如何把vuejs打包出來(lái)的文件整合到springboot里

    這篇文章主要介紹了如何把vuejs打包出來(lái)的文件整合到springboot里,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-07-07
  • 詳解如何搭建mpvue框架搭配vant組件庫(kù)的小程序項(xiàng)目

    詳解如何搭建mpvue框架搭配vant組件庫(kù)的小程序項(xiàng)目

    這篇文章主要介紹了詳解如何搭建mpvue框架搭配vant組件庫(kù)的小程序項(xiàng)目,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-05-05
  • vue2使用ts?vue-class-component的過(guò)程

    vue2使用ts?vue-class-component的過(guò)程

    vue-property-decorator?是一個(gè)?Vue.js?的裝飾器庫(kù),它提供了一些裝飾器來(lái)讓你在?Vue?組件中定義屬性、計(jì)算屬性、方法、事件等,本文給大家介紹vue2使用ts?vue-class-component的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2023-11-11
  • 基于Axios 常用的請(qǐng)求方法別名(詳解)

    基于Axios 常用的請(qǐng)求方法別名(詳解)

    下面小編就為大家分享一篇Axios 常用的請(qǐng)求方法別名,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • vue3使用wangeditor封裝和自定義上傳文件官方教程

    vue3使用wangeditor封裝和自定義上傳文件官方教程

    這篇文章主要為大家介紹了vue3使用wangeditor封裝和自定義上傳文件的官方教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>
    2023-06-06
  • 基于vue 實(shí)現(xiàn)token驗(yàn)證的實(shí)例代碼

    基于vue 實(shí)現(xiàn)token驗(yàn)證的實(shí)例代碼

    這篇文章主要介紹了基于vue 實(shí)現(xiàn)token驗(yàn)證的實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2017-12-12
  • VueJs與ReactJS和AngularJS的異同點(diǎn)

    VueJs與ReactJS和AngularJS的異同點(diǎn)

    這篇文章主要為大家詳細(xì)介紹了VueJs與ReactJS和AngularJS的異同點(diǎn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • vue3使用element?ui的方法實(shí)例

    vue3使用element?ui的方法實(shí)例

    vue3出來(lái)好一段時(shí)間了,一直想著用一下,下面這篇文章主要給大家介紹了關(guān)于vue3使用element?ui的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10

最新評(píng)論