最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入解析Vue源碼實例掛載與編譯流程實現(xiàn)思路詳解

 更新時間:2019年05月05日 09:13:42   作者:不做祖國的韭菜  
這篇文章主要介紹了Vue源碼實例掛載與編譯流程實現(xiàn)思路詳解,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下

在正文開始之前,先了解vue基于源碼構(gòu)建的兩個版本,一個是 runtime only ,另一個是 runtime加compiler 的版本,兩個版本的主要區(qū)別在于后者的源碼包括了一個編譯器。

什么是編譯器,百度百科上面的解釋是

簡單講,編譯器就是將“一種語言(通常為高級語言)”翻譯為“另一種語言(通常為低級語言)”的程序。一個現(xiàn)代編譯器的主要工作流程:源代碼 (source code) → 預(yù)處理器 (preprocessor) → 編譯器 (compiler) → 目標代碼 (object code) → 鏈接器 (Linker) → 可執(zhí)行程序 (executables)。

通俗點講,編譯器是一個提供了將源代碼轉(zhuǎn)化為目標代碼的工具。更進一步理解,vue內(nèi)置的編譯器實現(xiàn)了將 .vue 文件轉(zhuǎn)換編譯為可執(zhí)行javascript腳本的功能。

3.1.1 Runtime + Compiler

一個完整的vue版本是包含編譯器的,我們可以使用 template 進行模板編寫。編譯器會自動將模板編譯成 render 函數(shù)。

// 需要編譯器的版本
new Vue({
 template: '<div>{{ hi }}</div>'
})

3.1.2 Runtime Only

而對于一個不包含編譯器的 runtime-only 版本,需要傳遞一個編譯好的 render 函數(shù),如下所示:

// 不需要編譯器
new Vue({
 render (h) {
 return h('div', this.hi)
 }
})

很明顯,編譯過程對性能有一定的損耗,并且由于加入了編譯過程的代碼,vue代碼體積也更加龐大,所以我們可以借助webpack的vue-loader工具進行編譯,將編譯階段從vue的構(gòu)建中剝離出來,這樣既優(yōu)化了性能,也縮小了體積。

3.2 掛載的基本思路

vue掛載的流程是比較復(fù)雜的,我們通過流程圖理清基本的實現(xiàn)思路。

如果用一句話概括掛載的過程,可以描述為掛載組件,將渲染函數(shù)生成虛擬DOM,更新視圖時,將虛擬DOM渲染成為真正的DOM。

詳細的過程是:首先確定掛載的DOM元素,且必須保證該元素不能為 html,body 這類跟節(jié)點。判斷選項中是否有 render 這個屬性(如果不在運行時編譯,則在選項初始化時需要傳遞 render 渲染函數(shù))。當有 render 這個屬性時,默認我們使用的是 runtime-only 的版本,從而跳過模板編譯階段,調(diào)用真正的掛載函數(shù) $mount 。另一方面,當我們傳遞是 template 模板時(即在不使用外置編譯器的情況下,我們將使用 runtime+compile 的版本),Vue源碼將首先進入編譯階段。該階段的核心是兩步,一個是把模板解析成抽象的語法樹,也就是我們常聽到的 AST ,第二個是根據(jù)給定的AST生成目標平臺所需的代碼,在瀏覽器端是前面提到的 render 函數(shù)。完成模板編譯后,同樣會進入 $mount 掛載階段。真正的掛載過程,執(zhí)行的是 mountComponent 方法,該函數(shù)的核心是實例化一個渲染 watcher ,具體 watcher 的內(nèi)容,另外放章節(jié)討論。我們只要知道渲染 watcher 的作用,一個是初始化的時候會執(zhí)行回調(diào)函數(shù),另一個是當 vm 實例中監(jiān)測的數(shù)據(jù)發(fā)生變化的時候執(zhí)行回調(diào)函數(shù)。而這個回調(diào)函數(shù)就是 updateComponent ,這個方法會通過 vm._render 生成虛擬 DOM ,并最終通過 vm._update 將虛擬 DOM 轉(zhuǎn)化為真正的 DOM 。

往下,我們從代碼的角度出發(fā),了解一下掛載的實現(xiàn)思路,下面只提取mount骨架代碼說明。

// 內(nèi)部真正實現(xiàn)掛載的方法
Vue.prototype.$mount = function (el, hydrating) {
 el = el && inBrowser ? query(el) : undefined;
 // 調(diào)用mountComponent方法掛載
 return mountComponent(this, el, hydrating)
};
// 緩存了原型上的 $mount 方法
var mount = Vue.prototype.$mount;
// 重新定義$mount,為包含編譯器和不包含編譯器的版本提供不同封裝,最終調(diào)用的是緩存原型上的$mount方法
Vue.prototype.$mount = function (el, hydrating) {
 // 獲取掛載元素
 el = el && query(el);
 // 掛載元素不能為跟節(jié)點
 if (el === document.body || el === document.documentElement) {
 warn(
 "Do not mount Vue to <html> or <body> - mount to normal elements instead."
 );
 return this
 }
 var options = this.$options;
 // 需要編譯 or 不需要編譯
 if (!options.render) {
 ···
 // 使用內(nèi)部編譯器編譯模板
 }
 // 最終調(diào)用緩存的$mount方法
 return mount.call(this, el, hydrating)
}
// mountComponent方法思路
function mountComponent(vm, el, hydrating) {
 // 定義updateComponent方法,在watch回調(diào)時調(diào)用。
 updateComponent = function () {
 // render函數(shù)渲染成虛擬DOM, 虛擬DOM渲染成真實的DOM
 vm._update(vm._render(), hydrating);
 };
 // 實例化渲染watcher
 new Watcher(vm, updateComponent, noop, {})
}

3.3 編譯過程 - 模板編譯成 render 函數(shù)

通過文章前半段的學(xué)習(xí),我們對Vue的掛載流程有了一個初略的認識。接下來將先從模板編譯的過程展開。閱讀源碼時發(fā)現(xiàn),模板的編譯過程是相當復(fù)雜的,要在短篇幅內(nèi)將整個編譯的過程講開是不切實際的,因此這節(jié)剩余內(nèi)容只會對實現(xiàn)思路做簡單的介紹。

3.3.1 template的三種寫法

template模板的編寫有三種方式,分別是:

// 1. 熟悉的字符串模板
var vm = new Vue({
 el: '#app',
 template: '<div>模板字符串</div>'
})
// 2. 選擇符匹配元素的 innerHTML模板
<div id="app">
 <div>test1</div>
 <script type="x-template" id="test">
 <p>test</p>
 </script>
</div>
var vm = new Vue({
 el: '#app',
 template: '#test'
})
// 3. dom元素匹配元素的innerHTML模板
<div id="app">
 <div>test1</div>
 <span id="test"><div class="test2">test2</div></span>
</div>
var vm = new Vue({
 el: '#app',
 template: document.querySelector('#test')
})

三種寫法對應(yīng)代碼的三個不同分支。

var template = options.template;
 if (template) {
 // 針對字符串模板和選擇符匹配模板
 if (typeof template === 'string') {
 // 選擇符匹配模板,以'#'為前綴的選擇器
 if (template.charAt(0) === '#') {
 // 獲取匹配元素的innerHTML
 template = idToTemplate(template);
 /* istanbul ignore if */
 if (!template) {
  warn(
  ("Template element not found or is empty: " + (options.template)),
  this
  );
 }
 }
 // 針對dom元素匹配
 } else if (template.nodeType) {
 // 獲取匹配元素的innerHTML
 template = template.innerHTML;
 } else {
 // 其他類型則判定為非法傳入
 {
 warn('invalid template option:' + template, this);
 }
 return this
 }
 } else if (el) {
 // 如果沒有傳入template模板,則默認以el元素所屬的根節(jié)點作為基礎(chǔ)模板
 template = getOuterHTML(el);
 }

其中X-Template模板的方式一般用于模板特別大的 demo 或極小型的應(yīng)用,官方不建議在其他情形下使用,因為這會將模板和組件的其它定義分離開。

3.3.2 流程圖解

vue源碼中編譯流程代碼比較繞,涉及的函數(shù)處理邏輯比較多,實現(xiàn)流程中巧妙的運用了偏函數(shù)的技巧將配置項處理和編譯核心邏輯抽取出來,為了理解這個設(shè)計思路,我畫了一個邏輯圖幫助理解。

3.3.3 邏輯解析

即便有流程圖,編譯邏輯理解起來依然比較晦澀,接下來,結(jié)合代碼分析每個環(huán)節(jié)的執(zhí)行過程。

var ref = compileToFunctions(template, {
 outputSourceRange: "development" !== 'production',
 shouldDecodeNewlines: shouldDecodeNewlines,
 shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
 delimiters: options.delimiters,
 comments: options.comments
}, this);

// 將compileToFunction方法暴露給Vue作為靜態(tài)方法存在
Vue.compile = compileToFunctions;

這是編譯的入口,也是Vue對外暴露的編譯方法。 compileToFunctions 需要傳遞三個參數(shù): template 模板,編譯配置選項以及Vue實例。我們先大致了解一下配置中的幾個默認選項

1. delimiters 該選項可以改變純文本插入分隔符,當不傳遞值時,vue默認的分隔符為 {{}} ,用戶可通過該選項修改
2. comments 當設(shè)為 true 時,將會保留且渲染模板中的 HTML 注釋。默認行為是舍棄它們。

接著一步步尋找compileToFunctions根源

var createCompiler = createCompilerCreator(function baseCompile (template,options) {
 //把模板解析成抽象的語法樹
 var ast = parse(template.trim(), options);
 // 配置中有代碼優(yōu)化選項則會對Ast語法樹進行優(yōu)化
 if (options.optimize !== false) {
 optimize(ast, options);
 }
 var code = generate(ast, options);
 return {
 ast: ast,
 render: code.render,
 staticRenderFns: code.staticRenderFns
 }
});

createCompilerCreator 角色定位為創(chuàng)建編譯器的創(chuàng)建者。他傳遞了一個基礎(chǔ)的編譯器 baseCompile 作為參數(shù), baseCompile 是真正執(zhí)行編譯功能的地方,他傳遞template模板和基礎(chǔ)的配置選項作為參數(shù)。實現(xiàn)的功能有兩個

1.把模板解析成抽象的語法樹,簡稱 AST ,代碼中對應(yīng) parse 部分
2.可選:優(yōu)化 AST 語法樹,執(zhí)行 optimize 方法
3.根據(jù)不同平臺將 AST 語法樹生成需要的代碼,對應(yīng)的 generate 函數(shù)

具體看看 createCompilerCreator 的實現(xiàn)方式。

function createCompilerCreator (baseCompile) {
 return function createCompiler (baseOptions) {
 // 內(nèi)部定義compile方法
 function compile (template, options) {
 ···
 // 將剔除空格后的模板以及合并選項后的配置作為參數(shù)傳遞給baseCompile方法,其中finalOptions為baseOptions和用戶options的合并
 var compiled = baseCompile(template.trim(), finalOptions);
 {
  detectErrors(compiled.ast, warn);
 }
 compiled.errors = errors;
 compiled.tips = tips;
 return compiled
 }
 return {
 compile: compile,
 compileToFunctions: createCompileToFunctionFn(compile)
 }
 }
 }

createCompilerCreator 函數(shù)只有一個作用,利用偏函數(shù)將 baseCompile 基礎(chǔ)編譯方法緩存,并返回一個編譯器函數(shù),該函數(shù)內(nèi)部定義了真正執(zhí)行編譯的 compile 方法,并最終將 compile 和 compileToFunctons 作為兩個對象屬性返回,這也是 compileToFunctions 的來源。而內(nèi)部 compile 的作用,是為了將基礎(chǔ)的配置 baseOptions 和用戶自定義的配置 options 進行合并,( baseOptions 是跟外部平臺相關(guān)的配置),最終返回合并配置后的 baseCompile 編譯方法。

compileToFunctions 來源于 createCompileToFunctionFn 函數(shù)的返回值,該函數(shù)會將編譯的方法 compile 作為參數(shù)傳入。

function createCompileToFunctionFn (compile) {
 var cache = Object.create(null);

 return function compileToFunctions (template,options,vm) {
 options = extend({}, options);
 ···
 // 緩存的作用:避免重復(fù)編譯同個模板造成性能的浪費
 if (cache[key]) {
 return cache[key]
 }
 // 執(zhí)行編譯方法
 var compiled = compile(template, options);
 ···
 // turn code into functions
 var res = {};
 var fnGenErrors = [];
 // 編譯出的函數(shù)體字符串作為參數(shù)傳遞給createFunction,返回最終的render函數(shù)
 res.render = createFunction(compiled.render, fnGenErrors);
 // 渲染優(yōu)化相關(guān)
 res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
 return createFunction(code, fnGenErrors)
 });
 ···
 return (cache[key] = res)
 }
 }

最終,我們找到了 compileToFunctions 真正的執(zhí)行過程 var compiled = compile(template, options); ,并將編譯后的函數(shù)體字符串通過 creatFunction 轉(zhuǎn)化為 render 函數(shù)返回。

function createFunction (code, errors) {
 try {
 return new Function(code)
 } catch (err) {
 errors.push({ err: err, code: code });
 return noop
 }
}

其中函數(shù)體字符串類似于 "with(this){return _m(0)}" ,最終的render渲染函數(shù)為 function(){with(this){return _m(0)}}

至此,Vue中關(guān)于編譯過程的思路也梳理清楚了,編譯邏輯之所以繞,主要是因為Vue在不同平臺有不同的編譯過程,而每個編譯過程的 baseOptions 選項會有所不同,同時在同一個平臺下又不希望每次編譯時傳入相同的 baseOptions 參數(shù),因此在 createCompilerCreator 初始化編譯器時便傳入?yún)?shù),并利用偏函數(shù)將配置進行緩存。同時剝離出編譯相關(guān)的合并配置,這些都是Vue在編譯這塊非常巧妙的設(shè)計。

總結(jié)

以上所述是小編給大家介紹的Vue源碼實例掛載與編譯流程,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,煩請注明出處,謝謝!

相關(guān)文章

  • 如何使用el-cascader組件寫下拉級聯(lián)多選及全選功能

    如何使用el-cascader組件寫下拉級聯(lián)多選及全選功能

    這篇文章主要介紹了如何使用el-cascader組件寫下拉級聯(lián)多選及全選功能,因為是有全選的功能,所以不能直接使用el-cascader組件,?而是選擇使用el-select組件,?在此組件內(nèi)部使用el-cascader-panel級聯(lián)面板,感興趣的朋友跟隨小編一起看看吧
    2024-03-03
  • vue按需加載組件webpack require.ensure的方法

    vue按需加載組件webpack require.ensure的方法

    本篇文章主要介紹了vue按需加載組件webpack require.ensure的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • vue3保存屬性自動換行問題及解決

    vue3保存屬性自動換行問題及解決

    這篇文章主要介紹了vue3保存屬性自動換行問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • Vue.js 60分鐘輕松入門

    Vue.js 60分鐘輕松入門

    Vue.js提供了簡潔、易于理解的API,幫助大家快速靈活掌握Vue.js。這篇文章主要介紹了如何在60分鐘內(nèi)輕松學(xué)習(xí)Vue.js,感興趣的小伙伴們可以參考一下
    2016-11-11
  • 建立和維護大型 Vue.js 項目的 10 個最佳實踐

    建立和維護大型 Vue.js 項目的 10 個最佳實踐

    這篇文章小編要與大家分享的是建立和維護大型 Vue.js 項目的 10 個最佳實踐,需要的小伙伴請和小編一起學(xué)習(xí)下面文章的具體內(nèi)容吧
    2021-09-09
  • vue默認插槽的理解與實例代碼

    vue默認插槽的理解與實例代碼

    對于插槽的概念和使用,這是vue的一個難點,這需要我們靜下心來,慢慢研究,下面這篇文章主要給大家介紹了關(guān)于vue默認插槽的相關(guān)資料,需要的朋友可以參考下
    2021-11-11
  • 解決Vue.js Devtools未檢測到Vue實例的問題

    解決Vue.js Devtools未檢測到Vue實例的問題

    在開發(fā)Vue.js應(yīng)用時,Vue.js Devtools是一個不可或缺的調(diào)試工具,然而,有時我們可能會遇到“Vue.js not detected”的提示,這意味著Vue.js Devtools未能成功識別和連接到我們的Vue應(yīng)用,本文將詳細解析這個問題,并提供相應(yīng)的解決步驟與代碼示例,需要的朋友可以參考下
    2024-01-01
  • 如何使用Webpack優(yōu)化Vue項目的打包流程

    如何使用Webpack優(yōu)化Vue項目的打包流程

    在開發(fā)基于Vue.js的應(yīng)用時,隨著項目規(guī)模的擴大,單個文件的體積也會隨之增長,特別是當涉及到大量的依賴庫和復(fù)雜的業(yè)務(wù)邏輯時,本文將詳細介紹如何使用Webpack來優(yōu)化Vue項目的打包流程,需要的朋友可以參考下
    2024-09-09
  • Vue3實現(xiàn)富文本內(nèi)容導(dǎo)出為Word文檔

    Vue3實現(xiàn)富文本內(nèi)容導(dǎo)出為Word文檔

    這篇文章主要為大家詳細介紹了Vue3如何通過純前端方案將富文本內(nèi)容直接導(dǎo)出為符合中文排版規(guī)范的 Word 文檔,有需要的小伙伴可以參考下
    2025-03-03
  • 如何修改Vue項目運行的IP和端口

    如何修改Vue項目運行的IP和端口

    這篇文章主要介紹了修改Vue項目運行的IP和端口的方法,本文結(jié)合示例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-02-02

最新評論

九江县| 嘉义县| 梅河口市| 盈江县| 长乐市| 秀山| 聊城市| 教育| 东港市| 宣城市| 玛曲县| 三门县| 邵武市| 东海县| 宁国市| 浏阳市| 扶沟县| 新余市| 交口县| 历史| 通州区| 涿鹿县| 临城县| 南江县| 资中县| 台州市| 长武县| 郓城县| 澎湖县| 黄平县| 莱西市| 康平县| 鄯善县| 武冈市| 武乡县| 汉川市| 延边| 陆丰市| 盐池县| 余干县| 宕昌县|