本文深入解析JavaScript框架设计的核心模块,涵盖命名空间隔离与冲突解决、对象属性混合策略、类数组对象转换以及DOM就绪检测机制。重点探讨jQuery的noConflict处理多库冲突、mix函数的覆盖逻辑、slice方法在旧版IE中的兼容实现,以及基于readyState和doScroll的DOMReady跨浏览器解决方案。
命名空间与冲突解决
1.命名空间:
在JavaScript中,命名空间主要通过对象属性来实现层级扩展。例如,定义一个名为A的对象,其包含B和C两个属性,且B和C本身也是对象,结构为A={B:{},C:{}}。此时,可以在B和C中分别定义相同的方法或属性,二者互不干扰,形成独立的命名空间。调用时,通过A.B.like()或A.C.like()即可访问对应对象内的方法。需要注意的是,A对象本身通常挂载在window对象下。
然而,当页面同时引入多个库(如jquery.js和prototype.js)时,若它们都在window对象上定义了$属性,就会发生命名冲突。
为解决此冲突,jQuery提供了noConflict()方法。其执行流程如下:页面先引入prototype.js,此时$属性被prototype占用;随后引入jquery.js,jQuery将原有的$属性暂存于_$变量中,并接管$属性供自身使用。此时可通过$调用jQuery方法。若需切换回prototype,调用$.noConflict()即可将$还原为prototype对象,从而继续使用prototype的方法。
代码如下:
var _$ = window.$,_jQuery= window.jQuery;
noConflict:function(deep){
window.$ = _$;
if(deep) window.jQuery = _jQuery;
return jQuery; //返回值,你可以赋值给其他变量名,比如,chaojidan,这样你就可以通过chaojidan调用jQuery中的方法了。
}
对象扩展机制
2.对象扩展:
在建立命名空间后,通常需要扩展功能。例如,将A对象的所有属性和方法复制到B对象中,无需手动逐一编写。
代码如下:
function mix(target , source){
var args = [].slice.call(arguments),i=1,
isCover = typeof args[args.length-1] =="boolean" ? args.pop():true; //不写,默认为true,默认是覆盖。
if(args.length == 1){
target = !this.window? this:{};
//如果只有一个对象参数时,就扩展this对象。比如:我在A对象的context中调用mix(B),那么这时的this就是A,因此会把B的属性和方法添加到A对象中。但是如果是在window中调用mix(B),就会把B对象中的属性和方法添加到一个空的对象中,并返回这个空的对象,以防覆盖window对象中同名的属性和方法。(只有window对象拥有window属性)
i =0;
}
while((source = args[i++])){
for(key in source){
if(isCover || !(key in target)) //如果覆盖,就直接赋值,如果不覆盖,先判断key是否存在目标对象中,如果存在,就不赋值
{
target[key] = source[key];
}
}
}
return target;
}
大公司面试官很喜欢问数组的查重,大家可以去看看,数组中的每项可以是对象,而对象A和对象B即便一样的属性和方法,但是也是不相等的。字符串和数字,比如123和"123"等,网上一搜,就能找到很全的方法。
类数组对象转换
3.数组化:
浏览器中存在多种类数组对象,如arguments、document.forms、document.links、form.elements、document.getElementsByTagName、childNodes等(HTMLCollection,NodeList)。
此外,还有一种特殊格式的自定义对象:
代码如下:
var arrayLike = {
0:"a",
1:"b",
length:2
}
此对象写法,就是jQuery对象的写法。
我们需要将上述类数组对象转换为真正的数组对象。
虽然[].slice.call方法通常能解决此问题,但在旧版本IE中,HTMLCollection和NodeList并非Object的子类,无法直接使用该方法的call调用。
因此,需要重写一个兼容的slice方法。
代码如下:
A.slice = window.dispatchEvent ? function(nodes,start,end){ return [].slice.call(nodes,start,end); }
//如果window有dispatchEvent 属性就证明支持[].slice.call方法,能力检测。
:function(nodes,start,end){
var ret = [],n=nodes.length;
if(end == undefined || typeof end === "number" && isFinite(end)){ //&&优先级高于||,因此end没写,或者end是有限数字就进入
start = parseInt(start,10) || 0; //如果start不存在或者不是数字,则就赋值为0.
end = end == undefined ? n:parseInt(end,10); //如果end不存在,则赋值为n.
if(start < 0) start + = n;
if(end< 0) end + = n;
if(end>n) end = n;
for(var i = start;i<end;i++){
ret[i-start] = nodes[i]; //低版本IE使用数组赋值的形式
}
}
return ret;
}
类型判断方法
4.类型的判断:
JavaScript有五种基本数据类型:null、undefined、boolean、number、string。
复杂数据类型包括:Object、Function、RegExp、Date以及自定义对象(如Person)。
typeof通常用于判断基本类型,instanceof用于判断对象类型,但二者均有局限。例如,iframe内的数组实例并非父窗口的Array实例,使用instanceof会返回false(此点曾在360校招中出现)。此外,typeof new Boolean(true)返回"object",涉及包装对象的概念(详见《JavaScript高级程序设计》)。
有人使用typeof document.all判断IE,但这并不安全,因为Chrome和Firefox也支持该属性。在Chrome中,typeof document.all返回undefined,但document.all本身是HTMLAllCollection对象,存在读取值与类型判断不一致的情况。
目前推荐使用Object.prototype.toString.call方法判断类型,该方法可直接输出对象内部的[[Class]]属性。但在IE8及以下版本中,window对象无法使用此方法(IE6/7/8下window == document为true,而document == window为false)。
nodeType用于区分节点类型:1表示元素(Element),2表示属性(attribute),3表示文本(Text),9表示document。
jQuery中判断类型的实现方法如下:
代码如下:
class2type ={}
jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(i,name){
class2type [ "[object " + name + "]" ] = name.toLowerCase();
//class2type = {"[object Boolean]":boolean,"[object Number ]":number ,"[object String ]":string ,"[object Function ]":function ,"[object Array ]":array ......}
});
jQuery.type = function(obj){ //如果obj是null,undefined等,就返回字符串"null","undefined"。如果不是,就调用toString方法,如果可以调用就判断,出错就返回object(IE低版本下的window,Dom等ActiveXobject对象)
return obj == null ? String(obj) : class2type [ toString.call(obj) ] || "object";
}
DOM就绪检测方案
5.domReady
操作DOM节点前,必须确保DOM树已构建完成。传统做法是使用window.onload,但该事件需等待所有资源(图片、Flash等)加载完毕才触发。为提升页面响应速度,只需DOM树构建完成即可执行脚本,无需等待其他资源。
因此引入了DOMContentLoaded事件,在DOM树构建完成后触发。但旧版IE不支持该事件,需采用兼容方案(hack)。
代码如下:
if(document.readyState === "complete"){ //以防Dom文档加载完成后才加载js文件。这时通过此判断来执行fn方法(你要执行的方法)。因为文档加载完成后,document.readyState的值为complete
setTimeout(fn); //异步执行,可以让它后面的代码先执行。这里是jQuery里面的用法,可以不用理解。
}
else if(document.addEventListener){//支持DOMContentLoaded事件
document.addEventListener("DOMContentLoaded",fn,false); //冒泡
window.addEventListener("load",fn,false); //以防DOM树构建好之后才加载js文件。这时不会触发DOMContentLoaded事件(已经触发结束了),只会触发load事件
}
else{
document.attachEvent("onreadystatechange",function(){//针对IE下的iframe安全,有时会优先onload执行,有时不会。
if(document.readyState ==="complete"){
fn();
}
});
window.attachEvent("onload",fn); //总会起到作用,以防其他监听事件没获取到,这样至少可以通过onload事件触发fn方法。
var top = false;//看是否在iframe中
try{//window.frameElement即为包含本页面的iframe或frame对象。没有则为null。
top = window.frameElement == null && document.documentElement;
}catch(e){}
if(top && top.doScroll){ //如果没有iframe,并且是IE
(function doScrollCheck(){
try{
top.doScroll("left");//IE下,如果Dom树构建好,就可以调用html的doScroll方法
}catch(e){
return setTimeout(doScrollCheck,50); //如果还没构建好,则继续监听
}
fn();
})
}
}
fn方法中必须包含移除所有的绑定事件。
当然IE还可以使用script defer hack,原理就是:指定了defer的script会在DOM树构建完后才执行。但是这需要添加额外的js文件,很少在单独的库里面用到。
使用原理:在文档中添加script标签,并用script.src = "xxx.js",监听script的onreadystatechange事件,当this.readyState == "complete"时,就执行fn方法。
也就是说,DOM构建好之后,xxx.js才会执行,它的this.readyState才会变成complete。
以上便是javascript框架设计的第一章的读书笔记了,内容比较精简,方便大家更好的理解这章的基本内容。