JavaScript 高級框架設計 (二)
上一篇,JavaScript高級框架設計(一)我們 實現了對tag標簽的選擇
下來我們實現對id的選擇,即id選擇器.
我們將上一篇的get命名為getTag(),然后再編寫一個getId(),和getClass()
然后在總的get方法中調用,這樣做的好處就是模塊化,便於維護.
我所有的代碼都會托管到github上.
01.js
var getId = function (id, result) {
result = result || [];
// 由於獲取的Id是一個元素,所以這里使用call.
result.push.call(result,document.getElementById(id));
return result;
}
var getClass = function (className, result) {
result = result || [];
result.push.apply(result, document.getElementsByClassName(className));
return result;
}
好了,現在分別實現了三個get方法,但是怎么根據選擇器表達式來調用者三個方法呢?
下面引出一個關鍵正則:
/^(?:#([\w-]+)|\.([\w-]+)|([\w]+)|(\*))$/
這段正則目的就是匹配id,類和標簽選擇器,並且通過exec可以拿到匹配的內容.
那么,
var m = expr.exec(selector);
m[1] 就表示id選擇器,m[2]就表示類選擇器,m[3]表示標簽選擇器,m[4]表示通用選擇器
下面的代碼就順理成章了.
01.js
var get = function (selector, result) {
result = result || [];
var rquickExpr = /^(?:#([\w-]+)|\.([\w-]+)|([\w]+)|(\*))$/,
m = rquickExpr.exec(selector);
if (!m) {
return result;
}
if (m[1]) {
result = getId(selector);
} else if (m[2]) {
result = getClass(selector);
} else if (m[3]) {
result = getTag(selector);
} else if (m[4]) {
result = getAll(selector);
}
return result;
};
如果沒有匹配到,就直接返回result,如果有匹配到,那么就一定是選擇器中的一種,通過分組進行判斷.
在對每個分組的判斷可以優化,
if (m[1]) {
result = getId(m[1], result);
} else if (m[2]) {
result = getClass(m[2], result);
} else {
result = getTag(m[3] || '*', result)
}
現在這樣就已經可以完成對單獨選擇器的匹配了.
我們來進行測試一遍.
01.html
<body>
<div>1</div>
<div id="k">2</div>
<div class="pawn">3</div>
<p></p>
<p></p>
</body>
<script src="01.js"></script>
01.js
onload = function(){
each(get("p"),function(){
this.style.backgroundColor = "green";
})
each(get("#k"),function(){
this.style.backgroundColor = "red";
});
each(get(".pawn"),function () {
this.style.backgroundColor = "pink";
})
}
下面是結果,是不是歐了?
單獨的選擇器似乎已經完成,但是一個問題出現了? 有考慮過兼容性嗎?
document.getElementsByClassName() ie8以下是不兼容的!
下面引入兩個問題:
- 怎么判斷兼容性?
- 怎么解決不兼容?
怎么判斷兼容性?
我們通過chrome watch寫上document
然后查看getElementsByClassName() 方法.
經過不斷在原型鏈中搜索,
我們發現其嵌套很深,每次調用都在原型鏈中搜索.
當我們這樣的代碼在IE8 以下運行這樣的代碼時,
if(docuemnt.getElementsByClassName(className)){
//
}
它會一直向上尋找,直到Object對象,效率很低.
如果按照上面的代碼,那么每調用一次就要就行一次能力檢測,效率肯定太低了.
怎么辦呢?
方法就是只做一次能力檢測,並把它記錄下來.
其中,jQuery中就是這么做的,它擁有一個專門的功能檢測模塊,$.support,里面的屬性包含存在兼容問題的方法或屬性,並用布爾值來記錄.
所以在我們這里,在全局作用域中(最終都要用成沙箱或者閉包)提供一個support對象,里面提供所有的以方法名相同的屬性,值均為布爾值,在瀏覽器加載js的開始的時候,就進行能力判斷,凡是涉及到能力檢測的時候就直接檢測support即可.
所以我們在代碼開頭加入
var support = {};
support.getElementsByClassName = !!document.getElementsByClassName;
那么以后再需要能力檢測的時候,可以這么寫:
if(support.getElementsByClassName){
//...
}
似乎已經可以了.
但是,有一種攻擊叫做注入漏洞攻擊,我在文件開頭嵌入這樣的代碼(chrome是允許內嵌js代碼的!!!):
document.getElementsByClassName = 'pawn';
那么上面的檢測還有用嗎?
看看jQuery怎么做的,不僅要判斷它是否存在,還要判斷其能力是否符合要求.
看下面的代碼
var support = {};
support.getElementsByClassName = function () {
if (!document.getElementsByClassName) {
return false;
}
var div = document.createElement('div'),
pWithClass = document.createElement('p');
pWithClass.className = 'pawn-pawn-pawn';
div.appendChild(pWithClass);
var res = div.getElementsByClassName('pawn-pawn-pawn');
return res[0] === pWithClass;
} ();
然后我們在chrome控制台輸入
support.getElementsByClassName
在ie8里,
可以看到,這樣很有用.
現在查看jQuery源代碼
看它的方法,實在是太簡潔了,jQuery留給我們的,就是敬仰!!
現在檢查已經完了,怎么解決不兼容問題呢?
解決兼容
怎么解決呢? 首先進行能力檢測,如果有,就直接調用,如果沒有,實現了一個自己的myGetClassName()
方法.
原理很簡單,通過通用選擇器查找到所有的元素,然后看它有木有這個類名,如果有就push.
05.js
var getClass = function (className, result) {
result = result || [];
// 首先判斷我們的docoument.getElementsByClassName() 有沒有這個功能
var res;
if (support.getElementsByClassName) {
res = document.getElementsByClassName(className);
} else {
// 自己實現getElementByClassName
// 思路 : 首先獲得所有元素,然后再在所有元素中獲得帶有這個類的元素
res = myGetByClassName(className.document);
}
result.push.apply(result,res);
return result;
}
var myGetByClassName = function (className, context) {
var elements = context.getElementsByTagName("*"),
res = [];
// 循環判斷是否符合要求
each(elements, function () {
// 這個細節非常重要 !!!!!
if ((" " + this.className + " ").indexOf(" " + className + " ") != -1) {
res.push(this);
}
})
return res;
}
這個細節非常重要!!
if ((" " + this.className + " ").indexOf(" " + className + " ") != -1) {
res.push(this);
}
好了,我們已經添加了自己的getClassName() 方法,現在來測試一遍.
05.html
<body>
<div class="dv1">1</div>
<div class="dv1">2</div>
<div>3</div>
<p clss='p1'>4</p>
<p clss='p1'>5</p>
<p>6</p>
<script src="05.js"></script>
</body>
<script>
onload = function () {
each(get('dv1'), function () {
this.style.backgroundColor = "red";
});
each(get('p1'), function () {
this.style.backgroundColor = "green";
});
};
</script>
chrome下效果:
ie8下效果:
關於getElementsByClassName()的兼容問題就討論到這里,在下一篇,JavaScript高級框架設計(三),我會開始介紹push的兼容問題和 組合選擇器.
最后特別感謝恩師蔣坤老師(jk)對我的知識學習和人生引導的極大幫助,非常感謝他.