亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

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

2019-11-19 11:39:07
字體:
來源:轉載
供稿:網友

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

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

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

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

3.1.1 Runtime + Compiler

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

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

3.1.2 Runtime Only

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

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

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

3.2 掛載的基本思路

vue掛載的流程是比較復雜的,我們通過流程圖理清基本的實現思路。

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

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

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

// 內部真正實現掛載的方法Vue.prototype.$mount = function (el, hydrating) { el = el && inBrowser ? query(el) : undefined; // 調用mountComponent方法掛載 return mountComponent(this, el, hydrating)};// 緩存了原型上的 $mount 方法var mount = Vue.prototype.$mount;// 重新定義$mount,為包含編譯器和不包含編譯器的版本提供不同封裝,最終調用的是緩存原型上的$mount方法Vue.prototype.$mount = function (el, hydrating) { // 獲取掛載元素 el = el && query(el); // 掛載元素不能為跟節點 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) { ??? // 使用內部編譯器編譯模板 } // 最終調用緩存的$mount方法 return mount.call(this, el, hydrating)}// mountComponent方法思路function mountComponent(vm, el, hydrating) { // 定義updateComponent方法,在watch回調時調用。 updateComponent = function () { // render函數渲染成虛擬DOM, 虛擬DOM渲染成真實的DOM vm._update(vm._render(), hydrating); }; // 實例化渲染watcher new Watcher(vm, updateComponent, noop, {})}

3.3 編譯過程 - 模板編譯成 render 函數

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

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')})

三種寫法對應代碼的三個不同分支。

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元素所屬的根節點作為基礎模板 template = getOuterHTML(el); }

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

3.3.2 流程圖解

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

3.3.3 邏輯解析

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

var ref = compileToFunctions(template, { outputSourceRange: "development" !== 'production', shouldDecodeNewlines: shouldDecodeNewlines, shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments}, this);// 將compileToFunction方法暴露給Vue作為靜態方法存在Vue.compile = compileToFunctions;

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

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

接著一步步尋找compileToFunctions根源

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

createCompilerCreator 角色定位為創建編譯器的創建者。他傳遞了一個基礎的編譯器 baseCompile 作為參數, baseCompile 是真正執行編譯功能的地方,他傳遞template模板和基礎的配置選項作為參數。實現的功能有兩個

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

具體看看 createCompilerCreator 的實現方式。

function createCompilerCreator (baseCompile) { return function createCompiler (baseOptions) { // 內部定義compile方法 function compile (template, options) { ??? // 將剔除空格后的模板以及合并選項后的配置作為參數傳遞給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 函數只有一個作用,利用偏函數將 baseCompile 基礎編譯方法緩存,并返回一個編譯器函數,該函數內部定義了真正執行編譯的 compile 方法,并最終將 compile 和 compileToFunctons 作為兩個對象屬性返回,這也是 compileToFunctions 的來源。而內部 compile 的作用,是為了將基礎的配置 baseOptions 和用戶自定義的配置 options 進行合并,( baseOptions 是跟外部平臺相關的配置),最終返回合并配置后的 baseCompile 編譯方法。

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

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

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

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

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

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

總結

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

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美性受xxxx黑人猛交| 在线视频国产日韩| 日韩美女在线看| 久久久女人电视剧免费播放下载| 久久久久久久久综合| 热久久这里只有精品| 久久综合网hezyo| 热久久视久久精品18亚洲精品| 97在线精品视频| xxxx欧美18另类的高清| 蜜月aⅴ免费一区二区三区| 欧美成人免费在线视频| 精品自拍视频在线观看| 亚洲精品免费在线视频| 亚洲精品一区二区在线| 成人精品aaaa网站| 国产精品人成电影在线观看| 久久综合免费视频影院| 国产精品入口免费视| 色偷偷亚洲男人天堂| 亚洲精品www久久久| 久久久视频免费观看| 国产精品视频网站| 精品福利在线看| 欧美日韩亚洲视频一区| 欧美精品在线免费观看| 欧美国产亚洲精品久久久8v| 亚洲欧美国产一本综合首页| 一区二区av在线| 国产精品毛片a∨一区二区三区|国| 亚洲欧洲国产精品| 亚洲直播在线一区| 日韩在线观看免费高清| 永久免费精品影视网站| 中文字幕精品www乱入免费视频| 欧美日韩福利视频| 亚洲精品国产欧美| 精品久久久久久久久国产字幕| 久久久日本电影| 亚洲美女www午夜| 欧美日韩国产在线| 亚洲自拍高清视频网站| 亚洲自拍小视频| 久久精品国产免费观看| 自拍偷拍免费精品| 91在线观看免费网站| 亚洲第一色中文字幕| 九九精品视频在线| 欧美专区第一页| 国产亚洲欧美一区| 欧美巨乳美女视频| 亚洲第一男人天堂| 国产精品一区专区欧美日韩| 91高清免费在线观看| 国模视频一区二区| 欧美大人香蕉在线| 欧美精品免费在线观看| 欧美性xxxx18| 国产精品久久久久国产a级| 91免费福利视频| 欧美高清不卡在线| 日韩网站在线观看| 久久久最新网址| 国产成人一区二区三区| 国产精品人人做人人爽| 国产精品吊钟奶在线| 成人精品aaaa网站| 欧美电影在线观看完整版| 日韩视频在线一区| 亚洲精品97久久| 日韩成人久久久| 在线观看亚洲区| 情事1991在线| 国产日韩精品综合网站| 亚洲摸下面视频| 91国语精品自产拍在线观看性色| 91夜夜揉人人捏人人添红杏| 91夜夜揉人人捏人人添红杏| 全亚洲最色的网站在线观看| 欧美午夜性色大片在线观看| 中文字幕亚洲欧美一区二区三区| 美日韩精品免费视频| 欧美激情高清视频| 久久99青青精品免费观看| 日韩中文视频免费在线观看| 欧美性在线视频| 久久亚洲影音av资源网| 欧美日韩一区二区三区在线免费观看| 精品女厕一区二区三区| 91精品国产91久久久久久最新| 久久久视频在线| 日本最新高清不卡中文字幕| 久久手机精品视频| 91精品久久久久久久久不口人| 亚洲va国产va天堂va久久| 日本一区二区三区在线播放| 日韩国产欧美精品一区二区三区| 亚洲xxx大片| 欧美孕妇孕交黑巨大网站| 欧美性极品xxxx做受| 大伊人狠狠躁夜夜躁av一区| 成人免费视频在线观看超级碰| 国产又爽又黄的激情精品视频| 欧美亚洲日本网站| 91精品免费视频| 国产伦精品一区二区三区精品视频| 最近中文字幕2019免费| 欧美老女人性视频| 热99在线视频| 久久男人资源视频| 91午夜理伦私人影院| 57pao国产成人免费| 日韩一区av在线| 一本色道久久88综合亚洲精品ⅰ| 91精品视频大全| 91久久国产综合久久91精品网站| 国产成人一区二区三区小说| 成人黄色生活片| 国产成人拍精品视频午夜网站| 一二美女精品欧洲| 亚洲免费精彩视频| 91黑丝在线观看| 欧美精品激情在线| 欧美精品在线视频观看| 欧美福利视频网站| 亚洲欧洲在线视频| 2018日韩中文字幕| 亚洲va国产va天堂va久久| 91亚洲国产成人久久精品网站| 5566成人精品视频免费| 国产成人小视频在线观看| 色偷偷av一区二区三区乱| 国产成人av网| 97视频免费观看| 国产精品国模在线| 欧美电影免费观看高清| 日韩欧美一区二区三区| 亚洲国产欧美日韩精品| 97香蕉超级碰碰久久免费的优势| 在线精品视频视频中文字幕| 亚洲aaa激情| 精品视频在线播放色网色视频| 亚洲精品二三区| 国产精品h在线观看| 日韩中文字幕不卡视频| 中文字幕亚洲欧美日韩2019| 久久69精品久久久久久国产越南| 欧美高清在线视频观看不卡| 91免费精品视频| 久久久噜噜噜久噜久久| 成人性教育视频在线观看| 亚洲女人天堂网| 亚洲国产天堂网精品网站| 久久天天躁狠狠躁老女人| 欧美成人免费在线视频| 亚洲成人av片| 久久久久久国产精品| 国产精品美女呻吟| 中文字幕日韩电影| 91久久久久久| 日韩在线观看免费高清| 日韩在线一区二区三区免费视频| 亚洲综合精品伊人久久| 色爱av美腿丝袜综合粉嫩av|