vue原理Compile從新建實例到結束流程源碼

引言

Compile 的內容十分之多,今天先來個熱身,先不研究 compile 內部編譯細節,而是記錄一下

從新建實例開始,到結束 compile ,其中的大致外部流程,不涉及 compile 的內部流程

或者說,我們要研究 compile 這個函數是怎麼生成的

註意,如果你沒有準備好,請不要閱讀這篇文章

註意哦,會很繞,別暈瞭

好的,正文開始

首先,當我們通過 Vue 新建一個實例的時候會調用Vue

所以從 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 已經在這裡生成瞭

我們觀察到

在上面這個函數中,主要就做瞭三件事

1 獲取 template 模板

根據你傳入的參數,來各種獲取 template 模板

這裡應該都看得懂瞭,根據DOM,或者根據選擇器

2 生成 render

通過 compileToFunctions ,傳入 template

就可以生成 render 和 staticRenderFns

看著是挺簡單哦,就一個 compileToFunctions,但是我告訴你,這個函數的誕生可不是這麼容易的,兜兜轉轉,十分曲折,相當得曲折復雜,沒錯,這就是我們下面研究的重點

但是這流程其實好像也沒有什麼幫助?但是如果你閱讀源碼的話,或許可以對你理清源碼有些許幫助吧

再一次佩服 尤大的腦回路

3 保存 render

保存在 vm.$options 上,用於在後面調用

下面就來說 compileToFunctions 的誕生史

註意,很繞很繞,做好心理準備

首先我定位到 compileToFunctions,看到下面這段代碼

var ref$1 = createCompiler();
// compileToFunctions 會返回 render 函數 以及 staticRenderFns
var compileToFunctions = ref$1.compileToFunctions;

於是我知道

compileToFunctions 是 createCompiler 執行返回的!!

那麼繼續定位 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
        }
    }
);

臥槽,又來一個函數,別暈啊兄弟

不過,註意註意,這裡是重點,非常重

首先明確兩點

1、createCompiler 是 createCompilerCreator 生成的

2、給 createCompilerCreator 傳瞭一個函數 baseCompile

baseCompile

這個 baseCompile 就是 生成 render 的大佬

看到裡面包含瞭 渲染三巨頭,【parse,optimize,generate】

但是今天不是講這個的,這三個東西,每個內容都十分巨大

這裡先跳過,反正 baseCompile 很重要,會在後面被調用到,得先記著

然後,沒錯,我們又遇到瞭一個 函數 createCompilerCreator ,定位它!

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

內部存在一個函數 compile,這個函數主要作用是

調用 baseCompile,把 baseCompile 執行結果 return 出去

baseCompile 之前我們強調過的,就是那個生成 render 的大佬

忘記的,可以回頭看看,執行完畢會返回

{ render,staticRenderFns }

返回 compileToFunctions 和 compile

其實 返回的這兩個函數的作用大致都是一樣的

都是為瞭執行上面那個 內部 compile

但是為什麼分出一個 compileToFunctions 呢?

還記得開篇我們的 compileToFunctions 嗎

就是那個在 vm.$mount 裡我們要探索的東西啊

就是他這個吊毛,生成的 render 和 staticRenderFns

再看看那個 內部 compile,可以看到他執行完就是返回

{ render, staticRenderFns }

你看,內部 compile 就是 【vm.$mount 執行的 compileToFunctions】 啊

為什麼 compileToFunctions 沒有直接賦值為 compile 呢!!

因為要做模板緩存!!

可以看到,沒有直接讓 compileToFunctions = 內部compile

而是把 內部 compile 傳給瞭 createCompileToFunctionFn

沒錯 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 中,目的是進行 緩存

以上就是vue原理Compile從新建實例到結束流程源碼的詳細內容,更多關於vue原理Compile新建實例的資料請關註WalkonNet其它相關文章!

推薦閱讀: