prototype.js 源码解读v1.3.1版本(转)

prototype 1.3.1 版本和之前的 1.2.0 版本有了不少改进,并增加了新的功能:
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 是一个数组,数组中每个元素是一段
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值