论坛首页 Web前端技术论坛

[原创]ExtJS 2.3源码分析(2012年02月23日更新)

浏览 17965 次
精华帖 (3) :: 良好帖 (11) :: 新手帖 (1) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-02-21  
妈呀,太有才了,一个字顶,非常谢谢分享。。继续加油
0 请登录后投票
   发表时间:2012-02-22  
感谢LZ的分析 留下慢慢看
0 请登录后投票
   发表时间:2012-02-22  
分析得不错 谢谢 学习了
0 请登录后投票
   发表时间:2012-02-22  

对于下面的代码:

// 取得触发事件的dom元素
		getTarget : function( ev , resolveTextNode ) {
			// 这个browserEvent不知道怎么设置的,应该不是W3C的标准,貌似也不属于IE系列的
			ev = ev.browserEvent || ev;
			var t = ev.target || ev.srcElement;
			return this.resolveTextNode( t );
		},

 这个ev.browserEvent是EXT定义的专有属性,参考EventManager.js中的代码:

  Ext.EventObjectImpl.prototype = {
        /** The encapsulated browser event */
        browserEvent : null,
        /** The button pressed in a mouse event */
        button : -1,
        /** True if the shift key was down during the event */
        shiftKey : false,
   //.........此处省略很多..............

 /** @private */
        setEvent : function(e){
            if(e == this || (e && e.browserEvent)){ // already wrapped
                return e;
            }
            this.browserEvent = e;//此处进行的赋值哦。
            if(e){
   //.........此处省略很多..............
 
0 请登录后投票
   发表时间:2012-02-22  
adamed 写道

对于下面的代码:

// 取得触发事件的dom元素
		getTarget : function( ev , resolveTextNode ) {
			// 这个browserEvent不知道怎么设置的,应该不是W3C的标准,貌似也不属于IE系列的
			ev = ev.browserEvent || ev;
			var t = ev.target || ev.srcElement;
			return this.resolveTextNode( t );
		},

 这个ev.browserEvent是EXT定义的专有属性,参考EventManager.js中的代码:

  Ext.EventObjectImpl.prototype = {
        /** The encapsulated browser event */
        browserEvent : null,
        /** The button pressed in a mouse event */
        button : -1,
        /** True if the shift key was down during the event */
        shiftKey : false,
   //.........此处省略很多..............

 /** @private */
        setEvent : function(e){
            if(e == this || (e && e.browserEvent)){ // already wrapped
                return e;
            }
            this.browserEvent = e;//此处进行的赋值哦。
            if(e){
   //.........此处省略很多..............
 

原来是这样啊,我也是初学ExtJS。谢谢分享~~

0 请登录后投票
   发表时间:2012-02-23  
8用客气。
0 请登录后投票
   发表时间:2012-02-23  
// 取得文本节点
resolveTextNode : function( node ) {
			if ( Ext.isWebKit && node && 3 == node.nodeType ) {
				return node.parentNode;
			} else {
				return node;
			}
		}

 这个地方注释的不对。

这里不是取得文本节点而是处理文本节点因为在FF中计算兄弟节点、子节点时换行也算一个节点且为文本节点但是IE会忽略。

所以会出现计算子节点或兄弟节点个数和处理时不同浏览器不一致的问题。所以这里采用一种通用的处理方式就是忽略文本节点。

0 请登录后投票
   发表时间:2012-02-23  
adamed 写道
// 取得文本节点
resolveTextNode : function( node ) {
			if ( Ext.isWebKit && node && 3 == node.nodeType ) {
				return node.parentNode;
			} else {
				return node;
			}
		}

 这个地方注释的不对。

这里不是取得文本节点而是处理文本节点因为在FF中计算兄弟节点、子节点时换行也算一个节点且为文本节点但是IE会忽略。

所以会出现计算子节点或兄弟节点个数和处理时不同浏览器不一致的问题。所以这里采用一种通用的处理方式就是忽略文本节点。

解释的很对,是我没有说清楚。

0 请登录后投票
   发表时间:2012-02-23   最后修改:2012-02-23

好几天没有更新了,最近太忙。马上就会解析到精彩部分了,一个是Ext的动画实现,一个是DomQuery。

 

ExtJS的Ajax底层封装写的很好理解,每个函数都很清晰。但是有一些实现可能比较过时,这可能跟当时浏览器的实现有关。毕竟ExtJS 2.3写的时候比较早。那个时候我还不会javascript呢~!

 

读代码的时候,如果有的XMLHttpRequest API记的不是很清晰,可以到这里来查阅官方文档,http://www.w3.org/TR/XMLHttpRequest/

 

Ajax封装

Ext.lib.Ajax = {

        // request是Ajax的异步请求函数
        //
        // method String 代表Http请求方法,可以是GET或者POST
        // uri String 代表请求地址
        // cb Object 
        //     cb.timeout: 设置超时时间 
        // options Object
        //     options.headers : 设置Http Header
        //     options.xmlData : 向服务器发送XML格式数据
        //     options.method  : 这个参数应该用不上,只要method参数正确设置了,这个就没用
        //     options.jsonData: 向服务器发送Json格式数据
        request : function(method, uri, cb, data, options) {

            // 处理options参数,这个参数是可选的
            if(options){

                // 设置Http头
                var hs = options.headers;
                if(hs){
                    for(var h in hs){
                        if(hs.hasOwnProperty(h)){
                            this.initHeader(h, hs[h], false);
                        }
                    }
                }

                // 指定向服务器发送数据的格式
                if(options.xmlData){
                    // 向服务器发送XML格式数据
                    if (!hs || !hs['Content-Type']){
                        this.initHeader('Content-Type', 'text/xml', false);
                    }
                    method = (method ? method : (options.method ? options.method : 'POST'));
                    data = options.xmlData;
                }else if(options.jsonData){
                    // 向服务器发送Json格式数据
                    if (!hs || !hs['Content-Type']){
                        this.initHeader('Content-Type', 'application/json', false);
                    }
                    method = (method ? method : (options.method ? options.method : 'POST'));
                    data = typeof options.jsonData == 'object' ? Ext.encode(options.jsonData) : options.jsonData;
                }
            }

            // 调用异步请求asyncRequest函数
            return this.asyncRequest(method, uri, cb, data);
        },

        // 序列化表单,form可以是form的id或者直接就是form元素
        serializeForm : function(form) {

            // 如果form是id,那么取得form元素
            if(typeof form == 'string') {
                form = (document.getElementById(form) || document.forms[form]);
            }

            // el代表form的一个控件元素,disabled表示el是否被禁用,
            // name代表el的name值,val代表el提交的值,data是最后序列化的字符串
            // hasSubmit表示submit已经处理过一次了
            var el, name, val, disabled, data = '', hasSubmit = false;

            // 循环处理表单中的控件元素
            for (var i = 0; i < form.elements.length; i++) {

                
                el = form.elements[i];
                disabled = form.elements[i].disabled;
                name = form.elements[i].name;
                val = form.elements[i].value;

                if (!disabled && name){
                    // 处理没有被禁用并且有name值的控件
                    switch (el.type)
                            {
                        // 处理下拉框,单选和多选两种情况都写在一起了
                        case 'select-one':
                        case 'select-multiple':
                            for (var j = 0; j < el.options.length; j++) {
                                if (el.options[j].selected) {
                                    var opt = el.options[j],
                                        sel = (opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttributeNode('value').specified) ? opt.value : opt.text;
                                    data += encodeURIComponent(name) + '=' + encodeURIComponent(sel) + '&';
                                }
                            }
                            break;

                        // 单选和多选是一类的,放在一起处理了
                        case 'radio':
                        case 'checkbox':
                            if (el.checked) {
                                data += encodeURIComponent(name) + '=' + encodeURIComponent(val) + '&';
                            }
                            break;

                         // 文件域和按钮都不处理了
                        case 'file':

                        case undefined:

                        case 'reset':

                        case 'button':

                            break;

                        // submit按钮值也可以被提交,这个我还是第一次了解。
                        // hasSubmit是防止有两个submit按钮的时候,会处理两遍submit的值
                        case 'submit':
                            if(hasSubmit == false) {
                                data += encodeURIComponent(name) + '=' + encodeURIComponent(val) + '&';
                                hasSubmit = true;
                            }
                            break;

                        // 默认情况
                        default:
                            data += encodeURIComponent(name) + '=' + encodeURIComponent(val) + '&';
                            break;
                    }
                }
            }

            // 去掉最后一个 “&” 字符
            data = data.substr(0, data.length - 1);
            return data;
        },

        // http请求头
        headers:{},

        hasHeaders:false,

        useDefaultHeader:true,

        defaultPostHeader:'application/x-www-form-urlencoded; charset=UTF-8',

        useDefaultXhrHeader:true,

        defaultXhrHeader:'XMLHttpRequest',

        hasDefaultHeaders:true,

        defaultHeaders:{},

        poll:{},

        timeout:{},

        pollInterval:50,

        transactionId:0,

        // 这个是针对IE的,关于ActiveXObject的字符串,IE自己的各个版本都没有统一,这个函数是为了以后IE再出新花样吧
        setProgId:function(id)
        {
            this.activeX.unshift(id);
        },

        setDefaultPostHeader:function(b)
        {
            this.useDefaultHeader = b;
        },

        setDefaultXhrHeader:function(b)
        {
            this.useDefaultXhrHeader = b;
        },

        setPollingInterval:function(i)
        {
            if (typeof i == 'number' && isFinite(i)) {
                this.pollInterval = i;
            }
        },

        // 创建XmlHttpRequest对象
        createXhrObject:function(transactionId)
        {
            var obj,http;
            try
            {
                // 标准浏览器都有XMLHttpRequest对象。早期IE是没有XMLHttpRequest对象的,所以IE浏览器会抛出异常,走进catch代码段中
                http = new XMLHttpRequest();

                // tId代表事务id,一次请求就相当于一个事务,每个事务都有一个唯一的事务号
                obj = { conn:http, tId:transactionId };
            }
            catch(e)
            {

                // 处理IE的ActiveXObject对象,activeX是字符串数组。
                for (var i = 0; i < this.activeX.length; ++i) {
                    try
                    {

                        http = new ActiveXObject(this.activeX[i]);

                        obj = { conn:http, tId:transactionId };
                        break;
                    }
                    catch(e) {
                    }
                }
            }
            finally
            {
                return obj;
            }
        },

        // 调用createXhrObject函数,并自增事务号transactionId
        getConnectionObject:function()
        {
            var o;
            var tId = this.transactionId;

            try
            {
                o = this.createXhrObject(tId);
                if (o) {
                    this.transactionId++;
                }
            }
            catch(e) {
            }
            finally
            {
                return o;
            }
        },

        // 核心异步请求函数
        asyncRequest:function(method, uri, callback, postData)
        {

            // 获得XMLHttpRequest对象
            var o = this.getConnectionObject();

            if (!o) {
                // 容错处理,保护一下。
                return null;
            } else {

                // 调用XMLHttpRequest的open方法。
                // open方法签名是这样的,open(method, url, async, user, password),所以说这是个异步请求
                o.conn.open(method, uri, true);

                // 设置Http头,X-Requested-With:XMLHttpRequest,这样在后台处理的时候可以知道这个请求是异步请求
                if (this.useDefaultXhrHeader) {
                    if (!this.defaultHeaders['X-Requested-With']) {
                        this.initHeader('X-Requested-With', this.defaultXhrHeader, true);
                    }
                }

                // 设置Http头,Content-Type:application/x-www-form-urlencoded; charset=UTF-8
                if(postData && this.useDefaultHeader && (!this.hasHeaders || !this.headers['Content-Type'])){
                    this.initHeader('Content-Type', this.defaultPostHeader);
                }

                // 处理其他Http头
                 if (this.hasDefaultHeaders || this.hasHeaders) {
                    this.setHeader(o);
                }

                // 设置响应函数
                this.handleReadyState(o, callback);

                // 调用XMLHttpRequest的send方法
                o.conn.send(postData || null);

                return o;
            }
        },

        // 设置响应函数
        handleReadyState:function(o, callback)
        {
            var oConn = this;

            // 处理超时,超时就调用abort函数
            if (callback && callback.timeout) {
                this.timeout[o.tId] = window.setTimeout(function() {
                    oConn.abort(o, callback, true);
                }, callback.timeout);
            }

            // 早期浏览器的Ajax实现可能没有onreadystatechange事件函数,所以这里用了setInterval定时处理
            this.poll[o.tId] = window.setInterval(
                    function() {
                        // readyState有这么几个值,UNSET:0;OPENED:1;HEADERS_RECEIVED:2;LOADING:3;DONE:4;
                        // 所以4代表服务器的响应数据都已经接受完毕
                        if (o.conn && o.conn.readyState == 4) {

                            // 清除定时
                            window.clearInterval(oConn.poll[o.tId]);
                            delete oConn.poll[o.tId];

                            // 清除前面设置的超时
                            if (callback && callback.timeout) {
                                window.clearTimeout(oConn.timeout[o.tId]);
                                delete oConn.timeout[o.tId];
                            }

                            // 处理响应数据
                            oConn.handleTransactionResponse(o, callback);
                        }
                    }
                    , this.pollInterval);
        },

        // 处理响应数据
        handleTransactionResponse:function(o, callback, isAbort)
        {

            // 如果callback没有设置,说明不需要回调处理
            if (!callback) {
                this.releaseObject(o);
                return;
            }

            var httpStatus, responseObject;

            // 设置http状态码,正常http的状态码都是200、404、500等。
            // 13030、12002和12029等这些5位数字的状态码,我还真不知道是什么意思
            // 我上网搜索了一下,好像是Firefox的自定义状态码,但是https://developer.mozilla.org/en/XMLHttpRequest中并没有仔细说明
            // 要是有人知道这段历史,可以贴出来。估计这个5位数字是兼容以前旧的浏览器的。
            try
            {
                if (o.conn.status !== undefined && o.conn.status != 0) {
                    httpStatus = o.conn.status;
                }
                else {
                    httpStatus = 13030;
                }
            }
            catch(e) {


                httpStatus = 13030;
            }

            // httpStatus是2XX的,就表示请求成功
            if ((httpStatus >= 200 && httpStatus < 300) || (Ext.isIE && httpStatus == 1223)) {

                // 生成响应对象
                responseObject = this.createResponseObject(o, callback.argument);

                // 调用success方法
                if (callback.success) {
                    if (!callback.scope) {
                        callback.success(responseObject);
                    }
                    else {


                        callback.success.apply(callback.scope, [responseObject]);
                    }
                }
            } else {
                switch (httpStatus) {

                    // 下面这些case都是失败的情况
                    case 12002:
                    case 12029:
                    case 12030:
                    case 12031:
                    case 12152:
                    case 13030:
                        responseObject = this.createExceptionObject(o.tId, callback.argument, (isAbort ? isAbort : false));
                        if (callback.failure) {
                            if (!callback.scope) {
                                callback.failure(responseObject);
                            }
                            else {
                                callback.failure.apply(callback.scope, [responseObject]);
                            }
                        }
                        break;

                    // 这个是失败的情况的默认处理
                    default:
                        responseObject = this.createResponseObject(o, callback.argument);
                        if (callback.failure) {
                            if (!callback.scope) {
                                callback.failure(responseObject);
                            }
                            else {
                                callback.failure.apply(callback.scope, [responseObject]);
                            }
                        }
                }
            }

            // 释放响应对象
            this.releaseObject(o);
            responseObject = null;
        },

        // 创建响应对象
        createResponseObject:function(o, callbackArg)
        {
            var obj = {};
            var headerObj = {};

            // 解析响应的http头
            try
            {
                var headerStr = o.conn.getAllResponseHeaders();
                var header = headerStr.split('\n');
                for (var i = 0; i < header.length; i++) {
                    var delimitPos = header[i].indexOf(':');
                    if (delimitPos != -1) {
                        headerObj[header[i].substring(0, delimitPos)] = header[i].substring(delimitPos + 2);
                    }
                }
            }
            catch(e) {
            }

            // 设置回调函数中可能用到的值,够全的。

            // 事务ID
            obj.tId = o.tId;
            // http状态码
            obj.status = o.conn.status;
            // http状态字符串,例如,200 OK
            obj.statusText = o.conn.statusText;
            // http响应头函数,这里创建了一个闭包
            obj.getResponseHeader = function(header){return headerObj[header];};
            obj.getAllResponseHeaders = function(){return headerStr};
            // 响应文本
            obj.responseText = o.conn.responseText;
            // 响应XML
            obj.responseXML = o.conn.responseXML;
            // 自定义的回调参数
            if (typeof callbackArg !== undefined) {
                obj.argument = callbackArg;
            }

            return obj;
        },

        // 创建失败时候的响应对象,大概有两种情况,一种是请求失败,例如,500,服务器出错。一种是请求超时。
        createExceptionObject:function(tId, callbackArg, isAbort)
        {
            var COMM_CODE = 0;
            var COMM_ERROR = 'communication failure';
            var ABORT_CODE = -1;
            var ABORT_ERROR = 'transaction aborted';

            var obj = {};

            obj.tId = tId;
            // isAbort,请求超时的时候isAbort为true
            if (isAbort) {
                obj.status = ABORT_CODE;
                obj.statusText = ABORT_ERROR;
            }
            else {
                obj.status = COMM_CODE;
                obj.statusText = COMM_ERROR;
            }

            if (callbackArg) {
                obj.argument = callbackArg;
            }

            return obj;
        },

        // 添加http头
        initHeader:function(label, value, isDefault)
        {
            var headerObj = (isDefault) ? this.defaultHeaders : this.headers;

            if (headerObj[label] === undefined) {
                headerObj[label] = value;
            }
            else {


                headerObj[label] = value + "," + headerObj[label];
            }

            if (isDefault) {
                this.hasDefaultHeaders = true;
            }
            else {
                this.hasHeaders = true;
            }
        },

        // 设置http头
        setHeader:function(o)
        {
            if (this.hasDefaultHeaders) {
                for (var prop in this.defaultHeaders) {
                    if (this.defaultHeaders.hasOwnProperty(prop)) {
                        o.conn.setRequestHeader(prop, this.defaultHeaders[prop]);
                    }
                }
            }

            if (this.hasHeaders) {
                for (var prop in this.headers) {
                    if (this.headers.hasOwnProperty(prop)) {
                        o.conn.setRequestHeader(prop, this.headers[prop]);
                    }
                }
                this.headers = {};
                this.hasHeaders = false;
            }
        },

        // 清空http头
        resetDefaultHeaders:function() {
            delete this.defaultHeaders;
            this.defaultHeaders = {};
            this.hasDefaultHeaders = false;
        },

        // 中止请求
        abort:function(o, callback, isTimeout)
        {
            if (this.isCallInProgress(o)) {
                o.conn.abort();
                window.clearInterval(this.poll[o.tId]);
                delete this.poll[o.tId];
                if (isTimeout) {
                    delete this.timeout[o.tId];
                }

                this.handleTransactionResponse(o, callback, true);

                return true;
            }
            else {
                return false;
            }
        },

        // 判断一下请求是否在处理
        isCallInProgress:function(o)
        {


            if (o.conn) {
                return o.conn.readyState != 4 && o.conn.readyState != 0;
            }
            else {

                return false;
            }
        },

        // 释放XMLHttpRequest对象
        releaseObject:function(o)
        {

            o.conn = null;

            o = null;
        },

        // IE的activeX字符串
        activeX:[
        'MSXML2.XMLHTTP.3.0',
        'MSXML2.XMLHTTP',
        'Microsoft.XMLHTTP'
        ]


    };
0 请登录后投票
   发表时间:2012-04-25  
楼主,写的不错,给力啊。
怎么下边没有了。
期待中...
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics