由 prototype 談起
物件導向語言,支援「繼承」的概念,也就是父物件有的方法及屬性,物件本身也一應具備。於是設計人員可以直接在上層物件定義好一堆自己專屬或改良的方法和屬性,這比設計副程式庫還要來得方便許多。
舉個例子,Array 是 Javascript 中的原型物件,任何需要使用到陣列時,只要宣告:
var ay=new Array(); 或 ay=[];
- var ay=new Array();
- 或
- ay=[];
就可以使用陣列所有的方法和屬性,例如 ay.push()。
不僅如此,如果你嫌原生 Array 物件方法或屬性不夠時,你還可以自訂,例如:
Array.prototype.sayHello='Hello World';
- Array.prototype.sayHello='Hello World';
那麼,以下語法的測試結果就會跳出一個 Hello World
Array.prototype.sayHello='Hello World'; var ay=new Array(); alert(ay.sayHello);
- Array.prototype.sayHello='Hello World';
- var ay=new Array();
- alert(ay.sayHello);
上例是簡單加了個名為 sayHello 的屬性到 Array 物件中。所以,你就可以用同樣方法,設計出一套屬於自己的,或是你也願意開放出來給大家使用的自訂方法。
自訂物件
同樣的,你也可以自訂物件,並讓物件擁有自己的屬性及方法,如:
myObj=function(){ this.version='1.0'; } myObj.prototype.sayHello=function(){ alert('Hello'); } var myTest=new myObj(); myTest.sayHello();
- myObj=function(){
- this.version='1.0';
- }
- myObj.prototype.sayHello=function(){
- alert('Hello');
- }
- var myTest=new myObj();
- myTest.sayHello();
那麼,有沒有什麼方法,可以一次為所有 HTML 中的元件,加上自定的方法和屬性呢?這時就要來了解一下,瀏覽器對於 HTML 是怎麼對待的。
Element 物件
在使用 DOM 操作處理時,經常會用到一個指令,叫作 createElement(),這個指令相當於在 HTML 文件中加上新的標籤,所以,這些標籤,在瀏覽器眼中,統稱為「Element」,例如:
document.getElementById(node) 這是一個 HTMLDivElement
document.getElementsByTagName('li')[0] 這是一個 HTMLLiElement
document.getElementsByTagName('li') 這是一個 HTMLCollection (由一群 <li> 組成的集合)
注意上列三則,瀏覽器的行為,第三則,是一個集合,而非 Element。
所以,這時就產生一個有趣的物件,叫作 Element,由 id 取得的物件,就是 HTMLxxxElement,xxx 就是標籤種類,如果 xxx 是表單 <form> 那就是 HTMLFormElement,如果是段落 <p> 那就是 HTMLParagraphElement,依此類推。
這些物件中,共同特性就是全部都是 Element,所以,如果能在 Element 上加上自己的方法及屬性,那不就很完美?
是的!如果你腦筋動得快,jQuery 和 ptototype 都是這麼搞的!
然而,世上仍存在數量龐大的 IE6/7
很不幸的,在 IE6/7 (IE8 已經把 Element 列為物件)把前述所有項目都視為 [object],但是是什麼 object 也看不出來,因此,在 IE 6/7 中,沒有辦法對 Element 自訂方法和屬性,如果你在 IE 中測試:
alert(window.Element); 得到的結論是 undefined。
這就造成相當大困擾了。例如,現在想把圖層的高度回傳值,不要帶有 px 單位,一般是這麼寫:
parseInt(document.getElementById(node).style.height);
- parseInt(document.getElementById(node).style.height);
每次都要寫這麼長一串,不太經濟,於是有了快取函式(Cached Function)寫法,也就是 jQuery 和 prototype 的寫法,簡化如下:
$=function(node){ return document.getElementById(node); } Element.prototype.height=function(){ return parseInt(this.style.height); }
- $=function(node){
- return document.getElementById(node);
- }
- Element.prototype.height=function(){
- return parseInt(this.style.height);
- }
注意:上述只是原理例,並不嚴謹。
如此一來,就可以簡單使用以下語法取得不帶單位的圖層高度:
var h=$(node).height();
- var h=$(node).height();
可惜,這樣的語法,IE6/7 完全不能接受!除非你和我一樣,可以要求客戶不准用 IE,那就不用再看下去了。
解決方案
IE6/7 仍支援繼承(inheritance)只不過不支援 window.Element 而已,如果多花點功夫這麼寫:
$=function(node){ var elm=document.getElementById(node); elm.height=function(){ return parseInt(this.style.height); } return elm; }
- $=function(node){
- var elm=document.getElementById(node);
- elm.height=function(){
- return parseInt(this.style.height);
- }
- return elm;
- }
那就一樣可以用 $(node).height() 來取得物件高度。這個方法,在 FireFox、Safari、Opera、Chrome 也都適用。
原理就是先定義好物件,然後在每個物件加上自訂的方法和屬性。
上例中,只有一個自定方法,效能上就沒太大差別,如果你自定了一大堆方法和屬性,每次呼叫 $(node),就要把這個物件套上一堆辺法和屬性,效能上就差了,如果瀏覽器處理 Javascript 的功力上不足,那執行起來就鈍鈍的。
但如果是直接定義在 Element 的 prototype 中,就不一樣了,透過繼承的特性,每個 $(node ) 被定義出來就自動包含了這些方法和屬性,其效能,便利性自是不可同日而語。
不過,也沒辦法,暫時只能這樣,完整的範例如下:
window.$=function (node){ if (typeof node == 'string'){ //如果是字串,取得以 id 為識別的物件 node=document.getElementById(node); } if(node!=null && !node.test){ //如果不支援 Element 時,就逐一加上自訂方法 if (!window.Element) { __addMethod(node,__methods); } } return node; } function __addMethod(elm,obj){ for (var key in obj) { if (typeof elm[key]=='undefined'){ elm[key]=obj[key]; } } } if (window.Element) { //支援 Element 的瀏覽器, 直接把方法和屬性加到 Element 中 __addMethod(Element.prototype,__methods); } var __methods={ //這裡就是自訂方法和屬性了,如 test:true, version:'1.0', height:function(){return parseInt(this.style.height);}, width:function(){return parseInt(this.style.width);} }
- window.$=function (node){
- if (typeof node == 'string'){
- //如果是字串,取得以 id 為識別的物件
- node=document.getElementById(node);
- }
- if(node!=null && !node.test){
- //如果不支援 Element 時,就逐一加上自訂方法
- if (!window.Element) {
- __addMethod(node,__methods);
- }
- }
- return node;
- }
- function __addMethod(elm,obj){
- for (var key in obj) {
- if (typeof elm[key]=='undefined'){
- elm[key]=obj[key];
- }
- }
- }
- if (window.Element) {
- //支援 Element 的瀏覽器, 直接把方法和屬性加到 Element 中
- __addMethod(Element.prototype,__methods);
- }
- var __methods={
- //這裡就是自訂方法和屬性了,如
- test:true,
- version:'1.0',
- height:function(){return parseInt(this.style.height);},
- width:function(){return parseInt(this.style.width);}
- }
上例的自訂屬性中,加了一個 test 屬性,這是一個簡單判別,當 IE 同一 id 再被定義時,如果物件已經存在,就不需要再執行一次宣告自訂方法和屬性的動作。
建議事項
如果你明白了解決方案的原理,再仔細思考,就會發現,每次呼叫 $(node),在 IE 中就要套一堆內容。因此,為避免對瀏覽器消耗過甚,建議這麼寫:
var div=$(node); div.function1(); div.function2(); div.function3(); .....
- var div=$(node);
- div.function1();
- div.function2();
- div.function3();
- .....
Enjoy !