DOM原本的API在撰寫上不僅冗長,而且不方便,在這邊將為DOM API作為簡單的封裝,這邊以 建立核心公用函式 為基礎,繼續API的封裝。首先,調整一下原有的程式為如下的架構,相關說明直接撰寫於註解中:
(function(global) {
// 現在讓 XD 參考至函式,並作為名稱空間
var XD = function(selector, container) {
return new XD.mth.init(selector, container);
};
// 公用函式先放在一個暫時物件集中
var utils = {
trim: function(text) {
return (text || '').replace( /^(\s|\u00A0)+|(\s|\u00A0)+\$/g, '');
},
...
};
// 這個函式可以將source上的特性合併至target上成為特性
function extend(target, source) {
utils.each(source, function(value, key) {
target[key] = value;
});
}
// 將utils上的特性合併至XD
extend(XD, utils);
//extend函式亦可為公用函式,故以XD作為名稱空間
XD.extend = extend;
// 標準化屬性名稱
XD.props = {
'for': 'htmlFor',
'class': 'className',
readonly: 'readOnly',
maxlength: 'maxLength',
cellspacing: 'cellSpacing',
rowspan: 'rowSpan',
colspan: 'colSpan',
tabindex: 'tabIndex',
usemap: 'useMap',
frameborder: 'frameBorder'
};
// 這邊以下再解釋
XD.mth = XD.prototype = {
init: function(selector, container) {
...
}
};
XD.mth.init.prototype = XD.mth;
global.XD = XD;
})(this);
// 現在讓 XD 參考至函式,並作為名稱空間
var XD = function(selector, container) {
return new XD.mth.init(selector, container);
};
// 公用函式先放在一個暫時物件集中
var utils = {
trim: function(text) {
return (text || '').replace( /^(\s|\u00A0)+|(\s|\u00A0)+\$/g, '');
},
...
};
// 這個函式可以將source上的特性合併至target上成為特性
function extend(target, source) {
utils.each(source, function(value, key) {
target[key] = value;
});
}
// 將utils上的特性合併至XD
extend(XD, utils);
//extend函式亦可為公用函式,故以XD作為名稱空間
XD.extend = extend;
// 標準化屬性名稱
XD.props = {
'for': 'htmlFor',
'class': 'className',
readonly: 'readOnly',
maxlength: 'maxLength',
cellspacing: 'cellSpacing',
rowspan: 'rowSpan',
colspan: 'colSpan',
tabindex: 'tabIndex',
usemap: 'useMap',
frameborder: 'frameBorder'
};
// 這邊以下再解釋
XD.mth = XD.prototype = {
init: function(selector, container) {
...
}
};
XD.mth.init.prototype = XD.mth;
global.XD = XD;
})(this);
調整為以上架構的目的在於,希望XD()函式可以封裝getElementById()與getElementsByTagName()的操作。例如,你可以傳入id:
XD('#xyz'); // 傳回一個物件,物件索引0是id為xyz的元素。
XD('#xyz, #abc'); // 傳回一個物件,物件索引0是id為xyz的元素、索引1是id為abc的元素
XD('#xyz, #abc, #123'); // 如上類推,索引2是id為123的元素
XD('#xyz, #abc'); // 傳回一個物件,物件索引0是id為xyz的元素、索引1是id為abc的元素
XD('#xyz, #abc, #123'); // 如上類推,索引2是id為123的元素
也可以傳入標籤名:
XD('img'); // 傳回一個物件,收集了所有img元素,可使用索引存取
XD('img, div'); // 傳回一個物件,收集了所有img與div元素,可使用索引存取
XD('img, div, a'); // 傳回一個物件,收集了所有img、div、a元素,可使用索引存取
XD('img', XD('#container')); // 以id為container的元素開始,取得img元素
XD('img, div'); // 傳回一個物件,收集了所有img與div元素,可使用索引存取
XD('img, div, a'); // 傳回一個物件,收集了所有img、div、a元素,可使用索引存取
XD('img', XD('#container')); // 以id為container的元素開始,取得img元素
或者是傳入標籤來建立元素。例如:
XD('<img>'); // 建立img元素,收集在傳回的物件
XD('<img>,<div>'); // 建立img、div元素,收集在傳回的物件
XD('<img>,<div>'); // 建立img、div元素,收集在傳回的物件
從上面的架構知道,呼叫函式XD(),內部會建立XD.mth.init()的實例,而XD.mth.init()的實作如下:
XD.mth = XD.prototype = {
init: function(selector, container) {
// 這是元素
if(selector.nodeType) {
this[0] = selector;
this.length = 1;
return this;
}
// 如果有指定容器(XD()傳回的物件)
// 使用容器上第一個元素來呼叫getElementById()或getElementsByTagName()
if(container && container[0]) {
container = container[0];
}
else { // 否則,使用document
container = document;
}
// 先用Array收集元素
var elements = [];
// 可以指定多個,用,區隔
XD.each(selector.split(','), function(text) {
text = XD.trim(text);
if(text.charAt(0) === '#') { // 這是指定id
elements.push(container.getElementById(text.substring(1)));
}
else if(text.charAt(0) === '<') { // 這是指定<tag>
elements.push(document.createElement(
text.substring(1, text.length - 1)));
}
else { // 否則就是指定標籤
XD.each(container.getElementsByTagName(text), function(element) {
elements.push(element);
});
}
});
// 將Array上的元素複製至this
XD.extend(this, elements);
// 順便指定一下收集了幾個元素
this.length = elements.length;
}
};
要記得,new 之後接的函式若無傳回值,預設是傳回this。在上頭的程式中,XD.mth與XD.prototype都是參考至同一物件,之後 XD.mth.init.prototype再參考至XD.mth,這表示,透過XD.mth.init建立的實例,將可以取得XD.mth上的方法。例 如,如果在物件上新增一些簡單的方法:
XD.mth = XD.prototype = {
init: function(selector, container) {
...
},
size: function() { // 收集了幾個物件
return this.length;
},
isEmpty: function() { // 是否為空(沒有收集半個物件)
return this.length === 0;
}
};
假設文件中有兩個<img>,則你可以如下收集並得到size結果為2,isEmpty為false:
var size = XD('img').size(); // 2
var isEmpty = XD('img').isEmpty(); // false
var isEmpty = XD('img').isEmpty(); // false
這邊再示範幾個方法的建立:
XD.mth = XD.prototype = {
...
each: function(callback) {
return XD.each(this, callback);
},
html: function(value) {
if(value === undefined) {
return this[0] && this[0].nodeType === 1 ?
this[0].innerHTML : null;
}
else {
return XD.each(this, function(element) {
if(element.nodeType === 1) {
element.innerHTML = value;
}
});
}
},
attr: function(name, value) {
name = XD.props[name] || name;
if(value === undefined) {
return this[0] && this[0].nodeType !== 3 && this[0].nodeType !== 8 ?
this[0][name] : undefined;
}
else {
return XD.each(this, function(element) {
if(element.nodeType !== 3 && element.nodeType !== 8) {
element[name] = value;
}
});
}
},
val: function(value) {
// 先只處理 <input> 元素
if(value === undefined) {
return this[0] && this[0].nodeName === 'INPUT' ?
this[0].value : null;
}
else {
return XD.each(this, function(element) {
if(element.nodeName === 'INPUT') {
element.value = value;
}
});
}
},
append: function(childs) {
if(typeof childs === 'string' || childs.nodeType) {
childs = XD(childs);
}
if(this.length === 1) { // 只有一個父節點
var parent = this[0];
XD.each(childs, function(child) {
parent.appendChild(child);
});
}
else if(this.length > 1){ // 有多個父節點
XD.each(this, function(parent) {
childs.each(function(child) {
// 複製子節點
var container = document.createElement('div');
container.appendChild(child);
container.innerHTML = container.innerHTML;
parent.appendChild(container.firstChild);
});
});
}
return this;
},
remove: function() { // 從父節點移除
return XD.each(this, function(element) {
element.parentNode.removeChild(element);
});
}
};
each()方法可以讓你指定回呼函式,對收集的元素都會呼叫操作,每次呼叫時,回呼函式的第一個引數就是當時的DOM元素,而this也設為當時的DOM元素。例如:
XD('img').each(function(element) {
var src1 = element.src;
var src2 = this.src;
...
});
var src1 = element.src;
var src2 = this.src;
...
});
如果你有多個元素要設定其innerHTML,則可以如下:
XD('div').html('<b>文字</b>');
如果有多個div元素,則以上會將所有div元素的innerHTML都設為指定的文字。如果沒有指定文字,例如以下預設取得所收集元素的第一個div的innerHTML:
var html = XD('div').html();
類似的,attr()可用來設置取得取屬性:
XD('img').attr('src', 'caterpillar.jpg'); // 設定所有收集的img元素的src屬性
XD('img').attr('src'); // 取得所收集第一個img元素的src屬性
XD('img').attr('src'); // 取得所收集第一個img元素的src屬性
val()則可以讓你取得或設定<input>的value:
XD('input').val('default'); // 將所有<input>的value設定為'default'
XD('input').val(); // 取得第一個<input>的value
XD('input').val(); // 取得第一個<input>的value
append()方法可以用來附加子節點,如果有多個父節點,則來源節點會進行複製。remove()方法可以將自己從父節點移除。例如:
XD('div').append('<img>'); // 建立<img>附加至所有的<div>
XD('div').remove(); // 移除所有<div>
XD('div').remove(); // 移除所有<div>
像上面的attr()或html()方法,其實都是傳回收集物件本身(XD.each()第一個參數傳入this,最後XD.each()執行結束傳回的就是那個this),所以你可以如下進行鏈狀操作:
XD('<img>,<img>')
.attr('src', 'images/caterpillar_small.jpg')
.each(function(element) {
// ... 作一些事
});
.attr('src', 'images/caterpillar_small.jpg')
.each(function(element) {
// ... 作一些事
});
在將來,若別人想要擴充這個程式庫,可以在他的.js中撰寫:
(function(XD) {
XD.mth.get = function(index) {
// this 參考至收集元素的物件
return this[index];
};
})(this.XD);
XD.mth.get = function(index) {
// this 參考至收集元素的物件
return this[index];
};
})(this.XD);
那麼他就可以這麼使用:
XD('img').get(0); // 傳回第一個img元素
如果你曾經使用過 jQuery,對於以上的使用模式應該很眼熟,事實上,這邊的封裝與 建立核心公用函式 中的封裝,都是在模彷 jQuery 建立程式庫的方式,只不過這邊作了一定程度的簡化。就目前完成的.js,這邊命名為gossip-0.2.js,就在這邊找到完整內容:
如果你對jQuery的運作原理有興趣,這個簡化後的檔案可以作為開始,在之後的文件,也會適當地使用這個.js來簡化範例的撰寫。例如,可以稍微簡化一下 修改文件 中第一個範例
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<script type="text/javascript" src="js/gossip-0.2.js"></script>
<script type="text/javascript">
window.onload = function() {
XD('#add')[0].onclick = function() {
var imgXD = XD('<img>').attr('src', XD('#src').val());
imgXD[0].onclick = function() {
XD(this).remove();
};
XD('#images').append(imgXD);
};
};
</script>
</head>
<body>
<input id="src" type="text"><button id="add">新增圖片</button>
<div id="images"></div>
</body>
</html>
可以稍微簡化一下 修改文件 中第二個範例:
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<script type="text/javascript" src="js/gossip-0.2.js"></script>
<script type="text/javascript">
window.onload = function() {
var container1XD = XD('#container1');
var container2XD = XD('#container2');
XD('#image')[0].onclick = function() {
if(this.parentNode === container1XD[0]) {
container2XD.append(this);
}
else {
container1XD.append(this);
}
};
};
</script>
</head>
<body>
容器一:<div id="container1">
<img id="image" src=
"https://openhome.cc/Gossip/images/caterpillar_small.jpg"/>
</div><br>
容器二:<div id="container2"></div>
</body>
</html>
封裝還不完善,隨著文件的進行,之後還會逐步完善這個程式庫。