[转载]Prototype之详细解说[转] – Sam Lin – 博客园.
/* Prototype JavaScript framework, version 1.4.0 * (c) 2005 Sam Stephenson <sam@conio.net> * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://prototype.conio.net/ * 这是一个JavaScript的框架,致力于简化动态的Web开发,完全按照面对对象的思想 * 进行Javascript开发,添加了迭代器的概念增强Javascript的设计能力。 * Prototype框架构思独特充满了技巧性的代码,方便易用的工具类! * /*--------------------------------------------------------------------------*/ /* 【Prototype】定义一个全局对象,提供一个全局的基本信息和工具 */ var Prototype = { Version: '1.4.0', //可以作为版本检测用 //用于脚本检测的正则表达式,经常使用所以放在这里起到全局常量的作用 ScriptFragment: '(?:<script.*?>)(( | |.)*?)(?:</script>)', emptyFunction: function() {},//空函数 K: function(x) {return x}//K方法返回参数本身,在后面经常用到 } /*========================================================================================*/ /* *【Class】对象的作用只有一个就是提供了一个定义类的模式 * 仅含有create 一个方法,返回一个构造函数。 * 一般使用如下 * var X = Class.create(); 返回一个类型 * 要使用 X 类型,需继续用 new X()来获取一个实例 这与C# JAVA类似 * 返回的构造函数会执行名为 initialize 的方法, initialize 是 Ruby 对象的构造器方法名字。 * 此时initialize方法还没有定义,其后的代码中创建新类型时会建立相应的同名方法,可以看作是一个抽象方法。 * 从C#角度讲可以理解为用Class.create()创建一个继承Object基类的类。 * */ var Class = { create: function() { return function() { //下面使用Apply方法传递参数是一个常用的技巧 this.initialize.apply(this, arguments); } } } /*========================================================================================*/ //Abstract是一个空对象,它的作用仅仅是作为一个抽象的命名空间,所有在该对象下定义的类都是抽象类 //从而从形式上与实体类分开 var Abstract = new Object(); /* *Object是所有对象的基类,这个基类有两个静态方法 */ //把Source的所有属性和方法传递给Destination,实现了继承的效果 Object.extend = function(destination, source) { for (property in source) { destination[property] = source[property]; } return destination; } //观察Object的组成,需要参数中的object实现自己的inspect方法 Object.inspect = function(object) { try { if (object == undefined) return 'undefined'; if (object == null) return 'null'; return object.inspect ? object.inspect() : object.toString(); } catch (e) { if (e instanceof RangeError) return ''; throw e; } } /*========================================================================================*/ //【Function】是所有函数对象的基类,可以通过对它的扩展实现对所有函数对象添加功能 /* 这里在绑定的时候旧可以传递参数而不是调用的时候才指定参数,bind方法接收多个参数 将函数绑定到第一个参数指定的对象上,并返回该方法的调用句柄 */ Function.prototype.bind = function() { // $A()方法的作用是把参数专为数组 //数组的shift方法作用是删除数组的第一个元素 var __method = this, args = $A(arguments), object = args.shift(); return function() { return __method.apply(object, args.concat($A(arguments))); //这里的$A(arguments)是条用返回函数句柄时传递的参数,不是bind方法的参数 } } /** * 和bind一样,不过这个方法一般用做html控件对象的事件处理。所以要传递event对象 * 好像是重载了_method方法 */ Function.prototype.bindAsEventListener = function(object) { var __method = this; return function(event) { return __method.call(object, event || window.event); } } /*========================================================================================*/ //【Function】是所有数值类型的基类 Object.extend(Number.prototype, { toColorPart: function() { //RGB-》16进制颜色 var digits = this.toString(16); if (this < 16) return '0' + digits; return digits; }, succ: function() { //数值加一 var a=1; var b=a.succ; 那么b=2 return this + 1; }, times: function(iterator) { //这里的参数iterator是一个迭代器作用就是循环调用指定次数的iterator函数 //$R(start,end,exclusive)是创建ObjectRnge对象的快捷方式 $R(0, this, true).each(iterator); return this; } }); /*========================================================================================*/ //提供了一个方法these,要求参数是函数的句柄,可以有多个。作用就是返回第一个成功执行的函数的返回值 var Try = { these: function() { var returnValue; for (var i = 0; i < arguments.length; i++) { var lambda = arguments[i]; try { returnValue = lambda(); break; } catch (e) {} } return returnValue; } } /*========================================================================================*/ //定时器类用来实现Window.setInterval的效果,在给定时间间隔执行某一个函数,增加了对重复执行的控制S var PeriodicalExecuter = Class.create(); PeriodicalExecuter.prototype = { initialize: function(callback, frequency) { //构造函数指定回调函数和执行频率,单位是秒 this.callback = callback; this.frequency = frequency; this.currentlyExecuting = false; this.registerCallback(); }, /* 开始调用定时器,无需显示调用,在构造函数中就实现了自动调用,这一下面的 this.onTimerEvent.bind(this)如果写成this.onTimerEvent则this指针就会指向widows对象即setInterval的默认对象 从而不能正确的引用到下面两个函数上,也就失去了对正在执行函数的控制 */ registerCallback: function() { setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, //下面的函数相当于是回调函数的一个代理, setInterval是到了指定的时间就会强制执行而这里 //加入了一个判断如果callback函数执行的时间超过了一个时间片,则阻止其被重复执行 onTimerEvent: function() { if (!this.currentlyExecuting) { try { this.currentlyExecuting = true; this.callback(); } finally { this.currentlyExecuting = false; } } } } /*使用举例: function GetOnlineCount() {//获得在线人数 } new PeriodicalExecuter(GetOnlineCount,10)每10秒获取一次 */ /*========================================================================================*/ /* 框架核心内容--------【基础工具类】 /*========================================================================================*/ /*document.getElementById(id)获取一个指定ID的结点,是这个方法的快捷方式和扩展 可以指定多个参数返回一个对象数组。参数也不一定是ID也可以是对象本身的引用,例如 $('id')等价于$($('id')) */ function $() { var elements = new Array(); for (var i = 0; i < arguments.length; i++) { var element = arguments[i]; if (typeof element == 'string') element = document.getElementById(element); if (arguments.length == 1) return element; elements.push(element); } return elements; } /*========================================================================================*/ //【String】类的扩展 //删除字符串中的HTML标记 Object.extend(String.prototype, { stripTags: function() { return this.replace(/</?[^>]+>/gi, ''); }, //删除字符串中的脚本块 stripScripts: function() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); }, //提取字符串中的所有脚本块,作为数组返回,每一个脚本作为一个数组元素 extractScripts: function() { var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); }, //提取字符串中的脚本并执行 evalScripts: function() { return this.extractScripts().map(eval); }, //将字符串进行html编码例如"<" --》"<" escapeHTML: function() { var div = document.createElement('div'); var text = document.createTextNode(this); div.appendChild(text); return div.innerHTML; }, //对字符串进行html解码例如"<"--》"<" unescapeHTML: function() { var div = document.createElement('div'); div.innerHTML = this.stripTags(); return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; }, //将查询字符串格式的字符串转换为键值对数组 //例如var s="a=1&b=2&c=3"; var s2=s.toQueryParams(); s2为{a:1,b:2,c:3} toQueryParams: function() { var pairs = this.match(/^??(.*)$/)[1].split('&'); return pairs.inject({}, function(params, pairString) { var pair = pairString.split('='); params[pair[0]] = pair[1]; return params; }); }, //字符串转换为字符数组 "abc"-->['a','b','c'] toArray: function() { return this.split(''); }, //连字符--》骆驼样式 'good-man'-->'goodMan' camelize: function() { var oStringList = this.split('-'); if (oStringList.length == 1) return oStringList[0]; var camelizedString = this.indexOf('-') == 0 ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) : oStringList[0]; for (var i = 1, len = oStringList.length; i < len; i++) { var s = oStringList[i]; camelizedString += s.charAt(0).toUpperCase() + s.substring(1); } return camelizedString; }, //得到字符串的组成结构 inspect: function() { return "'" + this.replace('\', '\\').replace("'", '\'') + "'"; } }); //定义了一个等价的函数 String.prototype.parseQuery = String.prototype.toQueryParams; /*========================================================================================*/ //【Enumerable】可枚举接口 是整个1.4.0框架的核心工具,所有实现此接口的类必须要实现_each(iterator)方法 var $break = new Object(); //首先定义了两个异常对象,用于进行迭代计算的控制 var $continue = new Object(); var Enumerable = { //用于对对象的每一个元素遍历执行iterator迭代器函数 each: function(iterator) { var index = 0; //可选参数表示元素在枚举对象的次序 try { this._each(function(value) {//value是枚举元素的值 try { iterator(value, index++); } catch (e) { if (e != $continue) throw e; } }); } catch (e) { if (e != $break) throw e; } }, //判断是否所有的枚举元素都能使iterator返回true all: function(iterator) { var result = true; this.each(function(value, index) { result = result && !!(iterator || Prototype.K)(value, index); if (!result) throw $break; }); return result; }, //判断是否有枚举元素能使iterator返回true,有一个就是True any: function(iterator) { var result = true; this.each(function(value, index) { if (result = !!(iterator || Prototype.K)(value, index)) throw $break; }); return result; }, //对所有的枚举元素执行iterator迭代器函数 结果作为一个数组返回 collect: function(iterator) { var results = []; this.each(function(value, index) { results.push(iterator(value, index)); }); return results; }, //第一个素能使iterator返回true的枚举元素的值,没有返回undefined detect: function (iterator) { var result; this.each(function(value, index) { if (iterator(value, index)) { result = value; throw $break; } }); return result; }, //找到所有的能使iterator迭代器函数返回true的枚举元素 作为一个数组返回 findAll: function(iterator) { var results = []; this.each(function(value, index) { if (iterator(value, index)) results.push(value); }); return results; }, //找到素有匹配pattern的枚举元素,结果作为数组返回,iterator可选,如果不指定旧返回素有匹配pattern的枚举元素 grep: function(pattern, iterator) {//正则模式 迭代器 var results = []; this.each(function(value, index) { var stringValue = value.toString(); if (stringValue.match(pattern)) results.push((iterator || Prototype.K)(value, index)); }) return results; }, //判断枚举对象中是否含有参数Object指定的值 include: function(object) { var found = false; this.each(function(value) { if (value == object) { found = true; throw $break; } }); return found; }, //将memo作为iterator的第一个参数,枚举元素作为iterator的第二个参数,枚举元素的次序作为第三个 //参数每次迭代器的返回值将作为下一个iterator的memo参数,从而所有的迭代执行都通过memo联系起来了 inject: function(memo, iterator) { this.each(function(value, index) { memo = iterator(memo, value, index); }); return memo; }, //对所有的枚举元素执行method方法 后面是要传递的参数 invoke: function(method) { var args = $A(arguments).slice(1); return this.collect(function(value) { return value[method].apply(value, args); }); }, //返回的最大的迭代器执行结果 max: function(iterator) { var result; this.each(function(value, index) { value = (iterator || Prototype.K)(value, index); if (value >= (result || value)) result = value; }); return result; }, //反之 min: function(iterator) { var result; this.each(function(value, index) { value = (iterator || Prototype.K)(value, index); if (value <= (result || value)) result = value; }); return result; }, //返回两个数组一组能使iterator返回true 另一组返回false partition: function(iterator) { var trues = [], falses = []; this.each(function(value, index) { ((iterator || Prototype.K)(value, index) ? trues : falses).push(value); }); return [trues, falses]; }, //获取所有枚举元素的property属性值作为数组的返回 pluck: function(property) { var results = []; this.each(function(value, index) { results.push(value[property]); }); return results; }, //与findall相反 reject: function(iterator) { var results = []; this.each(function(value, index) { if (!iterator(value, index)) results.push(value); }); return results; }, //根据iterator的结果排序 最小的在前面 作为数组返回 sortBy: function(iterator) { return this.collect(function(value, index) { return {value: value, criteria: iterator(value, index)}; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }).pluck('value'); }, //枚举对象--》数组 toArray: function() { return this.collect(Prototype.K); }, //接收多个枚举对象参数,最后一个可以是迭代器用于阵列转换 zip: function() { var iterator = Prototype.K, args = $A(arguments); if (typeof args.last() == 'function') iterator = args.pop(); var collections = [this].concat(args).map($A); return this.map(function(value, index) { iterator(value = collections.pluck(index)); return value; }); }, //返回枚举对象的字符串描述 inspect: function() { return '#<Enumerable:' + this.toArray().inspect() + '>'; } } //等价函数定义 Object.extend(Enumerable, { map: Enumerable.collect, find: Enumerable.detect, select: Enumerable.findAll, member: Enumerable.include, entries: Enumerable.toArray }); /*========================================================================================*/ //【Array】数组对象 //参数转换为数组,如果参数定义了toarray则直接调用,否则枚举获得数组 var $A = Array.from = function(iterable) { if (!iterable) return []; if (iterable.toArray) { return iterable.toArray(); } else { var results = []; for (var i = 0; i < iterable.length; i++) results.push(iterable[i]); return results; } } //数组对象继承了Enumerable接口 Object.extend(Array.prototype, Enumerable); Array.prototype._reverse = Array.prototype.reverse; Object.extend(Array.prototype, { //实现了枚举接口方法,用于对数组内的元素执行迭代器 _each: function(iterator) { for (var i = 0; i < this.length; i++) iterator(this[i]); }, //清空数组 clear: function() { this.length = 0; return this; }, //取得第一个元素 first: function() { return this[0]; }, //取得最后一个元素 last: function() { return this[this.length - 1]; }, //删除数组元素中所有的null undefined值 作为新数组返回,原数组不受影响 compact: function() { return this.select(function(value) { return value != undefined || value != null; }); }, //展开所有数组元素不再嵌套 flatten: function() { return this.inject([], function(array, value) { return array.concat(value.constructor == Array ? value.flatten() : [value]); }); }, //数组中删除指定的元素,返回删除后的结果,原数组不受影响 without: function() { var values = $A(arguments); return this.select(function(value) { return !values.include(value); }); }, //Value元素在数组中的索引值 indexOf: function(object) { for (var i = 0; i < this.length; i++) if (this[i] == object) return i; return -1; }, //反转数组 reverse: function(inline) { return (inline !== false ? this : this.toArray())._reverse(); }, //删除数组中第一个元素并返回这个元素的值 shift: function() { var result = this[0]; for (var i = 0; i < this.length - 1; i++) this[i] = this[i + 1]; this.length--; return result; }, //得到数组的字符串描述例如arr=[1,2,3]-----> "[1,2,3]" inspect: function() { return '[' + this.map(Object.inspect).join(', ') + ']'; } }); /*========================================================================================*/ //【Hash】哈希对象 var Hash = { //实现枚举接口方法从而使Hash对象也是枚举对象 _each: function(iterator) { for (key in this) { var value = this[key]; if (typeof value == 'function') continue; var pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } }, //所有键数组 keys: function() { return this.pluck('key'); }, //所有值数组 values: function() { return this.pluck('value'); }, //合并哈希表,键相同就覆盖调用者 merge: function(hash) { return $H(hash).inject($H(this), function(mergedHash, pair) { mergedHash[pair.key] = pair.value; return mergedHash; }); }, //键值对组成查询字符串的形式 toQueryString: function() { return this.map(function(pair) { return pair.map(encodeURIComponent).join('='); }).join('&'); }, //获取Hash对象的字符串描述 inspect: function() { return '#<Hash:{' + this.map(function(pair) { return pair.map(Object.inspect).join(': '); }).join(', ') + '}>'; } } /*哈希表不是类而是对象所以不能用new的方法创建,而是用$H()函数 这个方法吧一个对象转换为哈希对象,对象的属性名为key 值为value 可枚举!! */ function $H(object) { var hash = Object.extend({}, object || {}); Object.extend(hash, Enumerable); Object.extend(hash, Hash); return hash; } /*========================================================================================*/ //【ObjectRange】用于进行指定次数的循环运算,同样继承于枚举接口并实现_each方法 //有了这个方法基本上Javascript里面就不用for循环了 ObjectRange = Class.create(); Object.extend(ObjectRange.prototype, Enumerable); Object.extend(ObjectRange.prototype, { //构造函数同事指定了3个参数的值 initialize: function(start, end, exclusive) { this.start = start; //其实索引 this.end = end;//结束索引 this.exclusive = exclusive;//表示是否包含结束索引 }, _each: function(iterator) { var value = this.start; do { iterator(value); value = value.succ(); } while (this.include(value)); }, //重写枚举接口的include方法,判断改循环的索引是否包含参数value的指定的值 include: function(value) { if (value < this.start) return false; if (this.exclusive) return value < this.end; return value <= this.end; } }); //使用$R()方法快速创建objectRange对象 var $R = function(start, end, exclusive) { return new ObjectRange(start, end, exclusive); } /*========================================================================================*/ /* 【Ajax模块】 /*========================================================================================*/ //【ajax对象】 var Ajax = {//创建浏览器兼容的XMLHttpRequest对象 getTransport: function() { return Try.these( function() {return new ActiveXObject('Msxml2.XMLHTTP')}, function() {return new ActiveXObject('Microsoft.XMLHTTP')}, function() {return new XMLHttpRequest()} ) || false; }, //当前激活的请求数目 activeRequestCount: 0 } //【Ajax.Responders对象】 Ajax.Responders = { responders: [],//表示所有响应处理代理 _each: function(iterator) { this.responders._each(iterator); }, //注册一个响应代理 register: function(responderToAdd) { if (!this.include(responderToAdd)) this.responders.push(responderToAdd); }, //删除一个响应代理 unregister: function(responderToRemove) { this.responders = this.responders.without(responderToRemove); }, //分发一个回调函数,让所有半酣callback回调函数事件标识符的响应代理都被调用 //传递三个参数最后一个可选表示Json对象 dispatch: function(callback, request, transport, json) { this.each(function(responder) { if (responder[callback] && typeof responder[callback] == 'function') { try { responder[callback].apply(responder, [request, transport, json]); } catch (e) {} } }); } }; Object.extend(Ajax.Responders, Enumerable); //统计当前活动请求的数目 Ajax.Responders.register({ //开始创建了一个请求 onCreate: function() { Ajax.activeRequestCount++; }, //请求结束 onComplete: function() { Ajax.activeRequestCount--; } }); //【Ajax.Base类】进行服务器通信的基类 Ajax.Base = function() {}; Ajax.Base.prototype = { setOptions: function(options) { this.options = { method: 'post', asynchronous: true, parameters: '' } Object.extend(this.options, options || {}); }, responseIsSuccess: function() { return this.transport.status == undefined || this.transport.status == 0 || (this.transport.status >= 200 && this.transport.status < 300); }, responseIsFailure: function() { return !this.responseIsSuccess(); } } //【Ajax.Request类】用于向服务器端发送请求,封装了XmlHttpRequest Ajax.Request = Class.create(); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Request.prototype = Object.extend(new Ajax.Base(), { initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options);//使用基类方法设置 this.request(url); }, //向指定url发送请求 一般不会显示调用 request: function(url) { var parameters = this.options.parameters || ''; if (parameters.length > 0) parameters += '&_='; try { this.url = url; if (this.options.method == 'get' && parameters.length > 0) this.url += (this.url.match(/?/) ? '&' : '?') + parameters; Ajax.Responders.dispatch('onCreate', this, this.transport); this.transport.open(this.options.method, this.url, this.options.asynchronous); if (this.options.asynchronous) { this.transport.onreadystatechange = this.onStateChange.bind(this); setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); } this.setRequestHeaders(); var body = this.options.postBody ? this.options.postBody : parameters; this.transport.send(this.options.method == 'post' ? body : null); } catch (e) { this.dispatchException(e); } }, //设置请求的Http头 类内部使用 一般无需显示调用 setRequestHeaders: function() { var requestHeaders = ['X-Requested-With', 'XMLHttpRequest', 'X-Prototype-Version', Prototype.Version]; if (this.options.method == 'post') { requestHeaders.push('Content-type', 'application/x-www-form-urlencoded'); /* Force "Connection: close" for Mozilla browsers to work around * a bug where XMLHttpReqeuest sends an incorrect Content-length * header. See Mozilla Bugzilla #246651. */ if (this.transport.overrideMimeType) requestHeaders.push('Connection', 'close'); } if (this.options.requestHeaders) requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); for (var i = 0; i < requestHeaders.length; i += 2) this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); }, //检测XMLHttpRquest对象的onstatechange事件类内部使用 一般无需显示调用 onStateChange: function() { var readyState = this.transport.readyState; if (readyState != 1) this.respondToReadyState(this.transport.readyState); }, //获取指定的Http头的内容 header: function(name) { try { return this.transport.getResponseHeader(name); } catch (e) {} }, //如果服务器返回了Http头"X-JOSN"则将其作为js执行并返回执行结果 evalJSON: function() { try { return eval(this.header('X-JSON')); } catch (e) {} }, //将XMLHttpRequest返回的responseText作为JS语句执行并返回执行结果 evalResponse: function() { try { return eval(this.transport.responseText); } catch (e) { this.dispatchException(e); } }, //处理XMLHttpRequest的readystate属性根据成功或失败调用Options对象中相应的回调函数 //同时通知Ajax.Responders中注册的响应处理句柄 respondToReadyState: function(readyState) { var event = Ajax.Request.Events[readyState]; var transport = this.transport, json = this.evalJSON(); if (event == 'Complete') { try { (this.options['on' + this.transport.status] || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] || Prototype.emptyFunction)(transport, json); } catch (e) { this.dispatchException(e); } if ((this.header('Content-type') || '').match(/^text/javascript/i)) this.evalResponse(); } try { (this.options['on' + event] || Prototype.emptyFunction)(transport, json); Ajax.Responders.dispatch('on' + event, this, transport, json); } catch (e) { this.dispatchException(e); } /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ if (event == 'Complete') this.transport.onreadystatechange = Prototype.emptyFunction; }, dispatchException: function(exception) { (this.options.onException || Prototype.emptyFunction)(this, exception); Ajax.Responders.dispatch('onException', this, exception); } }); /*========================================================================================*/ //【Ajax.Updater类】用于将获取的内容填充到指定的容器中去 Ajax.Updater = Class.create(); Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { //重写了构造函数 initialize: function(container, url, options) { this.containers = { success: container.success ? $(container.success) : $(container), failure: container.failure ? $(container.failure) : (container.success ? null : $(container)) } this.transport = Ajax.getTransport(); this.setOptions(options);//可以指定evalscripts属性和insertion属性 var onComplete = this.options.onComplete || Prototype.emptyFunction; this.options.onComplete = (function(transport, object) { this.updateContent(); onComplete(transport, object); }).bind(this); this.request(url); }, updateContent: function() { var receiver = this.responseIsSuccess() ? this.containers.success : this.containers.failure; var response = this.transport.responseText; if (!this.options.evalScripts) response = response.stripScripts(); if (receiver) { if (this.options.insertion) { new this.options.insertion(receiver, response); } else { Element.update(receiver, response); } } if (this.responseIsSuccess()) { if (this.onComplete) setTimeout(this.onComplete.bind(this), 10); } } }); /*========================================================================================*/ //【Ajax.PeriodicalUpdater类】用于定时执行服务器异步调用,功能与Updater相同只是有了定时功能 Ajax.PeriodicalUpdater = Class.create(); Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { initialize: function(container, url, options) { this.setOptions(options); this.onComplete = this.options.onComplete; //区别就在下面两个属性 频率 衰减参数 指数增长!只要有一次不同旧回复原有频率 this.frequency = (this.options.frequency || 2); this.decay = (this.options.decay || 1); this.updater = {}; this.container = container; this.url = url; this.start(); }, start: function() { this.options.onComplete = this.updateComplete.bind(this); this.onTimerEvent(); }, stop: function() { this.updater.onComplete = undefined; clearTimeout(this.timer); (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, updateComplete: function(request) { if (this.options.decay) { this.decay = (request.responseText == this.lastText ? this.decay * this.options.decay : 1); this.lastText = request.responseText; } this.timer = setTimeout(this.onTimerEvent.bind(this), this.decay * this.frequency * 1000); }, onTimerEvent: function() { this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); /*========================================================================================*/ /*【文档操作的封装】 /*========================================================================================*/ //返回了一个数组包含了所有符合条件的结点的引用 document.getElementsByClassName = function(className, parentElement) { //parentElement不指定就在全局查找 var children = ($(parentElement) || document.body).getElementsByTagName('*'); return $A(children).inject([], function(elements, child) { if (child.className.match(new RegExp("(^|\s)" + className + "(\s|$)"))) elements.push(child); return elements; }); } /*【Element对象】用于对文档结点做一些统一的操作 */ if (!window.Element) { var Element = new Object(); } Object.extend(Element, { visible: function(element) { return $(element).style.display != 'none'; }, //指定结点可见性的切换 toggle: function() { for (var i = 0; i < arguments.length; i++) { var element = $(arguments[i]); Element[Element.visible(element) ? 'hide' : 'show'](element); } }, hide: function() { for (var i = 0; i < arguments.length; i++) { var element = $(arguments[i]); element.style.display = 'none'; } }, show: function() { for (var i = 0; i < arguments.length; i++) { var element = $(arguments[i]); element.style.display = ''; } }, remove: function(element) { element = $(element); element.parentNode.removeChild(element); }, //将html片段填充到element指定的结点中 update: function(element, html) { $(element).innerHTML = html.stripScripts(); setTimeout(function() {html.evalScripts()}, 10); }, //获得element的绝对高度 getHeight: function(element) { element = $(element); return element.offsetHeight; }, classNames: function(element) { return new Element.ClassNames(element); }, hasClassName: function(element, className) { if (!(element = $(element))) return; return Element.classNames(element).include(className); }, addClassName: function(element, className) { if (!(element = $(element))) return; return Element.classNames(element).add(className); }, removeClassName: function(element, className) { if (!(element = $(element))) return; return Element.classNames(element).remove(className); }, // removes whitespace-only text node children cleanWhitespace: function(element) { element = $(element); for (var i = 0; i < element.childNodes.length; i++) { var node = element.childNodes[i]; if (node.nodeType == 3 && !/S/.test(node.nodeValue)) Element.remove(node); } }, empty: function(element) { return $(element).innerHTML.match(/^s*$/); }, //将浏览器的滚动条滚动到指定的结点的位置 scrollTo: function(element) { element = $(element); var x = element.x ? element.x : element.offsetLeft, y = element.y ? element.y : element.offsetTop; window.scrollTo(x, y); }, //得到element的结点的绝对样式 getStyle: function(element, style) { element = $(element); var value = element.style[style.camelize()]; if (!value) { if (document.defaultView && document.defaultView.getComputedStyle) { var css = document.defaultView.getComputedStyle(element, null); value = css ? css.getPropertyValue(style) : null; } else if (element.currentStyle) { value = element.currentStyle[style.camelize()]; } } if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) if (Element.getStyle(element, 'position') == 'static') value = 'auto'; return value == 'auto' ? null : value; }, //设置结点样式 var style={background-color:'black',color:'red'} Element.setStyle($('div1'),style) setStyle: function(element, style) { element = $(element); for (name in style) element.style[name.camelize()] = style[name]; }, //获得结点的宽度高度 该方法无论结点是否可见 都能获得其大小 getDimensions: function(element) { element = $(element); if (Element.getStyle(element, 'display') != 'none') return {width: element.offsetWidth, height: element.offsetHeight}; // All *Width and *Height properties give 0 on elements with display none, // so enable the element temporarily var els = element.style; var originalVisibility = els.visibility; var originalPosition = els.position; els.visibility = 'hidden'; els.position = 'absolute'; els.display = ''; var originalWidth = element.clientWidth; var originalHeight = element.clientHeight; els.display = 'none'; els.position = originalPosition; els.visibility = originalVisibility; return {width: originalWidth, height: originalHeight}; }, //相对定位 makePositioned: function(element) { element = $(element); var pos = Element.getStyle(element, 'position'); if (pos == 'static' || !pos) { element._madePositioned = true; element.style.position = 'relative'; // Opera returns the offset relative to the positioning context, when an // element is position relative but top and left have not been defined if (window.opera) { element.style.top = 0; element.style.left = 0; } } }, //取消相对定位 undoPositioned: function(element) { element = $(element); if (element._madePositioned) { element._madePositioned = undefined; element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = ''; } }, //使结点隐藏超出的部分 overflow=relative makeClipping: function(element) { element = $(element); if (element._overflow) return; element._overflow = element.style.overflow; if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') element.style.overflow = 'hidden'; }, undoClipping: function(element) { element = $(element); if (element._overflow) return; element.style.overflow = element._overflow; element._overflow = undefined; } }); /*========================================================================================*/ //Toggle对象是Element.toggle方法的一个快捷用法 var Toggle = new Object(); Toggle.display = Element.toggle; /*========================================================================================*/ //【Insertion命名空间】包含了4个类把指定的html片段插入到指定的结点的相应位置上 Abstract.Insertion = function(adjacency) { this.adjacency = adjacency; } Abstract.Insertion.prototype = { initialize: function(element, content) { this.element = $(element); this.content = content.stripScripts(); if (this.adjacency && this.element.insertAdjacentHTML) { try { this.element.insertAdjacentHTML(this.adjacency, this.content); } catch (e) { if (this.element.tagName.toLowerCase() == 'tbody') { this.insertContent(this.contentFromAnonymousTable()); } else { throw e; } } } else { this.range = this.element.ownerDocument.createRange(); if (this.initializeRange) this.initializeRange(); this.insertContent([this.range.createContextualFragment(this.content)]); } setTimeout(function() {content.evalScripts()}, 10); }, contentFromAnonymousTable: function() { var div = document.createElement('div'); div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>'; return $A(div.childNodes[0].childNodes[0].childNodes); } } var Insertion = new Object(); Insertion.Before = Class.create(); Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { initializeRange: function() { this.range.setStartBefore(this.element); }, insertContent: function(fragments) { fragments.each((function(fragment) { this.element.parentNode.insertBefore(fragment, this.element); }).bind(this)); } }); Insertion.Top = Class.create(); Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { initializeRange: function() { this.range.selectNodeContents(this.element); this.range.collapse(true); }, insertContent: function(fragments) { fragments.reverse(false).each((function(fragment) { this.element.insertBefore(fragment, this.element.firstChild); }).bind(this)); } }); Insertion.Bottom = Class.create(); Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { initializeRange: function() { this.range.selectNodeContents(this.element); this.range.collapse(this.element); }, insertContent: function(fragments) { fragments.each((function(fragment) { this.element.appendChild(fragment); }).bind(this)); } }); Insertion.After = Class.create(); Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { initializeRange: function() { this.range.setStartAfter(this.element); }, insertContent: function(fragments) { fragments.each((function(fragment) { this.element.parentNode.insertBefore(fragment, this.element.nextSibling); }).bind(this)); } }); /*========================================================================================*/ //【Element.ClassNames】获得一个结点的所有的class名称组成的对象,实现了枚举接口 Element.ClassNames = Class.create(); Element.ClassNames.prototype = { initialize: function(element) { this.element = $(element); }, _each: function(iterator) { this.element.className.split(/s+/).select(function(name) { return name.length > 0; })._each(iterator); }, set: function(className) { this.element.className = className; }, add: function(classNameToAdd) { if (this.include(classNameToAdd)) return; this.set(this.toArray().concat(classNameToAdd).join(' ')); }, remove: function(classNameToRemove) { if (!this.include(classNameToRemove)) return; this.set(this.select(function(className) { return className != classNameToRemove; }).join(' ')); }, toString: function() {//获得所有class的字符串表示空格隔开 return this.toArray().join(' '); } } //表单域对象工具类 Object.extend(Element.ClassNames.prototype, Enumerable); var Field = { clear: function() { for (var i = 0; i < arguments.length; i++) $(arguments[i]).value = ''; }, focus: function(element) { $(element).focus(); }, present: function() { for (var i = 0; i < arguments.length; i++) if ($(arguments[i]).value == '') return false; return true; }, //选中表单域 select: function(element) { $(element).select(); }, //focus + select activate: function(element) { element = $(element); element.focus(); if (element.select) element.select(); } } /*【Form】表单工具对象*/ var Form = { //表单数据序列化 serialize: function(form) { var elements = Form.getElements($(form)); var queryComponents = new Array(); for (var i = 0; i < elements.length; i++) { var queryComponent = Form.Element.serialize(elements[i]); if (queryComponent) queryComponents.push(queryComponent); } return queryComponents.join('&'); }, //获取所有表单域按照标记名排列 getElements: function(form) { form = $(form); var elements = new Array(); for (tagName in Form.Element.Serializers) { var tagElements = form.getElementsByTagName(tagName); for (var j = 0; j < tagElements.length; j++) elements.push(tagElements[j]); } return elements; }, //有一个满足就返回 getInputs: function(form, typeName, name) { form = $(form); var inputs = form.getElementsByTagName('input'); if (!typeName && !name) return inputs; var matchingInputs = new Array(); for (var i = 0; i < inputs.length; i++) { var input = inputs[i]; if ((typeName && input.type != typeName) || (name && input.name != name)) continue; matchingInputs.push(input); } return matchingInputs; }, //整个表单域禁用 disable: function(form) { var elements = Form.getElements(form); for (var i = 0; i < elements.length; i++) { var element = elements[i]; element.blur(); element.disabled = 'true'; } }, //启用 enable: function(form) { var elements = Form.getElements(form); for (var i = 0; i < elements.length; i++) { var element = elements[i]; element.disabled = ''; } }, //获取第一个可用的表单域 findFirstElement: function(form) { return Form.getElements(form).find(function(element) { return element.type != 'hidden' && !element.disabled && ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); }); }, //激活第一个表单域 focusFirstElement: function(form) { Field.activate(Form.findFirstElement(form)); }, reset: function(form) { $(form).reset(); } } //【Form.Element】 对特定的表单域进行操作 Form.Element = { //序列化形成"&name=value" serialize: function(element) { element = $(element); var method = element.tagName.toLowerCase(); var parameter = Form.Element.Serializers[method](element); if (parameter) { var key = encodeURIComponent(parameter[0]); if (key.length == 0) return; if (parameter[1].constructor != Array) parameter[1] = [parameter[1]]; return parameter[1].map(function(value) { return key + '=' + encodeURIComponent(value); }).join('&'); } }, //获得指定表单域的值 getValue: function(element) { element = $(element); var method = element.tagName.toLowerCase(); var parameter = Form.Element.Serializers[method](element); if (parameter) return parameter[1]; } } //【Form.Element.Serializers】针对不同的表单域进行序列化 Form.Element.Serializers = { //一般用所有的<input>标记的序列化都可以 input: function(element) { switch (element.type.toLowerCase()) { case 'submit': case 'hidden': case 'password': case 'text': return Form.Element.Serializers.textarea(element); case 'checkbox': case 'radio': return Form.Element.Serializers.inputSelector(element); } return false; }, inputSelector: function(element) { if (element.checked) return [element.name, element.value]; }, textarea: function(element) { return [element.name, element.value]; }, select: function(element) { return Form.Element.Serializers[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); }, selectOne: function(element) { var value = '', opt, index = element.selectedIndex; if (index >= 0) { opt = element.options[index]; value = opt.value; if (!value && !('value' in opt)) value = opt.text; } return [element.name, value]; }, selectMany: function(element) { var value = new Array(); for (var i = 0; i < element.length; i++) { var opt = element.options[i]; if (opt.selected) { var optValue = opt.value; if (!optValue && !('value' in opt)) optValue = opt.text; value.push(optValue); } } return [element.name, value]; } } //【获得表单域的值】 var $F = Form.Element.getValue; /*========================================================================================*/ /*【Observer模式框架】 观察对象仅仅针对表单和表单域*/ /*========================================================================================*/ Abstract.TimedObserver = function() {}//定义了基于定时器的观察模式基类后面的类都继承了这个类 Abstract.TimedObserver.prototype = { initialize: function(element, frequency, callback) { this.frequency = frequency;//观察频率 this.element = $(element);//被观察的对象 this.callback = callback;//回调函数接受参数 被观察对象自身和触发事件时的值 this.lastValue = this.getValue();//这是观察的值!! this.registerCallback(); }, //注册回调函数 registerCallback: function() { setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, //每一个时间片触发一次该事件 onTimerEvent: function() { var value = this.getValue(); if (this.lastValue != value) { this.callback(this.element, value); this.lastValue = value; } } } //【Form.Element.Observer类】实现了getvalue方法 Form.Element.Observer = Class.create(); Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue: function() { return Form.Element.getValue(this.element); } }); //【Form.Observer类】实现了getvalue方法,只要有一个表单域发生变化就会触发回调 Form.Observer = Class.create(); Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue: function() { return Form.serialize(this.element); } }); //【Abstract.EventObserver 类】基于事件的Oberver模式的基类 后面的具体类都继承这个类 Abstract.EventObserver = function() {} Abstract.EventObserver.prototype = { initialize: function(element, callback) { this.element = $(element); this.callback = callback; this.lastValue = this.getValue(); if (this.element.tagName.toLowerCase() == 'form') this.registerFormCallbacks(); else this.registerCallback(this.element); }, //每次发生事件时被调用 内部调用 无需显示调用 onElementEvent: function() { var value = this.getValue(); if (this.lastValue != value) { this.callback(this.element, value); this.lastValue = value; } }, //注册表表单域事件 registerFormCallbacks: function() { var elements = Form.getElements(this.element); for (var i = 0; i < elements.length; i++) this.registerCallback(elements[i]); }, registerCallback: function(element) { if (element.type) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': Event.observe(element, 'click', this.onElementEvent.bind(this)); break; case 'password': case 'text': case 'textarea': case 'select-one': case 'select-multiple': Event.observe(element, 'change', this.onElementEvent.bind(this)); break; } } } } //【Form.Element.EventObserver】 Form.Element.EventObserver = Class.create(); Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { getValue: function() { return Form.Element.getValue(this.element); } }); //【Form.EventObserver】 Form.EventObserver = Class.create(); Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { getValue: function() { return Form.serialize(this.element); } }); /*========================================================================================*/ /*【事件处理:对Event对象的扩展】对javascript事件处理机制进行封装*/ /*========================================================================================*/ if (!window.Event) { var Event = new Object(); } Object.extend(Event, { KEY_BACKSPACE: 8, KEY_TAB: 9, KEY_RETURN: 13, KEY_ESC: 27, KEY_LEFT: 37, KEY_UP: 38, KEY_RIGHT: 39, KEY_DOWN: 40, KEY_DELETE: 46, //获取触发事件的对象 element: function(event) { return event.target || event.srcElement; }, isLeftClick: function(event) { return (((event.which) && (event.which == 1)) || ((event.button) && (event.button == 1))); }, pointerX: function(event) { return event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)); }, pointerY: function(event) { return event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop)); }, //停止事件的传递 stop: function(event) { if (event.preventDefault) { event.preventDefault(); event.stopPropagation(); } else { event.returnValue = false; event.cancelBubble = true; } }, // find the first node with the given tagName, starting from the // node the event was triggered on; traverses the DOM upwards findElement: function(event, tagName) { var element = Event.element(event); while (element.parentNode && (!element.tagName || (element.tagName.toUpperCase() != tagName.toUpperCase()))) element = element.parentNode; return element; }, observers: false, _observeAndCache: function(element, name, observer, useCapture) { if (!this.observers) this.observers = []; if (element.addEventListener) { this.observers.push([element, name, observer, useCapture]); element.addEventListener(name, observer, useCapture); } else if (element.attachEvent) { this.observers.push([element, name, observer, useCapture]); element.attachEvent('on' + name, observer); } }, //删除所有通过Event.observers绑定的事件 unloadCache: function() { if (!Event.observers) return; for (var i = 0; i < Event.observers.length; i++) { Event.stopObserving.apply(this, Event.observers[i]); Event.observers[i][0] = null; } Event.observers = false; }, //绑定一个事件 observe: function(element, name, observer, useCapture) { var element = $(element); useCapture = useCapture || false; if (name == 'keypress' && (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.attachEvent)) name = 'keydown'; this._observeAndCache(element, name, observer, useCapture); }, //取消事件绑定 stopObserving: function(element, name, observer, useCapture) { var element = $(element); useCapture = useCapture || false; if (name == 'keypress' && (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.detachEvent)) name = 'keydown'; if (element.removeEventListener) { element.removeEventListener(name, observer, useCapture); } else if (element.detachEvent) { element.detachEvent('on' + name, observer); } } }); //【结点的位置处理:Position对象】 /* prevent memory leaks in IE */ Event.observe(window, 'unload', Event.unloadCache, false); var Position = { // set to true if needed, warning: firefox performance problems // NOT neeeded for page scrolling, only if draggable contained in // scrollable elements includeScrollOffsets: false, // must be called before calling withinIncludingScrolloffset, every time the // page is scrolled用于计算滚动条的位置信息 prepare: function() { this.deltaX = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; this.deltaY = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; }, //计算节点的绝对滚动位置返回包含两个元素的数组到文档左侧 顶部的距离 realOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.scrollTop || 0; valueL += element.scrollLeft || 0; element = element.parentNode; } while (element); return [valueL, valueT]; }, //计算结点相对于文档的绝对滚动位置,返回包含两个元素的数组到文档左侧 顶部的距离 cumulativeOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; } while (element); return [valueL, valueT]; }, //相对位置 positionedOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { p = Element.getStyle(element, 'position'); if (p == 'relative' || p == 'absolute') break; } } while (element); return [valueL, valueT]; }, offsetParent: function(element) { if (element.offsetParent) return element.offsetParent; if (element == document.body) return element; while ((element = element.parentNode) && element != document.body) if (Element.getStyle(element, 'position') != 'static') return element; return document.body; }, // caches x/y coordinate pair to use with overlap是否在element内 within: function(element, x, y) { if (this.includeScrollOffsets) return this.withinIncludingScrolloffsets(element, x, y); this.xcomp = x; this.ycomp = y; this.offset = this.cumulativeOffset(element); return (y >= this.offset[1] && y < this.offset[1] + element.offsetHeight && x >= this.offset[0] && x < this.offset[0] + element.offsetWidth); }, withinIncludingScrolloffsets: function(element, x, y) { var offsetcache = this.realOffset(element); this.xcomp = x + offsetcache[0] - this.deltaX; this.ycomp = y + offsetcache[1] - this.deltaY; this.offset = this.cumulativeOffset(element); return (this.ycomp >= this.offset[1] && this.ycomp < this.offset[1] + element.offsetHeight && this.xcomp >= this.offset[0] && this.xcomp < this.offset[0] + element.offsetWidth); }, // within must be called directly before overlap: function(mode, element) { if (!mode) return 0; if (mode == 'vertical') return ((this.offset[1] + element.offsetHeight) - this.ycomp) / element.offsetHeight; if (mode == 'horizontal') return ((this.offset[0] + element.offsetWidth) - this.xcomp) / element.offsetWidth; }, //克隆结点 常用于拖放 clone: function(source, target) { source = $(source); target = $(target); target.style.position = 'absolute'; var offsets = this.cumulativeOffset(source); target.style.top = offsets[1] + 'px'; target.style.left = offsets[0] + 'px'; target.style.width = source.offsetWidth + 'px'; target.style.height = source.offsetHeight + 'px'; }, page: function(forElement) { var valueT = 0, valueL = 0; var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; // Safari fix if (element.offsetParent==document.body) if (Element.getStyle(element,'position')=='absolute') break; } while (element = element.offsetParent); element = forElement; do { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } while (element = element.parentNode); return [valueL, valueT]; }, clone: function(source, target) { var options = Object.extend({ setLeft: true, setTop: true, setWidth: true, setHeight: true, offsetTop: 0, offsetLeft: 0 }, arguments[2] || {}) // find page position of source source = $(source); var p = Position.page(source); // find coordinate system to use target = $(target); var delta = [0, 0]; var parent = null; // delta [0,0] will do fine with position: fixed elements, // position:absolute needs offsetParent deltas if (Element.getStyle(target,'position') == 'absolute') { parent = Position.offsetParent(target); delta = Position.page(parent); } // correct by body offsets (fixes Safari) if (parent == document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; } // set position if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; if(options.setWidth) target.style.width = source.offsetWidth + 'px'; if(options.setHeight) target.style.height = source.offsetHeight + 'px'; }, //绝对定位 absolutize: function(element) { element = $(element); if (element.style.position == 'absolute') return; Position.prepare(); var offsets = Position.positionedOffset(element); var top = offsets[1]; var left = offsets[0]; var width = element.clientWidth; var height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); element._originalWidth = element.style.width; element._originalHeight = element.style.height; element.style.position = 'absolute'; element.style.top = top + 'px';; element.style.left = left + 'px';; element.style.width = width + 'px';; element.style.height = height + 'px';; }, //相对定位 relativize: function(element) { element = $(element); if (element.style.position == 'relative') return; Position.prepare(); element.style.position = 'relative'; var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.height = element._originalHeight; element.style.width = element._originalWidth; } } // Safari returns margins on body which is incorrect if the child is absolutely // positioned. For performance reasons, redefine Position.cumulativeOffset for // KHTML/WebKit only. if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { Position.cumulativeOffset = function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == document.body) if (Element.getStyle(element, 'position') == 'absolute') break; element = element.offsetParent; } while (element); return [valueL, valueT]; } } //--------->完成