前言
徹底擺脫秋招之后组底,我也來到了公司實(shí)習(xí)缀皱,我們主要使用的框架是Vue.js,如果自然而然地碌补,我需要學(xué)習(xí)這個(gè)框架虏束。平心而論,vue的api數(shù)量可以說是angular的1/10不到厦章,雖然簡(jiǎn)潔镇匀,但內(nèi)部實(shí)現(xiàn)并不簡(jiǎn)單。
隨便下個(gè)斷點(diǎn)沖進(jìn)vue源碼之處袜啃,都能感受到里面結(jié)構(gòu)的復(fù)雜汗侵,對(duì)于我等菜鳥來說實(shí)在是無法多待一會(huì)兒的。
所以群发,本著面對(duì)復(fù)雜問題先將其簡(jiǎn)單化的思維方式晰韵,我和另一位朋友決定先從一個(gè)將Vue核心api實(shí)現(xiàn)的高仿庫(kù)摸透,之后再來啃Vue源碼熟妓。
為什么選擇Moon
市面上有很多MVVM庫(kù)雪猪,Vue也有早期版本在github上供人研究,為什么我們選擇了Moon呢起愈?
原因有三:
- Moon源碼只有2000多行抬虽,卻實(shí)現(xiàn)了實(shí)例屬性(data、computed阐污、methods休涤、template)、render笛辟、指令功氨、組件以及組件通信疑故、生命鉤子、slot等大部分Vue核心功能钦铁,在各大MVVM庫(kù)中算是研究性價(jià)比最高的(目前為止我能找到的)。
- Moon的作者異常活躍阅虫,我們一旦有啥問題就可以和他郵件交流窝革。
- Moon相比Vue的早期版本也有它獨(dú)到的思想,作者本身實(shí)力也很厲害侮攀。比如他針對(duì)靜態(tài)HTML元素渲染做的優(yōu)化
研究源碼一般而言有兩種方法:自頂向下和自底向上蚪腐。
自頂向下就是先從最抽象的層面上觀察源碼正林,從它的結(jié)構(gòu)涵但、設(shè)計(jì)著手,再一步步深入到各個(gè)代碼模塊->子程序->具體語(yǔ)句劫侧。
自底向上就是特意選擇一個(gè)地方打一個(gè)斷點(diǎn)拳球,然后運(yùn)行代碼,通過一步步調(diào)試觀察它所經(jīng)過的調(diào)用棧和所有的中間變量。
我選擇綜合兩種方法來研究源碼:先自頂向下把它的代碼結(jié)構(gòu)摸清楚,再自底向上打斷點(diǎn)一步步照亮黑暗區(qū)域。和我們平時(shí)打游戲需要一個(gè)大地圖提供全局觀,然后自己探索其中的黑暗處道理相似。
本系列文章于2017.12.1開始寫针贬,作者打包的最新版本是v0.11.0桦他。
Moon源碼整體結(jié)構(gòu)
為了方便起見,我繪制了一張圖(建議大家下載下來看):
這里我簡(jiǎn)單把各個(gè)文件夾(標(biāo)了顏色的方塊)看成是類谆棱,然后實(shí)現(xiàn)了看成接口的各個(gè)代碼文件的功能础锐。
每個(gè)代碼文件里有若干個(gè)子程序截粗,沒有寫成函數(shù)的我就假裝它們是一個(gè)函數(shù)的內(nèi)容菊值,并且在這個(gè)我造的函數(shù)名前面打了星號(hào),虛線所指向的方塊是它們實(shí)際做的事情磅崭。
打開源碼的package.json儿子,可以看出作者是使用gulp打包構(gòu)建他的代碼的。而整塊代碼被他分割成了六個(gè)文件夾:
- compiler---編譯模板到dom樹的各個(gè)函數(shù)割岛。
- util---通用工具愉适、dom工具、vdom工具函數(shù)癣漆。
- observer---觀察者實(shí)例和相關(guān)的函數(shù)维咸。
- directives---處理指令的函數(shù)。
- instance---Moon實(shí)例上存在的函數(shù)。
- global---Moon對(duì)象上的靜態(tài)屬性和方法腰湾。
最后通過在index.js里逐個(gè)引入雷恃,交給wrapper.js(還有g(shù)ulp)打包成最終版本。
//index.js
"use strict";
/* ======= Global Variables ======= */
let directives = {};
let specialDirectives = {};
let components = {};
let eventModifiersCode = {
stop: 'event.stopPropagation();',
prevent: 'event.preventDefault();',
ctrl: 'if(event.ctrlKey === false) {return null;};',
shift: 'if(event.shiftKey === false) {return null;};',
alt: 'if(event.altKey === false) {return null;};',
enter: 'if(event.keyCode !== 13) {return null;};'
};
let eventModifiers = {};
/* ======= Observer ======= */
//=require observer/methods.js
//=require observer/computed.js
//=require observer/observer.js
//=require util/util.js
//=require util/dom.js
//=require util/vdom.js
/* ======= Compiler ======= */
//=require compiler/template.js
//=require compiler/lexer.js
//=require compiler/parser.js
//=require compiler/generator.js
//=require compiler/compiler.js
function Moon(options) {
//省略,這里不是這篇文章的重點(diǎn)
}
//=require instance/methods.js
//=require global/api.js
//=require directives/default.js
//wrapper.js
(function(root, factory) {
/* ======= Global Moon ======= */
(typeof module === "object" && module.exports) ? module.exports = factory() : root.Moon = factory();
}(this, function() {
//=require ../dist/moon.js
return Moon;
}));
一個(gè)Moon實(shí)例的一生
有了全局觀费坊,我們就從探究一個(gè)new一個(gè)的Moon過程開始倒槐,看看它究竟經(jīng)歷了些什么?:
<div id="app">
{{yf}}
</div>
<script src="./moon.js"></script>
<script>
debugger
const app = new Moon({
el: "#app",
data: {
yf: "云峰"
}
})
</script>
-
很好附井,我們進(jìn)來了:
-
嗯讨越,不出所料,首先進(jìn)的是Moon構(gòu)造函數(shù)永毅,畢竟new了一下把跨。
-
我們沒有定義methods,所以跳過了initMethods沼死,不過沒關(guān)系着逐,以后會(huì)有機(jī)會(huì)進(jìn)去的~
-
嘿!我們new了一個(gè)Observer對(duì)象意蛀,它肯定是觀察當(dāng)前這個(gè)實(shí)例變化的耸别!
-
initComputed同樣被我們跳過去了,不過沒關(guān)系县钥,我們要進(jìn)init了P憬恪!
-
init里貌似做了一些事情若贮,不過緊接著就進(jìn)入了mount省有。
-
mount貌似也做了一些事情,我感覺快要迷路了谴麦,不過這時(shí)候一個(gè)compile讓我好奇心大增蠢沿,它竟然把模板字符串轉(zhuǎn)成了一個(gè)匿名函數(shù)!做完這些它就進(jìn)了build细移。
-
build一上來就把上一步生成的函數(shù)執(zhí)行了搏予!并且按作者的注釋來看的話還得到了virtual node,我們發(fā)現(xiàn)光得到還不行弧轧,后續(xù)還要和node進(jìn)行patch一下雪侥。
-
在patch里我們因?yàn)関irtual node不是dom node類型跳轉(zhuǎn)到了一個(gè)hydrate的子程序里
- hydrate的子程序在作者注釋中被解釋為Hydrates Node and a VNode,也就是把人為生成的vnode混入到實(shí)際存在于dom樹中的node里去精绎。
在混入完成后我們便一步步出棧速缨,回到了起點(diǎn)。
也就是說代乃,我們的Moon實(shí)例經(jīng)歷了從init初始化->mount掛載->build建立的過程旬牲,期間實(shí)例化了一個(gè)Observer觀察者仿粹,對(duì)模板字符串進(jìn)行了編譯,并執(zhí)行得到vnode原茅,最后作用于dom樹中的node來改變最終渲染結(jié)果吭历。(template->vnode->node)
那這些過程中具體細(xì)節(jié)是什么樣的呢?我準(zhǔn)備在之后的文章中從下面幾個(gè)角度分析:
- render函數(shù)從一段code->html的過程
- compiler從html->code的過程
- 數(shù)據(jù)變更檢測(cè)
- 指令
- 組件
...