有人說js中有三座大三:this、原型鏈和scope tree,搞懂了他們就算是js成人禮。當然還有其他不同看法的js成人禮,如熟悉js的:OOP、AP、FP、DOP、AOP。當然還聽說一種最牛B的js成人禮:熟悉jQuery……= =!因為$裡面可以放下全世界,比如$(“全世界”)…
這篇文章主要講KMDjs利用Uglify2去分析出一個函式的所有依賴,之後才能正確地載入相關的js檔案。該文涉及到js中三座大山中的scope tree….先看下面這段程式:
function test() {
var user = new User();
}
很顯然,該函式依賴User。我一定要去載入User.js才能正確執行該函式。那麼我是不是可以寫一段非常牛B的正則找到new 和()之間的User。當然,這樣一定是不對的,因為js裡建立物件的例項可以省略括號,比如:
function test() {
var user = new User;
}
那麼,是不是可以寫一段非常牛B的正則,去查詢new與分號之間的東西?相對於這個函式,是可以的!但是如果,這個函式長成這個樣子:
function test(User) {
var user = new User;
}
可以看得到,該函式不依賴任何物件,User是test的引數傳遞進來…,那麼我是不是可以寫一段非常牛B的正則,先找到test(User)中的User引數,然後再通過正則找到new後面的所有物件,最後把第二次查詢到的結果過濾掉第一次查詢到的引數。好問題來了,如果程式長這樣子:
function test(User) { var xxx = "(User)"; var user = new User; }
可能有人會說了,只能說明你的正則不牛B。牛B的正則是可以確認是字串中的,還是非字串中的。那麼再看下一段程式:
function test() { var User = function (name) { this.name = name; } var u = new User(); }
可以看得出,User根本不是引數,而是直接在test內部定義!那麼這個函式其實是不依賴User。那麼,剛剛憋出的兩段牛B的正則就徹底廢掉了。再比如:
function test() { var vp = Bom.getViewport(); }
可以看到,直接訪問Bom的靜態方法,都不需要new,就能訪問Bom下的getViewport方法。所以,該函式依賴Bom。那麼,剛剛憋出的兩段牛B的正則再次徹底廢掉了。再比如:
function test() { var vp = new Array(); var el = document.getElementById("xx"); }
可以發現,window下的Array和document還需要過濾掉…,再比如
function test() { var h = 1; //這裡 h a 都是可以找到,不能判定為賴 function a(e) { //這裡面h a e i b都是可以找到,不能判定為賴 var i = 2; function b(f) { //這裡面h a e i b f c都是可以找到,不能判定為賴 function c(g) { //這裡面h a e i b f c g都是可以找到,不能判定為賴 } } } }
如上面註釋所描述,會有一串scope tree。那麼…怎麼辦?uglify2有強大無比的ast.walk和UglifyJS.TreeWalker!如:
http://lisperator.net/blog/using-uglifyjs-for-code-refactoring/
這是官方重構程式碼的簡單例子。受此文啟發,我便為kmdjs寫了個完美的依賴分析:
function getRef(fn) { var U2 = UglifyJS; var ast = U2.parse(fn.toString()); ast.figure_out_scope(); var result = []; ast.walk(new U2.TreeWalker(function (node) { if (node instanceof U2.AST_New) { var ex = node.expression; var name = ex.name; if (!isInScopeChainVariables(ex.scope, name)) { result.push(name); } } if (node instanceof U2.AST_Dot) { var ex = node.expression; var name = ex.name; if (!isInScopeChainVariables(ex.scope, name)) { result.push(name); } } })); return result; } function isInScopeChainVariables(scope, name) { var vars = scope.variables._values; if (Object.prototype.hasOwnProperty.call(vars, "$" + name)) { return true; } if (scope.parent_scope) { return isInScopeChainVariables(scope.parent_scope, name); } return false; }
意外驚喜,在kmdjs加入lazy(kmdjs.get)之後,lazy內部的依賴不會載入!且看下面這段程式碼
function test(DDDDD) { kmdjs.get("HelloKMD.Ball", function (Ball) { //因為Ball是引數,屬於該scope tree中的物件,所以不依賴 var ball = new Ball(100, 100, 28, 1, 2, "KMD.js"); }); }
完!