模仿vue自己動手寫響應式框架(二) - Vue物件建立

wls1036發表於2020-07-16

上一章節中,我們用vue實現了簡單的todo應用,如果我們把vue.js引用去掉,首先是介面顯示原始文字內容

然後console報錯

index.html:22 Uncaught ReferenceError: Vue is not defined
    at index.html:22

原因是我們在程式碼中透過程式碼var app = new Vue({...})建立了Vue物件,Vue物件是定義在vue.js中,現在沒了vue.js自然報錯。

這章的內容就是解決Vue物件找不到問題,讓程式碼不再報錯,為什麼要先解決js報錯問題而不是文字顯示問題?因為Vue物件提供了Vue所有的功能,其中就包括值繫結,解決了值繫結的問題,介面自然也會恢復正常,所以先解決Vue物件找不到問題。

匿名函式和閉包

先解釋什麼是匿名函式,顧名思義就是沒有定義名稱的函式,比如下面這幾種


// 函式表示式
var fn = function add(a,b){
    return a+b;
}

var c  = fn(1,2);

// 上面例子的結合
(function(a,b){return a+b;})(1,2);

//事件響應函式

var button = document.getElementById('submitButton');
button.onclick = function(){
    //do something
}

還有其他情況,這裡就不列舉,java也有匿名類,跟js的匿名函式類似。

再來聊一下前端面試必問,無數前端學子竟折腰的閉包是什麼東西,這東西確實難理解,我覺得很大一個原因是翻譯的問題,比如匿名函式,看名稱大概就知道什麼意思,閉包兩個字沒有任何想象的空間,而且還可能引起誤會,閉包實際上跟包並沒有什麼關係。我的不嚴謹但直觀的理解是閉包就是類,你可以理解為閉包就是java裡的類,在java類中,類可以定義變數和方法,方法可以引用類的變數,類比js,java類就是js的函式,java類變數就是js函式中定義的變數,java類的方法就是定義在js函式內部的函式。舉個例子:


function CircleArea(r){
    var PI=3.14159;
    
    function calculate(){
        return PI*r*r;
    }
    
    return calculate;
}

var circle2=CircleArea(2);
var area=circle2();
console.log(area);

正常當函式執行完變數會進行回收,在函式CircleArea中返回了calculate這個函式,理論上PI已經回收因為函式已經執行完,但在函式外部執行var area=circle2();時,結果依然時正確的,說明PI並沒有被回收,這個就是閉包的特性,函式和對其周圍狀態(lexical environment,詞法環境)的引用捆綁在一起構成閉包(closure),這個才是官方對閉包的定義。

那麼在我們這個應用中,Vue是以什麼形式存在,物件還是函式?毫無疑問肯定是函式,只有在函式中才能實現複雜的邏輯,才能實現生命週期的控制。所以如果想讓var app = new Vue({...})這行程式碼不報錯,一個可行的方案如下:


var Vue = function (options){
    console.log(options);
}

建立一個js檔案,名稱就叫做vuex.js,複製上面的程式碼到js檔案中,將上一章html中引用vue.js的改為引用vuex.js,重新執行,這時候雖然介面還是原樣輸出,但是console已經不報錯了,而且也正確列印出資料。

這一章的目的也就達到了。能不能更優雅點?如果看過jquery原始碼和vue原始碼,大家都能在程式碼的開頭看到這麼一段程式碼

(function( global, factory ) {....})(this,function(options){...})

是否有點像上面匿名函式(function(a,b){return a+b;})(1,2);,沒錯,這個就是立即執行的匿名函式,整個框架的邏輯就寫在function中,配置透過options傳入,這樣寫有什麼好處?

  • 更加優雅整潔
  • 擁有獨立的執行環境,多個框架之間不會相互衝突

其中保證擁有獨立執行環境才是重要原因,透過匿名函式實現了一個閉包,整個框架的邏輯在閉包中完成,透過function的return給呼叫者api。但如果透過匿名函式+閉包的方式,如何保證能new Vue({})呢,可以全域性宣告Vue變數,將上面的程式碼修改為以下程式碼:

(function(global, factory) {
    global.Vue = factory
})(this, function(options) {
    console.log(options);
});

其中global物件在呼叫時傳入this,this表示window物件,factory就是實現整個邏輯的匿名函式,透過global.Vue = factory將匿名函式定義在window.Vue上,這樣全域性都可以使用new Vue實現函式的呼叫。

參考

相關文章