主要集合了我写js时一些常用的方法,并参考了prototype.js,jquery,google,百度,有啊等框架。
工具库已经在近几个效果中使用:
JavaScript 多级联动浮动菜单 (第二版)
JavaScript 浮动定位提示效果
JavaScript Table行定位效果
JavaScript Table排序
这个工具库的主要特点是:
【跨浏览器】
能在以下浏览器使用: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,事件的相关操作和兼容处理;
$$S:代表String,保存字符串的相关方法。
虽然我不反对有节制地扩展原生对象,但可以的话还是避免命名污染吧。
用多个命名空间(而不用单个)只因管理容易,用起来方便。
用两个$,不是要更多美刀(虽然很想),而是避免跟流行的框架冲突。
使用全部变量时我没有用window.x的形式,因为那样会导致一些问题,具体参考这里。
【使用匿名函数】
貌似是jquery发扬光大的,就是把代码嵌在一个function里面。
其实就是利用闭包,一来可以使用局部变量,二来可以防止命名冲突。
【使用对象检测】
“对象检测天生地优于浏览器检测”,出自“ppk谈JavaScript”的真理。
能用对象检测的都尽量用,当然有些实在太难搞的也不要太执着。
对象检测方面jQuery的support做的很好,建议去看一下。
追求目标是:
【小体积】
这里的体积不是说字符的多少,而是属性和方法的数量。
工具库的属性和方法必须是很有用的,最好是“不得不加”的。
当然随着使用的增加,工具库也会慢慢的扩大,但要坚持这个原则。
【高效率】
高效是不变的追求,当然是在权衡利弊之后。
说到高效不得不佩服一下google,它不但代码追求效率,而且下载的代码是已经经过浏览器检测的。
具体可以自己用各个浏览器下载看看试试。
建立目的是:
【整合常用方法】
把常用的方法整合到一起,既利于代码复用,也便于维护。
但也不可避免地添加一些无关的方法,从而增加了代码量,降低了效率。
【解决兼容问题】
解决一些常见的兼容性问题,减轻编码负担。
各个部分说明
【Object】
命名空间是:$$
$$本身就是最常用的方法:document.getElementById
它还包括两个方法:extend和deepextend。
其中extend跟prototype.js的Object.extend是一样的,用来扩展对象,是用得最久的方法之一了。
而deepextend是深度扩展,这里的深度跟深度复制里面的意思差不多,参考的是jQuery的extend。
【Browser】
命名空间是:$$B
通过userAgent获取浏览器信息,主要获取浏览器的类型和版本。
这里基本是参考有啊的Browser,要了解这部分首先要知道各浏览器的userAgent。
下面是各浏览器的userAgent(除了ie其他是目前最新版):
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 = i;
}
}
if (b.safari) {
vMark = "version";
}
b.version = 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:判断对象是否数组
forEach:对数组中的每个元素都执行一次指定的函数(callback)。(参考地址)
filter:对数组中的每个元素都执行一次指定的函数(callback),并且创建一个新的数组,该数组元素是所有回调函数执行时返回值为 true 的原数组元素。(参考地址)
every:对数组中的每个元素都执行一次指定的函数(callback),直到此函数返回 false,如果发现这个元素,every 将返回 false,如果回调函数对每个元素执行后都返回 true ,every 将返回 true。(参考地址)
some:对数组中的每个元素都执行一次指定的函数(callback),直到此函数返回 true,如果发现这个元素,some 将返回 true,如果回调函数对每个元素执行后都返回 false ,some 将返回 false。(参考地址)
map:对数组中的每个元素都执行一次指定的函数(callback),并且以每次返回的结果为元素创建一个新数组。(参考地址)
indexOf:比较 searchElement 和数组的每个元素是否绝对一致(===),当有元素符合条件时,返回当前元素的索引。如果没有发现,就直接返回 -1 。(参考地址)
lastIndexOf:比较 searchElement 和数组的每个元素是否绝对一致(===),当有元素符合条件时,返回当前元素的索引。如果没有发现,就直接返回 -1 。(参考地址)
以上方法除了isArray,都是JavaScript 1.6里数组增加的方法,具体可以去mozilla查看。
不止数组能使用这些方法,只要对象能像数组那样历遍就能使用,例如NodeList对象,arguments对象。
【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来获取。
这里用的方法是:
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是用来获取元素指定样式属性的最终样式值的。
支持getComputedStyle的直接用它获取样式的computed value就行,关于computed value可以参考这里。
而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中同样的样式。
具体可以参考birdshome的“关于HTML Object中三个Style实例的区别”和“关于使用runtimeStyle属性问题讨论”
而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 + ")";
Quite a clever method, remember to multiply the value by 100 corresponding to w3c's "opacity".
As for "float", it is relatively simple. For IE, use "styleFloat" and for others, just use "cssFloat".
【Event】
The namespace is: $$E
This is an old issue of compatibility. It contains three methods: addEvent, removeEvent, and fixEvent.
addEvent and removeEvent are add and remove events respectively. In the past, I used IE's attachEvent to be compatible with w3c's addEventListener.
But after seeing Dean Edwards’ method, I switched to his method. In addition to better compatibility, it can also solve some bugs (see the bug section of cloneNode here for details).
The addEvent/removeEvent in the code has been modified based on Dean's code as needed, but the principle is still the same.
The fixEvent is used to correct the compatibility of the event object, mainly to add some w3c attributes and methods, which is used by bindAsEventListener above.
Here I only made the compatibility with IE, and the others used event directly. This way I cannot achieve detailed compatibility, but it is enough.
jQuery’s fix is relatively complete and worth studying.
[String]
The namespace is: $$S
I rarely do advanced applications of String, so there is no method that needs to be included for the time being.
There is a camelize method inside, which is used to convert strings in the form of horizontal bars (such as "border-top") into camel case form (such as "borderTop").
The principle is to use the trick when the second parameter of replace is a function:
return s.replace(/-([a-z])/ig , function(all, letter) { return letter.toUpperCase(); });
This can be used to convert style attribute names, and it is used in getStyle/setStyle.
Calling method
Finally, let’s talk about the calling method, which is the same as calling a general function method, except that it must be preceded by a namespace.
For example: $$.extend(...)
Because $$ itself is a function, you can use it directly like this: $$(...)
Chain calls may be cooler, but not very practical Suitable for use in such tool libraries, unless extending native objects (not used here either).
Full code
/*!
* Cloudgamer JavaScript Library v0.1
* Copyright (c) 2009 cloudgamer
* Blog: http://cloudgamer.cnblogs.com/
* Date: 2009-10-29
*/
var $$, $$B, $$A, $$F, $$D, $$E, $$S;
(function(){
var O, B, A, F, D, E, S;
/*Object*/
O = function (id) { return "string" == typeof id ? document.getElementById(id) : id; };
O.extend = function (destination, source) {
for (var property in source) {
destination[property] = source[property];
}
return destination;
};
O.deepextend = function (destination, source) {
for (var property in source) {
var copy = source[property];
if ( destination === copy ) continue;
if ( typeof copy === "object" ){
destination[property] = arguments.callee( destination[property] || {}, copy );
}else{
destination[property] = copy;
}
}
return destination;
};
/*Browser*/
/*from youa*/
B = (function(ua){
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)
};
var vMark = "";
for (var i in b) {
if (b[i]) {
vMark = i;
}
}
if (b.safari) {
vMark = "version";
}
b.version = RegExp("(?:" vMark ")[\/: ]([\d.] )").test(ua) ? RegExp.$1 : "0";
b.ie = b.msie;
b.ie6 = b.msie && parseInt(b.version) == 6;
b.ie7 = b.msie && parseInt(b.version) == 7;
b.ie8 = b.msie && parseInt(b.version) == 8;
return b;
})(window.navigator.userAgent.toLowerCase());
/*Array*/
A = {
isArray: function( obj ) {
return Object.prototype.toString.call(obj) === "[object Array]";
},
forEach: function( array, callback, thisp ){
if (array.forEach) {
array.forEach(callback, thisp);
} else {
for ( var i = 0, len = array.length; i < len; i ) {
callback.call( thisp, array[i], i, array );
}
}
},
filter: function( array, callback, thisp ){
if (array.filter) {
return array.filter(callback, thisp);
} else {
var res = [];
for ( var i = 0, len = array.length; i < len; i ) {
callback.call( thisp, array[i], i, array ) && res.push(array[i]);
};
return res;
}
},
every: function( array, callback, thisp ){
if (array.every) {
return array.every(callback, thisp);
} else {
for ( var i = 0, len = array.length; i < len; i ) {
if ( !callback.call( thisp, array[i], i, array ) ) return false;
};
return true;
}
},
some: function( array, callback, thisp ){
if (array.some) {
return array.some(callback, thisp);
} else {
for ( var i = 0, len = array.length; i < len; i ) {
if ( callback.call( thisp, array[i], i, array ) ) return true;
};
return false;
}
},
map: function( array, callback, thisp ){
if (array.map) {
return array.map(callback, thisp);
} else {
var res = [];
for ( var i = 0, len = array.length; i < len; i ) {
res.push( callback.call( thisp, array[i], i, array ) );
};
return res;
}
},
indexOf: function( array, elt ){
if (array.indexOf) {
return array.indexOf(elt);
} else {
for ( var i = 0, len = array.length; i < len; i ) {
if ( array[i] === elt ) return i;
};
return -1;
}
},
lastIndexOf: function( array, elt, from ){
if (array.lastIndexOf) {
return array.lastIndexOf(elt);
} else {
var len = array.length;
if ( isNaN(from) || from >= len - 1 ) {
from = len - 1;
} else {
from = from < 0 ? Math.ceil(from) len : Math.floor(from);
}
for ( ; from > -1; from-- ) { if ( array[i] === elt ) return i; };
return -1;
}
}
};
/*Function*/
F = (function(){
var slice = Array.prototype.slice;
return {
bind: function( fun, thisp ) {
var args = slice.call(arguments, 2);
return function() {
return fun.apply(thisp, args.concat(slice.call(arguments)));
}
},
bindAsEventListener: function( fun, thisp ) {
var args = slice.call(arguments, 2);
return function(event) {
return fun.apply(thisp, [E.fixEvent(event)].concat(args));
}
}
};
})();
/*Dom*/
D = {
getScrollTop: function(node){
var doc = node ? node.ownerDocument : document;
return doc.documentElement.scrollTop || doc.body.scrollTop;
},
getScrollLeft: function(node){
var doc = node ? node.ownerDocument : document;
return doc.documentElement.scrollLeft || doc.body.scrollLeft;
},
contains: function(a, b){
return (this.contains = a.compareDocumentPosition
? function(a, b){ return !!(a.compareDocumentPosition(b) & 16); }
: function(a, b){ return a != b && a.contains(b); }
)(a, b);
},
rect: function(node){
var left = 0, top = 0, right = 0, bottom = 0;
//ie8的getBoundingClientRect获取不准确
if ( !node.getBoundingClientRect || B.ie8 ) {
var n = node;
while (n) { left = n.offsetLeft, top = n.offsetTop; n = n.offsetParent; };
right = left node.offsetWidth; bottom = top node.offsetHeight;
} else {
var rect = node.getBoundingClientRect();
left = right = this.getScrollLeft(node); top = bottom = this.getScrollTop(node);
left = rect.left; right = rect.right;
top = rect.top; bottom = rect.bottom;
};
return { "left": left, "top": top, "right": right, "bottom": bottom };
},
clientRect: function(node){
var rect = this.rect(node), sLeft = this.getScrollLeft(node), sTop = this.getScrollTop(node);
rect.left -= sLeft; rect.right -= sLeft;
rect.top -= sTop; rect.bottom -= sTop;
return rect;
},
curStyle: function(elem){
return (this.curStyle = document.defaultView
? function(elem){ return document.defaultView.getComputedStyle(elem, null); }
: function(elem){ return elem.currentStyle; }
)(elem);
},
getStyle: function(elem, name){
return (this.getStyle = document.defaultView
? function(elem, name){
var style = document.defaultView.getComputedStyle(elem, null);
return name in style ? style[ name ] : style.getPropertyValue( name );
}
: function(elem, name){
var style = elem.currentStyle;
//透明度 from youa
if (name == "opacity") {
if (/alpha(opacity=(.*))/i.test(style.filter)) {
var opacity = parseFloat(RegExp.$1);
return opacity ? opacity / 100 : 0;
}
return 1;
};
if (name == "float") { name = "styleFloat"; }
var ret = style[ name ] || style[ S.camelize( name ) ];
//单位转换 from jqury
if ( !/^-?d (px)?$/i.test( ret ) && /^-?d/.test( ret ) ) {
style = elem.style, left = style.left, rsLeft = elem.runtimeStyle.left;
elem.runtimeStyle.left = elem.currentStyle.left;
style.left = ret || 0;
ret = style.pixelLeft "px";
style.left = left;
elem.runtimeStyle.left = rsLeft;
}
return ret;
}
)(elem, name);
},
setStyle: function(elems, style, value) {
if (!elems.length) { elems = [ elems ]; }
if (typeof style == "string") { var s = style; style = {}; style[s] = value; }
A.forEach(elems, function(elem){
for (var name in style) {
var value = style[name];
if (name == "opacity" && B.ie) {
//ie透明度设置 from jqury
elem.style.filter = (elem.currentStyle.filter || "").replace( /alpha([^)]*)/, "" )
"alpha(opacity=" value * 100 ")";
} else if (name == "float") {
elem.style[ B.ie ? "styleFloat" : "cssFloat" ] = value;
} else {
elem.style[ S.camelize( name ) ] = value;
}
};
});
}
};
/*Event*/
E = (function(){
/*from dean edwards*/
var addEvent, removeEvent, guid = 1;
if ( window.addEventListener ) {
addEvent = function(element, type, handler){
element.addEventListener(type, handler, false);
};
removeEvent = function(element, type, handler){
element.removeEventListener(type, handler, false);
};
} else {
addEvent = function(element, type, handler){
if (!handler.$$guid) handler.$$guid = guid ;
if (!element.events) element.events = {};
var handlers = element.events[type];
if (!handlers) {
handlers = element.events[type] = {};
if (element["on" type]) {
handlers[0] = element["on" type];
}
}
handlers[handler.$$guid] = handler;
element["on" type] = handleEvent;
};
removeEvent = function(element, type, handler){
if (element.events && element.events[type]) {
delete element.events[type][handler.$$guid];
}
};
function handleEvent() {
var returnValue = true, event = fixEvent();
var handlers = this.events[event.type];
for (var i in handlers) {
this.$$handleEvent = handlers[i];
if (this.$$handleEvent(event) === false) {
returnValue = false;
}
}
return returnValue;
};
}
function fixEvent(event) {
if (event) return event;
event = window.event;
event.pageX = event.clientX D.getScrollLeft();
event.pageY = event.clientY D.getScrollTop();
event.target = event.srcElement;
event.stopPropagation = stopPropagation;
event.preventDefault = preventDefault;
if(event.type == "mouseout") {
event.relatedTarget = event.toElement;
}else if(event.type == "mouseover") {
event.relatedTarget = event.fromElement;
}
return event;
};
function stopPropagation() { this.cancelBubble = true; };
function preventDefault() { this.returnValue = false; };
return {
"addEvent": addEvent,
"removeEvent": removeEvent,
"fixEvent": fixEvent
};
})();
/*String*/
S = {
camelize: function(s){
return s.replace(/-([a-z])/ig, function(all, letter) { return letter.toUpperCase(); });
}
};
/*System*/
// remove css image flicker
if (B.ie6) {
try {
document.execCommand("BackgroundImageCache", false, true);
} catch(e) {}
};
/**/
$$ = O; $$B = B; $$A = A; $$F = F; $$D = D; $$E = E; $$S = S;
})();
打包下载