自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Vue2剝絲抽繭-響應式系統(tǒng)之分支切換

開發(fā) 前端
今天這個主要就是對響應式系統(tǒng)的一點優(yōu)化,避免不必要的重新執(zhí)行。所做的事情就是重新調用函數(shù)的時候,把已經(jīng)沒有關聯(lián)的 Watcher 去除。

場景

我們考慮一下下邊的代碼會輸出什么。

import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
text: "hello, world",
ok: true,
};
observe(data);

const updateComponent = () => {
console.log("收到", data.ok ? data.text : "not");
};

new Watcher(updateComponent); // updateComponent 執(zhí)行一次函數(shù),輸出 hello, world

data.ok = false; // updateComponent 執(zhí)行一次函數(shù),輸出 not

data.text = "hello, liang"; // updateComponent 會執(zhí)行嗎?

我們來一步一步理清:

observer(data)

攔截了 data 中 text 和 ok 的 get、set,并且各自初始化了一個 Dep 實例,用來保存依賴它們的 Watcher 對象。

new Watcher(updateComponent);

這一步會執(zhí)行 updateComponent 函數(shù),執(zhí)行過程中用到的所有對象屬性,會將 Watcher 收集到相應對象屬性中的Dep 中。

當然這里的 Watcher 其實是同一個,所以用了指向的箭頭。

data.ok = false;

這一步會觸發(fā) set ,從而執(zhí)行 Dep 中所有的 Watcher ,此時就會執(zhí)行一次 updateComponent 。

執(zhí)行 updateComponent 就會重新讀取 data 中的屬性,觸發(fā) get,然后繼續(xù)收集 Watcher 。

重新執(zhí)行 updateComponent 函數(shù) 的時候:

const updateComponent = () => {
console.log("收到", data.ok ? data.text : "not");
};

因為 data.ok 的值變?yōu)?false ,所以就不會觸發(fā) data.text 的 get ,text 的 Dep 就不會變化了。

而 data.ok 會繼續(xù)執(zhí)行,觸發(fā) get 收集 Watcher ,但由于我們 Dep 中使用的是數(shù)組,此時收集到的兩個 Wacher 其實是同一個,這里是有問題,會導致 updateComponent 重復執(zhí)行,一會兒我們來解決下。

data.text = "hello, liang";

執(zhí)行這句的時候,會觸發(fā) text 的 set,所以會執(zhí)行一次 updateComponent 。但從代碼來看 updateComponent 函數(shù)中由于 data.ok 為 false,data.text 對輸出沒有任何影響,這次執(zhí)行其實是沒有必要的。

之所以執(zhí)行了,是因為第一次執(zhí)行 updateComponent 讀取了 data.text 從而收集了 Watcher ,第二次執(zhí)行 updateComponent 的時候,data.text 雖然沒有讀到,但之前的 Watcher 也沒有清除掉,所以這一次改變 data.text 的時候 updateComponent 依舊會執(zhí)行。

所以我們需要的就是當重新執(zhí)行 updateComponent 的時候,如果 Watcher 已經(jīng)不依賴于某個 Dep 了,我們需要將當前 Watcher 從該 Dep 中移除掉。

問題

總結下來我們需要做兩件事情。

  • 去重,Dep 中不要重復收集 Watcher 。
  • 重置,如果該屬性對 Dep 中的 Wacher 已經(jīng)沒有影響了(換句話就是,Watcher 中的 updateComponent 已經(jīng)不會讀取到該屬性了 ),就將該 Watcher 從該屬性的 Dep 中刪除。

去重

去重的話有兩種方案:

  • Dep 中的 subs 數(shù)組換為 Set。
  • 每個 Dep 對象引入 id ,Watcher 對象中記錄所有的 Dep 的 id,下次重新收集依賴的時候,如果 Dep 的 id 已經(jīng)存在,就不再收集該 Watcher 了。

Vue2 源碼中采用的是方案 2 這里我們實現(xiàn)下:

Dep 類的話只需要引入 id 即可。

/*************改動***************************/
let uid = 0;
/****************************************/
export default class Dep {
static target; //當前在執(zhí)行的函數(shù)
subs; // 依賴的函數(shù)
id; // Dep 對象標識
constructor() {
/**************改動**************************/
this.id = uid++;
/****************************************/
this.subs = []; // 保存所有需要執(zhí)行的函數(shù)
}

addSub(sub) {
this.subs.push(sub);
}
depend() {
if (Dep.target) {
// 委托給 Dep.target 去調用 addSub
Dep.target.addDep(this);
}
}

notify() {
for (let i = 0, l = this.subs.length; i < l; i++) {
this.subs[i].update();
}
}
}

Dep.target = null; // 靜態(tài)變量,全局唯一

在 Watcher 中,我們引入 this.depIds 來記錄所有的 id 。

import Dep from "./dep";
export default class Watcher {
constructor(Fn) {
this.getter = Fn;
/*************改動***************************/
this.depIds = new Set(); // 擁有 has 函數(shù)可以判斷是否存在某個 id
/****************************************/
this.get();
}

/**
* Evaluate the getter, and re-collect dependencies.
*/
get() {
Dep.target = this; // 保存包裝了當前正在執(zhí)行的函數(shù)的 Watcher
let value;
try {
value = this.getter.call();
} catch (e) {
throw e;
} finally {
this.cleanupDeps();
}
return value;
}

/**
* Add a dependency to this directive.
*/
addDep(dep) {
/*************改動***************************/
const id = dep.id;
if (!this.depIds.has(id)) {
dep.addSub(this);
}
/****************************************/

}

/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update() {
this.run();
}

/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run() {
this.get();
}
}

重置

同樣是兩個方案:

  • 全量式移除,保存 Watcher 所影響的所有 Dep 對象,當重新收集 Watcher 的前,把當前 Watcher 從記錄中的所有 Dep 對象中移除。
  • 增量式移除,重新收集依賴時,用一個新的變量記錄所有的 Dep 對象,之后再和舊的 Dep 對象列表比對,如果新的中沒有,舊的中有,就將當前 Watcher 從該 Dep 對象中移除。

Vue2 中采用的是方案 2,這里也實現(xiàn)下。

首先是 Dep 類,我們需要提供一個 removeSub 方法。

import { remove } from "./util";
/*
export function remove(arr, item) {
if (arr.length) {
const index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1);
}
}
}
*/
let uid = 0;

export default class Dep {
static target; //當前在執(zhí)行的函數(shù)
subs; // 依賴的函數(shù)
id; // Dep 對象標識
constructor() {
this.id = uid++;
this.subs = []; // 保存所有需要執(zhí)行的函數(shù)
}

addSub(sub) {
this.subs.push(sub);
}
/*************新增************************/
removeSub(sub) {
remove(this.subs, sub);
}
/****************************************/
depend() {
if (Dep.target) {
// 委托給 Dep.target 去調用 addSub
Dep.target.addDep(this);
}
}

notify() {
for (let i = 0, l = this.subs.length; i < l; i++) {
this.subs[i].update();
}
}
}

Dep.target = null; // 靜態(tài)變量,全局唯一

然后是 Watcher 類,我們引入 this.deps 來保存所有的舊 Dep 對象,引入 this.newDeps 來保存所有的新 Dep 對象。

import Dep from "./dep";
export default class Watcher {
constructor(Fn) {
this.getter = Fn;
this.depIds = new Set(); // 擁有 has 函數(shù)可以判斷是否存在某個 id
/*************新增************************/
this.deps = [];
this.newDeps = []; // 記錄新一次的依賴
this.newDepIds = new Set();
/****************************************/
this.get();
}

/**
* Evaluate the getter, and re-collect dependencies.
*/
get() {
Dep.target = this; // 保存包裝了當前正在執(zhí)行的函數(shù)的 Watcher
let value;
try {
value = this.getter.call();
} catch (e) {
throw e;
} finally {
/*************新增************************/
this.cleanupDeps();
/****************************************/
}
return value;
}

/**
* Add a dependency to this directive.
*/
addDep(dep) {
const id = dep.id;
/*************新增************************/
// 新的依賴已經(jīng)存在的話,同樣不需要繼續(xù)保存
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
/****************************************/
}

/**
* Clean up for dependency collection.
*/
/*************新增************************/
cleanupDeps() {
let i = this.deps.length;
// 比對新舊列表,找到舊列表里有,但新列表里沒有,來移除相應 Watcher
while (i--) {
const dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this);
}
}

// 新的列表賦值給舊的,新的列表清空
let tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
}
/****************************************/
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update() {
this.run();
}

/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run() {
this.get();
}
}

測試

回到開頭的代碼:

import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
text: "hello, world",
ok: true,
};
observe(data);

const updateComponent = () => {
console.log("收到", data.ok ? data.text : "not");
};

new Watcher(updateComponent); // updateComponent 執(zhí)行一次函數(shù),輸出 hello, world

data.ok = false; // updateComponent 執(zhí)行一次函數(shù),輸出 not

data.text = "hello, liang"; // updateComponent 會執(zhí)行嗎?

此時 data.text 修改的話就不會再執(zhí)行 updateComponent 了,因為第二次執(zhí)行的時候,我們把 data.text 中 Dep 里的 Watcher 清除了。

總結

今天這個主要就是對響應式系統(tǒng)的一點優(yōu)化,避免不必要的重新執(zhí)行。所做的事情就是重新調用函數(shù)的時候,把已經(jīng)沒有關聯(lián)的 Watcher 去除。

不知道看到這里大家有沒有一個疑問,我是一直沒想到說服我的點,歡迎一起交流:

在解決去重問題上,我們是引入了 id ,但如果直接用 set 其實就可以。在 Watcher 類中是用 Set 來存 id ,用數(shù)組來存 Dep 對象,為什么不直接用 Set 來存 Dep 對象呢?

責任編輯:武曉燕 來源: windliang
相關推薦

2022-03-29 09:59:58

響應式系統(tǒng)Vue2

2022-04-03 19:27:35

Vue2響應式系統(tǒng)

2022-04-06 07:28:47

數(shù)組響應式系統(tǒng)

2022-04-02 09:56:41

Vue2響應式系統(tǒng)

2022-04-14 08:46:46

響應式系統(tǒng)js

2022-04-12 10:05:18

響應式系統(tǒng)異步隊列

2022-04-10 11:04:40

響應式系統(tǒng)setdelete

2022-08-31 08:09:35

Vue2AST模版

2024-09-02 16:10:19

vue2前端

2024-03-07 12:54:06

數(shù)據(jù)分析師企業(yè)

2023-03-02 11:51:00

數(shù)據(jù)分析師企業(yè)

2019-04-25 14:20:56

數(shù)據(jù)分析套路工具

2021-05-19 14:25:19

前端開發(fā)技術

2024-03-15 11:47:19

Vue2前端權限控制

2022-06-26 00:00:02

Vue3響應式系統(tǒng)

2023-02-13 00:20:08

分布式系統(tǒng)安全

2023-11-19 18:53:27

Vue2MVVM

2016-10-19 20:47:55

vuevue-cli移動端

2020-09-25 07:40:39

技術開發(fā)選型

2020-06-09 11:35:30

Vue 3響應式前端
點贊
收藏

51CTO技術棧公眾號