prototype 1.3.1 版本和之前的 1.2.0 版本有了不少改进,并增加了新的功能:
1. 增加了事件注册管理
2. 增加了空间定位的常用函数
3. 改善了 xmlhttp 的封装
4. 移除了 Effect.js,交给 Rico 或者 script.aculo.us 这些扩展库类实现。
5. bug 修复
1. 增加了事件注册管理
2. 增加了空间定位的常用函数
3. 改善了 xmlhttp 的封装
4. 移除了 Effect.js,交给 Rico 或者 script.aculo.us 这些扩展库类实现。
5. bug 修复
1
2 /**
3 * 定义一个全局对象, 属性 Version 在发布的时候会替换为当前版本号
4 */
5 var Prototype = {
6 Version: '1.3.1',
7 // 一个空方法,其后的代码常会用到,先前的版本该方法被定义于 Ajax 类中。
8 emptyFunction: function() {}
9 }
10
11 /**
12 * 创建一种类型,注意其属性 create 是一个方法,返回一个构造函数。
13 * 一般使用如下
14 * var X = Class.create(); 返回一个类型,类似于 java 的一个Class实例。
15 * 要使用 X 类型,需继续用 new X()来获取一个实例,如同 java 的 Class.newInstance()方法。
16 *
17 * 返回的构造函数会执行名为 initialize 的方法, initialize 是 Ruby 对象的构造器方法名字。
18 * 此时initialize方法还没有定义,其后的代码中创建新类型时会建立相应的同名方法。
19 *
20 * 如果一定要从java上去理解。你可以理解为用Class.create()创建一个继承java.lang.Class类的类。当然java不允许这样做,因为Class类是final的
21 *
22 */
23 var Class = {
24 create: function() {
25 return function() {
26 this.initialize.apply(this, arguments);
27 }
28 }
29 }
30
31 /**
32 * 创建一个对象,从变量名来思考,本意也许是定义一个抽象类,以后创建新对象都 extend 它。
33 * 但从其后代码的应用来看, Abstract 更多是为了保持命名空间清晰的考虑。
34 * 也就是说,我们可以给 Abstract 这个对象实例添加新的对象定义。
35 *
36 * 从java去理解,就是动态给一个对象创建内部类。
37 */
38 var Abstract = new Object();
39
40
41 Object.extend = function(destination, source) {
42 for (property in source) {
43 destination[property] = source[property];
44 }
45 return destination;
46 }
47
48 /**
49 * 获取参数对象的所有属性和方法,有点象多重继承。但是这种继承是动态获得的。
50 * 如:
51 * var a = new ObjectA(), b = new ObjectB();
52 * var c = a.extend(b);
53 * 此时 c 对象同时拥有 a 和 b 对象的属性和方法。但是与多重继承不同的是,c instanceof ObjectB 将返回false。
54 *
55 * 旧版本的该方法定义如下:
56 * Object.prototype.extend = function(object) {
57 * for (property in object) {
58 * this[property] = object[property];
59 * }
60 * return this;
61 * }
62 *
63 * 新的形式新定义了一个静态方法 Object.extend,这样做的目的大概是为了使代码更为清晰
64 */
65 Object.prototype.extend = function(object) {
66 return Object.extend.apply(this, [this, object]);
67 }
68
69 /**
70 * 这个方法很有趣,它封装一个javascript函数对象,返回一个新函数对象,新函数对象的主体和原对象相同,但是bind()方法参数将被用作当前对象的对象。
71 * 也就是说新函数中的 this 引用被改变为参数提供的对象。
72 * 比如:
73 *
74 *
75 * .................
76 * <script type="text/javascript">
77 * var aaa = document.getElementById("aaa");
78 * var bbb = document.getElementById("bbb");
79 * aaa.showValue = function() {alert(this.value);}
80 * aaa.showValue2 = aaa.showValue.bind(bbb);
81 * </script>
82 * 那么,调用aaa.showValue 将返回"aaa", 但调用aaa.showValue2 将返回"bbb"。
83 *
84 * apply 是ie5.5后才出现的新方法(Netscape好像很早就支持了)。
85 * 该方法更多的资料参考MSDN http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthApply.asp
86 * 阅读其后的代码就会发现,bind 被应用的很广泛,该方法和 Object.prototype.extend 一样是 Prototype 的核心。
87 * 还有一个 call 方法,应用起来和 apply 类似。可以一起研究下。
88 */
89 Function.prototype.bind = function(object) {
90 var __method = this;
91 return function() {
92 __method.apply(object, arguments);
93 }
94 }
95
96 /**
97 * 和bind一样,不过这个方法一般用做html控件对象的事件处理。所以要传递event对象
98 * 注意这时候,用到了 Function.call。它与 Function.apply 的不同好像仅仅是对参数形式的定义。
99 * 如同 java 两个过载的方法。
100 */
101 Function.prototype.bindAsEventListener = function(object) {
102 var __method = this;
103 return function(event) {
104 __method.call(object, event || window.event);
105 }
106 }
107
108 /**
109 * 将整数形式RGB颜色值转换为HEX形式
110 */
111 Number.prototype.toColorPart = function() {
112 var digits = this.toString(16);
113 if (this < 16) return '0' + digits;
114 return digits;
115 }
116
117 /**
118 * 典型 Ruby 风格的函数,将参数中的方法逐个调用,返回第一个成功执行的方法的返回值
119 */
120 var Try = {
121 these: function() {
122 var returnValue;
123
124 for (var i = 0; i < arguments.length; i++) {
125 var lambda = arguments[i];
126 try {
127 returnValue = lambda();
128 break;
129 } catch (e) {}
130 }
131
132 return returnValue;
133 }
134 }
135
136 /*--------------------------------------------------------------------------*/
137
138 /**
139 * 一个设计精巧的定时执行器
140 * 首先由 Class.create() 创建一个 PeriodicalExecuter 类型,
141 * 然后用对象直接量的语法形式设置原型。
142 *
143 * 需要特别说明的是 rgisterCallback 方法,它调用上面定义的函数原型方法bind, 并传递自己为参数。
144 * 之所以这样做,是因为 setTimeout 默认总以 window 对象为当前对象,也就是说,如果 registerCallback 方法定义如下的话:
145 * registerCallback: function() {
146 * setTimeout(this.onTimerEvent, this.frequency * 1000);
147 * }
148 * 那么,this.onTimeoutEvent 方法执行失败,因为它无法访问 this.currentlyExecuting 属性。
149 * 而使用了bind以后,该方法才能正确的找到this,也就是PeriodicalExecuter的当前实例。
150 */
151 var PeriodicalExecuter = Class.create();
152 PeriodicalExecuter.prototype = {
153 initialize: function(callback, frequency) {
154 this.callback = callback;
155 this.frequency = frequency;
156 this.currentlyExecuting = false;
157
158 this.registerCallback();
159 },
160
161 registerCallback: function() {
162 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
163 },
164
165 onTimerEvent: function() {
166 if (!this.currentlyExecuting) {
167 try {
168 this.currentlyExecuting = true;
169 this.callback();
170 } finally {
171 this.currentlyExecuting = false;
172 }
173 }
174 }
175 }
176
177 /*--------------------------------------------------------------------------*/
178
179 /**
180 * 这个函数就 Ruby 了。我觉得它的作用主要有两个
181 * 1. 大概是 document.getElementById(id) 的最简化调用。
182 * 比如:$("aaa") 将返回 aaa 对象
183 * 2. 得到对象数组
184 * 比如: $("aaa","bbb") 返回一个包括id为"aaa"和"bbb"两个input控件对象的数组。
185 */
186 function $() {
187 var elements = new Array();
188
189 for (var i = 0; i < arguments.length; i++) {
190 var element = arguments[i];
191 if (typeof element == 'string')
192 element = document.getElementById(element);
193
194 if (arguments.length == 1)
195 return element;
196
197 elements.push(element);
198 }
199
200 return elements;
201 }
202
203 /**
204 * 为兼容旧版本的浏览器增加 Array 的 push 方法。
205 */
206 if (!Array.prototype.push) {
207 Array.prototype.push = function() {
208 var startLength = this.length;
209 for (var i = 0; i < arguments.length; i++)
210 this[startLength + i] = arguments[i];
211 return this.length;
212 }
213 }
214
215 /**
216 * 为兼容旧版本的浏览器增加 Function 的 apply 方法。
217 */
218 if (!Function.prototype.apply) {
219 // Based on code from http://www.youngpup.net/
220 Function.prototype.apply = function(object, parameters) {
221 var parameterStrings = new Array();
222 if (!object) object = window;
223 if (!parameters) parameters = new Array();
224
225 for (var i = 0; i < parameters.length; i++)
226 parameterStrings[i] = 'parameters[' + i + ']';
227
228 object.__apply__ = this;
229 var result = eval('object.__apply__(' +
230 parameterStrings.join(', ') + ')');
231 object.__apply__ = null;
232
233 return result;
234 }
235 }
236
237 /**
238 * 扩展 javascript 内置的 String 对象
239 */
240 String.prototype.extend({
241
242 /**
243 * 去掉字符串中的标签
244 */
245 stripTags: function() {
246 return this.replace(/<///?[^>]+>/gi, '');
247 },
248
249 /**
250 * 这个方法很常见,通常的实现都是用正则表达式替换特殊字符为html规范定义的命名实体或者十进制编码,比如:
251 * string.replace(/&/g, "&").replace(//g, ">");
252 * 而这里的实现借用浏览器自身的内部替换,确实巧妙。
253 */
254 escapeHTML: function() {
255 var div = document.createElement('div');
256 var text = document.createTextNode(this);
257 div.appendChild(text);
258 return div.innerHTML;
259 },
260
261 /**
262 * 同上
263 */
264 unescapeHTML: function() {
265 var div = document.createElement('div');
266 div.innerHTML = this.stripTags();
267 return div.childNodes[0].nodeValue;
268 }
269 });
270
271 /**
272 * 定义 Ajax 对象, 静态方法 getTransport 方法返回一个 XMLHttp 对象
273 */
274 var Ajax = {
275 getTransport: function() {
276 return Try.these(
277 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
278 function() {return new ActiveXObject('Microsoft.XMLHTTP')},
279 function() {return new XMLHttpRequest()}
280 ) || false;
281 }
282 }
283
284 /**
285 * 我以为此时的Ajax对象起到命名空间的作用。
286 * Ajax.Base 声明为一个基础对象类型
287 * 注意 Ajax.Base 并没有使用 Class.create() 的方式来创建,我想是因为作者并不希望 Ajax.Base 被库使用者实例化。
288 * 作者在其他对象类型的声明中,将会继承于它。
289 * 就好像 java 中的私有抽象类
290 */
291 Ajax.Base = function() {};
292 Ajax.Base.prototype = {
293 /**
294 * extend (见上) 的用法真是让人耳目一新
295 * options 首先设置默认属性,然后再 extend 参数对象,那么参数对象中也有同名的属性,那么就覆盖默认属性值。
296 * 想想如果我写这样的实现,应该类似如下:
297 setOptions: function(options) {
298 this.options.methed = options.methed? options.methed : 'post';
299 ..........
300 }
301 我想很多时候,java 限制了 js 的创意。
302 */
303 setOptions: function(options) {
304 this.options = {
305 method: 'post',
306 asynchronous: true,
307 parameters: ''
308 }.extend(options || {});
309 },
310
311 /**
312 * 如果 xmlhttp 调用返回正确的HTTP状态值,函数返回ture, 反之false。
313 * xmlhttp 的 readyState 属性不足以准确判断 xmlhttp 远程调用成功,该方法是readyState判断的一个前提条件
314 */
315 responseIsSuccess: function() {
316 return this.transport.status == undefined
317 || this.transport.status == 0
318 || (this.transport.status >= 200 && this.transport.status < 300);
319 },
320
321 /**
322 * 如果 xmlhttp 调用返回错误的HTTP状态值,函数返回ture, 反之false。
323 */
324 responseIsFailure: function() {
325 return !this.responseIsSuccess();
326 }
327 }
328
329 /**
330 * Ajax.Request 封装 XmlHttp
331 */
332 Ajax.Request = Class.create();
333
334 /**
335 * 定义四种事件(状态), 参考http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/readystate_1.asp
336 */
337 Ajax.Request.Events =
338 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
339
340 /**
341 * 相比先前的版本,对于 xmlhttp 的调用和返回值处理分离得更为清晰
342 */
343 Ajax.Request.prototype = (new Ajax.Base()).extend({
344 initialize: function(url, options) {
345 this.transport = Ajax.getTransport();
346 this.setOptions(options);
347 this.request(url);
348 },
349
350 /**
351 * 新增加 request 方法封装 xmlhttp 的调用过程。
352 */
353 request: function(url) {
354 var parameters = this.options.parameters || '';
355 if (parameters.length > 0) parameters += '&_=';
356
357 try {
358 if (this.options.method == 'get')
359 url += '?' + parameters;
360
361 this.transport.open(this.options.method, url,
362 this.options.asynchronous);
363
364 if (this.options.asynchronous) {
365 this.transport.onreadystatechange = this.onStateChange.bind(this);
366 setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
367 }
368
369 this.setRequestHeaders();
370
371 var body = this.options.postBody ? this.options.postBody : parameters;
372 this.transport.send(this.options.method == 'post' ? body : null);
373
374 } catch (e) {
375 }
376 },
377
378 /**
379 * 新增加的 setRequestHeaders 方法允许添加自定义的http header
380 */
381 setRequestHeaders: function() {
382 var requestHeaders =
383 ['X-Requested-With', 'XMLHttpRequest',
384 'X-Prototype-Version', Prototype.Version];
385
386 if (this.options.method == 'post') {
387 requestHeaders.push('Content-type',
388 'application/x-www-form-urlencoded');
389
390 /* Force "Connection: close" for Mozilla browsers to work around
391 * a bug where XMLHttpReqeuest sends an incorrect Content-length
392 * header. See Mozilla Bugzilla #246651.
393 */
394 if (this.transport.overrideMimeType)
395 requestHeaders.push('Connection', 'close');
396 }
397
398 /**
399 * 其后的 apply 方法的调用有些奇技淫巧的意味
400 * 从上下文中我们可以分析出 this.options.requestHeaders 是调用者自定义的http header数组。
401 * requestHeaders 也是一个数组,将一个数组中的元素逐个添加到另一个元素中,直接调用
402 * requestHeaders.push(this.options.requestHeaders)
403 * 是不行的,因为该调用导致 this.options.requestHeaders 整个数组作为一个元素添加到 requestHeaders中。
404 * javascript的Array对象还提供一个concat 的方法表面上满足要求,但是concat实际上是创建一个新数组,将两个数组的元素添加到新数组中。
405 * 所以,下面的代码也可以替换为
406 * requestHeaders = requestHeaders.concat(this.options.requestHeaders);
407 * 很显然,作者不喜欢这样的代码方式
408 * 而 apply 方法的语法 apply([thisObj[,argArray]]) 本身就要求第二个参数是一个数组或者arguments对象。
409 * 所以巧妙的实现了 concat 函数的作用。
410 * 令人拍案叫绝啊!
411 */
412 if (this.options.requestHeaders)
413 requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
414
415 for (var i = 0; i < requestHeaders.length; i += 2)
416 this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
417 },
418
419
420 onStateChange: function() {
421 var readyState = this.transport.readyState;
422 /**
423 * 如果不是 Loading 状态,就调用回调函数
424 */
425 if (readyState != 1)
426 this.respondToReadyState(this.transport.readyState);
427 },
428
429 /**
430 * 回调函数定义在 this.options 属性中,比如:
431 var option = {
432 onLoaded : function(req) {...};
433 ......
434 }
435 new Ajax.Request(url, option);
436 */
437 respondToReadyState: function(readyState) {
438 var event = Ajax.Request.Events[readyState];
439
440 /**
441 * 新增的回调函数处理,调用者还可以在options中定义 on200, onSuccess 这样的回调函数
442 * 在 readyState 为完成状态的时候调用
443 */
444 if (event == 'Complete')
445 (this.options['on' + this.transport.status]
446 || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
447 || Prototype.emptyFunction)(this.transport);
448
449 (this.options['on' + event] || Prototype.emptyFunction)(this.transport);
450
451 /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
452 if (event == 'Complete')
453 this.transport.onreadystatechange = Prototype.emptyFunction;
454 }
455 });
456
457 /**
458 * Ajax.Updater 用于绑定一个html元素与 XmlHttp调用的返回值。类似与 buffalo 的 bind。
459 * 如果 options 中有 insertion(见后) 对象的话, insertion 能提供更多的插入控制。
460 */
461 Ajax.Updater = Class.create();
462 Ajax.Updater.ScriptFragment = '(?:<script.*?>)((//n|.)*?)(?:<///script>)';
463
464 Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
465 initialize: function(container, url, options) {
466
467 /**
468 * containers 就是被绑定的 html 对象,xmlhttp的返回值被赋给该对象的 innerHTML 属性。
469 * 相比新版本,containers 根据container参数定义 success 和 failure 引用,如果它们被定义的话,根据xmlhttp调用是否成功来选择
470 * 更新对象,假想调用可能如下:
471 * var c = {success: $("successDiv"), failure: $("failureDiv")};
472 * new Ajax.Updater(c, url, options);
473 * 那么调用成功则 successDiv 显示成功信息或者数据,反之 failureDiv 显示错误信息
474 */
475 this.containers = {
476 success: container.success ? $(container.success) : $(container),
477 failure: container.failure ? $(container.failure) :
478 (container.success ? null : $(container))
479 }
480
481 this.transport = Ajax.getTransport();
482 this.setOptions(options);
483
484 var onComplete = this.options.onComplete || Prototype.emptyFunction;
485 this.options.onComplete = (function() {
486 this.updateContent();
487 onComplete(this.transport);
488 }).bind(this);
489
490 this.request(url);
491 },
492
493 updateContent: function() {
494 var receiver = this.responseIsSuccess() ?
495 this.containers.success : this.containers.failure;
496
497 var match = new RegExp(Ajax.Updater.ScriptFragment, 'img');
498 var response = this.transport.responseText.replace(match, '');
499 var scripts = this.transport.responseText.match(match);
500
501 if (receiver) {
502 if (this.options.insertion) {
503 new this.options.insertion(receiver, response);
504 } else {
505 receiver.innerHTML = response;
506 }
507 }
508
509 if (this.responseIsSuccess()) {
510 if (this.onComplete)
511 setTimeout((function() {this.onComplete(
512 this.transport)}).bind(this), 10);
513 }
514
515 /**
516 * 如果调用者在传入的options参数中定义 evalScripts=true,同时xmlhttp返回值的html中包含<script type="text/javascript">标签的话,执行该脚本
517 */
518 if (this.options.evalScripts && scripts) {
519 /**
520 * 注意前二十行左右还有一个 match 的声明
521 * var match = new RegExp(Ajax.Updater.ScriptFragment, 'img');
522 * 和此处的区别就是,正则表达式匹配标记多一个 "g"。
523 * 多个g, 所以 scripts 是一个数组,数组中每个元素是一段