论坛首页 Web前端技术论坛

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

浏览 17831 次
精华帖 (3) :: 良好帖 (11) :: 新手帖 (1) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-02-15   最后修改:2012-02-23

本帖主要是对ExtJS 2.3版的源代码经行逐行逐句的解析,欢迎大家一起讨论,有写错的地方,恳请指正,欢迎拍砖。谢谢~


选择2.3版本来分析,是因为其代码量相对较少,而且不依赖其他js库,值得一读。

 

官方下载地址:http://dev.sencha.com/deploy/ext-2.3.0.zip


一、Ext.js,2012年02月15日晚 21:50 分析完成

 

二、ext-base.js,解析中

 

   发表时间:2012-02-15   最后修改:2012-02-20

文件:ext-2.3.0/source/core/Ext.js

 

概述:Ext.js主要负责创建Ext全局对象,构建其命名空间,定义extend类继承方法,探测浏览器信息和对Javascript原生库进行扩展。

 

分析:

 

一、创建Ext全局对象

// 创建Ext全局对象,大多数JS库为了避免和其他JS库命名冲突,都会把自己创建的类或函数封装到一个全局变量中去,
// 这样就相当于创造了自己的命名空间,可以算是一个单例模式。例如,jQuery就是全部都封装到$变量中去。
Ext = {version: '2.3.0'};

 

二、设置全局undefined变量

// 兼容旧浏览器,早期的浏览器实现中,undefined并不是全局变量。就是说,你要判断一个变量是否是没定义,
// 你需要这样写if (typeof  a == 'undefined'),不可以写成if (a == undefined)。所以,上面的代码就可以理解了。
// 右面的window["undefined"],因为window对象没有undefined属性,所以其值为undefined,
// 把undefined赋值给window的undefined属性上,就相当于把undefined设置成了全局变量,
// 这样以后你再判断一个变量是否是未定义的时候,就不需要使用typeof,直接判断就可以了。
window["undefined"] = window["undefined"];

 

三、定义apply方法属性复制函数

// apply方法,把对象c中的属性复制到对象o中,支持默认属性defaults设置。这个方法属于对象属性的一个浅拷贝函数。
Ext.apply = function(o, c, defaults){
    if(defaults){
        // 如果默认值defaults存在,那么先把defaults上得属性复制给对象o
        Ext.apply(o, defaults);
    }
    if(o && c && typeof c == 'object'){
        for(var p in c){
            o[p] = c[p];
        }
    }
    return o;
};

 

四、扩展Ext对象

(function(){
    // idSeed,用来生成自增长的id值。
    var idSeed = 0;

    // ua,浏览器的用户代理,主要用来识别浏览器的型号、版本、内核和操作系统等。
    var ua = navigator.userAgent.toLowerCase(),
        check = function(r){
            return r.test(ua);
        },
      
        // isStrict,表示当前浏览器是否是标准模式。
        // 如果正确的设置了网页的doctype,则compatMode为CSS1Compat,否则为BackCompat
        isStrict = document.compatMode == "CSS1Compat",

        // isOpera,表示是否是opera浏览器。
        isOpera = check(/opera/),

        // isChrome,表示是否是谷歌浏览器。
        isChrome = check(/chrome/),

        // isWebKit,表示当前浏览器是否使用WebKit引擎。
        // WebKit是浏览器内核,Safari和Chrome使用WebKit引擎。
        isWebKit = check(/webkit/),

        // isSafari,表示是否是苹果浏览器,下面代码是对其版本识别。
        isSafari = !isChrome && check(/safari/),
        isSafari2 = isSafari && check(/applewebkit\/4/), // unique to Safari 2
        isSafari3 = isSafari && check(/version\/3/),
        isSafari4 = isSafari && check(/version\/4/),

        // isIE,表示是否是IE浏览器,下面代码是对其版本识别。
        isIE = !isOpera && check(/msie/),
        isIE7 = isIE && check(/msie 7/),
        isIE8 = isIE && check(/msie 8/),
        isIE6 = isIE && !isIE7 && !isIE8,

        // isGecko,表示当前浏览器是否使用Gecko引擎。
        // Gecko是浏览器内核,Firefox使用Gecko引擎。
        isGecko = !isWebKit && check(/gecko/),
        isGecko2 = isGecko && check(/rv:1\.8/),
        isGecko3 = isGecko && check(/rv:1\.9/),

        // isBorderBox,表示浏览器是否是IE的盒模式。
        // 众所周知,IE的盒模式和W3C的盒模式不一致。当IE浏览器在怪异模式下,就会导致错误的盒模式。
        isBorderBox = isIE && !isStrict,

        // isWindows,表示是否是windows操作系统。
        isWindows = check(/windows|win32/),

        // isMac,表示是否是苹果操作系统。
        isMac = check(/macintosh|mac os x/),

        // isAir,AIR(Adobe Integrated Runtime),是adobe开发的一个平台吧,不太了解,没用过。
        isAir = check(/adobeair/),

        // isLinux,表示是否是Liunx操作系统。
        isLinux = check(/linux/),

        // isSecure,表示是否是https连接。
        isSecure = /^https/i.test(window.location.protocol);

    // 缓存一下CSS的背景图像,防止图像闪烁,应该是IE6的一个bug。
    if(isIE6){
        try{
            document.execCommand("BackgroundImageCache", false, true);
        }catch(e){}
    }

    // 扩展Ext对象,有一些属性,这个文件中没有使用,现在先不解释其作用,后面遇到了再讲。
    Ext.apply(Ext, {

        // isStrict,表示是否是标准模式。
        isStrict : isStrict,

        // isSecure,表示是否是https连接。
        isSecure : isSecure,

        // isReady,表示Dom文档树是否加载完成
        isReady : false,

        // enableGarbageCollector和enableListenerCollection这两个变量在Element中使用了,解析到Element时再解释其含义。
        enableGarbageCollector : true,

        enableListenerCollection:false,

        // SSL_SECURE_URL,这个值在构造隐藏的iframe时,用来设置src属性的,只是当是https连接的时候才用。
        SSL_SECURE_URL : "javascript:false",

        // BLANK_IMAGE_URL,1像素透明图片地址
        BLANK_IMAGE_URL : "http:/"+"/extjs.com/s.gif",

        // emptyFn,空函数
        emptyFn : function(){},

        // applyIf,把对象c的属性复制到对象o上,只复制o没有的属性
        applyIf : function(o, c){
            if(o && c){
                for(var p in c){
                    if(typeof o[p] == "undefined"){ o[p] = c[p]; }
                }
            }
            return o;
        },

        // addBehaviors函数可以一次给多个Ext.Element添加不同的事件响应函数
        addBehaviors : function(o){

            // 判断Dom树是否已经加装成功
            if(!Ext.isReady){
                // 如果Dom树没有加载好,那么等到加载好了,再执行此函数
                Ext.onReady(function(){
                    Ext.addBehaviors(o);
                });
                return;
            }

            // cache,简单缓存一下选择过的CSS Selector。
            var cache = {}; 

            // 遍历对象o,b的格式应该是selector@eventName
            for(var b in o){

                // parts[0]=selector,parts[1]=eventName
                var parts = b.split('@');
                if(parts[1]){
                    // 如果事件名称存在,s为selector
                    var s = parts[0];

                    // 判断一下cache缓存中是否已经查询过该selector
                    if(!cache[s]){

                        // 如果没有查询过,那么用select方法查询一下
                        cache[s] = Ext.select(s);
                    }

                    // 调用Element的on方法来注册事件函数
                    cache[s].on(parts[1], o[b]);
                }
            }

            // 释放cache变量,防止内存泄露
            cache = null;
        },

        // 取得el的id属性。el可以是Ext.Element或者是Dom元素。
        // 如果el不存在,那么生成一个自增长的id,并返回这个id。
        // 如果el存在,但是没有id属性,那么生成一个自增长的id,并赋值给el的id属性,最后返回id值。
        // 如果el存在,并且也有id属性,那么直接返回el的id值。
        // prefix表示生成自增长的id的前缀,默认值为ext-gen
        id : function(el, prefix){
            prefix = prefix || "ext-gen";
            el = Ext.getDom(el);
            var id = prefix + (++idSeed);
            return el ? (el.id ? el.id : (el.id = id)) : id;
        },

        // 类继承函数,基于javascript的prototype,模仿面相对象的继承特性。
        // 整个ExtJS框架的继承机制就是这个函数实现的。
        extend : function(){
            // override函数,用来覆盖prototype上的属性的(私有对象,仅下面的return function内部可以使用)
            var io = function(o){
                for(var m in o){
                    this[m] = o[m];
                }
            };

            // Object的构造函数(私有对象,仅下面的return function内部可以使用)
            var oc = Object.prototype.constructor;

            return function(sb, sp, overrides){
                // sb表示subclass,sp表示superclass,overrides是默认值为对象型
                // 如果sp是对象,表示没有传sb变量进来,所以重新设置一下参数
                if(typeof sp == 'object'){
                    overrides = sp;
                    sp = sb;
                    // 如果overrides中提供了构造函数,那么就用提供的,
                    // 否则用下面这个匿名函数,匿名函数会调用父类的构造函数
                    sb = overrides.constructor != oc ? overrides.constructor : function(){sp.apply(this, arguments);};
                }

                // F是一个临时的类,其prototype指向superclass的prototype,
                // 同时也把subclass的prototype指向了F对象,
                // 这样可以避免在类继承的时候,调用superclass的构造函数
                var F = function(){}, sbp, spp = sp.prototype;
                F.prototype = spp;
                sbp = sb.prototype = new F();
                sbp.constructor=sb;
                sb.superclass=spp;
                if(spp.constructor == oc){
                    spp.constructor=sp;
                }

                // 覆盖函数
                sb.override = function(o){
                    Ext.override(sb, o);
                };
                sbp.override = io;

                // 设置默认值
                Ext.override(sb, overrides);

                // 继承函数,这样写方便,可以直接从别的类继承新类
                sb.extend = function(o){Ext.extend(sb, o);};
                return sb;
            };
        }(),

        // 覆盖函数,直接把属性复制到origclass的prototype上
        override : function(origclass, overrides){
            if(overrides){
                var p = origclass.prototype;
                for(var method in overrides){
                    p[method] = overrides[method];
                }

                // 下面是处理IE浏览器在枚举对象的属性时,
                // 原生的方法toString枚举不出来,即使是自定义的toString也不行
                if(Ext.isIE && overrides.toString != origclass.toString){
                    p.toString = overrides.toString;
                }
            }
        },

        // 生成命名空间。javascript语言没有命名空间这么一说,所以只好用对象的属性来实现。
        namespace : function(){
            var a=arguments, o=null, i, j, d, rt;
            for (i=0; i<a.length; ++i) {
                d=a[i].split(".");
                rt = d[0];
                eval('if (typeof ' + rt + ' == "undefined"){' + rt + ' = {};} o = ' + rt + ';');
                for (j=1; j<d.length; ++j) {
                    o[d[j]]=o[d[j]] || {};
                    o=o[d[j]];
                }
            }
        },

        // URL编码函数
        urlEncode : function(o){
            if(!o){
                return "";
            }
            var buf = [];
            for(var key in o){
                var ov = o[key], k = encodeURIComponent(key);
                var type = typeof ov;
                if(type == 'undefined'){
                    buf.push(k, "=&");
                }else if(type != "function" && type != "object"){
                    buf.push(k, "=", encodeURIComponent(ov), "&");
                }else if(Ext.isDate(ov)){
                    var s = Ext.encode(ov).replace(/"/g, '');
                    buf.push(k, "=", s, "&");
                }else if(Ext.isArray(ov)){
                    if (ov.length) {
                        for(var i = 0, len = ov.length; i < len; i++) {
                            buf.push(k, "=", encodeURIComponent(ov[i] === undefined ? '' : ov[i]), "&");
                        }
                    } else {
                        buf.push(k, "=&");
                    }
                }
            }
            buf.pop();
            return buf.join("");
        },

        // URL解码函数
        urlDecode : function(string, overwrite){
            if(!string || !string.length){
                return {};
            }
            var obj = {};
            var pairs = string.split('&');
            var pair, name, value;
            for(var i = 0, len = pairs.length; i < len; i++){
                pair = pairs[i].split('=');
                name = decodeURIComponent(pair[0]);
                value = decodeURIComponent(pair[1]);
                if(overwrite !== true){
                    if(typeof obj[name] == "undefined"){
                        obj[name] = value;
                    }else if(typeof obj[name] == "string"){
                        obj[name] = [obj[name]];
                        obj[name].push(value);
                    }else{
                        obj[name].push(value);
                    }
                }else{
                    obj[name] = value;
                }
            }
            return obj;
        },

        // each函数,迭代数组时候用的,和jQuery的each方法差不多,不过Ext的each只能迭代数组或者类数组
        each : function(array, fn, scope){
            if(typeof array.length == "undefined" || typeof array == "string"){
                array = [array];
            }
            for(var i = 0, len = array.length; i < len; i++){
                if(fn.call(scope || array[i], array[i], i, array) === false){ return i; };
            }
        },

        // 组合函数,标识了过期不推荐,这里也就不看了
        combine : function(){
            var as = arguments, l = as.length, r = [];
            for(var i = 0; i < l; i++){
                var a = as[i];
                if(Ext.isArray(a)){
                    r = r.concat(a);
                }else if(a.length !== undefined && !a.substr){
                    r = r.concat(Array.prototype.slice.call(a, 0));
                }else{
                    r.push(a);
                }
            }
            return r;
        },

        // 处理需要转义的字符,在需要转义的字符前面多加一个反斜线
        escapeRe : function(s) {
            return s.replace(/([.*+?^${}()|[\]\/\\])/g, "\\$1");
        },

        // 回调函数,可以指定this值和参数,还可以延迟执行
        callback : function(cb, scope, args, delay){
            if(typeof cb == "function"){
                if(delay){
                    cb.defer(delay, scope, args || []);
                }else{
                    cb.apply(scope, args || []);
                }
            }
        },

        // 取得html元素,el可以是id,也可以是Ext的Element对象
        getDom : function(el){
            if(!el || !document){
                return null;
            }
            return el.dom ? el.dom : (typeof el == 'string' ? document.getElementById(el) : el);
        },

        // 取得document的Element对象
        getDoc : function(){
            return Ext.get(document);
        },

        // 取得文档对象。
        // document.body = document.getElementsByTagName('body')[0];
        // document.documentElement = document.getElementsByTagName('html')[0];
        getBody : function(){
            return Ext.get(document.body || document.documentElement);
        },

        // 取得组件对象
        getCmp : function(id){
            return Ext.ComponentMgr.get(id);
        },

        // 这个用来取得数字,可以设置默认值
        num : function(v, defaultValue){
            v = Number(v == null || typeof v == 'boolean'? NaN : v);
            return isNaN(v)? defaultValue : v;
        },

        // 销毁函数,销毁的对象可以是Element或者是Component
        destroy : function(){
            for(var i = 0, a = arguments, len = a.length; i < len; i++) {
                var as = a[i];
                if(as){
                    if(typeof as.destroy == 'function'){
                        as.destroy();
                    }
                    else if(as.dom){
                        as.removeAllListeners();
                        as.remove();
                    }
                }
            }
        },

        // 清除Dom节点n
        removeNode : isIE ? function(){
            var d;
            return function(n){
                if(n && n.tagName != 'BODY'){
                    d = d || document.createElement('div');
                    d.appendChild(n);
                    d.innerHTML = '';
                }
            }
        }() : function(n){
            if(n && n.parentNode && n.tagName != 'BODY'){
                n.parentNode.removeChild(n);
            }
        },

        // javascript是一个弱类型的语音,所以下面这个type函数可以正确返回测试变量的类型
        type : function(o){
            // 其实undefined和null也可以算两种类型,这里把他们俩全归类到false了
            if(o === undefined || o === null){
                return false;
            }
            if(o.htmlElement){
                return 'element';
            }
            var t = typeof o;
            if(t == 'object' && o.nodeName) {
                switch(o.nodeType) {
                    case 1: return 'element';
                    case 3: return (/\S/).test(o.nodeValue) ? 'textnode' : 'whitespace';
                }
            }
            if(t == 'object' || t == 'function') {
                switch(o.constructor) {
                    case Array: return 'array';
                    case RegExp: return 'regexp';
                    case Date: return 'date';
                }
                if(typeof o.length == 'number' && typeof o.item == 'function') {
                    return 'nodelist';
                }
            }
            return t;
        },

        // 判断v是否为空或者未定义,allowBlank表示是否允许空字符串,默认值是false。
        // 当allowBlank设置为true时,isEmpty('')返回false
        isEmpty : function(v, allowBlank){
            return v === null || v === undefined || (!allowBlank ? v === '' : false);
        },

        // 判断v是否为空,为空的话可以设置默认值,不为空的话返回v值
        value : function(v, defaultValue, allowBlank){
            return Ext.isEmpty(v, allowBlank) ? defaultValue : v;
        },

        // 判断v是否是数组对象
        isArray : function(v){
            return v && typeof v.length == 'number' && typeof v.splice == 'function';
        },

        // 判断v是否是日期对象
        isDate : function(v){
            return v && typeof v.getFullYear == 'function';
        },

		// isOpera,表示是否是opera浏览器。
        isOpera : isOpera,

		// isWebKit,表示当前浏览器是否使用WebKit引擎。
        isWebKit: isWebKit,

		// isChrome,表示是否是谷歌浏览器。
        isChrome : isChrome,

		// isSafari,表示是否是苹果浏览器,下面代码是对其版本识别。
        isSafari : isSafari,
 
        isSafari4 : isSafari4,

        isSafari3 : isSafari3,

        isSafari2 : isSafari2,

		// isIE,表示是否是IE浏览器,下面代码是对其版本识别。
        isIE : isIE,

        isIE6 : isIE6,

        isIE7 : isIE7,

        isIE8 : isIE8,

		// isGecko,表示当前浏览器是否使用Gecko引擎。
        isGecko : isGecko,

        isGecko2 : isGecko2,

        isGecko3 : isGecko3,

		// isBorderBox,表示浏览器是否是IE的盒模式。
        isBorderBox : isBorderBox,

		// isLinux,表示是否是Liunx操作系统。
        isLinux : isLinux,

		// isWindows,表示是否是windows操作系统。
        isWindows : isWindows,

		// isMac,表示是否是苹果操作系统。
        isMac : isMac,

		// isAir,AIR(Adobe Integrated Runtime)
        isAir : isAir,

		// useShims,表示是IE 6浏览器或者是苹果系统上的Firefox浏览器,并且gecko内核版本小于3。
		// 具体哪里使用到了,还不知道,读到后面代码发现了,再解释。
        useShims : ((isIE && !(isIE7 || isIE8)) || (isMac && isGecko && !isGecko3))
    });

    // namespace函数的简写方式
    Ext.ns = Ext.namespace;
})(); 
 

五、创建Ext所用的命名空间

Ext.ns("Ext", "Ext.util", "Ext.grid", "Ext.dd", "Ext.tree", "Ext.data",
                "Ext.form", "Ext.menu", "Ext.state", "Ext.lib", "Ext.layout", "Ext.app", "Ext.ux");

 

六、扩展原生Function

Ext.apply(Function.prototype, {

    // 创建回调函数,这个有点太简单了,并且this指向了window,不可以自定义。功能不是很强
    createCallback : function(/*args...*/){
        var args = arguments;
        var method = this;
        return function() {
            return method.apply(window, args);
        };
    },

    // 创建委托(注:Delegate在C#里是叫委托的,其实就是c语音里的函数指针,js中叫匿名函数)
    // createDelegate比createCallback高级了一点可以设置this指针,同时也可以设置传入的参数
    createDelegate : function(obj, args, appendArgs){
        var method = this;
        return function() {
            var callArgs = args || arguments;
            if(appendArgs === true){
                callArgs = Array.prototype.slice.call(arguments, 0);
                callArgs = callArgs.concat(args);
            }else if(typeof appendArgs == "number"){
                callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
                var applyArgs = [appendArgs, 0].concat(args); // create method call params
                Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
            }
            return method.apply(obj || window, callArgs);
        };
    },

    // defer是createDelegate的延迟版,可以延迟执行
    defer : function(millis, obj, args, appendArgs){
        var fn = this.createDelegate(obj, args, appendArgs);
        if(millis){
            return setTimeout(fn, millis);
        }
        fn();
        return 0;
    },

    // 这个函数可以在你执行完原函数以后,执行一下自定义的函数。
    createSequence : function(fcn, scope){
        if(typeof fcn != "function"){
            return this;
        }
        var method = this;
        return function() {
            var retval = method.apply(this || window, arguments);
            fcn.apply(scope || this || window, arguments);
            return retval;
        };
    },

    // 这个就是完全的函数代理了,和Spring的AOP是一个概念。
    createInterceptor : function(fcn, scope){
        if(typeof fcn != "function"){
            return this;
        }
        var method = this;
        return function() {
            fcn.target = this;
            fcn.method = method;
            if(fcn.apply(scope || this || window, arguments) === false){
                return;
            }
            return method.apply(this || window, arguments);
        };
    }
});
 

七、扩展原生String

Ext.applyIf(String, {

    // 转义单引号和反斜杠
    escape : function(string) {
        return string.replace(/('|\\)/g, "\\$1");
    },

    // 这个函数是对数组进行空格补位
    leftPad : function (val, size, ch) {
        var result = new String(val);
        if(!ch) {
            ch = " ";
        }
        while (result.length < size) {
            result = ch + result;
        }
        return result.toString();
    },

    // 这个是格式化字符串,很多语言都有的功能
    format : function(format){
        var args = Array.prototype.slice.call(arguments, 1);
        return format.replace(/\{(\d+)\}/g, function(m, i){
            return args[i];
        });
    }
});

// 切换值函数
String.prototype.toggle = function(value, other){
    return this == value ? other : value;
};

// 去空格函数
String.prototype.trim = function(){
    var re = /^\s+|\s+$/g;
    return function(){ return this.replace(re, ""); };
}();
 

八、扩展原生Number

Ext.applyIf(Number.prototype, {
    // 对当前数值取一个范围
    constrain : function(min, max){
        return Math.min(Math.max(this, min), max);
    }
});
 

九、扩展原生Array

Ext.applyIf(Array.prototype, {
    
    indexOf : function(o){
       for (var i = 0, len = this.length; i < len; i++){
          if(this[i] == o) return i;
       }
       return -1;
    },

    
    remove : function(o){
       var index = this.indexOf(o);
       if(index != -1){
           this.splice(index, 1);
       }
       return this;
    }
});
 

十、扩展原生Date

// 返回一个时间差
Date.prototype.getElapsed = function(date) {
    return Math.abs((date || new Date()).getTime()-this.getTime());
};
 

 

0 请登录后投票
   发表时间:2012-02-16  
非常不错, 感谢分享 欢迎继续
0 请登录后投票
   发表时间:2012-02-17  
非常不错,期待继续。
0 请登录后投票
   发表时间:2012-02-18  
damoqiongqiu 写道
非常不错,期待继续。

谢谢大家鼓励,晚上我会更新adapter的代码解析部分。
0 请登录后投票
   发表时间:2012-02-18  
,期待继续。。。
希望以后文章中加点怎么更优的使用Ext。
0 请登录后投票
   发表时间:2012-02-18  
注释很给力啊。
0 请登录后投票
   发表时间:2012-02-19   最后修改:2012-02-19
 
0 请登录后投票
   发表时间:2012-02-20  
EXT的结果的确写得好,这就是卖钱的东西
0 请登录后投票
   发表时间:2012-02-20   最后修改:2012-02-20

文件:ext-2.3.0/source/adapter/ext-base.js

 

概述:ext-base.js主要是负责封装了Dom元素的位置和尺寸、事件模型、Ajax请求和动画实现。ext-base是处在Ext整个框架最底层,处理的主要都是浏览器兼容问题。例如,IE和W3C的事件模型,及XMLHttpRequest模型等。

 

一、享元模式

// 我把两处相关代码放到一起来解释了。flyweight就是使用了享元模式。
// 享元模式很好理解,就是大家共享同一个元素,以节省内存开销。
// 不过这种模式不适合在回调函数或者setInterval这种函数中执行。
var libFlyweight;
    
// el就是Dom元素
function fly(el) {
    if (!libFlyweight) {
        libFlyweight = new Ext.Element.Flyweight();
    }
    libFlyweight.dom = el;
    return libFlyweight;
}

二、CSS Object Model,Dom元素的尺寸和位置的获取和设置

 

    下面的代码中大量的涉及到了offsetWidth、scrollWidth、clientWidth和innerWidth等这些属性。这些属性都属于CSS Object Model中的一部分,所以如果想读懂Ext.lib.Dom模块代码,需要做点功课先。

 

    我平时在阅读JS代码时,主要参考文档:
    W3C:http://www.w3.org

    IE:http://msdn.microsoft.com/library/default.aspx (msdn一定要访问英文版,中文版的你什么也查不到,你懂的)
    Firefox:https://developer.mozilla.org/zh-CN/ (火狐的开发者网络,我主要是参考这个的)

 

    还有一个W3CSchool,我不推荐给大家,上面的解释和例子很有限,不是很详细透彻。英文还可以的童靴,最好不去看W3CSchool。

 

    CSS Object Model的文档,在这里:http://www.w3.org/TR/cssom-view/

 

    第一个需要解释的对象就是window,下面的代码我是直接从W3C文档中复制过来的。

 

partial interface Window {
  MediaQueryList matchMedia(DOMString media_query_list);
  readonly attribute Screen screen;

  // viewport
  readonly attribute long innerWidth;
  readonly attribute long innerHeight;

  // viewport scrolling
  readonly attribute long scrollX;
  readonly attribute long pageXOffset;
  readonly attribute long scrollY;
  readonly attribute long pageYOffset;
  void scroll(long x, long y);
  void scrollTo(long x, long y);
  void scrollBy(long x, long y);

  // client
  readonly attribute long screenX;
  readonly attribute long screenY;
  readonly attribute long outerWidth;
  readonly attribute long outerHeight;
};

 

    matchMedia:W3C文档并没有详细解释这个函数,只是说了一下具体实现的流程,你可以参看一下MDN的文档,https://developer.mozilla.org/en/DOM/window.matchMedia,那里有很详细的解释。

 

    这个media指的是介质,比如你用笔记本上网看网页,那media就指笔记本的屏幕,如果是ipad或者手机,那么可能就是ipad或者手机的屏幕。现在是互联网和移动的时代,所以网站支持不同分辨率的显示屏也是很有用的。你有时间的话可以看一下,https://developer.mozilla.org/en/CSS/Media_queries,就明白了这个函数的意义何在。

 

    screen:screen指的就是系统屏幕。

 

    innerWidth:指的是视图的宽度,如果有竖滚动条,那么innerWidth包括竖滚动条的宽度。
    innerHeight:指的是视图的高度,如果有横滚动条,那么innerHeight包括横滚动条的高度。
    视图其实指的就是浏览器窗口中的可视区域。

 

    scrollX和pageXOffset:指的是相对于视图原点(即左上角那个点)的横坐标值。
    scrollY和pageYOffset:指的是相对于视图原点的纵坐标值。
    scroll和scrollTo是一样的,就是使scrollX和scrollY直接等于传入的参数x和y,而scrollBy是scrollX和scrollY加上传入的参数x和y。

    screenX和screenY是相对于屏幕的横纵坐标,outerWidth和outerHeight也是屏幕的宽和高。

第二个需要解释的对象是Element

partial interface Element {
  ClientRectList getClientRects();
  ClientRect getBoundingClientRect();

  // scrolling
  void scrollIntoView(optional boolean top);
           attribute long scrollTop;   // scroll on setting
           attribute long scrollLeft;  // scroll on setting
  readonly attribute long scrollWidth;
  readonly attribute long scrollHeight;

  readonly attribute long clientTop;
  readonly attribute long clientLeft;
  readonly attribute long clientWidth;
  readonly attribute long clientHeight;
};

clientTop:指的是元素上边框的宽度。
clientLeft:指的是元素左边框的宽度。
clientWidth:指的是元素左右内边距值加上内容宽度(不包括滚动条的宽度,也不包括滚动的宽度)。
clientHeight:指的是元素上下内边距值加上内容高度(不包括滚动条的高度,也不包括滚动的高度)。

scrollTop:指的是元素向上已滚动的高度。
scrollLeft:指的是元素向左已滚动的宽度。
scrollWidth:指的是元素滚动的宽度。(不包括滚动条的宽度)
scrollHeight:指的是元素滚动的高度。(不包括滚动条的高度)

第三个需要解释的对象是HTMLElement

partial interface HTMLElement {
  readonly attribute Element offsetParent;
  readonly attribute long offsetTop;
  readonly attribute long offsetLeft;
  readonly attribute long offsetWidth;
  readonly attribute long offsetHeight;
};

offsetParent:返回不是static定位的父节点,如果没有这样的父节点,一致迭代到body元素。
offsetTop:相对于offsetParent的纵坐标。
offsetLeft:相对于offsetParent的横坐标。
offsetWidth:就是元素的宽度加上左右内边距值。
offsetHeight:就是元素的高度加上左右内边距的值。

最后一个需要解释的对象就是MouseEvent

partial interface MouseEvent {
  readonly attribute long screenX;
  readonly attribute long screenY;

  readonly attribute long pageX;
  readonly attribute long pageY;

  readonly attribute long clientX;
  readonly attribute long clientY;
  readonly attribute long x;
  readonly attribute long y;

  readonly attribute long offsetX;
  readonly attribute long offsetY;
};

 这个就很好解释了screenX和screenY是相当于屏幕的,pageX和pageY是相当于视图的,clientX和clientY是相对于本身内容区域的,x和y的意思和clientX和clientY的意义是一样的。offsetX和offsetY是相当于offsetParent的。

(function() {
    // libFlyweight是享元模式的变量
    var libFlyweight;

    Ext.lib.Dom = {

        // 下面有三组函数,大致的意思是这样的,getViewWidth和getViewHeight调用其他两组函数
        // getDocumentHeight和getDocumentWidth取的是当前文档的宽度和高度,这个包括滚动高度和宽度,取的就是scrollHeight和scrollWidth
        // getViewportHeight和getViewportWidth取的是当前视图的宽度和高度,取的是innerHeight和innerWidth

        // full等于true的时候调用getDocumentWidth,取的是包括滚动所有的宽度。而等于false的时候,取的是视图的宽度,没有滚动条那种情况。
        getViewWidth : function(full) {
            return full ? this.getDocumentWidth() : this.getViewportWidth();
        },

        getViewHeight : function(full) {
            return full ? this.getDocumentHeight() : this.getViewportHeight();
        },

        getDocumentHeight: function() {
            // 这个怪异模式和标准模式的body元素和documentElement元素的scrollHeight是不一样的,具体为啥,这个我也没弄明白,浏览器就这样实现的。
            // 最好的办法就是避免使用怪异模式,别给自己找麻烦。
            // 希望有知道的,给指点一下。
            var scrollHeight = (document.compatMode != "CSS1Compat") ? document.body.scrollHeight : document.documentElement.scrollHeight;
            return Math.max(scrollHeight, this.getViewportHeight());
        },

        getDocumentWidth: function() {
            var scrollWidth = (document.compatMode != "CSS1Compat") ? document.body.scrollWidth : document.documentElement.scrollWidth;
            return Math.max(scrollWidth, this.getViewportWidth());
        },

        getViewportHeight: function(){
            if(Ext.isIE){
                return Ext.isStrict ? document.documentElement.clientHeight :
                         document.body.clientHeight;
            }else{
                return self.innerHeight;
            }
        },

        getViewportWidth: function() {
            if(Ext.isIE){
                return Ext.isStrict ? document.documentElement.clientWidth :
                         document.body.clientWidth;
            }else{
                return self.innerWidth;
            }
        },

        isAncestor : function(p, c) {
            p = Ext.getDom(p);
            c = Ext.getDom(c);
            if (!p || !c) {
                return false;
            }

            // contains就是判断一个元素是不是两外一个元素的祖先,文档在这里https://developer.mozilla.org/en/DOM/Node.contains
            if (p.contains && !Ext.isWebKit) {
                return p.contains(c);
            } else if (p.compareDocumentPosition) {
                // compareDocumentPosition比较两个元素位置的,文档在这里https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition
                return !!(p.compareDocumentPosition(c) & 16);
            } else {

                下面代码逻辑很清楚,一直向上查找parentNode是否等于parent,如果等于返回true,否则返回false
                var parent = c.parentNode;
                while (parent) {
                    if (parent == p) {
                        return true;
                    }
                    else if (!parent.tagName || parent.tagName.toUpperCase() == "HTML") {
                        return false;
                    }
                    parent = parent.parentNode;
                }
                return false;
            }
        },

        // 这个函数后面解释,下面代码有。
        getRegion : function(el) {
            return Ext.lib.Region.getRegion(el);
        },

        getY : function(el) {
            return this.getXY(el)[1];
        },

        getX : function(el) {
            return this.getXY(el)[0];
        },

        // 下面代码就是获取el元素相对于文档的位置。
        // 下面代码中很多浏览器兼容处理,因为现在浏览器都已经更新换代好多次了
        // 所以下面的代码有关浏览器兼容的部分,由于作者也没有注释来解释是为什么这样写,我也很难理解。但是整体函数的意义是很明了的。
        getXY : function(el) {
            var p, pe, b, scroll, bd = (document.body || document.documentElement);
            el = Ext.getDom(el);

            // 如果el是body,则坐位为0,0
            if(el == bd){
                return [0, 0];
            }

            // getBoundingClientRect,获取一个元素的边界信息,文档在这里:https://developer.mozilla.org/en/DOM/element.getBoundingClientRect
            if (el.getBoundingClientRect) {
                b = el.getBoundingClientRect();
                // 如果当前文档有滚动条,并且有滚动高度或者宽度。
                scroll = fly(document).getScroll();

                // 把滚动的偏移加进去
                return [b.left + scroll.left, b.top + scroll.top];
            }

            var x = 0, y = 0;

            p = el;

            // 判断el是否是绝对定位,因为offsetLeft和offsetTop是相对于offsetParent的
            var hasAbsolute = fly(el).getStyle("position") == "absolute";

            // 如果p元素存在,p第一次是el,以后就是el.offsetParent
            while (p) {

                x += p.offsetLeft;
                y += p.offsetTop;

                // 如果el不是绝对定位,p是绝对定位,则hasAbsolute为true
                if (!hasAbsolute && fly(p).getStyle("position") == "absolute") {
                    hasAbsolute = true;
                }

                // 浏览器兼容代码处理
                if (Ext.isGecko) {
                    pe = fly(p);

                    var bt = parseInt(pe.getStyle("borderTopWidth"), 10) || 0;
                    var bl = parseInt(pe.getStyle("borderLeftWidth"), 10) || 0;


                    x += bl;
                    y += bt;


                    if (p != el && pe.getStyle('overflow') != 'visible') {
                        x += bl;
                        y += bt;
                    }
                }
                p = p.offsetParent;
            }

            // 浏览器兼容代码处理
            if (Ext.isWebKit && hasAbsolute) {
                x -= bd.offsetLeft;
                y -= bd.offsetTop;
            }

            // 浏览器兼容代码处理
            if (Ext.isGecko && !hasAbsolute) {
                var dbd = fly(bd);
                x += parseInt(dbd.getStyle("borderLeftWidth"), 10) || 0;
                y += parseInt(dbd.getStyle("borderTopWidth"), 10) || 0;
            }

            // 浏览器兼容代码处理
            p = el.parentNode;
            while (p && p != bd) {
                if (!Ext.isOpera || (p.tagName != 'TR' && fly(p).getStyle("display") != "inline")) {
                    x -= p.scrollLeft;
                    y -= p.scrollTop;
                }
                p = p.parentNode;
            }
            return [x, y];
        },

        setXY : function(el, xy) {
            el = Ext.fly(el, '_setXY');
            el.position();
            var pts = el.translatePoints(xy);
            if (xy[0] !== false) {
                el.dom.style.left = pts.left + "px";
            }
            if (xy[1] !== false) {
                el.dom.style.top = pts.top + "px";
            }
        },

        setX : function(el, x) {
            this.setXY(el, [x, false]);
        },

        setY : function(el, y) {
            this.setXY(el, [false, y]);
        }
    };
0 请登录后投票
论坛首页 Web前端技术版

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