不夸張,這真的是本Vue.js寶藏書!360前端工程師Vue.js源碼解析

優(yōu)秀源代碼背后的思想是永恒的、普適的。

這些年來,前端行業(yè)一直在飛速發(fā)展。行業(yè)的進(jìn)步,導(dǎo)致對(duì)從業(yè)人員的要求不斷攀升。放眼未來,雖然僅僅會(huì)用某些框架還可以找到工作,但僅僅滿足于會(huì)用,一定無法走得更遠(yuǎn)。隨著越來越多“聰明又勤奮”的人加入前端行列,能否洞悉前沿框架的設(shè)計(jì)和實(shí)現(xiàn)已經(jīng)成為高級(jí)人才與普通人才的“分水嶺”。

本文將通過探究Vue.js渲染中變化偵測(cè)的實(shí)現(xiàn)原理,來解讀Github上最流行Web框架Vue.js源碼背后的思想,讓你親身體驗(yàn)從“知其然”到“知其所以然”的蛻變!

變化偵測(cè)的實(shí)現(xiàn)原理

Vue.js 最獨(dú)特的特性之一是看起來并不顯眼的響應(yīng)式系統(tǒng)。數(shù)據(jù)模型僅僅是普通的JavaScript 對(duì)象。而當(dāng)你修改它們時(shí),視圖會(huì)進(jìn)行更新。這使得狀態(tài)管理非常簡單、直接。不過理解其工作原理同樣重要,這樣你可以回避一些常見的問題?!俜轿臋n

從狀態(tài)生成DOM,再輸出到用戶界面顯示的一整套流程叫作渲染,應(yīng)用在運(yùn)行時(shí)會(huì)不斷地進(jìn)行重新渲染。而響應(yīng)式系統(tǒng)賦予框架重新渲染的能力,其重要組成部分是變化偵測(cè)。變化偵測(cè)是響應(yīng)式系統(tǒng)的核心,沒有它,就沒有重新渲染??蚣茉谶\(yùn)行時(shí),視圖也就無法隨著狀態(tài)的變化而變化。

簡單來說,變化偵測(cè)的作用是偵測(cè)數(shù)據(jù)的變化。當(dāng)數(shù)據(jù)變化時(shí),會(huì)通知視圖進(jìn)行相應(yīng)的更新。正如文檔中所說,深入理解變化偵測(cè)的工作原理,既可以幫助我們?cè)陂_發(fā)應(yīng)用時(shí)回避一些很常見的問題,也可以在應(yīng)用程序出問題時(shí),快速調(diào)試并修復(fù)問題。

本文中,我們將針對(duì)變化偵測(cè)的實(shí)現(xiàn)原理做一個(gè)詳細(xì)介紹,并且會(huì)帶著你一步一步從0 到1實(shí)現(xiàn)一個(gè)變化偵測(cè)的邏輯。

什么是變化偵測(cè)

Vue.js 會(huì)自動(dòng)通過狀態(tài)生成DOM,并將其輸出到頁面上顯示出來,這個(gè)過程叫渲染。Vue.js的渲染過程是聲明式的,我們通過模板來描述狀態(tài)與DOM之間的映射關(guān)系。

通常,在運(yùn)行時(shí)應(yīng)用內(nèi)部的狀態(tài)會(huì)不斷發(fā)生變化,此時(shí)需要不停地重新渲染。這時(shí)如何確定狀態(tài)中發(fā)生了什么變化?

變化偵測(cè)就是用來解決這個(gè)問題的,它分為兩種類型:一種是“推”(push),另一種是“拉”(pull)。

Angular 和React 中的變化偵測(cè)都屬于“拉”,這就是說當(dāng)狀態(tài)發(fā)生變化時(shí),它不知道哪個(gè)狀態(tài)變了,只知道狀態(tài)有可能變了,然后會(huì)發(fā)送一個(gè)信號(hào)告訴框架,框架內(nèi)部收到信號(hào)后,會(huì)進(jìn)行一個(gè)暴力比對(duì)來找出哪些DOM 節(jié)點(diǎn)需要重新渲染。這在Angular 中是臟檢查的流程,在React中使用的是虛擬DOM。

而Vue.js 的變化偵測(cè)屬于“推”。當(dāng)狀態(tài)發(fā)生變化時(shí),Vue.js 立刻就知道了,而且在一定程度上知道哪些狀態(tài)變了。因此,它知道的信息更多,也就可以進(jìn)行更細(xì)粒度的更新。

所謂更細(xì)粒度的更新,就是說:假如有一個(gè)狀態(tài)綁定著好多個(gè)依賴,每個(gè)依賴表示一個(gè)具體的DOM節(jié)點(diǎn),那么當(dāng)這個(gè)狀態(tài)發(fā)生變化時(shí),向這個(gè)狀態(tài)的所有依賴發(fā)送通知,讓它們進(jìn)行DOM更新操作。相比較而言,“拉”的粒度是最粗的。

但是它也有一定的代價(jià),因?yàn)榱6仍郊?xì),每個(gè)狀態(tài)所綁定的依賴就越多,依賴追蹤在內(nèi)存上的開銷就會(huì)越大。因此,從Vue.js 2.0 開始,它引入了虛擬DOM,將粒度調(diào)整為中等粒度,即一個(gè)狀態(tài)所綁定的依賴不再是具體的DOM 節(jié)點(diǎn),而是一個(gè)組件。這樣狀態(tài)變化后,會(huì)通知到組件,組件內(nèi)部再使用虛擬DOM 進(jìn)行比對(duì)。這可以大大降低依賴數(shù)量,從而降低依賴追蹤所消耗的內(nèi)存。

Vue.js 之所以能隨意調(diào)整粒度,本質(zhì)上還要?dú)w功于變化偵測(cè)。因?yàn)椤巴啤鳖愋偷淖兓瘋蓽y(cè)可以隨意調(diào)整粒度。

如何追蹤變化

關(guān)于變化偵測(cè),首先要問一個(gè)問題,在JavaScript(簡稱JS)中,如何偵測(cè)一個(gè)對(duì)象的變化?

其實(shí)這個(gè)問題還是比較簡單的。學(xué)過JavaScript 的人都知道,有兩種方法可以偵測(cè)到變化:使用Object.defineProperty 和ES6 的Proxy。

由于ES6 在瀏覽器中的支持度并不理想,到目前為止Vue.js 還是使用Object.define-Property 來實(shí)現(xiàn)的,所以文中也會(huì)使用它來介紹變化偵測(cè)的原理。

由于使用Object.defineProperty 來偵測(cè)變化會(huì)有很多缺陷,所以Vue.js 的作者尤雨溪說日后會(huì)使用Proxy 重寫這部分代碼。好在本文講的是原理和思想,所以即便以后用Proxy 重寫了這部分代碼,文中介紹的原理也不會(huì)變。

知道了Object.defineProperty 可以偵測(cè)到對(duì)象的變化,那么我們可以寫出這樣的代碼:

01 function defineReactive (data, key, val) {

02 Object.defineProperty(data, key, {

03 enumerable: true,

04 configurable: true,

05 get: function () {

06 return val

07 },

08 set: function (newVal) {

09 if(val === newVal){

10 return

11 }

12 val = newVal

13 }

14 })

15 }

這里的函數(shù)defineReactive 用來對(duì)Object.defineProperty 進(jìn)行封裝。從函數(shù)的名字可以看出,其作用是定義一個(gè)響應(yīng)式數(shù)據(jù)。也就是在這個(gè)函數(shù)中進(jìn)行變化追蹤,封裝后只需要傳遞data、key 和val 就行了。

封裝好之后,每當(dāng)從data 的key 中讀取數(shù)據(jù)時(shí),get 函數(shù)被觸發(fā);每當(dāng)往data 的key 中設(shè)置數(shù)據(jù)時(shí),set 函數(shù)被觸發(fā)。

如何收集依賴

如果只是把Object.defineProperty 進(jìn)行封裝,那其實(shí)并沒什么實(shí)際用處,真正有用的是收集依賴。

現(xiàn)在我要問第二個(gè)問題:如何收集依賴?

思考一下,我們之所以要觀察數(shù)據(jù),其目的是當(dāng)數(shù)據(jù)的屬性發(fā)生變化時(shí),可以通知那些曾經(jīng)使用了該數(shù)據(jù)的地方。

舉個(gè)例子:

01 <template>

02 <h1>{{ name }}</h1>

03 </template>

該模板中使用了數(shù)據(jù)name,所以當(dāng)它發(fā)生變化時(shí),要向使用了它的地方發(fā)送通知。

注意:在Vue.js 2.0 中,模板使用數(shù)據(jù)等同于組件使用數(shù)據(jù),所以當(dāng)數(shù)據(jù)發(fā)生變化時(shí),會(huì)將通知發(fā)送到組件,然后組件內(nèi)部再通過虛擬DOM重新渲染。

對(duì)于上面的問題,我的回答是,先收集依賴,即把用到數(shù)據(jù)name 的地方收集起來,然后等屬性發(fā)生變化時(shí),把之前收集好的依賴循環(huán)觸發(fā)一遍就好了。

總結(jié)起來,其實(shí)就一句話,在getter 中收集依賴,在setter 中觸發(fā)依賴。

依賴收集在哪里

現(xiàn)在我們已經(jīng)有了很明確的目標(biāo),就是要在getter 中收集依賴,那么要把依賴收集到哪里去呢?

思考一下,首先想到的是每個(gè)key 都有一個(gè)數(shù)組,用來存儲(chǔ)當(dāng)前key 的依賴。假設(shè)依賴是一個(gè)函數(shù),保存在window.target 上,現(xiàn)在就可以把defineReactive 函數(shù)稍微改造一下:

01 function defineReactive (data, key, val) {

02 let dep = [] // 新增

03 Object.defineProperty(data, key, {

04 enumerable: true,

05 configurable: true,

06 get: function () {

07 dep.push(window.target) // 新增

08 return val

09 },

10 set: function (newVal) {

11 if(val === newVal){

12 return

13 }

14 // 新增

15 for (let i = 0; i < dep.length; i++) {

16 dep[i](newVal, val)

17 }

18 val = newVal

19 }

20 })

21 }

這里我們新增了數(shù)組dep,用來存儲(chǔ)被收集的依賴。

然后在set 被觸發(fā)時(shí),循環(huán)dep 以觸發(fā)收集到的依賴。

但是這樣寫有點(diǎn)耦合,我們把依賴收集的代碼封裝成一個(gè)Dep 類,它專門幫助我們管理依賴。使用這個(gè)類,我們可以收集依賴、刪除依賴或者向依賴發(fā)送通知等。其代碼如下:

01 export default class Dep {

02 constructor () {

03 this.subs = []

04 }

05

06 addSub (sub) {

07 this.subs.push(sub)

08 }

09

10 removeSub (sub) {

11 remove(this.subs, sub)

12 }

13

14 depend () {

15 if (window.target) {

16 this.addSub(window.target)

17 }

18 }

19

20 notify () {

21 const subs = this.subs.slice()

22 for (let i = 0, l = subs.length; i < l; i++) {

23 subs[i].update()

24 }

25 }

26 }

27

28 function remove (arr, item) {

29 if (arr.length) {

30 const index = arr.indexOf(item)

31 if (index > -1) {

32 return arr.splice(index, 1)

33 }

34 }

35 }

之后再改造一下defineReactive:

01 function defineReactive (data, key, val) {

02 let dep = new Dep() // 修改

03 Object.defineProperty(data, key, {

04 enumerable: true,

05 configurable: true,

06 get: function () {

07 dep.depend() // 修改

08 return val

09 },

10 set: function (newVal) {

11 if(val === newVal){

12 return

13 }

14 val = newVal

15 dep.notify() // 新增

16 }

17 })

18 }

此時(shí)代碼看起來清晰多了,這也順便回答了上面的問題,依賴收集到哪兒?收集到Dep 中。

依賴是誰

在上面的代碼中,我們收集的依賴是window.target,那么它到底是什么?我們究竟要收集誰呢?

收集誰,換句話說,就是當(dāng)屬性發(fā)生變化后,通知誰。

我們要通知用到數(shù)據(jù)的地方,而使用這個(gè)數(shù)據(jù)的地方有很多,而且類型還不一樣,既有可能是模板,也有可能是用戶寫的一個(gè)watch,這時(shí)需要抽象出一個(gè)能集中處理這些情況的類。然后,我們?cè)谝蕾囀占A段只收集這個(gè)封裝好的類的實(shí)例進(jìn)來,通知也只通知它一個(gè)。接著,它再負(fù)責(zé)通知其他地方。所以,我們要抽象的這個(gè)東西需要先起一個(gè)好聽的名字。嗯,就叫它Watcher 吧。

現(xiàn)在就可以回答上面的問題了,收集誰?Watcher!

什么是Watcher

Watcher 是一個(gè)中介的角色,數(shù)據(jù)發(fā)生變化時(shí)通知它,然后它再通知其他地方。

關(guān)于Watcher,先看一個(gè)經(jīng)典的使用方式:

01 // keypath

02 vm.$watch('a.b.c', function (newVal, oldVal) {

03 // 做點(diǎn)什么

04 })

這段代碼表示當(dāng)data.a.b.c 屬性發(fā)生變化時(shí),觸發(fā)第二個(gè)參數(shù)中的函數(shù)。

思考一下,怎么實(shí)現(xiàn)這個(gè)功能呢?好像只要把這個(gè)watcher 實(shí)例添加到data.a.b.c 屬性的Dep 中就行了。然后,當(dāng)data.a.b.c 的值發(fā)生變化時(shí),通知Watcher。接著,Watcher 再執(zhí)行參數(shù)中的這個(gè)回調(diào)函數(shù)。

好,思考完畢,寫出如下代碼:

01 export default class Watcher {

02 constructor (vm, expOrFn, cb) {

03 this.vm = vm

04 // 執(zhí)行this.getter(),就可以讀取data.a.b.c 的內(nèi)容

05 this.getter = parsePath(expOrFn)

06 this.cb = cb

07 this.value = this.get()

08 }

09

10 get() {

11 window.target = this

12 let value = this.getter.call(this.vm, this.vm)

13 window.target = undefined

14 return value

15 }

16

17 update () {

18 const oldValue = this.value

19 this.value = this.get()

20 this.cb.call(this.vm, this.value, oldValue)

21 }

22 }

這段代碼可以把自己主動(dòng)添加到data.a.b.c 的Dep 中去,是不是很神奇?

因?yàn)槲以趃et 方法中先把window.target 設(shè)置成了this,也就是當(dāng)前watcher 實(shí)例,然后再讀一下data.a.b.c 的值,這肯定會(huì)觸發(fā)getter。

觸發(fā)了getter,就會(huì)觸發(fā)收集依賴的邏輯。而關(guān)于收集依賴,上面已經(jīng)介紹了,會(huì)從window.target 中讀取一個(gè)依賴并添加到Dep 中。

這就導(dǎo)致,只要先在window.target 賦一個(gè)this,然后再讀一下值,去觸發(fā)getter,就可以把this 主動(dòng)添加到keypath 的Dep 中。有沒有很神奇的感覺???

依賴注入到Dep 中后,每當(dāng)data.a.b.c 的值發(fā)生變化時(shí),就會(huì)讓依賴列表中所有的依賴循環(huán)觸發(fā)update 方法,也就是Watcher 中的update 方法。而update 方法會(huì)執(zhí)行參數(shù)中的回調(diào)函數(shù),將value 和oldValue 傳到參數(shù)中。

所以,其實(shí)不管是用戶執(zhí)行的vm.$watch('a.b.c', (value, oldValue) => {}),還是模板中用到的data,都是通過Watcher 來通知自己是否需要發(fā)生變化。

這里有些小伙伴可能會(huì)好奇上面代碼中的parsePath 是怎么讀取一個(gè)字符串的keypath 的,下面用一段代碼來介紹其實(shí)現(xiàn)原理:

01 /**

02 * 解析簡單路徑

03 */

04 const bailRE = /[^w.$]/

05 export function parsePath (path) {

06 if (bailRE.test(path)) {

07 return

08 }

09 const segments = path.split('.')

10 return function (obj) {

11 for (let i = 0; i < segments.length; i++) {

12 if (!obj) return

13 obj = obj[segments[i]]

14 }

15 return obj

16 }

17 }

可以看到,這其實(shí)并不復(fù)雜。先將keypath 用 . 分割成數(shù)組,然后循環(huán)數(shù)組一層一層去讀數(shù)據(jù),最后拿到的obj 就是keypath 中想要讀的數(shù)據(jù)。

遞歸偵測(cè)所有key

現(xiàn)在,其實(shí)已經(jīng)可以實(shí)現(xiàn)變化偵測(cè)的功能了,但是前面介紹的代碼只能偵測(cè)數(shù)據(jù)中的某一個(gè)屬性,我們希望把數(shù)據(jù)中的所有屬性(包括子屬性)都偵測(cè)到,所以要封裝一個(gè)Observer 類。這個(gè)類的作用是將一個(gè)數(shù)據(jù)內(nèi)的所有屬性(包括子屬性)都轉(zhuǎn)換成getter/setter 的形式,然后去追蹤它們的變化:

01 /**

02 * Observer 類會(huì)附加到每一個(gè)被偵測(cè)的object 上。

03 * 一旦被附加上,Observer 會(huì)將object 的所有屬性轉(zhuǎn)換為getter/setter 的形式

04 * 來收集屬性的依賴,并且當(dāng)屬性發(fā)生變化時(shí)會(huì)通知這些依賴

05 */

06 export class Observer {

07 constructor (value) {

08 this.value = value

09

10 if (!Array.isArray(value)) {

11 this.walk(value)

12 }

13 }

14

15 /**

16 * walk 會(huì)將每一個(gè)屬性都轉(zhuǎn)換成getter/setter 的形式來偵測(cè)變化

17 * 這個(gè)方法只有在數(shù)據(jù)類型為Object 時(shí)被調(diào)用

18 */

19 walk (obj) {

20 const keys = Object.keys(obj)

21 for (let i = 0; i < keys.length; i++) {

22 defineReactive(obj, keys[i], obj[keys[i]])

23 }

24 }

25 }

26

27 function defineReactive (data, key, val) {

28 // 新增,遞歸子屬性

29 if (typeof val === 'object') {

30 new Observer(val)

31 }

32 let dep = new Dep()

33 Object.defineProperty(data, key, {

34 enumerable: true,

35 configurable: true,

36 get: function () {

37 dep.depend()

38 return val

39 },

40 set: function (newVal) {

41 if(val === newVal){

42 return

43 }

44

45 val = newVal

46 dep.notify()

47 }

48 })

49 }

在上面的代碼中,我們定義了Observer 類,它用來將一個(gè)正常的object 轉(zhuǎn)換成被偵測(cè)的object。

然后判斷數(shù)據(jù)的類型,只有Object 類型的數(shù)據(jù)才會(huì)調(diào)用walk 將每一個(gè)屬性轉(zhuǎn)換成getter/setter 的形式來偵測(cè)變化。

最后,在defineReactive 中新增new Observer(val)來遞歸子屬性,這樣我們就可以把data 中的所有屬性(包括子屬性)都轉(zhuǎn)換成getter/setter 的形式來偵測(cè)變化。

當(dāng)data 中的屬性發(fā)生變化時(shí),與這個(gè)屬性對(duì)應(yīng)的依賴就會(huì)接收到通知。

也就是說,只要我們將一個(gè)object 傳到Observer 中,那么這個(gè)object 就會(huì)變成響應(yīng)式的object。

關(guān)于Object 的問題

前面介紹了Object 類型數(shù)據(jù)的變化偵測(cè)原理,了解了數(shù)據(jù)的變化是通過getter/setter 來追蹤的。也正是由于這種追蹤方式,有些語法中即便是數(shù)據(jù)發(fā)生了變化,Vue.js 也追蹤不到。

比如,向object 添加屬性:

01 var vm = new Vue({

02 el: '#el',

03 template: '#demo-template',

04 methods: {

05 action () {

06 this.obj.name = 'berwin'

07 }

08 },

09 data: {

10 obj: {}

11 }

12 })

在action 方法中,我們?cè)趏bj 上面新增了name 屬性,Vue.js 無法偵測(cè)到這個(gè)變化,所以不會(huì)向依賴發(fā)送通知。

再比如,從obj 中刪除一個(gè)屬性:

01 var vm = new Vue({

02 el: '#el',

03 template: '#demo-template',

04 methods: {

05 action () {

06 delete this.obj.name

07 }

08 },

09 data: {

10 obj: {

11 name: 'berwin'

12 }

13 }

14 })

在上面的代碼中,我們?cè)赼ction 方法中刪除了obj 中的name 屬性,而Vue.js 無法偵測(cè)到這個(gè)變化,所以不會(huì)向依賴發(fā)送通知。

Vue.js 通過Object.defineProperty 來將對(duì)象的key 轉(zhuǎn)換成getter/setter 的形式來追蹤變化,但getter/setter 只能追蹤一個(gè)數(shù)據(jù)是否被修改,無法追蹤新增屬性和刪除屬性,所以才會(huì)導(dǎo)致上面例子中提到的問題。

但這也是沒有辦法的事,因?yàn)樵贓S6 之前,JavaScript 沒有提供元編程的能力,無法偵測(cè)到一個(gè)新屬性被添加到了對(duì)象中,也無法偵測(cè)到一個(gè)屬性從對(duì)象中刪除了。為了解決這個(gè)問題,Vue.js 提供了兩個(gè)API——vm.$set 與vm.$delete,本文暫不介紹。

總結(jié)

變化偵測(cè)就是偵測(cè)數(shù)據(jù)的變化。當(dāng)數(shù)據(jù)發(fā)生變化時(shí),要能偵測(cè)到并發(fā)出通知。

Object 可以通過Object.defineProperty 將屬性轉(zhuǎn)換成getter/setter 的形式來追蹤變化。讀取數(shù)據(jù)時(shí)會(huì)觸發(fā)getter,修改數(shù)據(jù)時(shí)會(huì)觸發(fā)setter。

我們需要在getter 中收集有哪些依賴使用了數(shù)據(jù)。當(dāng)setter 被觸發(fā)時(shí),去通知getter 中收集的依賴數(shù)據(jù)發(fā)生了變化。

收集依賴需要為依賴找一個(gè)存儲(chǔ)依賴的地方,為此我們創(chuàng)建了Dep,它用來收集依賴、刪除依賴和向依賴發(fā)送消息等。

所謂的依賴,其實(shí)就是Watcher。只有Watcher 觸發(fā)的getter 才會(huì)收集依賴,哪個(gè)Watcher觸發(fā)了getter,就把哪個(gè)Watcher 收集到Dep 中。當(dāng)數(shù)據(jù)發(fā)生變化時(shí),會(huì)循環(huán)依賴列表,把所有的Watcher 都通知一遍。

Watcher 的原理是先把自己設(shè)置到全局唯一的指定位置(例如window.target),然后讀取數(shù)據(jù)。因?yàn)樽x取了數(shù)據(jù),所以會(huì)觸發(fā)這個(gè)數(shù)據(jù)的getter。接著,在getter 中就會(huì)從全局唯一的那個(gè)位置讀取當(dāng)前正在讀取數(shù)據(jù)的Watcher,并把這個(gè)Watcher 收集到Dep 中去。通過這樣的方式,Watcher 可以主動(dòng)去訂閱任意一個(gè)數(shù)據(jù)的變化。

此外,我們創(chuàng)建了Observer 類,它的作用是把一個(gè)object 中的所有數(shù)據(jù)(包括子數(shù)據(jù))都轉(zhuǎn)換成響應(yīng)式的,也就是它會(huì)偵測(cè)object 中所有數(shù)據(jù)(包括子數(shù)據(jù))的變化。

由于在ES6 之前JavaScript 并沒有提供元編程的能力,所以在對(duì)象上新增屬性和刪除屬性都無法被追蹤到。

圖2-1 給出了Data、Observer、Dep 和Watcher 之間的關(guān)系。

圖2-1 Data、Observer、Dep 和Watcher 之間的關(guān)系

Data 通過Observer 轉(zhuǎn)換成了getter/setter 的形式來追蹤變化。

當(dāng)外界通過Watcher 讀取數(shù)據(jù)時(shí),會(huì)觸發(fā)getter 從而將Watcher 添加到依賴中。

當(dāng)數(shù)據(jù)發(fā)生了變化時(shí),會(huì)觸發(fā)setter,從而向Dep 中的依賴(Watcher)發(fā)送通知。

Watcher 接收到通知后,會(huì)向外界發(fā)送通知,變化通知到外界后可能會(huì)觸發(fā)視圖更新,也有可能觸發(fā)用戶的某個(gè)回調(diào)函數(shù)等。

——本文節(jié)選自《深入淺出Vue.js》

劉博文 著

360前端工程師精心打造!

360 奇舞團(tuán)團(tuán)長月影和《JavaScript高級(jí)程序設(shè)計(jì)》譯者李松峰作序推薦

同樣在360工作的《JavaScript高級(jí)程序設(shè)計(jì)(第3版)》譯者李松峰在圖靈待過幾年,很熟悉什么樣的書會(huì)更暢銷,他早就跟博文說過:要想讓技術(shù)書暢銷,一是讀者定位必須是新手,因?yàn)樾率秩藬?shù)眾多;二是要注重實(shí)用,書中的例子最好能立即照搬到項(xiàng)目上。

然而,這本書的讀者定位顯然不是新手,而且書中的源碼分析似乎也不能直接套用到項(xiàng)目上。其實(shí)這也是沒辦法的事,因?yàn)椴┪膶戇@本書的初衷就是把自己研究Vue.js 源碼的心得分享出來。

Vue.js 是一個(gè)優(yōu)秀的前端框架。一個(gè)優(yōu)秀的前端框架如果沒有一本優(yōu)秀的解讀著作,確實(shí)是一大缺憾。應(yīng)該說,本書正是一本優(yōu)秀的Vue.js 源碼解讀專著。

全書從一個(gè)新穎的“入口點(diǎn)”——“變化偵測(cè)”切入,逐步過渡到“虛擬DOM”和“模板編譯”,最后展開分析Vue.js的整體架構(gòu)。如果想讀懂這本書,讀者不僅要有一些Vue.js 的實(shí)際使用經(jīng)驗(yàn),而且還要有一些編譯原理(比如AST)相關(guān)的知識(shí)儲(chǔ)備,這樣才能更輕松地理解模板解析、優(yōu)化與代碼生成的原理。

本書最后幾章對(duì)Vue.js 的實(shí)例方法和全局API,以及生命周期、指令和過濾器的解讀,雖然借鑒了Vue.js 官方文檔,但作者更注重實(shí)現(xiàn)原理的分析,彌補(bǔ)了文檔的不足。

早一天讀到,早一天受益,僅此而已。

目錄

第1章 Vue.js簡介

第一篇 變化偵測(cè)

第2章 Object的變化偵測(cè)

第3章 Array的變化偵測(cè)

第4章 變化偵測(cè)相關(guān)的API實(shí)現(xiàn)原理

第二篇 虛擬DOM

第5章 虛擬DOM簡介

第6章 VNode

第7章 patch

第三篇 模板編譯原理

第8章 模板編譯

第9章 解析器

第10章 優(yōu)化器

第11章 代碼生成器

第四篇 整體流程

第12章 架構(gòu)設(shè)計(jì)與項(xiàng)目結(jié)構(gòu)

第13章 實(shí)例方法與全局API的實(shí)現(xiàn)原理

第14章 生命周期

第15章 指令的奧秘

第16章 過濾器的奧秘

了解更多

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容