最近我們公司的iClient產品需要改動,由於我們的包大概有1M了,使用者在使用的過程中如果網速不是很好的話,載入的就比較慢,使用者體驗就不好,上面要求使用者初始化的時候只有基礎包,比較小,當使用者用到哪一塊功能就載入哪塊的功能,這樣體驗上要好得多,但是我們的使用者已經有很多了,那麼我們就不能讓使用者改變以前的代碼,只能讓使用者把包替換一下,這裡研究了幾天研究了一種辦法和大家分享一下。對於做產品的朋友可能會有協助吧!
對於產品包來說,無非就是N多的類。使用者一般使用的時候都是new一個對象,然後開始使用它!比如我們有一個Person類,這裡就會涉及到當瀏覽器運行到var person = new Person()這裡時,其實記憶體裡面還沒有這個類,我們並沒有在一開始就導進來,但是如果沒有的話瀏覽器肯定會報錯,那我們在new之前加一句代碼來導包,這樣不就是需要更改使用者的原始碼了嗎?肯定不可取。我們需要在瀏覽器new對象的時候把對應的類導進來,而又不改變任何原始碼,如何做到?
我們可以在基礎包裡面對每一個類寫一個假的類,但是命名空間、類名必須都是一樣的,但類裡面除了建構函式什麼都沒有,這樣假類就很小,基本不會使基礎包的大小改變多少,這樣在瀏覽器運行到如var person = new Person()這一句的時候至少不會報告錯誤,但是產生的對象person是假的,停用,我們得想辦法讓它變成真的,怎麼才能讓new出的對象不是自己而是其他的對象呢?
我嘗試了很多種辦法,比如在建構函式裡面改變過this.__proto__.constructor、this.__proto__、this等手段,希望最後new出來的對象不是自身,而和new Person(真的)一樣,但是你可以調試發現沒法做到一模一樣,最後無意間發現在建構函式裡面使用return就可以改變new出來的對象,如下面new Person()出一個對象。
function Person() { return new Date(); }
你會發現new出來的不是Person的對象,而是一個Date對象,而且和new Date()出來的對象一模一樣,這是js這門語言故意這樣設計吧!也不知道最開始的目的是啥!如果使用return;或者return null;最後new出來的對象還是本身。這樣第一個問題就解決了,可以new出一個非自身的對象。
繼續討論,現在我們new出來的對象需要時真的那個類,而不是那個假的類,但是兩個類的命名空間和類名都是一樣的,這可怎麼辦,其實大家都知道所有的類無非就是window的屬性而已,其實都是指標,我把指向的部分改變後不久實現了嗎?
我們先需要寫一個真的類,建立一個Person.js檔案,開啟後寫入如下代碼:
function Person(name,age){ this.name = name; this.age = age; this.getName = function(){ return this.name; } this.getAge = function(){ return this.age; }}
這是一個真的類,有name和age兩個屬性以及getName()和getAge()兩個方法。在在同樣的檔案夾下建立一個html檔案,代碼如下:
<html><head> <title></title> <script type="text/javascript" src="loadJS.js"></script> <script type="text/javascript"> function init() { var person = new Person("Bill",23); var name = person.getName(); var age = person.getAge(); alert(name + "_" + age); } function Person(name,age) { loadJS("idPerson","Person.js"); return new Person(name,age); } </script></head><body> <button onclick="init()">測試</button></body></html>
這裡有一個loadJS.js檔案,這是一個動態同步載入js檔案的一個包,我自己寫的,很簡單,裡面的內容詳見《js動態載入指令碼》裡面的第六種方法裡有原始碼,其實就一個方法loadJS(id,url)。上面代碼已經有一個假的Person存在了,裡面有兩句代碼,第一句是載入真的Person,第二句是返回一個Person對象,當我們調試的時候你會驚奇的發現,當真的Person載入進來後Person這個類就變成了真的類,而假的類在new以後就沒有了,因為沒有指標指向它了,它接下來就會被回收機制給回收掉,new玩後發現person就成為了真的那個Person的對象,當你再次new Person()的時候發現直接就是真的Person對象了,因為已經載入過一次了。
我們仔細思考一下,如果Person這個類用到了我們其他沒載入的類,瀏覽器也會先尋找到假的那個類,再次去載入需要的類,這就形成了一個迴圈,需要什麼類的時候再載入什麼,不會多載入任何一個多餘的類。前提是你得每一個類對應一個js檔案。你也可以分成多少個模組,每個模組裡面有多個類,那麼每一次去載入就會載入一個模組,一個模組載入進來後會替換掉其中一部分假類。無論怎麼設計都很方便。
好了,所有問題都解決了,我們需要把假的類封裝進我們的產品包,然後讓使用者把這個包替換掉,使用者不需要改變任何以前項目的原始碼,就可以做到動態載入需要的指令碼。這個辦法是不是很好用?好用就幫我頂一下!