圍觀STK


先科譜一下,STK是新浪微博的前端javascript腳本庫,這個介紹很風趣:

   STK起源於sina twitter`s kit。
   后來內部改成史塔克了,是鋼鐵俠的主角托尼·史塔克的意思,世界最大的軍火商。
   但這史塔克這名字太晦澀難記,后來傳着傳着就成沙灘褲了,沒啥具體含義,只是諧音好說。

其實聽到STK已經有很多次了,不過可能是因為新浪的知識產權保護策略,源代碼一直沒有公開,所以我也一直沒有去仔細了解。
最近又聽CJ同學講到了STK,於是決心再去看一下,就算是苦B的看壓縮后的代碼。
壓縮后的代碼來自這個網址:http://js.t.sinajs.cn/t4/home/js/base.js?version=3c017d092cab8939
注:可以將上面的代碼復制到這個網址進行格式化:http://jsbeautifier.org/


1. STK主干
STK是一個命名空間,從主干代碼可以看出是按YUI2的思路來整的一個靜態方法與工具庫。代碼如下:

View Code
var STK = function () {
    var a = {}, b = [];
    a.inc = function (a, b) {
        return !0
    };
    a.register = function (c, d) {
        var e = c.split("."),
            f = a,
            g = null;
        while (g = e.shift()) if (e.length) {
            f[g] === undefined && (f[g] = {});
            f = f[g]
        } else if (f[g] === undefined) try {
            f[g] = d(a)
        } catch (h) {
            b.push(h)
        }
    };
    a.regShort = function (b, c) {
        if (a[b] !== undefined) throw "[" + b + "] : short : has been register";
        a[b] = c
    };
    a.IE = /msie/i.test(navigator.userAgent);
    a.E = function (a) {
        return typeof a == "string" ? document.getElementById(a) : a
    };
    a.C = function (a) {
        var b;
        a = a.toUpperCase();
        a == "TEXT" ? b = document.createTextNode("") : a == "BUFFER" ? b = document.createDocumentFragment() : b = document.createElement(a);
        return b
    };
    a.log = function (a) {
        b.push("[" + (new Date).getTime() % 1e5 + "]: " + a)
    };
    a.getErrorLogInformationList = function (a) {
        return b.splice(0, a || b.length)
    };
    return a
}();

其中最重要的是兩個方法:inc與register。

inc方法在發布時變成了一個空方法,應該是跟模塊加載有關,具體就不妄測了。

register方法,第一個參數是命名(包括命名空間與本身名字);第二個參數是一個生成器函數。
JK小注:第二個參數只能是生成器,這樣可以后面的代碼看起來很統一,不過也會造成一些小小的不便,例如以下代碼,使用生成器顯得有點臃腫:

STK.register("core.func.empty", function () {
    return function () {}
});

如果regShort的第一個參數也支持類似“core.func.empty”的帶點字符串的話,這樣使,上面的代碼可以改成這一種的:

STK.regShort("core.func.empty", function () {});

 

2. STK堆砌
主干之外,剩下的大部分內容就是利用STK.register進行堆砌了。
這種堆砌也大略可以分成兩種:方法粒度的堆砌、與命名空間或類粒度的堆砌。
方法粒度的堆砌:例如register "core.func.empty"、"core.obj.parseParam",生成器會產生一個靜態函數

View Code
STK.register("core.func.empty", function () {
    return function () {}
});
STK.register("core.obj.parseParam", function (a) {
    return function (a, b, c) {
        var d, e = {};
        b = b || {};
        for (d in a) {
            e[d] = a[d];
            b[d] != null && (c ? a.hasOwnProperty[d] && (e[d] = b[d]) : e[d] = b[d])
        }
        return e
    }
});

命名空間或類粒度的堆砌:例如register "core.util.cookie"生成器會產生一個子命名空間;例如register "core.util.drag"生成器會產生"類"

View Code
STK.register("core.util.cookie", function (a) {
    var b = {
        set: function (b, c, d) {
            var e = [],
                f, g, h = a.core.obj.parseParam({
                    expire: null,
                    path: "/",
                    domain: null,
                    secure: null,
                    encode: !0
                }, d);
            h.encode == !0 && (c = escape(c));
            e.push(b + "=" + c);
            h.path != null && e.push("path=" + h.path);
            h.domain != null && e.push("domain=" + h.domain);
            h.secure != null && e.push(h.secure);
            if (h.expire != null) {
                f = new Date;
                g = f.getTime() + h.expire * 36e5;
                f.setTime(g);
                e.push("expires=" + f.toGMTString())
            }
            document.cookie = e.join(";")
        },
        get: function (a) {
            a = a.replace(/([\.\[\]\$])/g, "\\$1");
            var b = new RegExp(a + "=([^;]*)?;", "i"),
                c = document.cookie + ";",
                d = c.match(b);
            return d ? d[1] || "" : ""
        },
        remove: function (a, c) {
            c = c || {};
            c.expire = -10;
            b.set(a, "", c)
        }
    };
    return b
});
STK.register("core.util.drag", function (a) {
    var b = function (a) {
        a.cancelBubble = !0;
        return !1
    }, c = function (b, c) {
        b.clientX = c.clientX;
        b.clientY = c.clientY;
        b.pageX = c.clientX + a.core.util.scrollPos().left;
        b.pageY = c.clientY + a.core.util.scrollPos().top;
        return b
    };
    return function (d, e) {
        if (!a.core.dom.isNode(d)) throw "core.util.drag need Element as first parameter";
        var f = a.core.obj.parseParam({
            actRect: [],
            actObj: {}
        }, e),
            g = {}, h = a.core.evt.custEvent.define(f.actObj, "dragStart"),
            i = a.core.evt.custEvent.define(f.actObj, "dragEnd"),
            j = a.core.evt.custEvent.define(f.actObj, "draging"),
            k = function (d) {
                var e = c({}, d);
                document.body.onselectstart = function () {
                    return !1
                };
                a.core.evt.addEvent(document, "mousemove", l);
                a.core.evt.addEvent(document, "mouseup", m);
                a.core.evt.addEvent(document, "click", b, !0);
                if (!a.IE) {
                    d.preventDefault();
                    d.stopPropagation()
                }
                a.core.evt.custEvent.fire(h, "dragStart", e);
                return !1
            }, l = function (b) {
                var d = c({}, b);
                b.cancelBubble = !0;
                a.core.evt.custEvent.fire(h, "draging", d)
            }, m = function (d) {
                var e = c({}, d);
                document.body.onselectstart = function () {
                    return !0
                };
                a.core.evt.removeEvent(document, "mousemove", l);
                a.core.evt.removeEvent(document, "mouseup", m);
                a.core.evt.removeEvent(document, "click", b, !0);
                a.core.evt.custEvent.fire(h, "dragEnd", e)
            };
        a.core.evt.addEvent(d, "mousedown", k);
        g.destroy = function () {
            a.core.evt.removeEvent(d, "mousedown", k);
            f = null
        };
        g.getActObj = function () {
            return f.actObj
        };
        return g
    }
});

JK小注:這里有一個很個性化的做法,例如cascadeNode,drag,函數運行后都返回一個{}對象,而不像別人使用的類。其中cascadeNode方法,每次調用就會產生很多function,產生一些不必要的副作用。
JK小注2:register的拆分粒度,也體現了這種代碼可定制的粒度。如果粒度精細到方法,會有一些副作用:讓最后產出的代碼里有很多冗余。
例如這樣一段代碼:

View Code
STK.register("core.arr.isArray", function (a) {
    return function (a) {
        return Object.prototype.toString.call(a) === "[object Array]"
    }
});
STK.register("core.arr.foreach", function (a) {
    var b = function (a, b) {
        var c = [];
        for (var d = 0, e = a.length; d < e; d += 1) {
            var f = b(a[d], d);
            if (f === !1) break;
            f !== null && (c[d] = f)
        }
        return c
    }, c = function (a, b) {
        var c = {};
        for (var d in a) {
            var e = b(a[d], d);
            if (e === !1) break;
            e !== null && (c[d] = e)
        }
        return c
    };
    return function (d, e) {
        if (a.core.arr.isArray(d) || d.length && d[0] !== undefined) return b(d, e);
        if (typeof d == "object") return c(d, e);
        return null
    }
});
STK.register("core.arr.indexOf", function (a) {
    return function (a, b) {
        if (b.indexOf) return b.indexOf(a);
        for (var c = 0, d = b.length; c < d; c++) if (b[c] === a) return c;
        return -1
    }
});
STK.register("core.arr.inArray", function (a) {
    return function (b, c) {
        return a.core.arr.indexOf(b, c) > -1
    }
});

把拆分精度調成命名空間級別,代碼變少了(673/807 約= 83%):

View Code
STK.register("core.arr", function (a) {
    var b = function (a, b) {
        var c = [];
        for (var d = 0, e = a.length; d < e; d += 1) {
            var f = b(a[d], d);
            if (f === !1) break;
            f !== null && (c[d] = f)
        }
        return c
    }, c = function (a, b) {
        var c = {};
        for (var d in a) {
            var e = b(a[d], d);
            if (e === !1) break;
            e !== null && (c[d] = e)
        }
        return c
    };
    return {
        isArray: function (a) {
            return Object.prototype.toString.call(a) === "[object Array]"
        },
        foreach: function (d, e) {
            if (a.core.arr.isArray(d) || d.length && d[0] !== undefined) return b(d, e);
            if (typeof d == "object") return c(d, e);
            return null
        },
        indexOf: function (a, b) {
            if (b.indexOf) return b.indexOf(a);
            for (var c = 0, d = b.length; c < d; c++) if (b[c] === a) return c;
            return -1
        },
        inArray: function (b, c) {
            return a.core.arr.indexOf(b, c) > -1
        }
    }
});

JK小注3:STK里都是用嚴格的靜態調用方式,所以可以簡單的直接用文本分析來抽取依賴,而不必進行依賴配置,就可以進行代碼定制(類似於QWrap的codepicker)或代碼抽取(類似於QWrap的solo)。----估計新浪同學已經實現了,只是我們外面的同學看不到。

3. Retouch加工

STK庫里做了一些易用性的Retouch,例如為了鏈式調用而添加的cascadeNode、為了減少命名空間深度添加的STK.regShort(c, b[c])序列等

 

4. 其它雜想:
JK小注:腳本庫以一個簡單的主干開始,其后就是統一的用register來堆砌,代碼風格簡朴(很少用this、prototype等)也很一致,看代碼比較輕松,很容易理解。
JK小注2:帶了很多組件級別(簡單又常用的)的內容放在核心庫,最后的總體大小也才89k,贊一下。
JK小注3:最終的使用方式還是靜態調用為主,在易用性上還有很大的提升空間。
JK小注4:接口命名、參數等可以有更多的參考一下標准、或大家的約定俗成,例如foreach應為forEach、setXY與position命名不配、有stopEvent但是沒有stopPropagation、insertHTML的參數與標准的insertAdjacentHTML參數順序不一致、等等。
JK小注5:代碼還可以寫得更簡單一些的,例如str.trim的寫法可以簡化一下、Browser應該可以省一半(可以參考一下別的腳本庫)、由於register的第二個參數只支持生成器導致臃腫、由於拆分粒度導致的代碼臃腫、Retouch可以有一些技巧來省代碼(用methodize變換可以省cascadeNode里的代碼)。

5. 一個小禮物
聽說STK的幫助文檔還有些欠缺,這里恰好有QWrap的一個冒煙測試工具,可以臨時簡陋的替代一下,幫助用戶了解STK提供哪些方法與功能,也可以快速的看到方法對應的代碼。
注:因為引用的是壓縮后的STK,所以參數名都是abcd這樣的無意義名字,如果改成引用壓縮前的代碼,情況會得到改善。
注:引用的是壓縮后的代碼,所以請用firefox看。
http://dev.qwrap.com/resource/js/_tools/smokingtest/_examples/SmokingTest_STK.html

 

特別聲明:因為STK還未開源,而本文未經新浪授權,若有版權問題,請通知我把此文撤下。非常感謝!

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM