天天看點

JavaScript 工具庫:Cloudgamer JavaScript Library v0.1 釋出

研究了一年多的js,也差不多寫一個自己的js庫了。

我寫這個不算架構,隻是一個小型的js工具庫,是以我用的名字是Library。

主要集合了我寫js時一些常用的方法,并參考了prototype.js,jquery,google,百度,有啊等架構。

這個工具庫的主要特點是:

【跨浏覽器】

能在以下浏覽器使用:IE6,IE7,IE8,Firefox 3.5.3,Chrome 3.0,Safari 4.0.3,Opera 10.10

ie系列是必須的,其他能支援最新版本就夠了。

【使用命名空間】

當然不是真正“命名空間”,隻是一些全局變量,用途相似而已。

有如下命名空間:

$$:代表Object,儲存對象相關方法,也代替最常用的getElementById方法;

$$B:代表Browser,儲存浏覽器資訊;

$$A:代表Array,儲存數組和類數組的相關方法;

$$F:代表Function,儲存函數的相關方法;

$$D:代表Dom,文檔對象的相關操作和方法;

$$E:代表Event,dom事件的相關操作和相容處理;

$$CE:代表CustomEvent,用于程式對象的自定義事件;

$$S:代表String,儲存字元串的相關方法。

雖然我不反對有節制地擴充原生對象,但可以的話還是避免命名污染吧。

用多個命名空間(而不用單個)隻因管理容易,用起來友善。

用兩個$,不是要更多美刀(雖然很想),而是避免跟流行的架構沖突。

【使用匿名函數】

貌似是jquery發揚光大的,就是把代碼嵌在一個function裡面。

其實就是利用閉包,一來可以使用局部變量,二來可以防止命名沖突。

【使用對象檢測】

“對象檢測天生地優于浏覽器檢測”,出自“ppk談JavaScript”的真理。

能用對象檢測的都盡量用,當然有些實在太難搞的也不要太執着。

對象檢測方面jQuery的support做的很好,建議去看一下。

追求目标是:

【小體積】

這裡的體積不是說字元的多少,而是屬性和方法的數量。

工具庫的屬性和方法必須是很有用的,最好是“不得不加”的。

當然随着使用的增加,工具庫也會慢慢的擴大,但要堅持這個原則。

【高效率】

高效是不變的追求,當然是在權衡利弊之後。

說到高效不得不佩服一下google,它不但代碼追求效率,而且下載下傳的代碼是已經經過浏覽器檢測的。

具體可以自己用各個浏覽器下載下傳看看試試。

建立目的是:

【整合常用方法】

把常用的方法整合到一起,既利于代碼複用,也便于維護。

但也不可避免地添加一些無關的方法,進而增加了代碼量,降低了效率。

【解決相容問題】

解決一些常見的相容性問題,減輕編碼負擔。

各個部分說明

【Object】

命名空間是:$$

$$本身就是最常用的方法:document.getElementById

它還包括以下幾個方法:extend、deepextend和wrapper。

其中extend跟prototype.js的Object.extend是一樣的,用來擴充對象,是用得最久的方法之一了。

而deepextend是深度擴充,這裡的深度跟深度複制裡面的意思差不多,參考的是jQuery的extend。

emptyFunction儲存了一個空的function,主要用來代替空function節省資源。

wrapper就複雜一點,主要用來做繼承,主要參考有啊的$extends(跟prototype的Class.create也類似)。

wrapper是$extends的簡化版,隻保留了關鍵的幾個部分:

O.wrapper = function(me, parent) {

    var ins = function() { me.apply(this, arguments); };

    var subclass = function() {};

    subclass.prototype = parent.prototype;

    ins.prototype = new subclass;

    return ins;

};

跟傳統原型繼承的差別是用了一個空的函數作為初始化程式,防止非prototype屬性方法的繼承。

【Browser】

命名空間是:$$B

通過userAgent擷取浏覽器資訊,主要擷取浏覽器的類型和版本。

這裡基本是參考有啊的Browser,要了解這部分首先要知道各浏覽器的userAgent。

下面是各浏覽器(ie系列和其他浏覽器的最新版)的userAgent:

ie6

Mozilla/4.0 (compatible; MSIE 6.0; ...)

ie7

Mozilla/4.0 (compatible; MSIE 7.0; ...)

ie8

Mozilla/4.0 (compatible; MSIE 8.0; ...)

ff

Mozilla/5.0 (...) Gecko/20090824 Firefox/3.5.3

chrome

Mozilla/5.0 (...) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/3.0.195.27 Safari/532.0

safari

Mozilla/5.0 (...) AppleWebKit/531.9 (KHTML, like Gecko) Version/4.0.3 Safari/531.9.1

opera

Opera/9.80 (...) Presto/2.2.15 Version/10.10

先通過判斷特有字元來判斷浏覽器類型:

var b = {

    msie: /msie/.test(ua) && !/opera/.test(ua),

    opera: /opera/.test(ua),

    safari: /webkit/.test(ua) && !/chrome/.test(ua),

    firefox: /firefox/.test(ua),

    chrome: /chrome/.test(ua)

擷取版本資訊就比較麻煩,有啊Browser的方法就比較巧妙(有修改):

var vMark = "";

for (var i in b) {

    if (b[i]) { vMark = "safari" == i ? "version" : i; break; }

}

b.version = vMark && RegExp("(?:" + vMark + ")[\\/: ]([\\d.]+)").test(ua) ? RegExp.$1 : "0";

但參考上面的userAgent會發現opera的擷取應該也是用"version"才對啊,問題是它在10之前的userAgent是這樣的:

Opera/9.99 (...) Presto/9.9.9

并沒有用"version",為了适用大部分情況還是不要用"version"好了,而且這個判斷用的也不多。

【Array】

命名空間是:$$A

以上方法除了isArray,都是JavaScript 1.6裡數組增加的方法。

其中isArray用來判斷對象是否數組,indexOf和lastIndexOf是元素定位方法,其他幾個是疊代方法。

在自定義的這些疊代方法,不但能應用在數組上,還能應用在類數組,像NodeList,arguments這些對象,也能應用在一般的對象上。

這裡我參考了愛民的JSEnhance.js的數組擴充部分,對這幾個自定義疊代方法中相同的結構進行整合。

首先定義一個基本的疊代函數each:

function each( object, callback ) {

    if ( undefined === object.length ){

        for ( var name in object ) {

            if (false === callback( object[name], name, object )) break;

        }

    } else {

        for ( var i = 0, len = object.length; i < len; i++ ) {

            if (i in object) { if (false === callback( object[i], i, object )) break; }

    }

當有length屬性(數組或類數組)時根據索引疊代,否則用for...in曆遍對象的屬性。

由于在方法的說明中規定了"elements that are deleted are not visited",是以要用if (i in object)來判斷一下。

要注意的是當callback傳回false時會跳出循環,利用callback的傳回值,就可以在callback内部間接控制外部的跳出操作了。

然後準備自定義的疊代方法:

each({

        forEach: function( object, callback, thisp ){

            each( object, function(){ callback.apply(thisp, arguments); } );

        },

        map: function( object, callback, thisp ){

            var ret = [];

            each( object, function(){ ret.push(callback.apply(thisp, arguments)); });

            return ret;

        filter: function( object, callback, thisp ){

            each( object, function(item){

                    callback.apply(thisp, arguments) && ret.push(item);

                });

        every: function( object, callback, thisp ){

            var ret = true;

            each( object, function(){

                    if ( !callback.apply(thisp, arguments) ){ ret = false; return false; };

        some: function( object, callback, thisp ){

            var ret = false;

                    if ( callback.apply(thisp, arguments) ){ ret = true; return false; };

疊代部分給each來做,這裡隻要控制callback的具體操作就可以了。

這裡利用了閉包,使callback可以修改疊代方法的傳回值。

every和some就利用callback的傳回值跳出循環。

定義好這些方法後,再用each曆遍這些方法,并加入到命名空間中:

each(

JavaScript 工具庫:Cloudgamer JavaScript Library v0.1 釋出

     , function(method, name){

        ret[name] = function( object, callback, thisp ){

            if (object[name]) {

                return object[name]( callback, thisp );

            } else {

                return method( object, callback, thisp );

            }

    });

在方法執行時,會先判斷對象本身有沒有指定的方法,沒有的話才使用自定義的疊代方法。

【Function】

命名空間是:$$F

裡面現在隻有兩個方法:bind和bindAsEventListener。

這兩個是prototype.js裡面的經典方法了,是用來給function綁定this的。

原理是利用call/apply改變調用方法的對象:

var args = slice.call(arguments, 2);

return function() {

    return fun.apply(thisp, args.concat(slice.call(arguments)));

其中用到Array.prototype.slice把arguments對象轉成數組,不知道是誰發現的,知道這個用法就行了。

ps:不止slice,其他像concat,join等也能這樣使用。

bindAsEventListener跟bind不同的是會把第一個參數設定為event對象,專門用在事件回調函數中:

return function(event) {

    return fun.apply(thisp, [E.fixEvent(event)].concat(args));

其中用到fixEvent處理event的相容性,後面Event的部分會詳細說明。

【Dom】

命名空間是:$$D

這部分是工具庫中最大,最複雜也最重要的部分。

主要是儲存了一些Dom操作,并解決一般的相容性問題。

其中getScrollTop和getScrollLeft分别是擷取文檔滾動的scrollTop和scrollLeft。

一般來說如果在标準模式下應該用documentElement擷取,否則用body擷取。

但chrome和safari(都是用WebKit渲染引擎)即使在标準模式下也要用body來擷取。

這裡用的方法是:

var doc = node ? node.ownerDocument : document;

return doc.documentElement.scrollTop || doc.body.scrollTop;

優先擷取documentElement的再選擇body的,這樣就基本能解決了。

但這個其實是不完善的,如果給文檔添加如下樣式:

body{height:300px;overflow:scroll;width:500px;}

在ie6/7會發現在标準模式下body的部分會按指定高度和寬度呈現,而且能帶滾動條。

就是說documentElement和body能各自設定scrollTop。

那這個時候該擷取哪個就說不清了,還好一般情況并不需要這樣設定的(至少我是沒碰過)。

對于這樣的特例,知道有這個情況就行了,沒必要為了它增加太多代碼。

ps:擷取的scrollLeft/scrollLeft是不會有負值的。

contains方法是判斷參數1元素對象是否包含了參數2元素對象。

主要利用ie的contains和w3c的compareDocumentPosition來判斷。

有兩個元素坐标相關的方法:rect和clientRect。

其中rect是相對浏覽器文檔的位置,clientRect是相對浏覽器視窗的位置。

當支援getBoundingClientRect時,利用它配合getScrollLeft/getScrollTop擷取文檔位置。

否則用循環擷取offsetParent的offsetLeft/offsetTop的方式擷取。

還有三個樣式相關的方法:curStyle、getStyle、setStyle

curStyle是用來擷取元素的最終樣式表的,根據支援情況傳回getComputedStyle(w3c)或currentStyle(ie)。

ps:這裡要優先判斷getComputedStyle,因為opera也支援currentStyle。

getStyle是用來擷取元素指定樣式屬性的最終樣式值的。

而currentStyle雖然跟getComputedStyle有點像都是擷取最終樣式,但兩者得到的值的形式是不同的。

它不像getComputedStyle那樣傳回渲染完成後準确的規格統一的值,而隻是一個設定值。

而且這個值還不一定就是渲染後的準确值。

程式主要做的就是在ie中盡量擷取接近getComputedStyle的值。

首先是處理透明度,ie雖然用的是濾鏡但它的值除以100就跟w3c的"opacity"的值一樣了: 

if (/alpha\(opacity=(.*)\)/i.test(style.filter)) {

    var opacity = parseFloat(RegExp.$1);

    return opacity ? opacity / 100 : 0;

return 1;

還有"float",這個比較簡單換成"styleFloat"就行了。

擷取樣式後還有一個工作是轉換機關。當判斷得到的值是一個數值而機關又不是px的話,就會進行轉換。

方法是參考jQuery的curCSS的,了解之前先認識兩個比較少用的屬性:runtimeStyle和pixelLeft。

runtimeStyle是ie特有的屬性,用法跟style差不多,但它有着最高的優先級。

就是說如果在runtimeStyle設定了樣式就會忽略掉style中同樣的樣式。

而pixelLeft的作用是以像素px為機關傳回元素的left樣式值,ie(還能用在runtimeStyle)和opera支援。

知道這兩個東西後,就能了解它的原理了:

1,先備份原來的值:

style = elem.style, left = style.left, rsLeft = elem.runtimeStyle.left;

2,設定runtimeStyle的left為currentStyle的left:

elem.runtimeStyle.left = elem.currentStyle.left;

目的是利用runtimeStyle的優先級保證修改style後能按原來的樣式顯示;

3,設定style的left為要轉換的值,并巧妙地利用pixelLeft擷取這個值的px機關形式:

style.left = ret || 0;

ret = style.pixelLeft + "px";

4,最後恢複原來的left值:

style.left = left;

elem.runtimeStyle.left = rsLeft;

這樣就能在不改變渲染樣式的情況下轉換成像素值了。

ps:jQuery中有說明這個方法也是Dean Edwards提出的,神啊。

最後還有一個setStyle用來設定樣式,主要用來批量設定樣式和解決一些相容問題。

可以用以下兩種方式的調用:

$$D.setStyle(元素或元素集合, { 樣式屬性名: 屬性值, ... })

$$D.setStyle(元素或元素集合, 樣式屬性名, 屬性值)

第一個參數是要設定樣式的元素或元素集合,如果是單個元素會自動轉成單元素集合:

if (!elems.length) { elems = [ elems ]; }

第二個參數是一個鍵值對集合,鍵是樣式屬性名,值是對應的屬性值。

如果隻設定一個樣式,可以設第二個參數是樣式屬性名,第三個參數是屬性值,由程式建立一個鍵值對集合:

if (typeof style == "string") { var s = style; style = {}; style[s] = value; }

再用forEach曆遍元素集合,綁定的函數裡給元素設定用for in列出的所有樣式。

ps:單個元素設定單個樣式應該直接設定,除非是有相容問題。

剩下的就是解決相容問題了。

首先是透明度,ie是用濾鏡的,如果直接設定filter會把其他濾鏡都替換沒了。

參考jQuery的方法,先擷取原來的filter,替換掉透明濾鏡的部分,再加上要設定好的透明濾鏡:

elem.style.filter = (elem.currentStyle.filter || "").replace( /alpha\([^)]*\)/, "" ) +

    "alpha(opacity=" + value * 100 + ")";

挺巧妙的方法,記得值要乘以100對應w3c的"opacity"。

至于"float"就比較簡單,ie用"styleFloat"其他用"cssFloat"就行了。

【Event】

命名空間是:$$E

這個是相容性的老問題了,這裡包含三個方法:addEvent,removeEvent,fixEvent。

addEvent和removeEvent分别是添加和移除事件,以前我是用ie的attachEvent跟w3c的addEventListener做相容。

代碼中的addEvent/removeEvent根據需要在Dean代碼的基礎上做了些修改,不過原理還是一樣的。

還相容了"mouseenter"和"mouseleave"事件。

而fixEvent是用來修正event對象的相容性的,主要是添加一些w3c的屬性和方法,上面bindAsEventListener就用到了它。

這裡我隻做了ie的相容,其他都是直接使用event,這樣就做不到細緻的相容,不過夠用就行了。

jQuery的fix就做的比較完善,值得研究。

【CustomEvent】

命名空間是:$$CE

上面的Event隻能用在dom事件上,但程式有時會需要一些“自定義”事件。

CustomEvent就是用在程式的自定義事件的。

這裡主要仿照Event來做的,也包括addEvent和removeEvent,還有一個fireEvent方法用于手動觸發事件。

它的主要用途是給程式添加鈎子(hook),能同時添加多個程式。

【String】

命名空間是:$$S

我比較少做String的進階應用,是以暫時也沒什麼方法需要放進來。

裡面有一個camelize方法,用來把橫杠形式的字元串(例如"border-top")轉換成駝峰形式(例如"borderTop")。

原理是利用replace第二個參數是function時的技巧:

return s.replace(/-([a-z])/ig, function(all, letter) { return letter.toUpperCase(); });

這個可以用在樣式屬性名的轉換,在getStyle/setStyle中就使用了它。

調用方式

最後說說調用方式,跟調用一般函數方法是一樣的,隻是前面要帶上命名空間。

例如:$$.extend(...)

像$$由于本身就是function,可以直接這樣用:$$(...)

鍊式調用或許比較酷,但不太适合用在這樣的工具庫上,除非是擴充原生對象(這裡也沒有使用)。

版本下載下傳

Cloudgamer JavaScript Library v0.1

繼續閱讀