Vue編譯器源碼分析compileToFunctions作用詳解
引言
接上篇Vue編譯器源碼分析文章我們來(lái)分析:compileToFunctions的作用。
經(jīng)過(guò)前面的講解,我們已經(jīng)知道了 compileToFunctions 的真正來(lái)源你可能會(huì)問(wèn)為什么要弄的這么復(fù)雜?為了搞清楚這個(gè)問(wèn)題,我們還需要繼續(xù)接觸完整的代碼。
下面我們繼續(xù)探索compileToFunctions是如何把模板字符串template編譯成渲染函數(shù)render的。
Vue.prototype.$mount函數(shù)體
回歸到Vue.prototype.$mount函數(shù)體內(nèi)。
var ref = compileToFunctions(template, {
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
在此傳遞給compileToFunctions的第一個(gè)參數(shù)就是模板字符串template,而第二個(gè)參數(shù)則是一個(gè)配置選項(xiàng)options。
先說(shuō)說(shuō)這些配置選項(xiàng)中的屬性!
shouldDecodeNewlines
源碼出處
// check whether current browser encodes a char inside attribute values
var div;
function getShouldDecode(href) {
div = div || document.createElement('div');
div.innerHTML = href ? "<a href=\"\n\"/>" : "<div a=\"\n\"/>";
return div.innerHTML.indexOf(' ') > 0
}
// #3663: IE encodes newlines inside attribute values while other browsers don't
var shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false;
// #6828: chrome encodes content in a[href]
var shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false;
這個(gè)是什么意思呢?
大致的翻譯下,在我們innerHTML獲取內(nèi)容時(shí),換行符和制表符分別被轉(zhuǎn)換成了
和	。在IE中,不僅僅是 a 標(biāo)簽的 href 屬性值,任何屬性值都存在這個(gè)問(wèn)題。
這就會(huì)影響Vue的編譯器在對(duì)模板進(jìn)行編譯后的結(jié)果,為了避免這些問(wèn)題Vue需要知道什么時(shí)候要做兼容工作,如果 shouldDecodeNewlines 為 true,意味著 Vue 在編譯模板的時(shí)候,要對(duì)屬性值中的換行符或制表符做兼容處理。而shouldDecodeNewlinesForHref為true 意味著Vue在編譯模板的時(shí)候,要對(duì)a標(biāo)簽的 href 屬性值中的換行符或制表符做兼容處理。
options.delimiters & options.comments
兩者都是當(dāng)前Vue實(shí)例的$options屬性,并且delimiters和comments都是 Vue 提供的選項(xiàng)。


現(xiàn)在我們已經(jīng)搞清楚了這些配置選項(xiàng)是什么意思,那接下來(lái)我們把目光放在《Vue編譯器源碼分析(二)》針對(duì)compileToFunctions函數(shù)逐行分析。
compileToFunctions函數(shù)逐行分析
function createCompileToFunctionFn(compile) {
var cache = Object.create(null);
return function compileToFunctions(
template,
options,
vm
) {
options = extend({}, options);
var warn$$1 = options.warn || warn;
delete options.warn;
/* istanbul ignore if */
{
// detect possible CSP restriction
try {
new Function('return 1');
} catch (e) {
if (e.toString().match(/unsafe-eval|CSP/)) {
warn$$1(
'It seems you are using the standalone build of Vue.js in an ' +
'environment with Content Security Policy that prohibits unsafe-eval. ' +
'The template compiler cannot work in this environment. Consider ' +
'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
'templates into render functions.'
);
}
}
}
// check cache
var key = options.delimiters ?
String(options.delimiters) + template :
template;
if (cache[key]) {
return cache[key]
}
// compile
var compiled = compile(template, options);
// check compilation errors/tips
{
if (compiled.errors && compiled.errors.length) {
warn$$1(
"Error compiling template:\n\n" + template + "\n\n" +
compiled.errors.map(function(e) {
return ("- " + e);
}).join('\n') + '\n',
vm
);
}
if (compiled.tips && compiled.tips.length) {
compiled.tips.forEach(function(msg) {
return tip(msg, vm);
});
}
}
// turn code into functions
var res = {};
var fnGenErrors = [];
res.render = createFunction(compiled.render, fnGenErrors);
res.staticRenderFns = compiled.staticRenderFns.map(function(code) {
return createFunction(code, fnGenErrors)
});
// check function generation errors.
// this should only happen if there is a bug in the compiler itself.
// mostly for codegen development use
/* istanbul ignore if */
{
if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
warn$$1(
"Failed to generate render function:\n\n" +
fnGenErrors.map(function(ref) {
var err = ref.err;
var code = ref.code;
return ((err.toString()) + " in\n\n" + code + "\n");
}).join('\n'),
vm
);
}
}
return (cache[key] = res)
}
}
注意compileToFunctions函數(shù)是接收三個(gè)參數(shù)的,第三個(gè)參數(shù)是當(dāng)前Vue實(shí)例。
首先:
options = extend({}, options);
var warn$$1 = options.warn || warn;
delete options.warn;
通過(guò)extend 把 options 配置對(duì)象上的屬性擴(kuò)展一份到新對(duì)象上,定義warn$$1變量。warn是一個(gè)錯(cuò)誤信息提示的函數(shù)。
接下來(lái):
// detect possible CSP restriction
try {
new Function('return 1');
} catch (e) {
if (e.toString().match(/unsafe-eval|CSP/)) {
warn$$1(
'It seems you are using the standalone build of Vue.js in an ' +
'environment with Content Security Policy that prohibits unsafe-eval. ' +
'The template compiler cannot work in this environment. Consider ' +
'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
'templates into render functions.'
);
}
}
這段代碼使用 try catch 語(yǔ)句塊對(duì) new Function('return 1') 這句代碼進(jìn)行錯(cuò)誤捕獲,如果有錯(cuò)誤發(fā)生且錯(cuò)誤的內(nèi)容中包含如 'unsafe-eval' 或者 'CSP' 這些字樣的信息時(shí)就會(huì)給出一個(gè)警告。
CSP全稱Content Security Policy ,可以直接翻譯為內(nèi)容安全策略,說(shuō)白了,就是為了頁(yè)面內(nèi)容安全而制定的一系列防護(hù)策略. 通過(guò)CSP所約束的的規(guī)責(zé)指定可信的內(nèi)容來(lái)源(這里的內(nèi)容可以指腳本、圖片、iframe、fton、style等等可能的遠(yuǎn)程的資源)。通過(guò)CSP協(xié)定,讓W(xué)EB處于一個(gè)安全的運(yùn)行環(huán)境中。
如果你的策略比較嚴(yán)格,那么 new Function() 將會(huì)受到影響,從而不能夠使用。但是將模板字符串編譯成渲染函數(shù)又依賴 new Function(),所以解決方案有兩個(gè):
- 1、放寬你的CSP策略
- 2、預(yù)編譯
這段代碼的作用就是檢測(cè) new Function() 是否可用,并在某些極端情況下給你一個(gè)有用的提示。
接下來(lái)是:
var key = options.delimiters ?
String(options.delimiters) + template :
template;
if (cache[key]) {
return cache[key]
}
options.delimiters這個(gè)選項(xiàng)是改變純文本插入分隔符,如果options.delimiters存在,則使用String 方法將其轉(zhuǎn)換成字符串并與 template 拼接作為 key 的值,否則直接使用 template 字符串作為 key 的值,然后判斷 cache[key] 是否存在,如果存在直接返回cache[key]。
這么做的目的是緩存字符串模板的編譯結(jié)果,防止重復(fù)編譯,提升性能,我們?cè)倏匆幌耤ompileToFunctions函數(shù)的最后一句代碼:
return (cache[key] = res)
這句代碼在返回編譯結(jié)果的同時(shí),將結(jié)果緩存,這樣下一次發(fā)現(xiàn)如果 cache 中存在相同的 key則不需要再次編譯,直接使用緩存的結(jié)果就可以了。
接下來(lái):
// compile
var compiled = compile(template, options);
// check compilation errors/tips
if (compiled.errors && compiled.errors.length) {
warn$$1(
"Error compiling template:\n\n" + template + "\n\n" +
compiled.errors.map(function(e) {
return ("- " + e);
}).join('\n') + '\n',
vm
);
}
if (compiled.tips && compiled.tips.length) {
compiled.tips.forEach(function(msg) {
return tip(msg, vm);
});
}
}
compile 是引用了來(lái)自 createCompileToFunctionFn 函數(shù)的形參稍后會(huì)重點(diǎn)來(lái)介紹它。
在使用 compile 函數(shù)對(duì)模板進(jìn)行編譯后會(huì)返回一個(gè)結(jié)果 compiled,返回結(jié)果 compiled 是一個(gè)對(duì)象且這個(gè)對(duì)象可能包含兩個(gè)屬性 errors 和 tips 。這兩個(gè)屬性分別包含了編譯過(guò)程中的錯(cuò)誤和提示信息。所以上面那段代碼的作用就是用來(lái)檢查使用 compile 對(duì)模板進(jìn)行編譯的過(guò)程中是否存在錯(cuò)誤和提示的,如果存在那么需要將其打印出來(lái)。
接下來(lái):
// turn code into functions
var res = {};
var fnGenErrors = [];
res.render = createFunction(compiled.render, fnGenErrors);
res.staticRenderFns = compiled.staticRenderFns.map(function(code) {
return createFunction(code, fnGenErrors)
});
res 是一個(gè)空對(duì)象且它是最終的返回值,fnGenErrors 是一個(gè)空數(shù)組。
在 res 對(duì)象上添加一個(gè) render 屬性,這個(gè) render 屬性,就是最終生成的渲染函數(shù),它的值是通過(guò) createFunction 創(chuàng)建出來(lái)的。
createFunction 函數(shù)源碼
function createFunction(code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({
err: err,
code: code
});
return noop
}
}
createFunction 函數(shù)接收兩個(gè)參數(shù),第一個(gè)參數(shù) code 為函數(shù)體字符串,該字符串將通過(guò)new Function(code) 的方式創(chuàng)建為函數(shù)。
第二個(gè)參數(shù) errors 是一個(gè)數(shù)組,作用是當(dāng)采用 new Function(code) 創(chuàng)建函數(shù)發(fā)生錯(cuò)誤時(shí)用來(lái)收集錯(cuò)誤的。
已知,傳遞給 createFunction 函數(shù)的第一個(gè)參數(shù)是 compiled.render,所以 compiled.render 應(yīng)該是一個(gè)函數(shù)體字符串,且我們知道 compiled 是 compile 函數(shù)的返回值,這說(shuō)明:compile函數(shù)編譯模板字符串后所得到的是字符串形式的函數(shù)體。
傳遞給 createFunction 函數(shù)的第二個(gè)參數(shù)是之前聲明的 fnGenErrors 常量,也就是說(shuō)當(dāng)創(chuàng)建函數(shù)出錯(cuò)時(shí)的錯(cuò)誤信息被 push 到這個(gè)數(shù)組里了。
在這句代碼之后,又在 res 對(duì)象上添加了 staticRenderFns 屬性:
res.staticRenderFns = compiled.staticRenderFns.map(function(code) {
return createFunction(code, fnGenErrors)
});
由這段代碼可知 res.staticRenderFns 是一個(gè)函數(shù)數(shù)組,是通過(guò)對(duì)compiled.staticRenderFns遍歷生成的,這說(shuō)明:compiled 除了包含 render 字符串外,還包含一個(gè)字符串?dāng)?shù)組staticRenderFns ,且這個(gè)字符串?dāng)?shù)組最終也通過(guò) createFunction 轉(zhuǎn)為函數(shù)。staticRenderFns 的主要作用是渲染優(yōu)化,我們后面詳細(xì)講解。
最后的代碼:
// check function generation errors.
// this should only happen if there is a bug in the compiler itself.
// mostly for codegen development use
/* istanbul ignore if */
if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
warn$$1(
"Failed to generate render function:\n\n" +
fnGenErrors.map(function(ref) {
var err = ref.err;
var code = ref.code;
return ((err.toString()) + " in\n\n" + code + "\n");
}).join('\n'),
vm
);
}
return (cache[key] = res)
這段代碼主要的作用是用來(lái)打印在生成渲染函數(shù)過(guò)程中的錯(cuò)誤,返回結(jié)果的同時(shí)將結(jié)果緩存,接下來(lái)我們講講compile 的作用。
以上就是Vue編譯器源碼分析compileToFunctions作用詳解的詳細(xì)內(nèi)容,更多關(guān)于Vue編譯器compileToFunctions的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Vue ElementUI手動(dòng)上傳excel文件到服務(wù)器
這篇文章主要介紹了詳解Vue ElementUI手動(dòng)上傳excel文件到服務(wù)器,對(duì)ElementUI感興趣的同學(xué),可以參考下2021-05-05
vue 中監(jiān)聽(tīng)生命周期事件的操作方式
vue2 提供了一些生命周期事件的方式,在組件銷毀后觸發(fā)一個(gè)事件,父組件可監(jiān)聽(tīng)到該事件,然后執(zhí)行某些操作,這篇文章主要介紹了vue 中監(jiān)聽(tīng)生命周期事件的操作方式,需要的朋友可以參考下2024-06-06
vue項(xiàng)目前端加前綴(包括頁(yè)面及靜態(tài)資源)的操作方法
這篇文章主要介紹了vue項(xiàng)目前端加前綴(包括頁(yè)面及靜態(tài)資源)的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-12-12
vue使用echarts實(shí)現(xiàn)中國(guó)地圖和點(diǎn)擊省份進(jìn)行查看功能
這篇文章主要介紹了vue使用echarts實(shí)現(xiàn)中國(guó)地圖和點(diǎn)擊省份進(jìn)行查看功能,本文通過(guò)實(shí)例代碼給大家詳細(xì)講解,對(duì)vue echarts 中國(guó)地圖相關(guān)知識(shí)感興趣的朋友一起看看吧2022-12-12
vuejs 制作背景淡入淡出切換動(dòng)畫的實(shí)例
今天小編就為大家分享一篇vuejs 制作背景淡入淡出切換動(dòng)畫的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
vue+elementUI的select下拉框回顯為數(shù)字問(wèn)題
這篇文章主要介紹了vue+elementUI的select下拉框回顯為數(shù)字問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06

