Vue AST源碼解析第一篇
講完了數(shù)據(jù)劫持原理和一堆初始化,現(xiàn)在是DOM相關(guān)的代碼了。
上一節(jié)是從這個(gè)函數(shù)開(kāi)始的:
// Line-3924
Vue.prototype._init = function(options) {
// 大量初始化
// ...
// Go!
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
弄完data屬性的數(shù)據(jù)綁定后,開(kāi)始處理el屬性,也就是掛載的DOM節(jié)點(diǎn),這里的vm.$options.el也就是傳進(jìn)去的'#app'字符串。
有一個(gè)值得注意的點(diǎn)是,源碼中有2個(gè)$mount函數(shù)都是Vue$3的原型函數(shù),其中一個(gè)標(biāo)記了注釋public mount method,在7531行,另外一個(gè)在9553行。打斷點(diǎn)進(jìn)入的是后面,因?yàn)槎x的晚,覆蓋了前面的函數(shù)。
// Line-7531
// public mount method
Vue$3.prototype.$mount = function(el,hydrating) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
// Line-9552
var mount = Vue$3.prototype.$mount;
Vue$3.prototype.$mount = function(
el,
hydrating
) {
// ...很多代碼
return mount.call(this, el, hydrating)
};
現(xiàn)在進(jìn)入后面的$mount函數(shù)看看內(nèi)部結(jié)構(gòu):
// Line-9552
var mount = Vue$3.prototype.$mount;
Vue$3.prototype.$mount = function(el,hydrating) {
// 將el格式化為DOM節(jié)點(diǎn)
el = el && query(el);
// 判斷是否掛載到body或者h(yuǎn)tml標(biāo)簽上
if (el === document.body || el === document.documentElement) {
"development" !== 'production' && warn(
"Do not mount Vue to <html> or <body> - mount to normal elements instead."
);
return this
}
var options = this.$options;
// 處理template/el 轉(zhuǎn)換為渲染函數(shù)
if (!options.render) {
// ...非常多代碼
}
return mount.call(this, el, hydrating)
};
代碼前半段首先將el轉(zhuǎn)換為DOM節(jié)點(diǎn),并判斷是否掛載到body或者h(yuǎn)tml標(biāo)簽,看看簡(jiǎn)單的query函數(shù):
// Line-4583
function query(el) {
// 如果是字符串就調(diào)用querySelector
if (typeof el === 'string') {
var selected = document.querySelector(el);
if (!selected) {
"development" !== 'production' && warn(
'Cannot find element: ' + el
);
// 找不到就返回一個(gè)div
return document.createElement('div')
}
return selected
}
// 不是字符串就默認(rèn)傳進(jìn)來(lái)的是DOM節(jié)點(diǎn)
else {
return el
}
}
函數(shù)比較簡(jiǎn)單,值得注意的幾個(gè)點(diǎn)是,由于調(diào)用的是querySelector方法,所以可以傳標(biāo)簽名、類(lèi)名、C3新選擇器等,都會(huì)返回查詢到的第一個(gè)。當(dāng)然,總是傳一個(gè)ID或者確定的DOM節(jié)點(diǎn)才是正確用法。
下面看接下來(lái)的代碼:
// Line-9552
var mount = Vue$3.prototype.$mount;
Vue$3.prototype.$mount = function(el,hydrating) {
// ...el轉(zhuǎn)換為DOM節(jié)點(diǎn)
// ...
// 沒(méi)有render屬性 進(jìn)入代碼段
if (!options.render) {
var template = options.template;
// 沒(méi)有template 跳
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template);
/* istanbul ignore if */
if ("development" !== 'production' && !template) {
warn(
("Template element not found or is empty: " + (options.template)),
this
);
}
}
} else if (template.nodeType) {
template = template.innerHTML;
} else {
{
warn('invalid template option:' + template, this);
}
return this
}
}
// 有el 獲取字符串化的DOM樹(shù)
else if (el) {
template = getOuterHTML(el);
}
if (template) {
// ...小段代碼
}
}
return mount.call(this, el, hydrating)
};
由于沒(méi)有template屬性,會(huì)直接進(jìn)入第二個(gè)判斷條件,調(diào)用getOuterHTML來(lái)初始化template變量,函數(shù)比較簡(jiǎn)單, 來(lái)看看:
// Line-9623
function getOuterHTML(el) {
if (el.outerHTML) {
return el.outerHTML
}
// 兼容IE中的SVG
else {
var container = document.createElement('div');
container.appendChild(el.cloneNode(true));
return container.innerHTML
}
}
簡(jiǎn)單來(lái)講,就是調(diào)用outerHTML返回DOM樹(shù)的字符串形式,看圖就明白了:

下面看最后一段代碼:
// Line-9552
var mount = Vue$3.prototype.$mount;
Vue$3.prototype.$mount = function(el,hydrating) {
// ...el轉(zhuǎn)換為DOM節(jié)點(diǎn)
// ...
// 沒(méi)有render屬性 進(jìn)入代碼段
if (!options.render) {
// ...處理template
// ...
if (template) {
// 編譯開(kāi)始
if ("development" !== 'production' && config.performance && mark) {
mark('compile');
}
// 將DOM樹(shù)字符串編譯為函數(shù)
var ref = compileToFunctions(template, {
shouldDecodeNewlines: shouldDecodeNewlines,
delimiters: options.delimiters
}, this);
// options添加屬性
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render;
options.staticRenderFns = staticRenderFns;
// 編譯結(jié)束
if ("development" !== 'production' && config.performance && mark) {
mark('compile end');
measure(((this._name) + " compile"), 'compile', 'compile end');
}
}
}
return mount.call(this, el, hydrating)
};
忽略2段dev模式下的提示代碼,剩下的代碼做了3件事,調(diào)用compileToFunctions函數(shù)肢解DOM樹(shù)字符串,將返回的對(duì)象屬性添加到options上,再次調(diào)用mount函數(shù)。
首先看一下compileToFunctions函數(shù),該函數(shù)接受3個(gè)參數(shù),分別為字符串、配置對(duì)象、當(dāng)前vue實(shí)例。
由于函數(shù)比較長(zhǎng),而且部分是錯(cuò)誤判斷,簡(jiǎn)化后如下:
// Line-9326
function compileToFunctions(template,options,vm) {
// 獲取配置參數(shù)
options = options || {};
// ...
var key = options.delimiters ?
String(options.delimiters) + template :
template;
// 檢測(cè)緩存
if (functionCompileCache[key]) {
return functionCompileCache[key]
}
// 1
var compiled = compile(template, options);
// ...
// 2
var res = {};
var fnGenErrors = [];
res.render = makeFunction(compiled.render, fnGenErrors);
var l = compiled.staticRenderFns.length;
res.staticRenderFns = new Array(l);
for (var i = 0; i < l; i++) {
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors);
}
// ...
// 3
return (functionCompileCache[key] = res)
}
可以看到,這個(gè)函數(shù)流程可以分為4步,獲取參數(shù) => 調(diào)用compile函數(shù)進(jìn)行編譯 => 將得到的compiled轉(zhuǎn)換為函數(shù) => 返回并緩存。
第一節(jié)現(xiàn)在這樣吧。一張圖總結(jié)下:

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vite搭建vue2項(xiàng)目的實(shí)戰(zhàn)過(guò)程
自從體驗(yàn)了一下vite之后,真的太快了,然而對(duì)vue3還不是很熟練,就想著在vue2的項(xiàng)目中使用以下vite,下面這篇文章主要給大家介紹了關(guān)于vite搭建vue2項(xiàng)目的相關(guān)資料,需要的朋友可以參考下2022-11-11
vue實(shí)現(xiàn)PC端錄音功能的實(shí)例代碼
這篇文章主要介紹了vue實(shí)現(xiàn)PC端錄音功能的實(shí)例代碼,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-06-06
vue-cli如何快速構(gòu)建vue項(xiàng)目
本篇文章主要介紹了vue-cli如何快速構(gòu)建vue項(xiàng)目,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04
electron-vite新一代electron開(kāi)發(fā)構(gòu)建工具
這篇文章主要介紹了electron-vite新一代electron開(kāi)發(fā)構(gòu)建工具,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
Vue Router 實(shí)現(xiàn)動(dòng)態(tài)路由和常見(jiàn)問(wèn)題及解決方法
動(dòng)態(tài)路由不同于常見(jiàn)的靜態(tài)路由,可以根據(jù)不同的「因素」而改變站點(diǎn)路由列表。這篇文章主要介紹了Vue Router 實(shí)現(xiàn)動(dòng)態(tài)路由和常見(jiàn)問(wèn)題解決方案,需要的朋友可以參考下2020-03-03
Vue實(shí)現(xiàn)批量注冊(cè)全局組件的示例代碼
在項(xiàng)目開(kāi)發(fā)中,我們經(jīng)常會(huì)封裝一些全局組件,然后在入口文件中統(tǒng)一導(dǎo)入,所以本文主要為大家詳細(xì)介紹了Vue如何批量注冊(cè)全局組件,感興趣的小伙伴可以了解下2024-01-01
Vue 利用指令實(shí)現(xiàn)禁止反復(fù)發(fā)送請(qǐng)求的兩種方法
這篇文章主要介紹了Vue 利用指令實(shí)現(xiàn)禁止反復(fù)發(fā)送請(qǐng)求的兩種方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09
vue實(shí)現(xiàn)行列轉(zhuǎn)換的一種方法
這篇文章主要介紹了vue實(shí)現(xiàn)行列轉(zhuǎn)換的一種方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08
vue3實(shí)現(xiàn)旋轉(zhuǎn)圖片驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了vue3實(shí)現(xiàn)旋轉(zhuǎn)圖片驗(yàn)證,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04

