vue原理Compile從新建實(shí)例到結(jié)束流程源碼
引言
Compile 的內(nèi)容十分之多,今天先來(lái)個(gè)熱身,先不研究 compile 內(nèi)部編譯細(xì)節(jié),而是記錄一下
從新建實(shí)例開(kāi)始,到結(jié)束 compile ,其中的大致外部流程,不涉及 compile 的內(nèi)部流程
或者說(shuō),我們要研究 compile 這個(gè)函數(shù)是怎么生成的
注意,如果你沒(méi)有準(zhǔn)備好,請(qǐng)不要閱讀這篇文章
注意哦,會(huì)很繞,別暈了
好的,正文開(kāi)始
首先,當(dāng)我們通過(guò) Vue 新建一個(gè)實(shí)例的時(shí)候會(huì)調(diào)用Vue
所以從 Vue 函數(shù)入手
function Vue(){ // ..... vm.$mount(vm.$options.el); }
然后內(nèi)部的其他處理都可以忽視,直接定位到 vm.$mount,就是從這里開(kāi)始去編譯的
繼續(xù)去查找這個(gè)函數(shù)
Vue.prototype.$mount = function(el) { var options = this.$options; if (!options.render) { var tpl= options.template; // 獲取模板字符串 if (tpl) { // 根據(jù)傳入的選擇器找到元素,然后拿到該元素內(nèi)的模板 // 本來(lái)有很多種獲取方式,但是為了簡(jiǎn)單,我們簡(jiǎn)化為一種,知道意思就可以了 tpl = document.querySelector(tpl).innerHTML; } if (tpl) { // 生成 render 函數(shù)保存 var ref = compileToFunctions(tpl, {},this); // 每一個(gè)組件,都有自己的 render options.render = ref.render options.staticRenderFns =ref.staticRenderFns; } } // 執(zhí)行上面生成的 render,生成DOM,掛載DOM,這里忽略不討論 return mount.call(this, el) };
compile 的主要作用就是,根據(jù) template 模板,生成 render 函數(shù)
那么到這里,整個(gè)流程就走完了,因?yàn)?render 已經(jīng)在這里生成了
我們觀察到
在上面這個(gè)函數(shù)中,主要就做了三件事
1 獲取 template 模板
根據(jù)你傳入的參數(shù),來(lái)各種獲取 template 模板
這里應(yīng)該都看得懂了,根據(jù)DOM,或者根據(jù)選擇器
2 生成 render
通過(guò) compileToFunctions ,傳入 template
就可以生成 render 和 staticRenderFns
看著是挺簡(jiǎn)單哦,就一個(gè) compileToFunctions,但是我告訴你,這個(gè)函數(shù)的誕生可不是這么容易的,兜兜轉(zhuǎn)轉(zhuǎn),十分曲折,相當(dāng)?shù)们蹚?fù)雜,沒(méi)錯(cuò),這就是我們下面研究的重點(diǎn)
但是這流程其實(shí)好像也沒(méi)有什么幫助?但是如果你閱讀源碼的話,或許可以對(duì)你理清源碼有些許幫助吧
再一次佩服 尤大的腦回路
3 保存 render
保存在 vm.$options 上,用于在后面調(diào)用
下面就來(lái)說(shuō) compileToFunctions 的誕生史
注意,很繞很繞,做好心理準(zhǔn)備
首先我定位到 compileToFunctions,看到下面這段代碼
var ref$1 = createCompiler(); // compileToFunctions 會(huì)返回 render 函數(shù) 以及 staticRenderFns var compileToFunctions = ref$1.compileToFunctions;
于是我知道
compileToFunctions 是 createCompiler 執(zhí)行返回的??!
那么繼續(xù)定位 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 } } );
臥槽,又來(lái)一個(gè)函數(shù),別暈啊兄弟
不過(guò),注意注意,這里是重點(diǎn),非常重
首先明確兩點(diǎn)
1、createCompiler 是 createCompilerCreator 生成的
2、給 createCompilerCreator 傳了一個(gè)函數(shù) baseCompile
baseCompile
這個(gè) baseCompile 就是 生成 render 的大佬
看到里面包含了 渲染三巨頭,【parse,optimize,generate】
但是今天不是講這個(gè)的,這三個(gè)東西,每個(gè)內(nèi)容都十分巨大
這里先跳過(guò),反正 baseCompile 很重要,會(huì)在后面被調(diào)用到,得先記著
然后,沒(méi)錯(cuò),我們又遇到了一個(gè) 函數(shù) createCompilerCreator ,定位它!
createCompilerCreator
function createCompilerCreator(baseCompile) { return function () { // 作用是合并選項(xiàng),并且調(diào)用 baseCompile function compile(template) { // baseCompile 就是 上一步傳入的,這里執(zhí)行得到 {ast,render,statickRenderFn} var compiled = baseCompile(template); return compiled } return { // compile 執(zhí)行會(huì)返回 baseCompile 返回的 字符串 render compile: compile, // 為了創(chuàng)建一層 緩存閉包,并且閉包保存 compile // 得到一個(gè)函數(shù),這個(gè)函數(shù)是 把 render 字符串包在 函數(shù) 中 compileToFunctions: createCompileToFunctionFn(compile) } } }
這個(gè)函數(shù)執(zhí)行過(guò)后,會(huì)返回一個(gè)函數(shù)
很明顯,返回的函數(shù)就 直接賦值 給了上面講的的 createCompiler
我們看下這個(gè)返回給 createCompiler 的函數(shù)里面都干了什么?
生成一個(gè)函數(shù) compile
內(nèi)部存在一個(gè)函數(shù) compile,這個(gè)函數(shù)主要作用是
調(diào)用 baseCompile,把 baseCompile 執(zhí)行結(jié)果 return 出去
baseCompile 之前我們強(qiáng)調(diào)過(guò)的,就是那個(gè)生成 render 的大佬
忘記的,可以回頭看看,執(zhí)行完畢會(huì)返回
{ render,staticRenderFns }
返回 compileToFunctions 和 compile
其實(shí) 返回的這兩個(gè)函數(shù)的作用大致都是一樣的
都是為了執(zhí)行上面那個(gè) 內(nèi)部 compile
但是為什么分出一個(gè) compileToFunctions 呢?
還記得開(kāi)篇我們的 compileToFunctions 嗎
就是那個(gè)在 vm.$mount 里我們要探索的東西啊
就是他這個(gè)吊毛,生成的 render 和 staticRenderFns
再看看那個(gè) 內(nèi)部 compile,可以看到他執(zhí)行完就是返回
{ render, staticRenderFns }
你看,內(nèi)部 compile 就是 【vm.$mount 執(zhí)行的 compileToFunctions】 啊
為什么 compileToFunctions 沒(méi)有直接賦值為 compile 呢!!
因?yàn)橐瞿0寰彺妫。?/h3>
可以看到,沒(méi)有直接讓 compileToFunctions = 內(nèi)部compile
而是把 內(nèi)部 compile 傳給了 createCompileToFunctionFn
沒(méi)錯(cuò) createCompileToFunctionFn 就是做緩存的
為了避免每個(gè)實(shí)例都被編譯很多次,所以做緩存,編譯一次之后就直接取緩存
createCompileToFunctionFn
來(lái)看看內(nèi)部的源碼,緩存的代碼已經(jīng)標(biāo)紅
function createCompileToFunctionFn(compile) { // 作為緩存,防止每次都重新編譯 // template 字符串 為 key , 值是 render 和 staticRenderFns var cache = Object.create(null); return function compileToFunctions(template, options, vm) { var key = template; // 有緩存的時(shí)候直接取出緩存中的結(jié)果即可 if (cache[key]) return cache[key] // compile 是 createCompileCreator 傳入的compile var compiled = compile(template, options); var res = { // compiled.render 是字符串,需要轉(zhuǎn)成函數(shù) render : new Function(compiled.render) staticRenderFns : compiled.staticRenderFns.map(function(code) { return new Function(code, fnGenErrors) }); }; return (cache[key] = res) } }
額外:render 字符串變成可執(zhí)行函數(shù)
var res = { render: new Function(compiled.render) , staticRenderFns: compiled.staticRenderFns.map(function(code) { return new Function(code, fnGenErrors) }); };
這段代碼把 render 字符串可執(zhí)行函數(shù),因?yàn)閞ender生成的形態(tài)是一個(gè)字符串,如果后期要調(diào)用運(yùn)行,比如轉(zhuǎn)成函數(shù)
所以這里使用了 new Function() 轉(zhuǎn)化成函數(shù)
同理,staticRenderFns 也一樣,只不過(guò)他是數(shù)組,需要遍歷,逐個(gè)轉(zhuǎn)化成函數(shù)
他的緩存是怎么做的
使用一個(gè) cache 閉包變量
template 為 key
生成的 render 作為 value
當(dāng)實(shí)例第一次渲染解析,就會(huì)被存到 cache 中
當(dāng)實(shí)例第二次渲染解析,那么就會(huì)從 cache 中直接獲取
什么時(shí)候?qū)嵗龝?huì)解析第二次?
比如 頁(yè)面A到頁(yè)面B,頁(yè)面B又轉(zhuǎn)到頁(yè)面A。
頁(yè)面A 這個(gè)實(shí)例,按理就需要解析兩次,但是有緩存之后就不會(huì)
理清思路
也就是說(shuō),compileToFunctions 其實(shí)內(nèi)核就是 baseCompile!
不過(guò) compileToFunctions 是經(jīng)過(guò)了 兩波包裝的 baseCompile
第一波包裝在 createCompilerCreator 中的 內(nèi)部 compile 函數(shù)中
內(nèi)部函數(shù)的作用是
合并公共options和 自定義options ,但是相關(guān)代碼已經(jīng)省略,
另一個(gè)就是執(zhí)行 baseCompile
第二波包裝在 createCompileToFunctions 中,目的是進(jìn)行 緩存
以上就是vue原理Compile從新建實(shí)例到結(jié)束流程源碼的詳細(xì)內(nèi)容,更多關(guān)于vue原理Compile新建實(shí)例的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue進(jìn)階之CodeMirror的應(yīng)用小結(jié)
CodeMirror支持在線編輯代碼,風(fēng)格包括js, java, php, c++等等100多種語(yǔ)言,下面這篇文章主要來(lái)和大家講講CodeMirror的應(yīng)用,感興趣的可以了解一下2023-06-06vue中實(shí)時(shí)監(jiān)聽(tīng)div元素盒子的寬高方法
這篇文章主要給大家介紹了關(guān)于vue中如何實(shí)時(shí)監(jiān)聽(tīng)div元素盒子的寬高的相關(guān)資料,在Vue中你可以使用Vue的計(jì)算屬性和偵聽(tīng)器來(lái)動(dòng)態(tài)監(jiān)測(cè)元素的高度,文中給出了簡(jiǎn)單代碼示例,需要的朋友可以參考下2023-09-09vue2.0 與 bootstrap datetimepicker的結(jié)合使用實(shí)例
本篇文章主要介紹了vue2.0 與 bootstrap datetimepicker的結(jié)合使用實(shí)例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05利用Vue的v-for和v-bind實(shí)現(xiàn)列表顏色切換
這篇文章主要介紹了利用Vue的v-for和v-bind實(shí)現(xiàn)列表顏色切換,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07vue-video-player?播放m3u8視頻流的實(shí)現(xiàn)
本文主要介紹了vue-video-player?播放m3u8視頻流的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04vue 解決addRoutes多次添加路由重復(fù)的操作
這篇文章主要介紹了vue 解決addRoutes多次添加路由重復(fù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08vant組件中 dialog的確認(rèn)按鈕的回調(diào)事件操作
這篇文章主要介紹了vant組件中 dialog的確認(rèn)按鈕的回調(diào)事件操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11Vue實(shí)現(xiàn)側(cè)邊菜單欄手風(fēng)琴效果實(shí)例代碼
本文通過(guò)一段簡(jiǎn)單的代碼給大家介紹了基于純vue實(shí)現(xiàn)側(cè)邊菜單欄手風(fēng)琴效果,代碼很簡(jiǎn)單,感興趣的朋友跟隨腳本之家小編一起看看吧2018-05-05vue使用pinia實(shí)現(xiàn)全局無(wú)縫通信
這篇文章主要為大家詳細(xì)介紹了vue如何使用pinia實(shí)現(xiàn)全局無(wú)縫通信,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下2023-11-11