`

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

阅读更多

元素操作核心类Ext.Element

 

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

 

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的定义

 

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调用函数则会出现非预期的效果。对于这点说明详见:具名函数的四种调用方式 系列

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

 

var DH = Ext.DomHelper,
      El = Ext.Element,
      EC = Ext.elCache;

 

       Ext采用这种简单的设计模式:组合。Bruce Eckel在《Think in Java》中所言,避免滥用继承,多数情况下组合已足以。下面看原型prototype的实现

 

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);该方法后续会讲到。

      接下来看代码

 

defaultUnit : "px",

     CSS值的单位。如不指定则默认为px。

 

    is : function(simpleSelector){
        return Ext.DomQuery.is(this.dom, simpleSelector);
    },

      该方法功能为:如果这个元素符合传入的简易选择符的条件就返回true,简易选择符形如div.some-class或span:first-child。实现详见Ext.DomQuery

 

     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

 

blur : function() {
        try{
            this.dom.blur();
        }catch(e){}
        return this;
    },

下面看方法getValue

 

    getValue : function(asNumber){
        var val = this.dom.value;
        return asNumber ? parseInt(val, 10) : val;
    },

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

     下面看方法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;
    },

    运行该方法时,会调用请求,用返回的值赋给元素的innerHTML,该方法的实现参见Ext.Ajax.request

 

   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。

 

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

        if (dom) {
            delete me.dom;
            Ext.removeNode(dom);
        }
    },

 

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

 

   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已经扩展实现了这两个事件不同浏览器之间的兼容。

 

   contains : function(el){
        return !el ? false : Ext.lib.Dom.isAncestor(this.dom, el.dom ? el.dom : el);
    },

    该方法用来判断当前元素是否是传入元素的父辈级元素(ancestor),是返回true。

    下面看代码

 

    getAttributeNS : function(ns, name){
        return this.getAttribute(name, ns);
    },
   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];
    },

      这两个方法功能为返回DOM节点中的某个元素属性的值。当有命名空间时,应传入ns参数,注意IE和非IE的实现不一样。IE中对undefined,unknow类型做了处理,其它浏览器则是标准的实现。关于该方法带命名空间的使用下面给出一个简单的例子

 

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

var attDemo = Ext.get('attDemo');
var att = attDemo.getAttribute('url','ext');
alert(att);

    最后看方法

 

    update : function(html) {
        if (this.dom) {
            this.dom.innerHTML = html;
        }
        return this;
    }

    更新该元素的innerHTML

    下面看Ext.Element的静态方法

 

var ep = El.prototype;
El.addMethods = function(o){
      Ext.apply(ep, o);
};

    该方法内部调用Ext.apply,把o中的所有属性克隆到ep上

    下面看重要方法get

 

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。该方法调用了以下方法

 

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保存了以事件名为属性的事件信息,包括调用函数,作用域等。
      接下来是一个垃圾回收函数

 

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

 

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),它通过颗粒化将属性分为内部及外部以达到共享从而节省内存。享元模式的实现通常需用到工厂模式。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics