想起几年 Dean Edwards 大神发布Base2时那句注释:
You know, writing a javascript library is awfully time consuming.
虽然javascript看似容易入门,但想深入是极其困难,因为它与java这些古典式的语言非常不同,最可恨的是那狗屎一般的DOM实现——API千差万别,bugs层出不同。因此光凭javascript是无能为力,DOM API的地位是相当重要。我们不需要hash,堆这样数据结构,加之IE6也承载不了这样复杂的东西,只需要能对DOM进行强大的处理就行了,随之很多东西都建立于其上。也基于这个原因,我的框架就直接叫dom了(终于被我想到一个与closure一样酷的名字了,泪目)。虽然有人不同意对原生对象进行扩展,但既然javascript提供了如此强大的入侵式语法,放弃它太可惜了,因此本框架还是走Prototypejs与mootools的走路。原型的充分利用能让我们真正实现那句所谓的:"Write Less, Do More!"
由于我的东西是定位于"框架",非"类库",因此涉及的东西也非常多。由于战线过长,漫长的时间中发生各种事,导致有些模块被反复修改许多次,内部版本已达到3.0以上,有时模块则还没有完工。不过不管了,先放出来吧,长期的闭门造车也是个问题。
为了开发这东西,我很久没有上来冒泡了,憋环了,唠叨了这么多。好了,先介绍一下我的核心模块。由于加载系统还没有选型好,现在但求轻松选用最简单的AJAX同步阻塞,也就是我原来的第一版实现。其次是浏览器的特性侦测,浏览器检测,对集合的操作等一些常用函数。最后还有domReady,本来想让它独立为一个模块的,不过如果颗粒过细就意味着请求很多,对服务器的压力太大了。当然,我最后放出来还是一个JS文件。我没有服务器,不能像jQuery,mootools那样提供合并模块的服务。不过,为了方便大家合并,它的设计也是非常良好,直接剪切到核心模块文件就行了。每一个模块都是一个自执行函数,不会让那些变量什么的跑出去。
下面是源码,我会逐一讲解它们的用法,如果你们有什么好的实现也请不吝赐救!
/*dom Framework version 1.0
/*dom Framework version 1.0
Copyright 2010
Dual licensed under the MIT or GPL Version 2 licenses.
author: <ruby> <rb>司徒正美<rp>(zhongqincheng)</rp></rb><rt>しとぅなさみ</rt></ruby>
http://www.cnblogs.com/rubylouvre/
*/
(function(){
var window = this,
dom = function (selector,context) {
dom.require("node");
return (this instanceof dom) ? this.init.apply(this,arguments) : new dom(selector,context)
},
//几个简写
fn = "prototype",
co = "constructor",
has = "hasOwnProperty",
tags = "getElementsByTagName",
to_s = Object[fn].toString,
//使用eval大法防止IE的条件编译在压缩时被删掉
ie = eval("''+/*@cc_on"+" @_jscript_version@*/-0")*1,
//把别人的库保存到一个临时变量中
_dom = window.dom,
//永久性命名空间,如果这个名字被其他库占用就没救了
namespace = escape(document.URL.split("#")[0]),
//判定原生对象与基本类型,第二个参数为字符串,大小写敏感
is = function (obj,type) {
return (type === "Object" && obj === Object(obj)) ||
(type === "Number" && obj === +obj ) ||
(type === "Null" && obj === null) ||
(type === "Undefined" && obj === void 0 ) ||
to_s.call(obj).slice(8,-1) === type;
},
//=========================================
// 判定是否为纯净的对象,
// 指以{},{aa:1,bb:1}或new Object(不带参数,见ecma262v5 15.2.2.1)形式得到的对象实例,用于深拷贝
//=========================================
isPureObject = function(obj){
return !!(obj && is(obj,"Object") && obj[co] === Object)
},
dontEnum = true;
for (var i in {
toString: 1
}) dontEnum = null;//只处理这三个关键的不遍历属性
if (dontEnum) dontEnum = ["constructor", "toString", "valueOf"];
//=========================================
// 特征侦探
//==========================================
dom.env = new function(){
var div = dom.parser = document.createElement("div"),
root = document.documentElement,sliceNodes = true;
div.innerHTML = ' <link/><a href="/nasami" name="'+namespace+'" style="float:left;opacity:.25;"></a>'+
'<input type="radio" name="n" checked="checked"/><object><param/></object><table></table>';
var a = div[tags]("a")[0],s = a.style; a.expando = true;
try{
Array[fn].slice.call(div.childNodes)
}catch(e){
sliceNodes = false;
}
var box = div.cloneNode(true);
box.style.width = box.style.paddingLeft = "1px";
root.insertBefore(box, root.firstChild);
var w3cBox = box.offsetWidth === 2,
mixupsName = document.getElementById(namespace) === box[tags]("a")[0],
method = a.matchesSelector || a.webkitMatchesSelector || a.mozMatchesSelector
root.removeChild(box);
return {
//某些浏览器的innerHTML会自动去掉标签外的空白
removeBlank: div.innerHTML.charAt(0) === "<",
//某些浏览器会自动为table添加tbody
insertTbody: !!div[tags]("tbody").length,
sliceNodes:sliceNodes,
//IE67会混淆id与name
mixupsName:mixupsName,
//某些浏览器会自动补全路径
convertUrl: a.getAttribute("href") !== "/nasami",
//某些浏览器使用document.getElementByTagName("*")遍历Object元素下的param元素
traverseAllElements: !!div[tags]("param").length,
//http://www.cnblogs.com/rubylouvre/archive/2010/01/09/1642978.html
traverseAllProperties: !dontEnum,
//https://prototype.lighthouseapp.com/projects/8886/tickets/264-ie-can-t-create-link-elements-from-html-literals
//某些浏览器不能通过innerHTML序列化link,style,script等元素
serializeAll: !!div[tags]("link").length,
//IE的cloneNode才是真正意义的复制,能复制动态添加的自定义属性与事件
cloneAll: !!a.cloneNode(true).expando,
//http://www.cnblogs.com/rubylouvre/archive/2010/05/16/1736711.html
//在safari下,指定了name属性的radio是无法复制checked属性的
cloneChecked: div[tags]("input")[0].cloneNode(true).checked,
//IE67是没有style特性(特性的值的类型为文本),只有el.style(CSSStyleDeclaration)
hasStyleAttribute:a.getAttribute("style") !== s,
//http://www.cnblogs.com/rubylouvre/archive/2010/05/16/1736535.html
//IE8返回".25" ,IE9pp2返回0.25,chrome等返回"0.25"
w3cOpacity: s.opacity == "0.25",
//某些浏览器不支持w3c的cssFloat属性来获取浮动样式,而是使用独家的styleFloat属性
w3cFloat: !!s.cssFloat,
//某些浏览器存在怪异模式,此时盒子模型的宽高所围成的矩形等于border围成的矩形
//就像开发商计算建筑面积时用的是IE6模型 , 业主计算套内面积时用的是W3C模型
w3cBox: w3cBox,
//IE8等支持W3C的selector APIs
querySelector:!!(document.querySelectorAll && div.querySelectorAll ),
matchesSelector: method && method.name,//返回对应的方法名,没有为undefined
//除safari与IE外默认新添加的option为选中状态
optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected,
//http://oreilly.com/catalog/jscript4/chapter/ch17.html
w3cRange:!!(document.implementation && document.implementation.hasFeature("Range","2.0"))
}
};
//=========================================
// 数组化
//==========================================
dom.slice = function(){
var item = arguments[0]||[], method = Array[fn].slice;
//IE中不能slice节点集合,它们是基于COM的,并非Object的实例,需要转换为原生数组
if(!dom.env.sliceNodes && !(item instanceof Object)){
var i = item.length,ret = [];
while(i--){
ret[i] = item[i]
}
item = ret;
}
return method.apply(item, method.call(arguments, 1));
}
//==========================================
// 混入方法
//==========================================
dom.mixin = function() {
var queue = dom.slice(arguments),deep = false,tk,sk;
if(typeof queue[0] === "boolean"){
deep = queue.shift();
}
var target = queue[0], source = queue[1]
if(queue.length===1){
target = this; source = queue[0]
}
if (target && source ){
for(var key in source){
if(source[has](key)){
tk = target[key]; sk = source[key];
if(target === tk) continue;//如window.window === window,会陷入死循环,
//如果是深拷贝,则检测source的当前属性是否为纯对象或数组,是则特殊处理它,
//对于其他类型的属性,它们的处理同浅拷贝
if ( deep && sk && ( dom.isPureObject(sk) || dom.isArray(sk) ) ) {
var clone = tk && ( dom.isPureObject(tk) || dom.isArray(tk) ) ? tk
: dom.isArray(tk) ? [] : {};
target[ key ] = dom.mixin(deep, clone, sk );
//属性值绝对不能为undefined
} else if ( sk !== undefined ) {
target[ key ] = sk;
}
}
}
if(!dom.env.traverseAllProperties && source[has]){
var d = 3;
while ((key = dontEnum[--d])) {
source[has](key) && (target[key] = source[key]);
}
}
}
if(queue.length > 2 ){
var others = queue.slice(2);//dom.slice(arguments,2);
for(var i=0,n=others.length;i<n;i++){
target = arguments.callee(deep,target,others[i]);
}
}
return target;
};
dom.include = function(obj){
dom.require("node")
dom.mixin(dom.prototype,obj)
};
dom.mixin({
//=========================================
// 浏览器嗅探
//==========================================
//除了另无它法,肯定不使用navigator.userAgent来判定浏览器。因为在第一次浏览器大战初期,
//Netscape占绝对统计地位,大部分人们不愿意兼容其他浏览器,并通过检测其UA让他们的网站
//只允许Netscape访问,这就逼使其他浏览器(包括IE)修改自己的UA伪装成Netscape来通过那
//些自以为是的脚本,于是出现每个人都声称自己是别人的局面,即使最新的IE9的UA也是这样
//Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/5.0)
ie: !!ie,//内核为trident
ie5: ie === 5.5,
ie6: ie === 5.6,
ie7: ie === 5.7,
//指IE8以下的版本或IE8运行于兼容模式下
ie67 :!-[1,] && dom.env.querySelector === false,
ie8: ie === 5.8,
ie9: ie === 5.9,
firefox: !!top.crypto,//内核Gecko
opera: is(top.opera,"Opera"),//内核 Presto 9.5为Kestrel 10为Carakan
chrome: !!(top.google && top.chrome) ,//内核V8
safari: /apple/i.test(navigator.vendor),// 内核 WebCore
quirk: !dom.env.w3cBox,
//=========================================
// 获取特殊节点
//==========================================
//参数都是可选,除了script方法要求可选参数为普通对象,其他为DOM对象
document : function (obj) {
if(obj){ //IE5.5中,HTML元素不存在ownerDocument属性
return obj.documentElement ? obj : (obj.ownerDocument || obj.document);
}else{
return window.document;
}
},
root:function(obj){
return dom.document(obj).documentElement;
},
head:function(obj){
return dom.document(obj)[tags]("HEAD")[0] ;
},
body: function(obj){
return dom.document(obj).body
},
script:function(obj){
//注意,charset属性当且仅当同时指定了src属性才有效
//注意,IE下用innerHTML序列化script标签存在bug,script标签前面要有其他节点
dom.parser.innerHTML = '<br><script type="text/javascript" charset="utf-8" ><\/script>'
return dom.mixin(dom.parser.childNodes[1],obj);
},
//=========================================
// 各种判定
//==========================================
is:is,
isPureObject:isPureObject,
//判断是否为XML文档
//http://www.cnblogs.com/rubylouvre/archive/2010/03/14/1685360.html
isXML: function(el){
var doc = dom.document(el);
return (!!doc.xmlVersion) || (!!doc.xml) || is(doc,"XMLDocument") || (!doc.body);
},
//是否这元素节点,如果传入两个参数,则判定元素的标签类型
isElement : function (obj,tag) {
if(arguments.length === 2){
tag = dom.isXML(obj) ? tag : tag.toUpperCase()
return obj.nodeName === tag ;
}
return !! (obj && obj.nodeType === 1) ;
},
isFormElement : function(obj){
return !!(obj.tagName && "name" in obj && "form" in obj);
},
isNative : function(obj){//判定是否为原生方法
return !! obj && (/\{\s*\[native code\]\s*\}/.test(String(obj)) ||
/\{\s*\/\* source code not available \*\/\s*\}/.test(String(obj)));
},
//包括Array,Arguments,NodeList,HTMLCollection,IXMLDOMNodeList与自定义类数组对象
//select.options集合(它们两个都有item与length属性)
isArrayLike : function (obj) {
if(!obj || obj.document || obj.nodeType || is(obj,"Function")) return false;
return isFinite(obj.length) ;
},
//检测是否为空对象,只检测本地属性
isEmptyObject: function(obj ) {
for ( var key in obj )
if(obj[has] && obj[has](key))
return false;
return true;
},
isInDomTree : function(node,context){
var root = (context || document).documentElement;
return node === root || dom.contains(node,root);
},
contains :function(el, root) {//分别为子节点与父节点
if (el.compareDocumentPosition)
return (el.compareDocumentPosition(root) & 8) === 8;
if (root.contains && el.nodeType === 1){
return root.contains(el) && root !== el;
}
while ((el = el.parentNode))
if (el === root) return true;
return false;
},
//=========================================
// 各种处理集合的方法
//==========================================
keys : function(obj){
var result = [],ri = 0;
if(!dom.env.traverseAllProperties && obj[has]){
obj[has]("constructor") && (result[ri++] = "constructor");
obj[has]("toString") && (result[ri++] = "toString");
obj[has]("valueOf") && (result[ri++]= "valueOf");
}
for(var key in obj)
if(obj[has] && obj[has](key))
result[ri++] = key;
return result;
},
each: function (obj, fn, bind ) {
if (dom.isArrayLike(obj)) {
for (var i = 0, n = obj.length ; i < n; i++) {
if ( fn.call(bind || obj[i], obj[i], i, obj) === false ) {//绑定作用域
break;
}
}
}else if(obj[has]){
for(var prop in obj){
if(obj[has](prop)){//value,key,obj,绑定对象默认为value
if ( fn.call(bind || obj[prop], obj[prop], prop, obj) === false ) {
break;
}
}
}
if(!dom.env.traverseAllProperties){
var d = 3;
while ((prop = dontEnum[--d])) {
if(obj[has](prop)){
if ( fn.call(bind || obj[prop], obj[prop], prop, obj) === false ) {
break;
}
}
}
}
}
return obj
},
map:function(array, fn, bind){
var result = [],ri = 0, value
for (var i = 0,n = array.length; i < n; i++){
value = fn.call(bind || array[i],array[i],i,array)
if(value != null){
result[ri++] = value;
}
}
return result;
},
filter:function(array, fn, bind){
var result = [],ri = 0;
for (var i = 0,n = array.length; i < n; i++){
if(fn.call(bind || array[i],array[i],i,array)){
result[ ri++] = array[i];
}
}
return result;
},
//类似python中的range()函数
range : function() {
var a = dom.slice(arguments);
var solo = a.length <= 1;
var start = solo ? 0 : a[0],
stop = solo ? a[0] : a[1],
step = a[2] || 1;
var len = Math.ceil((stop - start) / step);
if (len <= 0) return [];
var range = [];
for (var i = start, ri = 0; true; i += step) {
if ((step > 0 ? i - stop : stop - i) >= 0) return range;
range[ri++] = i;
}
},
merge : function(array,args) {//合并集合
array = dom.slice(array);
var arrayLength = array.length, length = args.length;
while (length--) array[arrayLength + length] = args[length];
return array;
},
inArray : function(el,arr){
if(arr.indexOf)
return arr.indexOf(el) !== -1;
for (var i = 0, n = arr.length; i < n; i++)
if (arr[i] === el) return true;
return false;
},
now: Date.now || function(){
return new Date().valueOf();
},
random : function(min, max, exact){
var range = min + (Math.random()*(max - min));
return exact === void(0) ? Math.round(range) : range.toFixed(exact);
},
//var a = {length:4,0:1,1:2,2:3,3:4};
//dom.console.log(dom.toArray(a)
toArray : function (obj) {
return obj != null ? dom.isArrayLike(obj) ? dom.slice(obj): [obj] :[]
},
alias : function(newName) {
//如果不指定新名,则随机生成一个,换言之,则进入忍者模式,需要用一个变量来接受它
newName = newName || "__dom__"+dom.now();
window.dom = _dom;
return window[namespace] = window[newName] = dom;
},
//生成一个值都相同的对象,用于if语句进行过滤
oneObject : function(array,val){
var result = {},value = val !== undefined ? val :1;
for(var i=0,n=array.length;i<n;i++)
result[array[i]] = value;
return result;
},
globalEval: function( code ) {
//IE中,window.eval()和eval()一样只在当前作用域生效。
//Firefox,Safari,Opera中,直接调用eval()为当前作用域,window.eval()调用为全局作用域。
if ( code && /\S/.test(code) ) {
var method = window.execScript ? "execScript" : "eval"
try{
window[method](code);
}catch(e){}
}
},
uuid:1,
expando : "dom" + (new Date-0),//设在元素上的自定义属性
noop:function(){},
cacher : function(fn, bind, post) {
return function (){
var self = arguments.callee,
array = dom.slice(arguments),
args = array.join("\u25ba"),//►,一个黑色的三角形
cache = self.cache = self.cache || {},
count = self.count = self.count || [];
if (cache.hasOwnProperty(args)) {
return post ? post(cache[args]) : cache[args];
}
count.length >= 1e3 && delete cache[count.shift()];
count.push(args);
cache[args] = fn.apply(bind, array);
return post ? post(cache[args]) : cache[args];
}
},
//此方法只对FF浏览器有效,调试用
//https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Object/noSuchMethod
__noSuchMethod__: function(name, args) {
dom.require("console")
dom.console.error("尝试用以下参数("+args+")执行当前对象的'" + name + "'方法遭遇失败!");
},
ready: function( fn ) {//domReady
var self = arguments.callee;
self.init();
if ( self.status ) {
fn();
} else if ( self.list) {
self.list.push( fn );
}
}
});
dom.mixin(dom.ready,{
list:[],
status:false,
init: function(){
if (arguments.callee.used ) {
return;
}
arguments.callee.used = true;
var fire = dom.ready.fire
//用于window.onload的内部
if ( document.readyState === "complete" ) {
return fire();
}
if (-[1,]) {//Safari3.1+,Chrome,Firefox2+ ,Opera9+,听说IE9也支持DOMContentLoaded
//https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
document.addEventListener( "DOMContentLoaded", function() {
document.removeEventListener( "DOMContentLoaded", arguments.callee , false );
fire();
}, false );
} else {//IE5 IE6 IE7 IE8
//http://dev.jquery.com/ticket/2614
//当页面包含图片时,onreadystatechange事件会触发在window.onload之后,
//换言之,它只能正确地执行于页面不包含二进制资源或非常少或者被缓存时
document.attachEvent("onreadystatechange", function(e) {
if ( document.readyState == "complete" ) {
document.detachEvent("onreadystatechange", arguments.callee );
fire();
}
});
//doScroll方法通常只会正确执行一个全新的页面
(function(){
if ( dom.ready.status ) {
return;
}
//doScroll存在于所有标签而不管其是否支持滚动条
//当DOM树时我们就可以调用其doSroll方法
//若用document.documentElement.doScroll(),我们需要判定其是否位于顶层document
//http://msdn.microsoft.com/en-us/library/ms536414(VS.85).aspx
var node = new Image
try {
node.doScroll();
node = null//防止IE内存泄漏
} catch( e ) {
//javascrpt最短时钟间隔为16ms,这里取其倍数
//http://blog.csdn.net/aimingoo/archive/2006/12/21/1451556.aspx
setTimeout( arguments.callee, 64 );
return;
}
fire();
});
}
},
fire: function() {
if ( !dom.ready.status ) {
if ( !document.body ) {
return setTimeout(arguments.callee, 16 );
}
dom.ready.status = true;
if ( dom.ready.list ) {
for(var i=0, fn;fn = dom.ready.list[i++];)
fn();
delete dom.ready.list;
}
}
}
});
//添加更多见词明义的判定
dom.each(["Array","Function","Number","String","Undefined","Null"],function(name){
dom["is"+name] = function(obj){
return is(obj,name);
}
});
// ECMA-5 15.4.3.2
if(dom.isNative(Array.isArray)){
dom.isArray = Array.isArray;
}
// ECMA-5 15.2.3.14
if(dom.isNative(Object.keys)){
dom.keys = Object.keys;
}
(function(){
//游览器环境不能使用Msxml2.XMLHTTP.5.0与Msxml2.XMLHTTP.4.0
//http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
var s = ["XMLHttpRequest",
"ActiveXObject('Msxml2.XMLHTTP.6.0')",
"ActiveXObject('Msxml2.XMLHTTP.3.0')",
"ActiveXObject('Msxml2.XMLHTTP')",
"ActiveXObject('Microsoft.XMLHTTP')"];
if(dom.ie7 && location.protocol === "file:"){
s.shift();
}
for(var i = 0 ,el;el=s[i++];){
try{
if(eval("new "+el)){
dom.xhr = new Function( "return new "+el)
break;
}
}catch(e){}
}
})();
//=========================================
// 核心模块 模块加载系统
//==========================================
//dom有三个重要的信息储存区,
//一个是env用于储存与浏览器有关的东西
//一个是lib用于储存与框架有关的东西
//一个是cache用于储存运行收集的东西
dom.lib = {
loaded:{},
//获取核心模块所在的JS文件所在的文件夹路径
baseUrl :(function(){
var result;
try{
throw ""
}catch(e){
result = e.fileName || e.sourceURL;
}
if(!result){
var scripts = document[tags]('script'),
script = scripts[scripts.length - 1];
result = script.src;
}
return result.substr( 0, result.lastIndexOf('/'));
})()
}
//name为模块名,create不存在此属性是否创建一个空对象
dom.mixin({
//创建一个命名空间
namespace:function(name,create,context){
var parts=name.split("."),obj = context || window;
for(var i=0, p; obj && (p=parts[i]); i++){
if(i == 0 && this[p]){
p = this[p];
}
obj = (p in obj ? obj[p] : (create ? obj[p]={} : undefined));
}
return obj;
},
//同步加载模块
require : function(name,timeout){
name = name.indexOf("dom.") === 0 ? name.slice(4):name
timeout = timeout || 2000
var module = "dom."+name,url;
if(dom.lib.loaded[name]) return
//处理dom.node(http://www.cnblogs.com/rubylouvre/dom/node.js)的情形
var _url = module.match(/\(([^)]+)\)/);
url = _url && _url[1] ? _url[1] : dom.lib.baseUrl+"/"+ module.replace(/\./g, "/") + ".js";
var xhr = dom.xhr();
xhr.open("GET",url,false);
xhr.setRequestHeader("If-Modified-Since","0");
xhr.send(null);
dom.globalEval( xhr.responseText|| "")
setTimeout(function(){
try{
xhr.abort()
}catch(e){}
},timeout);
},
//提供命名空间,标识此模块已经加载过
provide : function(name){
name = name.indexOf("dom.") === 0 ? name.slice(4):name
dom.lib.loaded[name] = true;
dom.namespace("dom."+name,true)
}
});
//====================添加其他模块======================
window[namespace] = window.dom = dom;
})();
![所有模块](https://i-blog.csdnimg.cn/blog_migrate/a70878eed51597f8e9813a442ff27288.jpeg)
许多代码我以前都放过出来了,应该没有什么难度,我也将会在下一篇讲解它们的。如果到时能把chm文档搞出来就最好不过了,现在姑且先放出让大家瞧瞧。最后还是那句老话,如果某某方法有什么好的实现,请不吝赐救!
下载回来后对着文件点右键-->属性-->解除锁定。