精华帖 (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,解析中
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间: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()); };
|
|
返回顶楼 | |
发表时间:2012-02-16
非常不错, 感谢分享 欢迎继续
|
|
返回顶楼 | |
发表时间:2012-02-17
非常不错,期待继续。
|
|
返回顶楼 | |
发表时间:2012-02-18
damoqiongqiu 写道 非常不错,期待继续。 谢谢大家鼓励,晚上我会更新adapter的代码解析部分。 |
|
返回顶楼 | |
发表时间:2012-02-18
,期待继续。。。
希望以后文章中加点怎么更优的使用Ext。 |
|
返回顶楼 | |
发表时间:2012-02-18
注释很给力啊。
|
|
返回顶楼 | |
发表时间:2012-02-19
最后修改:2012-02-19
|
|
返回顶楼 | |
发表时间:2012-02-20
EXT的结果的确写得好,这就是卖钱的东西
|
|
返回顶楼 | |
发表时间: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代码时,主要参考文档: IE:http://msdn.microsoft.com/library/default.aspx (msdn一定要访问英文版,中文版的你什么也查不到,你懂的)
还有一个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包括竖滚动条的宽度。
scrollX和pageXOffset:指的是相对于视图原点(即左上角那个点)的横坐标值。 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:指的是元素上边框的宽度。 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元素。 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]); } }; |
|
返回顶楼 | |