研究了一年多的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的簡化版,隻保留了關鍵的幾個部分:
跟傳統原型繼承的差別是用了一個空的函數作為初始化程式,防止非prototype屬性方法的繼承。
【Browser】
命名空間是:$$B
通過userAgent擷取浏覽器資訊,主要擷取浏覽器的類型和版本。
這裡基本是參考有啊的Browser,要了解這部分首先要知道各浏覽器的userAgent。
下面是各浏覽器(ie系列和其他浏覽器的最新版)的userAgent:
先通過判斷特有字元來判斷浏覽器類型:
擷取版本資訊就比較麻煩,有啊Browser的方法就比較巧妙(有修改):
但參考上面的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:
當有length屬性(數組或類數組)時根據索引疊代,否則用for...in曆遍對象的屬性。
由于在方法的說明中規定了"elements that are deleted are not visited",是以要用if (i in object)來判斷一下。
要注意的是當callback傳回false時會跳出循環,利用callback的傳回值,就可以在callback内部間接控制外部的跳出操作了。
然後準備自定義的疊代方法:
疊代部分給each來做,這裡隻要控制callback的具體操作就可以了。
這裡利用了閉包,使callback可以修改疊代方法的傳回值。
every和some就利用callback的傳回值跳出循環。
定義好這些方法後,再用each曆遍這些方法,并加入到命名空間中:
在方法執行時,會先判斷對象本身有沒有指定的方法,沒有的話才使用自定義的疊代方法。
【Function】
命名空間是:$$F
裡面現在隻有兩個方法:bind和bindAsEventListener。
這兩個是prototype.js裡面的經典方法了,是用來給function綁定this的。
原理是利用call/apply改變調用方法的對象:
其中用到Array.prototype.slice把arguments對象轉成數組,不知道是誰發現的,知道這個用法就行了。
ps:不止slice,其他像concat,join等也能這樣使用。
bindAsEventListener跟bind不同的是會把第一個參數設定為event對象,專門用在事件回調函數中:
其中用到fixEvent處理event的相容性,後面Event的部分會詳細說明。
【Dom】
命名空間是:$$D
這部分是工具庫中最大,最複雜也最重要的部分。
主要是儲存了一些Dom操作,并解決一般的相容性問題。
其中getScrollTop和getScrollLeft分别是擷取文檔滾動的scrollTop和scrollLeft。
一般來說如果在标準模式下應該用documentElement擷取,否則用body擷取。
但chrome和safari(都是用WebKit渲染引擎)即使在标準模式下也要用body來擷取。
這裡用的方法是:
優先擷取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"的值一樣了:
還有"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(元素或元素集合, 樣式屬性名, 屬性值)
第一個參數是要設定樣式的元素或元素集合,如果是單個元素會自動轉成單元素集合:
第二個參數是一個鍵值對集合,鍵是樣式屬性名,值是對應的屬性值。
如果隻設定一個樣式,可以設第二個參數是樣式屬性名,第三個參數是屬性值,由程式建立一個鍵值對集合:
再用forEach曆遍元素集合,綁定的函數裡給元素設定用for in列出的所有樣式。
ps:單個元素設定單個樣式應該直接設定,除非是有相容問題。
剩下的就是解決相容問題了。
首先是透明度,ie是用濾鏡的,如果直接設定filter會把其他濾鏡都替換沒了。
參考jQuery的方法,先擷取原來的filter,替換掉透明濾鏡的部分,再加上要設定好的透明濾鏡:
挺巧妙的方法,記得值要乘以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