AMD and CMD are dead之KMDjs核心之依賴分析

【當耐特】發表於2014-07-13

有人說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");

    });
}

完!

github:https://github.com/kmdjs/kmdjs

相關文章