Vue內(nèi)部運(yùn)行機(jī)制解析

1乾胶,vue的運(yùn)行機(jī)制

捕獲.PNG
在 new Vue() 之后。 Vue 會(huì)調(diào)用 init 函數(shù)進(jìn)行初始化飒泻,其中最重要的是通過 Object.defineProperty 設(shè)置 setter 與 getter 函數(shù)崭捍,用來實(shí)現(xiàn)「響應(yīng)式」以及「依賴收集」

Object.defineProperty()
方法會(huì)直接在一個(gè)對象上定義一個(gè)新屬性,或者修改一個(gè)已經(jīng)存在的屬性侯嘀, 并返回這個(gè)對象。
Object.defineProperty(obj,prop,descriptor)
參數(shù)
obj:需要定義屬性的對象谱轨。
prop:需定義或修改的屬性的名字戒幔。
descriptor:將被定義或修改的屬性的描述符。
對象里目前存在的屬性描述符有兩種主要形式:數(shù)據(jù)描述符和存取描述符土童。數(shù)據(jù)描述符是一個(gè)擁有可寫或不可寫值的屬性诗茎。存取描述符是由一對 getter-setter 函數(shù)功能來描述的屬性。
數(shù)據(jù)描述符和存取描述符均具有以下可選鍵值:
configurable
當(dāng)且僅當(dāng)該屬性的 configurable 為 true 時(shí)献汗,該屬性描述符才能夠被改變错沃,也能夠被刪除。默認(rèn)為 false雀瓢。
enumerable
當(dāng)且僅當(dāng)該屬性的 enumerable 為 true 時(shí),該屬性才能夠出現(xiàn)在對象的枚舉屬性中玉掸。默認(rèn)為 false刃麸。
數(shù)據(jù)描述符同時(shí)具有以下可選鍵值:
value
該屬性對應(yīng)的值∷纠耍可以是任何有效的 JavaScript 值(數(shù)值泊业,對象把沼,函數(shù)等)。默認(rèn)為 undefined吁伺。
writable
當(dāng)且僅當(dāng)該屬性的 writable 為 true 時(shí)饮睬,該屬性才能被賦值運(yùn)算符改變。默認(rèn)為 false篮奄。
存取描述符(第三個(gè)參數(shù)對象)同時(shí)具有以下可選鍵值:
get
一個(gè)給屬性提供 getter 的方法捆愁,如果沒有 getter 則為undefined。當(dāng)我們讀取某個(gè)屬性的時(shí)候窟却,其實(shí)是在對象內(nèi)部調(diào)用了該方法昼丑,此方法必須要有return語句。該方法返回值被用作屬性值夸赫。默認(rèn)為 undefined菩帝。
set
一個(gè)給屬性提供 setter 的方法,如果沒有 setter 則為 undefined茬腿。該方法將接受唯一參數(shù)呼奢,并將該參數(shù)的新值分配給該屬性。默認(rèn)為 undefined切平。也就是說握础,當(dāng)我們設(shè)置某個(gè)屬性的時(shí)候,實(shí)際上是在對象的內(nèi)部調(diào)用了該方法揭绑。

function defineReactive (obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true,       /* 屬性可枚舉 */
        configurable: true,     /* 屬性可被修改或刪除 */
        get: function reactiveGetter () {
            return val;         /* 實(shí)際上會(huì)依賴收集弓候,下一小節(jié)會(huì)講 */
        },
        set: function reactiveSetter (newVal) {
            if (newVal === val) return;
            cb(newVal);
        }
    });
}
這個(gè)方法通過 Object.defineProperty 來實(shí)現(xiàn)對對象的「響應(yīng)式」化,入?yún)⑹且粋€(gè) obj(需要綁定的對象)他匪、key(obj的某一個(gè)屬性)菇存,val(具體的值)。經(jīng)過 defineReactive 處理以后邦蜜,我們的 obj 的 key 屬性在「讀」的時(shí)候會(huì)觸發(fā) reactiveGetter 方法依鸥,而在該屬性被「寫」的時(shí)候則會(huì)觸發(fā) reactiveSetter 方法。
初始化之后調(diào)用 $mount 會(huì)掛載組件悼沈,進(jìn)行「編譯」步驟贱迟。
compile編譯可以分成 parse、optimize 與 generate 三個(gè)階段絮供,最終需要得到 render function

parse
parse 會(huì)用正則等方式解析 template 模板中的指令衣吠、class、style等數(shù)據(jù)壤靶,形成AST缚俏。
optimize
optimize 的主要作用是標(biāo)記 static 靜態(tài)節(jié)點(diǎn),這是 Vue 在編譯過程中的一處優(yōu)化,后面當(dāng) update 更新界面時(shí)忧换,會(huì)有一個(gè) patch 的過程恬惯, diff 算法會(huì)直接跳過靜態(tài)節(jié)點(diǎn),從而減少了比較的過程亚茬,優(yōu)化了 patch 的性能酪耳。
generate
generate 是將 AST 轉(zhuǎn)化成 render function 字符串的過程,得到結(jié)果是 render 的字符串以及 staticRenderFns 字符串刹缝。

在經(jīng)歷過 parse碗暗、optimize 與 generate 這三個(gè)階段以后,組件中就會(huì)存在渲染 VNode 所需的 render function 了赞草。
接下來我們來介紹一下「依賴收集」是如何實(shí)現(xiàn)的讹堤。
class Dep {
    constructor () {
        /* 用來存放Watcher對象的數(shù)組 */
        this.subs = [];
    }

    /* 在subs中添加一個(gè)Watcher對象 */
    addSub (sub) {
        this.subs.push(sub);
    }

    /* 通知所有Watcher對象更新視圖 */
    notify () {
        this.subs.forEach((sub) => {
            sub.update();
        })
    }
}

用 addSub 方法可以在目前的 Dep 對象中增加一個(gè) Watcher 的訂閱操作;
用 notify 方法通知目前 Dep 對象的 subs 中的所有 Watcher 對象觸發(fā)更新操作厨疙。
首先在 observer 的過程中會(huì)注冊 get 方法洲守,該方法用來進(jìn)行「依賴收集」。在它的閉包中會(huì)有一個(gè) Dep 對象沾凄,這個(gè)對象用來存放 Watcher 對象的實(shí)例知残。其實(shí)「依賴收集」的過程就是把 Watcher 實(shí)例存放到對應(yīng)的 Dep 對象中去屡穗。get 方法可以讓當(dāng)前的 Watcher 對象(Dep.target)存放到它的 subs 中(addSub)方法习劫,在數(shù)據(jù)變化時(shí)许溅,set 會(huì)調(diào)用 Dep 對象的 notify 方法通知它內(nèi)部所有的 Watcher 對象進(jìn)行視圖更新。

在修改對象的值的時(shí)候保屯,會(huì)觸發(fā)對應(yīng)的 setter手负, setter 通知之前「依賴收集」得到的 Dep 中的每一個(gè) Watcher,告訴它們自己的值改變了姑尺,需要重新渲染視圖竟终。這時(shí)候這些 Watcher 就會(huì)開始調(diào)用 update 來更新視圖,最終是將新產(chǎn)生的 VNode 節(jié)點(diǎn)與老 VNode 進(jìn)行一個(gè) patch 的過程切蟋,比對得出「差異」统捶,最終將這些「差異」更新到視圖上。
我們知道柄粹,render function 會(huì)被轉(zhuǎn)化成 VNode 節(jié)點(diǎn)喘鸟。Virtual DOM 其實(shí)就是一棵以 JavaScript 對象( VNode 節(jié)點(diǎn))作為基礎(chǔ)的樹,用對象屬性來描述節(jié)點(diǎn)驻右,實(shí)際上它只是一層對真實(shí) DOM 的抽象什黑。最終可以通過一系列操作使這棵樹映射到真實(shí)環(huán)境上

首先說一下 patch 的核心 diff 算法,我們用 diff 算法可以比對出兩顆樹的「差異」堪夭,我們來看一下兑凿,假設(shè)我們現(xiàn)在有如下兩顆樹凯力,它們分別是新老 VNode 節(jié)點(diǎn),這時(shí)候到了 patch 的過程礼华,我們需要將他們進(jìn)行比對
diff 算法是通過同層的樹節(jié)點(diǎn)進(jìn)行比較而非對樹進(jìn)行逐層搜索遍歷的方式,所以時(shí)間復(fù)雜度只有 O(n)拗秘,是一種相當(dāng)高效的算法圣絮,如下圖。


捕獲.PNG

這張圖中的相同顏色的方塊中的節(jié)點(diǎn)會(huì)進(jìn)行比對雕旨,比對得到「差異」后將這些「差異」更新到視圖上扮匠。因?yàn)橹贿M(jìn)行同層級的比對,所以十分高效凡涩。

function patch (oldVnode, vnode, parentElm) {
    if (!oldVnode) {
        addVnodes(parentElm, null, vnode, 0, vnode.length - 1);
    } else if (!vnode) {
        removeVnodes(parentElm, oldVnode, 0, oldVnode.length - 1);
    } else {
        if (sameVnode(oldVNode, vnode)) {
            patchVnode(oldVNode, vnode);
        } else {
            removeVnodes(parentElm, oldVnode, 0, oldVnode.length - 1);
            addVnodes(parentElm, null, vnode, 0, vnode.length - 1);
        }
    }
}

首先在 oldVnode(老 VNode 節(jié)點(diǎn))不存在的時(shí)候棒搜,相當(dāng)于新的 VNode 替代原本沒有的節(jié)點(diǎn),所以直接用 addVnodes 將這些節(jié)點(diǎn)批量添加到 parentElm 上活箕。
然后同理力麸,在 vnode(新 VNode 節(jié)點(diǎn))不存在的時(shí)候,相當(dāng)于要把老的節(jié)點(diǎn)刪除育韩,所以直接使用 removeVnodes 進(jìn)行批量的節(jié)點(diǎn)刪除即可克蚂。
最后一種情況,當(dāng) oldVNode 與 vnode 都存在的時(shí)候筋讨,需要判斷它們是否屬于 sameVnode(相同的節(jié)點(diǎn))埃叭。如果是則進(jìn)行patchVnode(比對 VNode )操作,否則刪除老節(jié)點(diǎn)悉罕,增加新節(jié)點(diǎn)赤屋。

Vue.js 是在我們修改 data 中的數(shù)據(jù)后修改視圖其實(shí)就是一個(gè)“setter -> Dep -> Watcher -> patch -> 視圖”的過程。

假設(shè)我們有如下這么一種情況壁袄。

<template>
  <div>
    <div>{{number}}</div>
    <div @click="handleClick">click</div>
  </div>
</template>
export default {
    data () {
        return {
            number: 0
        };
    },
    methods: {
        handleClick () {
            for(let i = 0; i < 1000; i++) {
                this.number++;
            }
        }
    }
}

當(dāng)我們按下 click 按鈕的時(shí)候类早,number 會(huì)被循環(huán)增加1000次。

那么按照之前的理解然想,每次 number 被 +1 的時(shí)候莺奔,都會(huì)觸發(fā) number 的 setter 方法,從而根據(jù)上面的流程一直跑下來最后修改真實(shí) DOM变泄。那么在這個(gè)過程中令哟,DOM 會(huì)被更新 1000 次!太可怕了妨蛹。

Vue.js 肯定不會(huì)以如此低效的方法來處理屏富。Vue.js在默認(rèn)情況下,每次觸發(fā)某個(gè)數(shù)據(jù)的 setter 方法后蛙卤,對應(yīng)的 Watcher 對象其實(shí)會(huì)被 push 進(jìn)一個(gè)隊(duì)列 queue 中狠半,在下一個(gè) tick 的時(shí)候?qū)⑦@個(gè)隊(duì)列 queue 全部拿出來 run( Watcher 對象的一個(gè)方法噩死,用來觸發(fā) patch 操作) 一遍。
因?yàn)?number 執(zhí)行 ++ 操作以后對應(yīng)的 Watcher 對象都是同一個(gè)神年,我們并不需要在下一個(gè) tick 的時(shí)候執(zhí)行 1000 個(gè)同樣的 Watcher 對象去修改界面已维,而是只需要執(zhí)行一個(gè) Watcher 對象,使其將界面上的 0 變成 1000 即可已日。
那么垛耳,我們就需要執(zhí)行一個(gè)過濾的操作,同一個(gè)的 Watcher 在同一個(gè) tick 的時(shí)候應(yīng)該只被執(zhí)行一次飘千,也就是說隊(duì)列 queue 中不應(yīng)該出現(xiàn)重復(fù)的 Watcher 對象堂鲜。...

let has = {};
let queue = [];
let waiting = false;

function queueWatcher(watcher) {
    const id = watcher.id;
    if (has[id] == null) {
        has[id] = true;
        queue.push(watcher);

        if (!waiting) {
            waiting = true;
            nextTick(flushSchedulerQueue);
        }
    }
}

number 會(huì)被不停地進(jìn)行 ++ 操作,不斷地觸發(fā)它對應(yīng)的 Dep 中的 Watcher 對象的 update 方法护奈。然后最終 queue 中因?yàn)閷ο嗤?id 的 Watcher 對象進(jìn)行了篩選缔莲,從而 queue 中實(shí)際上只會(huì)存在一個(gè) number 對應(yīng)的 Watcher 對象。在下一個(gè) tick 的時(shí)候(此時(shí) number 已經(jīng)變成了 1000)霉旗,觸發(fā) Watcher 對象的 run 方法來更新視圖痴奏,將視圖上的 number 從 0 直接變成 1000。
參考于https://juejin.im/book/5a36661851882538e2259c0f

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奖慌,一起剝皮案震驚了整個(gè)濱河市抛虫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌简僧,老刑警劉巖建椰,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異岛马,居然都是意外死亡棉姐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門啦逆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伞矩,“玉大人,你說我怎么就攤上這事夏志∧死ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵沟蔑,是天一觀的道長湿诊。 經(jīng)常有香客問我,道長瘦材,這世上最難降的妖魔是什么厅须? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮食棕,結(jié)果婚禮上朗和,老公的妹妹穿的比我還像新娘错沽。我一直安慰自己,他們只是感情好眶拉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布千埃。 她就那樣靜靜地躺著,像睡著了一般镀层。 火紅的嫁衣襯著肌膚如雪镰禾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天唱逢,我揣著相機(jī)與錄音,去河邊找鬼屋休。 笑死坞古,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的劫樟。 我是一名探鬼主播痪枫,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叠艳!你這毒婦竟也來了奶陈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤附较,失蹤者是張志新(化名)和其女友劉穎吃粒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拒课,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡徐勃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了早像。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片僻肖。...
    茶點(diǎn)故事閱讀 40,001評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖卢鹦,靈堂內(nèi)的尸體忽然破棺而出臀脏,到底是詐尸還是另有隱情,我是刑警寧澤冀自,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布揉稚,位于F島的核電站,受9級特大地震影響凡纳,放射性物質(zhì)發(fā)生泄漏窃植。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一荐糜、第九天 我趴在偏房一處隱蔽的房頂上張望巷怜。 院中可真熱鬧葛超,春花似錦、人聲如沸延塑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽关带。三九已至侥涵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宋雏,已是汗流浹背芜飘。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留磨总,地道東北人嗦明。 一個(gè)月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像蚪燕,于是被迫代替她去往敵國和親娶牌。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容