終於越來越接近Ext的核心了。這篇開始Ext.Element,這里說的Ext元素指的是Ext.Element類的實例。
任何前端庫都會涉及到對HTMLElement的操作,JQuery更是以其為中心,一個$()函數調用后將DOM元素以索引方式存在 JQuery對象 中。
Ext則使用了一個稱為 Ext.Element 的類,如 Ext.get/Ext.fly 返回的都是該類的實例對象。許多操作如樣式,添加事件等都封裝到該類里。
Ext.Element的定義大概框架如下
Ext.Element = function(element, forceNew){ var dom = typeof element == "string" ? DOC.getElementById(element) : element, id; if(!dom) return null; id = dom.id; if(!forceNew && id && Ext.elCache[id]){ // element object already exists return Ext.elCache[id].el; } this.dom = dom; this.id = id || Ext.id(dom); }; var D = Ext.lib.Dom, DH = Ext.DomHelper, E = Ext.lib.Event, A = Ext.lib.Anim, El = Ext.Element, EC = Ext.elCache; El.prototype = { ... }
可以看到,采用了的是很傳統的JS寫類方式:構造器+原型 方式。構造器內執行大概如下
1,如果參數element是字符串則使用document.getElementById獲取,否則直接返回element(通常是HTMLElement)。
2,給 變量 id賦值,來自dom.id
3,forceNew不傳或傳false情況下,優先從緩存中取。
4,給this掛上dom和id屬性。
3處有點繞人,
return Ext.elCache[id].el;
我們知道這是在定義一個類,使用new操作符。如
var ele = new Ext.Element('head');
既然是new,里面怎么又return了呢?一般是函數調用(),里面有return。構造器里一般都是定義一些字段。其實當構造器中return的是對象類型時是允許的,如果return的是基本類型,又使用new調用函數則會出現非預期的效果。對於這點感興趣的詳見:具名函數的四種調用方式 系列 。
構造器內其實很少代碼,比較簡單。隨后是定義了一些變量如D,DH,這些多數是之前篇幅中提到的。Ext采用這種簡單的設計模式:組合。Bruce Eckel在《Think in Java》中所言,避免濫用繼承,多數情況下組合已足以。
接着看掛在prototype上的第一個方法 set
set : function(o, useSet){ var el = this.dom, attr, val, useSet = (useSet !== false) && !!el.setAttribute; for(attr in o){ if (o.hasOwnProperty(attr)) { val = o[attr]; if (attr == 'style') { DH.applyStyles(el, val); } else if (attr == 'cls') { el.className = val; } else if (useSet) { el.setAttribute(attr, val); } else { el[attr] = val; } } } return this; },
該方法用來設置HTMLElement(如div)的屬性如id、style、class等。第一個參數是個對象(Hash)如{id:'uname'},第二個參數布爾類型,顧名思義useSet為true即使用setAttribute來設置屬性。
至於什么使用setAttribute,什么時候使用點操作符(.)給元素設置屬性。這里 給出了詳盡的解釋。
該方法內部使用for in來遍歷對象,我們知道默認情況下 for in 不會去遍歷原形鏈的屬性 的。
對象的hasOwnProperty方法對繼承於原形鏈的屬性是 檢測不到 的,即返回false。因此這里hasOwnProperty(attr) 這個判斷完全是多余的。
接着看,又是多個if分支
style,調用DH.applyStyles。但DH.applyStyles方法有bug 。
cls,設置類名(class)
useSet為true,使用setAttribute設置
最后使用中括號操作符(等價於點操作符)
看下一個方法 is
is : function(simpleSelector){ return Ext.DomQuery.is(this.dom, simpleSelector); },
該方法用來檢測dom元素是否具有給定的css選擇器。
如對於<div id="d1" class="container"></div>,Ext.get('d1').is('.container')返回的是true。注意字符串container前得有個點。因為是css類選擇器。
該方法內部調用Ext.DomQuery.is,暫不提。
接下來是 focus 方法,
focus : function(defer, /* private */ dom) { var me = this, dom = dom || me.dom; try{ if(Number(defer)){ me.focus.defer(defer, null, [null, dom]); }else{ dom.focus(); } }catch(e){} return me; },
focus方法用來獲取焦點,defer為數字,即延遲函數調用的時間,單位是毫秒。
參數dom對於使用Ext庫的客戶端程序員來說是用不到的,僅提供給庫內部使用,即遞歸調用。
me.focus.defer(defer, null, [null, dom]);
這句較難理解,即遞歸調用。me是this,this是Ext.Element對象自身。在focus內部又調用自己。
me.focus.defer 中的 defer哪來的? 還記得第三篇 原型擴展 中提到的為Function.prototype擴展的defer方法嗎?沒錯,就是它。
如果沒有傳defer,那么直接調用dom.focus。這里使用了try catch,是因為某些情況下會出異常。
接下來是 blur 方法,很簡單就不說了。往下看 getValue 方法
getValue : function(asNumber){ var val = this.dom.value; return asNumber ? parseInt(val, 10) : val; },
獲取元素的value,一般是form內元素如input,select,textarea等。這里很巧妙,如果asNumber為true則偷偷的將其 轉換為數字類型 了。
接下來是 addListener / removeListener / removeAllListeners / purgeAllListeners ,它們內部使用的是Ext.EventManager。第九篇 提到了,不說了。
往下是 addUnits 方法,該方法是內部用的。暫不提。
接下來是 load 方法
load : function(url, params, cb){ Ext.Ajax.request(Ext.apply({ params: params, url: url.url || url, callback: cb, el: this.dom, indicatorText: url.indicatorText || '' }, Ext.isObject(url) ? url : {})); return this; },
該方法把請求返回的html片段作為該元素的innerHTML填充。內部調用的是Ext.Ajax.request。
接着是isBorderBox 方法
isBorderBox : function(){ return noBoxAdjust[(this.dom.tagName || "").toLowerCase()] || Ext.isBorderBox; },
標准模式中對於具有border的元素如select會返回1,怪異模式中對於input,select,textarea元素返回1。如果不是這些元素會直接返回Ext.isBorderBox。
isBorderBox = isIE && !isStrict,
接下來是 remove 方法,
remove : function(){ var me = this, dom = me.dom; if (dom) { delete me.dom; Ext.removeNode(dom); } },
該方法會刪除該dom元素,包括dom文檔中的,ext緩存中的,及dom元素上的事件handler。
往下是 hover 方法,
hover : function(overFn, outFn, scope, options){ var me = this; me.on('mouseenter', overFn, scope || me.dom, options); me.on('mouseleave', outFn, scope || me.dom, options); return me; },
意義及用法就不提了,內部使用me.on,on是addListener的簡寫。mouseenter和mouseleave 的實現在 第四篇 提到 了。
接着是 contains 方法,
contains : function(el){ return !el ? false : Ext.lib.Dom.isAncestor(this.dom, el.dom ? el.dom : el); },
判斷是否包含el元素,內部實現也在第四篇中提到。
再看 getAttribute 方法,
getAttribute : Ext.isIE ? function(name, ns){ var d = this.dom, type = typeof d[ns + ":" + name]; if(['undefined', 'unknown'].indexOf(type) == -1){ return d[ns + ":" + name]; } return d[name]; } : function(name, ns){ var d = this.dom; return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name) || d.getAttribute(name) || d[name]; },
該方法用來獲取元素的屬性值。可以看到對於IE和非IE有兩個版本,IE中對undefined,unknow類型做了處理。其它瀏覽器則是標准的實現。
最后一個是 update 方法,
update : function(html) { if (this.dom) { this.dom.innerHTML = html; } return this; }
該方法使用innerHTML來更新元素內容。