寫文章不容易,點個讚唄兄弟
專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧 研究基於 Vue版本 【2.5.17】數組
若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧緩存
【Vue原理】Compile - 源碼版 之 重新建實例到 compile結束的主要流程 bash
Compile 的內容十分之多,今天先來個熱身,先不研究 compile 內部編譯細節,而是記錄一下閉包
重新建實例開始,到結束 compile ,其中的大體外部流程,不涉及 compile 的內部流程函數
或者說,咱們要研究 compile 這個函數是怎麼生成的學習
注意,若是你沒有準備好,請不要閱讀這篇文章ui
注意哦,會很繞,別暈了this
好的,正文開始spa
首先,當咱們經過 Vue 新建一個實例的時候會調用Vueprototype
因此從 Vue 函數入手
function Vue(){
// .....
vm.$mount(vm.$options.el);
}
複製代碼
而後內部的其餘處理均可以忽視,直接定位到 vm.$mount,就是從這裏開始去編譯的
繼續去查找這個函數
Vue.prototype.$mount = function(el) {
var options = this.$options;
if (!options.render) {
var tpl= options.template;
// 獲取模板字符串
if (tpl) {
// 根據傳入的選擇器找到元素,而後拿到該元素內的模板
// 原本有不少種獲取方式,可是爲了簡單,咱們簡化爲一種,知道意思就能夠了
tpl = document.querySelector(tpl).innerHTML;
}
if (tpl) {
// 生成 render 函數保存
var ref = compileToFunctions(tpl, {},this);
// 每個組件,都有本身的 render
options.render = ref.render
options.staticRenderFns =ref.staticRenderFns;
}
}
// 執行上面生成的 render,生成DOM,掛載DOM,這裏忽略不討論
return mount.call(this, el)
};
複製代碼
compile 的主要做用就是,根據 template 模板,生成 render 函數
那麼到這裏,整個流程就走完了,由於 render 已經在這裏生成了
咱們觀察到
在上面這個函數中,主要就作了三件事
根據你傳入的參數,來各類獲取 template 模板
這裏應該都看得懂了,根據DOM,或者根據選擇器
經過 compileToFunctions ,傳入 template
就能夠生成 render 和 staticRenderFns
看着是挺簡單哦,就一個 compileToFunctions,可是我告訴你,這個函數的誕生可不是這麼容易的,兜兜轉轉,十分曲折,至關得曲折複雜,沒錯,這就是咱們下面研究的重點
可是這流程其實好像也沒有什麼幫助?可是若是你閱讀源碼的話,或許能夠對你理清源碼有些許幫助吧
再一次佩服 尤大的腦回路
保存在 vm.$options 上,用於在後面調用
下面就來講 compileToFunctions 的誕生史
注意,很繞很繞,作好心理準備
首先我定位到 compileToFunctions,看到下面這段代碼
var ref$1 = createCompiler();
// compileToFunctions 會返回 render 函數 以及 staticRenderFns
var compileToFunctions = ref$1.compileToFunctions;
複製代碼
因而我知道
compileToFunctions 是 createCompiler 執行返回的!!
那麼繼續定位 createCompiler
var createCompiler = createCompilerCreator(
function baseCompile(template, options) {
var ast = parse(template.trim(), options);
if (options.optimize !== false) {
optimize(ast, options);
}
var code = generate(ast, options);
return {
ast: ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
}
);
複製代碼
臥槽,又來一個函數,別暈啊兄弟
首先明確兩點
一、createCompiler 是 createCompilerCreator 生成的
二、給 createCompilerCreator 傳了一個函數 baseCompile
這個 baseCompile 就是 生成 render 的大佬
看到裏面包含了 渲染三巨頭,【parse,optimize,generate】
可是今天不是講這個的,這三個東西,每一個內容都十分巨大
這裏先跳過,反正 baseCompile 很重要,會在後面被調用到,得先記着
而後,沒錯,咱們又遇到了一個 函數 createCompilerCreator ,定位它!
function createCompilerCreator(baseCompile) {
return function () {
// 做用是合併選項,而且調用 baseCompile
function compile(template) {
// baseCompile 就是 上一步傳入的,這裏執行獲得 {ast,render,statickRenderFn}
var compiled = baseCompile(template);
return compiled
}
return {
// compile 執行會返回 baseCompile 返回的 字符串 render
compile: compile,
// 爲了建立一層 緩存閉包,而且閉包保存 compile
// 獲得一個函數,這個函數是 把 render 字符串包在 函數 中
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
複製代碼
這個函數執行事後,會返回一個函數
很明顯,返回的函數就 直接賦值 給了上面講的的 createCompiler
咱們看下這個返回給 createCompiler 的函數裏面都幹了什麼?
內部存在一個函數 compile,這個函數主要做用是
調用 baseCompile,把 baseCompile 執行結果 return 出去
baseCompile 以前咱們強調過的,就是那個生成 render 的大佬
忘記的,能夠回頭看看,執行完畢會返回
{ render,staticRenderFns }
其實 返回的這兩個函數的做用大體都是同樣的
都是爲了執行上面那個 內部 compile
還記得開篇咱們的 compileToFunctions 嗎
就是那個在 vm.$mount 裏咱們要探索的東西啊
就是他這個吊毛,生成的 render 和 staticRenderFns
再看看那個 內部 compile,能夠看到他執行完就是返回
{ render, staticRenderFns }
你看,內部 compile 就是 【vm.$mount 執行的 compileToFunctions】 啊
爲何 compileToFunctions 沒有直接賦值爲 compile 呢!!
能夠看到,沒有直接讓 compileToFunctions = 內部compile
而是把 內部 compile 傳給了 createCompileToFunctionFn
沒錯 createCompileToFunctionFn 就是作緩存的
爲了不每一個實例都被編譯不少次,因此作緩存,編譯一次以後就直接取緩存
來看看內部的源碼,緩存的代碼已經標紅
function createCompileToFunctionFn(compile) {
// 做爲緩存,防止每次都從新編譯
// template 字符串 爲 key , 值是 render 和 staticRenderFns
var cache = Object.create(null);
return function compileToFunctions(template, options, vm) {
var key = template;
// 有緩存的時候直接取出緩存中的結果便可
if (cache[key]) return cache[key]
// compile 是 createCompileCreator 傳入的compile
var compiled = compile(template, options);
var res = {
// compiled.render 是字符串,須要轉成函數
render : new Function(compiled.render)
staticRenderFns : compiled.staticRenderFns.map(function(code) {
return new Function(code, fnGenErrors)
});
};
return (cache[key] = res)
}
}
複製代碼
額外:render 字符串變成可執行函數
var res = {
render: new Function(compiled.render) ,
staticRenderFns: compiled.staticRenderFns.map(function(code) {
return new Function(code, fnGenErrors)
});
};
複製代碼
這段代碼把 render 字符串可執行函數,由於render生成的形態是一個字符串,若是後期要調用運行,好比轉成函數
因此這裏使用了 new Function() 轉化成函數
同理,staticRenderFns 也同樣,只不過他是數組,須要遍歷,逐個轉化成函數
使用一個 cache 閉包變量
template 爲 key
生成的 render 做爲 value
當實例第一次渲染解析,就會被存到 cache 中
當實例第二次渲染解析,那麼就會從 cache 中直接獲取
何時實例會解析第二次?
好比 頁面A到頁面B,頁面B又轉到頁面A。
頁面A 這個實例,按理就須要解析兩次,可是有緩存以後就不會
也就是說,compileToFunctions 其實內核就是 baseCompile!
不過 compileToFunctions 是通過了 兩波包裝的 baseCompile
第一波包裝在 createCompilerCreator 中的 內部 compile 函數中
合併公共options和 自定義options ,可是相關代碼已經省略,
另外一個就是執行 baseCompile
第二波包裝在 createCompileToFunctions 中,目的是進行 緩存