您的位置:首页 > Web前端 > JavaScript

ExtJs源码分析与学习—ExtJs元素Element(二)

2011-07-27 23:18 429 查看
元素操作核心类Ext.Element 类Ext.Element对DOM元素的进行了封装,并对DOM加入了简单的操作,实现了各浏览器的兼容;该类的实例可以更容易的对DOM elements进行操作;类中的事件,即封装了浏览器的事件,也可以访问底层浏览器事件(Ext.EventObject{@}#browserEvent);该类操作DOM元素时,很多情况下会用到一些动画效果(可选的),要设置动画效果,一般是把方法中的最后一个参数设为true或配置动画参数,如下代码 Js代码

var el = Ext.get("my-div");
el.setWidth(100, {
duration: 1,//动画持续的时间(单位:秒)
callback: function(){alert('动画结束');},
scope: this
});
<div id='my-div'>
动画效果
</div>
该类的实现同样也采用了匿名闭包自执行函数来实现,执行的结果把函数赋给了Ext.Element,同时扩展了Ext.Element的prototype。首先看Ext.Element的定义 Js代码

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;
}

/**
* The DOM element
* @type HTMLElement
*/
this.dom = dom;

/**
* The DOM element ID
* @type String
*/
this.id = id || Ext.id(dom);
};
该类的实现采用了构造器方法,两个参数分别为
element:要封转的DOM元素或id,通常为HTMLElement
forceNew:(可选的) 构建函数默认会检查在Cache中是否已经有这个element实例。如果有,则返回缓存中的对象。如果设置为否,则跳过缓存的验查。
该构造器中初始了元素id(如果没有设置则调用Ext.id(dom))和dom,注意如果forceNew 没有传入或设为false先从缓存中找,找到返回该元素封装后的Element
return Ext.elCache[id].el;
我们知道这是在定义一个类,使用new操作符。如
var ele = new Ext.Element('head');
既然是new,里面怎么又return了呢?一般函数调用,有return时,构造器中都会定义一些字段。其实当构造器中return的是对象类型时是允许的,如果return的是基本类型,又使用new调用函数则会出现非预期的效果。对于这点说明详见:具名函数的四种调用方式 系列

利用构造器定义完该类后,代码定义了一些变量 Js代码

var DH = Ext.DomHelper,
El = Ext.Element,
EC = Ext.elCache;
Ext采用这种简单的设计模式:组合。Bruce Eckel在《Think in Java》中所言,避免滥用继承,多数情况下组合已足以。下面看原型prototype的实现 Js代码

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;
},
该方法设置元素属性(attributes)(一个样式属性可以是字符串,对象或函数function)。传入的属性会优先设置style,其次是cls,然后是setAttribute,最后为直接设置元素属性,如table.width = '10px; '。其中参数useSet (optional) false表示用元素的setAttribute方法来设置属性值,关于setAttribute的使用以及不同浏览器的不兼容型,该文章 给出了详细的说明。
该方法内部使用for in来遍历对象,我们知道默认情况下 for in 不会去遍历原形链属性的
对象的hasOwnProperty方法对继承于原形链的属性是检测不到 的,即返回false。因此这里hasOwnProperty(attr) 这个判断完全是多余的。
在设置style调用了DH.applyStyles(el, val);该方法后续会讲到。

接下来看代码 Js代码

defaultUnit : "px",
CSS值的单位。如不指定则默认为px。 Js代码

is : function(simpleSelector){
return Ext.DomQuery.is(this.dom, simpleSelector);
},
该方法功能为:如果这个元素符合传入的简易选择符的条件就返回true,简易选择符形如div.some-class或span:first-child。实现详见Ext.DomQuery Js代码

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 (optional)为延时执行focus的毫秒数。该方法忽略了任何已捕获的异常。与focus对应的方法为blur Js代码

blur : function() {
try{
this.dom.blur();
}catch(e){}
return this;
},
下面看方法getValue Js代码

getValue : function(asNumber){
var val = this.dom.value;
return asNumber ? parseInt(val, 10) : val;
},
返回元素的value属性值,如果asNumber为true,则转换为十进制数字
下面的几个方法是与事件相关的,主要调用Ext.EventManager来实现的,这里说明一下,通过这几个方法的定义,该类实现了元素对浏览器所有事件的支持。
addListener / removeListener / removeAllListeners / purgeAllListeners

下面看方法load Js代码

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;
},
运行该方法时,会调用请求,用返回的值赋给元素的innerHTML,该方法的实现参见Ext.Ajax.request Js代码

isBorderBox : function(){
return Ext.isBorderBox || Ext.isForcedBorderBox || noBoxAdjust[(this.dom.tagName || "").toLowerCase()];
},
该方法测试不同的CSS规则/浏览器以确定该元素是否使用Border Box。IE并且是标准模式则返回Ext.isBorderBox,如果是非IE并且强制设置border-box model则返回1,否则标准模式中对于具有border的元素如select会返回1,怪异模式中对于input,select,textarea元素返回1,同时利用代码noBoxAdjust['button'] = 1,button也会返回1。 Js代码

remove : function(){
var me = this,
dom = me.dom;

if (dom) {
delete me.dom;
Ext.removeNode(dom);
}
},
从DOM里面移除当前元素,并删除缓存以及该element注册的事件(参见Ext.removeNode)。
看以下代码 Js代码

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,前面已讲过该方法的使用。方法中用mouseenter和mouseleave,而没有用mouseover和mouseout,好处前面已提到,ExtJs已经扩展实现了这两个事件不同浏览器之间的兼容。 Js代码

contains : function(el){
return !el ? false : Ext.lib.Dom.isAncestor(this.dom, el.dom ? el.dom : el);
},
该方法用来判断当前元素是否是传入元素的父辈级元素(ancestor),是返回true。

下面看代码 Js代码

getAttributeNS : function(ns, name){
return this.getAttribute(name, ns);
},
etAttribute : 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];
},
这两个方法功能为返回DOM节点中的某个元素属性的值。当有命名空间时,应传入ns参数,注意IE和非IE的实现不一样。IE中对undefined,unknow类型做了处理,其它浏览器则是标准的实现。关于该方法带命名空间的使用下面给出一个简单的例子 Js代码

<div id='attDemo' ext:url='自定义属性测试结果' style="width :200px;">带命名空间的自定义属性</div>

var attDemo = Ext.get('attDemo');
var att = attDemo.getAttribute('url','ext');
alert(att);
最后看方法 Js代码

update : function(html) {
if (this.dom) {
this.dom.innerHTML = html;
}
return this;
}
更新该元素的innerHTML

下面看Ext.Element的静态方法 Js代码

var ep = El.prototype;
El.addMethods = function(o){
Ext.apply(ep, o);
};
该方法内部调用Ext.apply,把o中的所有属性克隆到ep上

下面看重要方法get Js代码

El.get = function(el){
var ex,
elm,
id;
if(!el){ return null; }
if (typeof el == "string") { // element id
if (!(elm = DOC.getElementById(el))) {
return null;
}
if (EC[el] && EC[el].el) {
ex = EC[el].el;
ex.dom = elm;
} else {
ex = El.addToCache(new El(elm));
}
return ex;
} else if (el.tagName) { // dom element
if(!(id = el.id)){
id = Ext.id(el);
}
if (EC[id] && EC[id].el) {
ex = EC[id].el;
ex.dom = el;
} else {
ex = El.addToCache(new El(el));
}
return ex;
} else if (el instanceof El) {
if(el != docEl){
// refresh dom element in case no longer valid,
// catch case where it hasn't been appended

// If an el instance is passed, don't pass to getElementById without some kind of id
if (Ext.isIE && (el.id == undefined || el.id == '')) {
el.dom = el.dom;
} else {
el.dom = DOC.getElementById(el.id) || el.dom;
}
}
return el;
} else if(el.isComposite) {
return el;
} else if(Ext.isArray(el)) {
return El.select(el);
} else if(el == DOC) {
// create a bogus element object representing the document object
if(!docEl){
var f = function(){};
f.prototype = El.prototype;
docEl = new f();
docEl.dom = DOC;
}
return docEl;
}
return null;
};
该方法是一静态方法,他的功能是获取元素对象。get方法不会获取组件对象Ext.Component Component,而只是对DOM元素进行Ext.Element封装,要想获取组件,请调用方法Ext.ComponentMgr.get。Ext.get是该方法的别名。对于传入的参数el可分为以下几种情况
1、传入参数是dom的id:首先利用DOC.getElementById(el)判断该dom是否存在,存在的话先从缓存中取,不存在的话实例化该对象并压入到缓存中
2、传入参数是HTMLElement对象:首先判断该dom是否有id值(没有则创建),对于该种情况也是先从缓存中取,与1一样
3、传入参数是Ext.Element,即已经封装好的对象:如果不是docEl,重新给dom属性赋值后直接返回
4、传入参数是Ext.CompositeElementLite:直接返回
Ext.CompositeElementLite 在后续会介绍,这个类用来操作批量的Ext.Element
5、传入参数是数组的话:调用El.select(el)返回,后续会介绍
6、传入参数是document:
如果docEl不存在则创建。因为document是唯一的,因此该对象只创建一次。后续直接返回即可。可以看到docEl与普通Ext.Element不同之处在于其dom属性的不同。
这几种情况如果传入的元素不存在则返回null。该方法调用了以下方法 Js代码

El.addToCache = function(el, id){
id = id || el.id;
EC[id] = {
el: el,
data: {},
events: {}
};
return el;
};
该方法,会在Ext缓存中以id为key存放到Ext.elCache中,存放的对象有三个属性,分别为Ext.Element,存储数据(El.data用到)、Ext封装的事件(见Ext.EventManager方法addListener),events保存了以事件名为属性的事件信息,包括调用函数,作用域等。
接下来是一个垃圾回收函数 Js代码

function garbageCollect(){
if(!Ext.enableGarbageCollector){
clearInterval(El.collectorThreadId);
} else {
var eid,
el,
d,
o;
for(eid in EC){
o = EC[eid];
if(o.skipGC){
continue;
}
el = o.el;
d = el.dom;
if(!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))){
if(Ext.enableListenerCollection){
Ext.EventManager.removeAll(d);
}
delete EC[eid];
}
}
// Cleanup IE Object leaks
if (Ext.isIE) {
var t = {};
for (eid in EC) {
t[eid] = EC[eid];
}
EC = Ext.elCache = t;
}
}
}
El.collectorThreadId = setInterval(garbageCollect, 30000);
可以看到,该函数每隔30秒会进行一次垃圾回收。即遍历Ext.elCache,取Ext元素的dom属性,如果存在以下条件则清空(包括其上的所有事件handler) dom不存在
dom的父元素不存在
页面上获取不到该dom元素

用户也可以取消垃圾回收,只要把参数Ext.enableGarbageCollector设为false即可。该方法的思想也可以用到其他函数或类中
接下来是Ext.Element.fly方法,他的别名是El.fly Js代码

var flyFn = function(){};
flyFn.prototype = El.prototype;
// dom is optional
El.Flyweight = function(dom){
this.dom = dom;
};
El.Flyweight.prototype = new flyFn();
El.Flyweight.prototype.isFlyweight = true;
El._flyweights = {};

El.fly = function(el, named){
var ret = null;
named = named || '_global';

if (el = Ext.getDom(el)) {
(El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
ret = El._flyweights[named];
}
return ret;
};
首先利用javascript的简单继承定义flyFn,该类继承Ext.Element,然后添加El.Flyweight类,同时该类的原型是flyFn的实例对象,并增加了属性isFlyweight,来标示对象是否为Ext.Element.Flyweight类型对象。而El._flyweights为暂时对象。每次调用Ext.fly(El.fly)方法时,都会把El.Flyweight对象缓存到El._flyweights的nameed中,并且将覆盖之前的(除非每次所传的第二个参数named不同),而El.Flyweight中dom保存了当前元素的dom。
Ext.fly不同于Ext.get。Ext.fly采用享元模式(Flyweight),它通过颗粒化将属性分为内部及外部以达到共享从而节省内存。享元模式的实现通常需用到工厂模式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: