prototype 源码解读 之 prototype.js
1
/**
2
* 定义一个全局对象, 属性 Version 在发布的时候会替换为当前版本号
3
*/
4
var Prototype = {
5
Version: '@@VERSION@@'
6
}
7
8
/**
9
* 创建一种类型,注意其属性 create 是一个方法,返回一个构造函数。
10
* 一般使用如下
11
* var X = Class.create(); 返回一个类型,类似于 java 的一个Class实例。
12
* 要使用 X 类型,需继续用 new X()来获取一个实例,如同 java 的 Class.newInstance()方法。
13
*
14
* 返回的构造函数会执行名为 initialize 的方法, initialize 是 Ruby 对象的构造器方法名字。
15
* 此时initialize方法还没有定义,其后的代码中创建新类型时会建立相应的同名方法。
16
*
17
* 如果一定要从java上去理解。你可以理解为用Class.create()创建一个继承java.lang.Class类的类。当然java不允许这样做,因为Class类是final的
18
*
19
*/
20
var Class = {
21
create: function() {
22
return function() {
23
this.initialize.apply(this, arguments);
24
}
25
}
26
}
27
28
/**
29
* 创建一个对象,从变量名来思考,本意也许是定义一个抽象类,以后创建新对象都 extend 它。
30
* 但从其后代码的应用来看, Abstract 更多是为了保持命名空间清晰的考虑。
31
* 也就是说,我们可以给 Abstract 这个对象实例添加新的对象定义。
32
*
33
* 从java去理解,就是动态给一个对象创建内部类。
34
*/
35
var Abstract = new Object();
36
37
/**
38
* 获取参数对象的所有属性和方法,有点象多重继承。但是这种继承是动态获得的。
39
* 如:
40
* var a = new ObjectA(), b = new ObjectB();
41
* var c = a.extend(b);
42
* 此时 c 对象同时拥有 a 和 b 对象的属性和方法。但是与多重继承不同的是,c instanceof ObjectB 将返回false。
43
*/
44
Object.prototype.extend = function(object) {
45
for (property in object) {
46
this[property] = object[property];
47
}
48
return this;
49
}
50
51
/**
52
* 这个方法很有趣,它封装一个javascript函数对象,返回一个新函数对象,新函数对象的主体和原对象相同,但是bind()方法参数将被用作当前对象的对象。
53
* 也就是说新函数中的 this 引用被改变为参数提供的对象。
54
* 比如:
55
* <input type="text" id="aaa" value="aaa">
56
* <input type="text" id="bbb" value="bbb">
57
* 



..
58
* <script>
59
* var aaa = document.getElementById("aaa");
60
* var bbb = document.getElementById("bbb");
61
* aaa.showValue = function() {alert(this.value);}
62
* aaa.showValue2 = aaa.showValue.bind(bbb);
63
* </script>
64
* 那么,调用aaa.showValue 将返回"aaa", 但调用aaa.showValue2 将返回"bbb"。
65
*
66
* apply 是ie5.5后才出现的新方法(Netscape好像很早就支持了)。
67
* 该方法更多的资料参考MSDN http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthApply.asp
68
* 还有一个 call 方法,应用起来和 apply 类似。可以一起研究下。
69
*/
70
Function.prototype.bind = function(object) {
71
var method = this;
72
return function() {
73
method.apply(object, arguments);
74
}
75
}
76
77
/**
78
* 和bind一样,不过这个方法一般用做html控件对象的事件处理。所以要传递event对象
79
* 注意这时候,用到了 Function.call。它与 Function.apply 的不同好像仅仅是对参数形式的定义。
80
* 如同 java 两个过载的方法。
81
*/
82
Function.prototype.bindAsEventListener = function(object) {
83
var method = this;
84
return function(event) {
85
method.call(object, event || window.event);
86
}
87
}
88
89
/**
90
* 将整数形式RGB颜色值转换为HEX形式
91
*/
92
Number.prototype.toColorPart = function() {
93
var digits = this.toString(16);
94
if (this < 16) return '0' + digits;
95
return digits;
96
}
97
98
/**
99
* 典型 Ruby 风格的函数,将参数中的方法逐个调用,返回第一个成功执行的方法的返回值
100
*/
101
var Try = {
102
these: function() {
103
var returnValue;
104
105
for (var i = 0; i < arguments.length; i++) {
106
var lambda = arguments[i];
107
try {
108
returnValue = lambda();
109
break;
110
} catch (e) {}
111
}
112
113
return returnValue;
114
}
115
}
116
117
/*--------------------------------------------------------------------------*/
118
119
/**
120
* 一个设计精巧的定时执行器
121
* 首先由 Class.create() 创建一个 PeriodicalExecuter 类型,
122
* 然后用对象直接量的语法形式设置原型。
123
*
124
* 需要特别说明的是 rgisterCallback 方法,它调用上面定义的函数原型方法bind, 并传递自己为参数。
125
* 之所以这样做,是因为 setTimeout 默认总以 window 对象为当前对象,也就是说,如果 registerCallback 方法定义如下的话:
126
* registerCallback: function() {
127
* setTimeout(this.onTimerEvent, this.frequency * 1000);
128
* }
129
* 那么,this.onTimeoutEvent 方法执行失败,因为它无法访问 this.currentlyExecuting 属性。
130
* 而使用了bind以后,该方法才能正确的找到this,也就是PeriodicalExecuter的当前实例。
131
*/
132
var PeriodicalExecuter = Class.create();
133
PeriodicalExecuter.prototype = {
134
initialize: function(callback, frequency) {
135
this.callback = callback;
136
this.frequency = frequency;
137
this.currentlyExecuting = false;
138
139
this.registerCallback();
140
},
141
142
registerCallback: function() {
143
setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000);
144
},
145
146
onTimerEvent: function() {
147
if (!this.currentlyExecuting) {
148
try {
149
this.currentlyExecuting = true;
150
this.callback();
151
} finally {
152
this.currentlyExecuting = false;
153
}
154
}
155
156
this.registerCallback();
157
}
158
}
159
160
/*--------------------------------------------------------------------------*/
161
162
/**
163
* 这个函数就 Ruby 了。我觉得它的作用主要有两个
164
* 1. 大概是 document.getElementById(id) 的最简化调用。
165
* 比如:$("aaa") 将返回上 aaa 对象
166
* 2. 得到对象数组
167
* 比如: $("aaa","bbb") 返回一个包括id为"aaa"和"bbb"两个input控件对象的数组。
168
*/
169
function $() {
170
var elements = new Array();
171
172
for (var i = 0; i < arguments.length; i++) {
173
var element = arguments[i];
174
if (typeof element == 'string')
175
element = document.getElementById(element);
176
177
if (arguments.length == 1)
178
return element;
179
180
elements.push(element);
181
}
182
183
return elements;
184
}
185
186
187
/**
188
* 定义 Ajax 对象, 静态方法 getTransport 方法返回一个 XMLHttp 对象
189
*/
190
var Ajax = {
191
getTransport: function() {
192
return Try.these(
193
function() {return new ActiveXObject('Msxml2.XMLHTTP')},
194
function() {return new ActiveXObject('Microsoft.XMLHTTP')},
195
function() {return new XMLHttpRequest()}
196
) || false;
197
},
198
199
emptyFunction: function() {}
200
}
201
202
/**
203
* 我以为此时的Ajax对象起到命名空间的作用。
204
* Ajax.Base 声明为一个基础对象类型
205
* 注意 Ajax.Base 并没有使用 Class.create() 的方式来创建,我想是因为作者并不希望 Ajax.Base 被库使用者实例化。
206
* 作者在其他对象类型的声明中,将会继承于它。
207
* 就好像 java 中的私有抽象类
208
*/
209
Ajax.Base = function() {};
210
Ajax.Base.prototype = {
211
/**
212
* extend (见prototype.js中的定义) 的用法真是让人耳目一新
213
* options 首先设置默认属性,然后再 extend 参数对象,那么参数对象中也有同名的属性,那么就覆盖默认属性值。
214
* 想想如果我写这样的实现,应该类似如下:
215
setOptions: function(options) {
216
this.options.methed = options.methed? options.methed : 'post';
217


.
218
}
219
我想很多时候,java 限制了 js 的创意。
220
*/
221
setOptions: function(options) {
222
this.options = {
223
method: 'post',
224
asynchronous: true,
225
parameters: ''
226
}.extend(options || {});
227
}
228
}
229
230
231
/**
232
* Ajax.Request 封装 XmlHttp
233
*/
234
Ajax.Request = Class.create();
235
236
/**
237
* 定义四种事件(状态), 参考http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/readystate_1.asp
238
*/
239
Ajax.Request.Events =
240
['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
241
242
/**
243
*
244
*/
245
Ajax.Request.prototype = (new Ajax.Base()).extend({
246
initialize: function(url, options) {
247
this.transport = Ajax.getTransport();
248
this.setOptions(options);
249
250
try {
251
if (this.options.method == 'get')
252
url += '?' + this.options.parameters + '&_=';
253
254
/**
255
* 此处好像强制使用了异步方式,而不是依照 this.options.asynchronous 的值
256
*/
257
this.transport.open(this.options.method, url, true);
258
259
/**
260
* 这里提供了 XmlHttp 传输过程中每个步骤的回调函数
261
*/
262
if (this.options.asynchronous) {
263
this.transport.onreadystatechange = this.onStateChange.bind(this);
264
setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
265
}
266
267
this.transport.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
268
this.transport.setRequestHeader('X-Prototype-Version', Prototype.Version);
269
270
if (this.options.method == 'post') {
271
this.transport.setRequestHeader('Connection', 'close');
272
this.transport.setRequestHeader('Content-type',
273
'application/x-www-form-urlencoded');
274
}
275
276
this.transport.send(this.options.method == 'post' ?
277
this.options.parameters + '&_=' : null);
278
279
} catch (e) {
280
}
281
},
282
283
onStateChange: function() {
284
var readyState = this.transport.readyState;
285
/**
286
* 如果不是 Loading 状态,就调用回调函数
287
*/
288
if (readyState != 1)
289
this.respondToReadyState(this.transport.readyState);
290
},
291
292
/**
293
* 回调函数定义在 this.options 属性中,比如:
294
var option = {
295
onLoaded : function(req) {
};
296

297
}
298
new Ajax.Request(url, option);
299
*/
300
respondToReadyState: function(readyState) {
301
var event = Ajax.Request.Events[readyState];
302
(this.options['on' + event] || Ajax.emptyFunction)(this.transport);
303
}
304
});
305
306
/**
307
* Ajax.Updater 用于绑定一个html元素与 XmlHttp调用的返回值。类似与 buffalo 的 bind。
308
* 如果 options 中有 insertion(from dom.js) 对象的话, insertion 能提供更多的插入控制。
309
*/
310
Ajax.Updater = Class.create();
311
Ajax.Updater.prototype = (new Ajax.Base()).extend({
312
initialize: function(container, url, options) {
313
this.container = $(container);
314
this.setOptions(options);
315
316
if (this.options.asynchronous) {
317
this.onComplete = this.options.onComplete;
318
this.options.onComplete = this.updateContent.bind(this);
319
}
320
321
this.request = new Ajax.Request(url, this.options);
322
323
if (!this.options.asynchronous)
324
this.updateContent();
325
},
326
327
updateContent: function() {
328
if (this.options.insertion) {
329
new this.options.insertion(this.container,
330
this.request.transport.responseText);
331
} else {
332
this.container.innerHTML = this.request.transport.responseText;
333
}
334
335
if (this.onComplete) {
336
setTimeout((function() {this.onComplete(this.request)}).bind(this), 10);
337
}
338
}
339
});
340
341
342
/**
343
* 针对 页面元素对象 的工具类,提供一些简单静态方法
344
*/
345
var Field = {
346
/**
347
* 清除参数引用对象的值
348
*/
349
clear: function() {
350
for (var i = 0; i < arguments.length; i++)
351
$(arguments[i]).value = '';
352
},
353
354
/**
355
* 使参数引用对象获取焦点
356
*/
357
focus: function(element) {
358
$(element).focus();
359
},
360
361
/**
362
* 判断参数引用对象值是否为空,如为空,返回false, 反之true
363
*/
364
present: function() {
365
for (var i = 0; i < arguments.length; i++)
366
if ($(arguments[i]).value == '') return false;
367
return true;
368
},
369
370
/**
371
* 使选中参数引用对象
372
*/
373
select: function(element) {
374
$(element).select();
375
},
376
377
/**
378
* 使参数引用对象处于可编辑状态
379
*/
380
activate: function(element) {
381
$(element).focus();
382
$(element).select();
383
}
384
}
385
386
/*--------------------------------------------------------------------------*/
387
388
/**
389
* 表单工具类
390
*/
391
var Form = {
392
/**
393
* 将表单元素序列化后的值组合成 QueryString 的形式
394
*/
395
serialize: function(form) {
396
var elements = Form.getElements($(form));
397
var queryComponents = new Array();
398
399
for (var i = 0; i < elements.length; i++) {
400
var queryComponent = Form.Element.serialize(elements[i]);
401
if (queryComponent)
402
queryComponents.push(queryComponent);
403
}
404
405
return queryComponents.join('&');
406
},
407
408
/**
409
* 得到表单的所有元素对象
410
*/
411
getElements: function(form) {
412
form = $(form);
413
var elements = new Array();
414
415
for (tagName in Form.Element.Serializers) {
416
var tagElements = form.getElementsByTagName(tagName);
417
for (var j = 0; j < tagElements.length; j++)
418
elements.push(tagElements[j]);
419
}
420
return elements;
421
},
422
423
/**
424
* 将指定表单的元素置于不可用状态
425
*/
426
disable: function(form) {
427
var elements = Form.getElements(form);
428
for (var i = 0; i < elements.length; i++) {
429
var element = elements[i];
430
element.blur();
431
element.disable = 'true';
432
}
433
},
434
435
/**
436
* 使表单的第一个非 hidden 类型而且处于可用状态的元素获得焦点
437
*/
438
focusFirstElement: function(form) {
439
form = $(form);
440
var elements = Form.getElements(form);
441
for (var i = 0; i < elements.length; i++) {
442
var element = elements[i];
443
if (element.type != 'hidden' && !element.disabled) {
444
Field.activate(element);
445
break;
446
}
447
}
448
},
449
450
/*
451
* 重置表单
452
*/
453
reset: function(form) {
454
$(form).reset();
455
}
456
}
457
458
/**
459
* 表单元素工具类
460
*/
461
Form.Element = {
462
/**
463
* 返回表单元素的值先序列化再进行 URL 编码后的值
464
*/
465
serialize: function(element) {
466
element = $(element);
467
var method = element.tagName.toLowerCase();
468
var parameter = Form.Element.Serializers[method](element);
469
470
if (parameter)
471
return encodeURIComponent(parameter[0]) + '=' +
472
encodeURIComponent(parameter[1]);
473
},
474
475
/**
476
* 返回表单元素序列化后的值
477
*/
478
getValue: function(element) {
479
element = $(element);
480
var method = element.tagName.toLowerCase();
481
var parameter = Form.Element.Serializers[method](element);
482
483
if (parameter)
484
return parameter[1];
485
}
486
}
487
488
/**
489
* prototype 的所谓序列化其实就是将表单的名字和值组合成一个数组
490
*/
491
Form.Element.Serializers = {
492
input: function(element) {
493
switch (element.type.toLowerCase()) {
494
case 'hidden':
495
case 'password':
496
case 'text':
497
return Form.Element.Serializers.textarea(element);
498
case 'checkbox':
499
case 'radio':
500
return Form.Element.Serializers.inputSelector(element);
501
}
502
return false;
503
},
504
505
inputSelector: function(element) {
506
if (element.checked)
507
return [element.name, element.value];
508
},
509
510
textarea: function(element) {
511
return [element.name, element.value];
512
},
513
514
/**
515
* 看样子,也不支持多选框(select-multiple)
516
*/
517
select: function(element) {
518
var index = element.selectedIndex;
519
var value = element.options[index].value || element.options[index].text;
520
return [element.name, (index >= 0) ? value : ''];
521
}
522
}
523
524
/*--------------------------------------------------------------------------*/
525
526
/**
527
* Form.Element.getValue 也许会经常用到,所以做了一个快捷引用
528
*/
529
var $F = Form.Element.getValue;
530
531
/*--------------------------------------------------------------------------*/
532
533
/**
534
* Abstract.TimedObserver 也没有用 Class.create() 来创建,和Ajax.Base 意图应该一样
535
* Abstract.TimedObserver 顾名思义,是套用Observer设计模式来跟踪指定表单元素,
536
* 当表单元素的值发生变化的时候,就执行回调函数
537
*
538
* 我想 Observer 与注册onchange事件相似,不同点在于 onchange 事件是在元素失去焦点的时候才激发。
539
* 同样的与 onpropertychange 事件也相似,不过它只关注表单元素的值的变化,而且提供timeout的控制。
540
*
541
* 除此之外,Observer 的好处大概就在与更面向对象,另外可以动态的更换回调函数,这就比注册事件要灵活一些。
542
* Observer 应该可以胜任动态数据校验,或者多个关联下拉选项列表的连动等等
543
*
544
*/
545
Abstract.TimedObserver = function() {}
546
547
/**
548
* 这个设计和 PeriodicalExecuter 一样,bind 方法是实现的核心
549
*/
550
Abstract.TimedObserver.prototype = {
551
initialize: function(element, frequency, callback) {
552
this.frequency = frequency;
553
this.element = $(element);
554
this.callback = callback;
555
556
this.lastValue = this.getValue();
557
this.registerCallback();
558
},
559
560
registerCallback: function() {
561
setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000);
562
},
563
564
onTimerEvent: function() {
565
var value = this.getValue();
566
if (this.lastValue != value) {
567
this.callback(this.element, value);
568
this.lastValue = value;
569
}
570
571
this.registerCallback();
572
}
573
}
574
575
/**
576
* Form.Element.Observer 和 Form.Observer 其实是一样的
577
* 注意 Form.Observer 并不是用来跟踪整个表单的,我想大概只是为了减少书写(这是Ruby的一个设计原则)
578
*/
579
Form.Element.Observer = Class.create();
580
Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({
581
getValue: function() {
582
return Form.Element.getValue(this.element);
583
}
584
});
585
586
Form.Observer = Class.create();
587
Form.Observer.prototype = (new Abstract.TimedObserver()).extend({
588
getValue: function() {
589
return Form.serialize(this.element);
590
}
591
});
592
593
594
/**
595
* 根据 class attribute 的名字得到对象数组,支持 multiple class
596
*
597
*/
598
document.getElementsByClassName = function(className) {
599
var children = document.getElementsByTagName('*') || document.all;
600
var elements = new Array();
601
602
for (var i = 0; i < children.length; i++) {
603
var child = children[i];
604
var classNames = child.className.split(' ');
605
for (var j = 0; j < classNames.length; j++) {
606
if (classNames[j] == className) {
607
elements.push(child);
608
break;
609
}
610
}
611
}
612
613
return elements;
614
}
615
616
/*--------------------------------------------------------------------------*/
617
618
/**
619
* Element 就象一个 java 的工具类,主要用来 隐藏/显示/销除 对象,以及获取对象的简单属性。
620
*
621
*/
622
var Element = {
623
toggle: function() {
624
for (var i = 0; i < arguments.length; i++) {
625
var element = $(arguments[i]);
626
element.style.display =
627
(element.style.display == 'none' ? '' : 'none');
628
}
629
},
630
631
hide: function() {
632
for (var i = 0; i < arguments.length; i++) {
633
var element = $(arguments[i]);
634
element.style.display = 'none';
635
}
636
},
637
638
show: function() {
639
for (var i = 0; i < arguments.length; i++) {
640
var element = $(arguments[i]);
641
element.style.display = '';
642
}
643
},
644
645
remove: function(element) {
646
element = $(element);
647
element.parentNode.removeChild(element);
648
},
649
650
getHeight: function(element) {
651
element = $(element);
652
return element.offsetHeight;
653
}
654
}
655
656
/**
657
* 为 Element.toggle 做了一个符号连接,大概是兼容性的考虑
658
*/
659
var Toggle = new Object();
660
Toggle.display = Element.toggle;
661
662
/*--------------------------------------------------------------------------*/
663
664
/**
665
* 动态插入内容的实现,MS的Jscript实现中对象有一个 insertAdjacentHTML 方法(http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/insertadjacenthtml.asp)
666
* 这里算是一个对象形式的封装。
667
*/
668
Abstract.Insertion = function(adjacency) {
669
this.adjacency = adjacency;
670
}
671
672
Abstract.Insertion.prototype = {
673
initialize: function(element, content) {
674
this.element = $(element);
675
this.content = content;
676
677
if (this.adjacency && this.element.insertAdjacentHTML) {
678
this.element.insertAdjacentHTML(this.adjacency, this.content);
679
} else {
680
/**
681
* gecko 不支持 insertAdjacentHTML 方法,但可以用如下代码代替
682
*/
683
this.range = this.element.ownerDocument.createRange();
684
/**
685
* 如果定义了 initializeRange 方法,则实行,这里相当与定义了一个抽象的 initializeRange 方法
686
*/
687
if (this.initializeRange) this.initializeRange();
688
this.fragment = this.range.createContextualFragment(this.content);
689
690
/**
691
* insertContent 也是一个抽象方法,子类必须实现
692
*/
693
this.insertContent();
694
}
695
}
696
}
697
698
/**
699
* prototype 加深了我的体会,就是写js 如何去遵循 Don’t Repeat Yourself (DRY) 原则
700
* 上文中 Abstract.Insertion 算是一个抽象类,定义了名为 initializeRange 的一个抽象方法
701
* var Insertion = new Object() 建立一个命名空间
702
* Insertion.Before|Top|Bottom|After 就象是四个java中的四个静态内部类,而它们分别继承于Abstract.Insertion,并实现了initializeRange方法。
703
*/
704
var Insertion = new Object();
705
706
Insertion.Before = Class.create();
707
Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({
708
initializeRange: function() {
709
this.range.setStartBefore(this.element);
710
},
711
712
/**
713
* 将内容插入到指定节点的前面, 与指定节点同级
714
*/
715
insertContent: function() {
716
this.element.parentNode.insertBefore(this.fragment, this.element);
717
}
718
});
719
720
Insertion.Top = Class.create();
721
Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({
722
initializeRange: function() {
723
this.range.selectNodeContents(this.element);
724
this.range.collapse(true);
725
},
726
727
/**
728
* 将内容插入到指定节点的第一个子节点前,于是内容变为该节点的第一个子节点
729
*/
730
insertContent: function() {
731
this.element.insertBefore(this.fragment, this.element.firstChild);
732
}
733
});
734
735
Insertion.Bottom = Class.create();
736
Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({
737
initializeRange: function() {
738
this.range.selectNodeContents(this.element);
739
this.range.collapse(this.element);
740
},
741
742
/**
743
* 将内容插入到指定节点的最后,于是内容变为该节点的最后一个子节点
744
*/
745
insertContent: function() {
746
this.element.appendChild(this.fragment);
747
}
748
});
749
750
751
Insertion.After = Class.create();
752
Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({
753
initializeRange: function() {
754
this.range.setStartAfter(this.element);
755
},
756
757
/**
758
* 将内容插入到指定节点的后面, 与指定节点同级
759
*/
760
insertContent: function() {
761
this.element.parentNode.insertBefore(this.fragment,
762
this.element.nextSibling);
763
}
764
});
765
/** 2
* 定义一个全局对象, 属性 Version 在发布的时候会替换为当前版本号 3
*/ 4
var Prototype = { 5
Version: '@@VERSION@@' 6
} 7

8
/** 9
* 创建一种类型,注意其属性 create 是一个方法,返回一个构造函数。 10
* 一般使用如下 11
* var X = Class.create(); 返回一个类型,类似于 java 的一个Class实例。 12
* 要使用 X 类型,需继续用 new X()来获取一个实例,如同 java 的 Class.newInstance()方法。 13
* 14
* 返回的构造函数会执行名为 initialize 的方法, initialize 是 Ruby 对象的构造器方法名字。 15
* 此时initialize方法还没有定义,其后的代码中创建新类型时会建立相应的同名方法。 16
* 17
* 如果一定要从java上去理解。你可以理解为用Class.create()创建一个继承java.lang.Class类的类。当然java不允许这样做,因为Class类是final的 18
* 19
*/ 20
var Class = { 21
create: function() { 22
return function() { 23
this.initialize.apply(this, arguments); 24
} 25
} 26
} 27

28
/** 29
* 创建一个对象,从变量名来思考,本意也许是定义一个抽象类,以后创建新对象都 extend 它。 30
* 但从其后代码的应用来看, Abstract 更多是为了保持命名空间清晰的考虑。 31
* 也就是说,我们可以给 Abstract 这个对象实例添加新的对象定义。 32
* 33
* 从java去理解,就是动态给一个对象创建内部类。 34
*/ 35
var Abstract = new Object(); 36

37
/** 38
* 获取参数对象的所有属性和方法,有点象多重继承。但是这种继承是动态获得的。 39
* 如: 40
* var a = new ObjectA(), b = new ObjectB(); 41
* var c = a.extend(b); 42
* 此时 c 对象同时拥有 a 和 b 对象的属性和方法。但是与多重继承不同的是,c instanceof ObjectB 将返回false。 43
*/ 44
Object.prototype.extend = function(object) { 45
for (property in object) { 46
this[property] = object[property]; 47
} 48
return this; 49
} 50

51
/** 52
* 这个方法很有趣,它封装一个javascript函数对象,返回一个新函数对象,新函数对象的主体和原对象相同,但是bind()方法参数将被用作当前对象的对象。 53
* 也就是说新函数中的 this 引用被改变为参数提供的对象。 54
* 比如: 55
* <input type="text" id="aaa" value="aaa"> 56
* <input type="text" id="bbb" value="bbb"> 57
* 



.. 58
* <script> 59
* var aaa = document.getElementById("aaa"); 60
* var bbb = document.getElementById("bbb"); 61
* aaa.showValue = function() {alert(this.value);} 62
* aaa.showValue2 = aaa.showValue.bind(bbb); 63
* </script> 64
* 那么,调用aaa.showValue 将返回"aaa", 但调用aaa.showValue2 将返回"bbb"。 65
* 66
* apply 是ie5.5后才出现的新方法(Netscape好像很早就支持了)。 67
* 该方法更多的资料参考MSDN http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthApply.asp 68
* 还有一个 call 方法,应用起来和 apply 类似。可以一起研究下。 69
*/ 70
Function.prototype.bind = function(object) { 71
var method = this; 72
return function() { 73
method.apply(object, arguments); 74
} 75
} 76

77
/** 78
* 和bind一样,不过这个方法一般用做html控件对象的事件处理。所以要传递event对象 79
* 注意这时候,用到了 Function.call。它与 Function.apply 的不同好像仅仅是对参数形式的定义。 80
* 如同 java 两个过载的方法。 81
*/ 82
Function.prototype.bindAsEventListener = function(object) { 83
var method = this; 84
return function(event) { 85
method.call(object, event || window.event); 86
} 87
} 88

89
/** 90
* 将整数形式RGB颜色值转换为HEX形式 91
*/ 92
Number.prototype.toColorPart = function() { 93
var digits = this.toString(16); 94
if (this < 16) return '0' + digits; 95
return digits; 96
} 97

98
/** 99
* 典型 Ruby 风格的函数,将参数中的方法逐个调用,返回第一个成功执行的方法的返回值 100
*/ 101
var Try = { 102
these: function() { 103
var returnValue; 104
105
for (var i = 0; i < arguments.length; i++) { 106
var lambda = arguments[i]; 107
try { 108
returnValue = lambda(); 109
break; 110
} catch (e) {} 111
} 112
113
return returnValue; 114
} 115
} 116

117
/*--------------------------------------------------------------------------*/ 118

119
/** 120
* 一个设计精巧的定时执行器 121
* 首先由 Class.create() 创建一个 PeriodicalExecuter 类型, 122
* 然后用对象直接量的语法形式设置原型。 123
* 124
* 需要特别说明的是 rgisterCallback 方法,它调用上面定义的函数原型方法bind, 并传递自己为参数。 125
* 之所以这样做,是因为 setTimeout 默认总以 window 对象为当前对象,也就是说,如果 registerCallback 方法定义如下的话: 126
* registerCallback: function() { 127
* setTimeout(this.onTimerEvent, this.frequency * 1000); 128
* } 129
* 那么,this.onTimeoutEvent 方法执行失败,因为它无法访问 this.currentlyExecuting 属性。 130
* 而使用了bind以后,该方法才能正确的找到this,也就是PeriodicalExecuter的当前实例。 131
*/ 132
var PeriodicalExecuter = Class.create(); 133
PeriodicalExecuter.prototype = { 134
initialize: function(callback, frequency) { 135
this.callback = callback; 136
this.frequency = frequency; 137
this.currentlyExecuting = false; 138
139
this.registerCallback(); 140
}, 141
142
registerCallback: function() { 143
setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); 144
}, 145
146
onTimerEvent: function() { 147
if (!this.currentlyExecuting) { 148
try { 149
this.currentlyExecuting = true; 150
this.callback(); 151
} finally { 152
this.currentlyExecuting = false; 153
} 154
} 155
156
this.registerCallback(); 157
} 158
} 159

160
/*--------------------------------------------------------------------------*/ 161

162
/** 163
* 这个函数就 Ruby 了。我觉得它的作用主要有两个 164
* 1. 大概是 document.getElementById(id) 的最简化调用。 165
* 比如:$("aaa") 将返回上 aaa 对象 166
* 2. 得到对象数组 167
* 比如: $("aaa","bbb") 返回一个包括id为"aaa"和"bbb"两个input控件对象的数组。 168
*/ 169
function $() { 170
var elements = new Array(); 171
172
for (var i = 0; i < arguments.length; i++) { 173
var element = arguments[i]; 174
if (typeof element == 'string') 175
element = document.getElementById(element); 176

177
if (arguments.length == 1) 178
return element; 179
180
elements.push(element); 181
} 182
183
return elements; 184
} 185

186

187
/** 188
* 定义 Ajax 对象, 静态方法 getTransport 方法返回一个 XMLHttp 对象 189
*/ 190
var Ajax = { 191
getTransport: function() { 192
return Try.these( 193
function() {return new ActiveXObject('Msxml2.XMLHTTP')}, 194
function() {return new ActiveXObject('Microsoft.XMLHTTP')}, 195
function() {return new XMLHttpRequest()} 196
) || false; 197
}, 198
199
emptyFunction: function() {} 200
} 201

202
/** 203
* 我以为此时的Ajax对象起到命名空间的作用。 204
* Ajax.Base 声明为一个基础对象类型 205
* 注意 Ajax.Base 并没有使用 Class.create() 的方式来创建,我想是因为作者并不希望 Ajax.Base 被库使用者实例化。 206
* 作者在其他对象类型的声明中,将会继承于它。 207
* 就好像 java 中的私有抽象类 208
*/ 209
Ajax.Base = function() {}; 210
Ajax.Base.prototype = { 211
/** 212
* extend (见prototype.js中的定义) 的用法真是让人耳目一新 213
* options 首先设置默认属性,然后再 extend 参数对象,那么参数对象中也有同名的属性,那么就覆盖默认属性值。 214
* 想想如果我写这样的实现,应该类似如下: 215
setOptions: function(options) { 216
this.options.methed = options.methed? options.methed : 'post'; 217


. 218
} 219
我想很多时候,java 限制了 js 的创意。 220
*/ 221
setOptions: function(options) { 222
this.options = { 223
method: 'post', 224
asynchronous: true, 225
parameters: '' 226
}.extend(options || {}); 227
} 228
} 229

230

231
/** 232
* Ajax.Request 封装 XmlHttp 233
*/ 234
Ajax.Request = Class.create(); 235

236
/** 237
* 定义四种事件(状态), 参考http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/readystate_1.asp 238
*/ 239
Ajax.Request.Events = 240
['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; 241

242
/** 243
* 244
*/ 245
Ajax.Request.prototype = (new Ajax.Base()).extend({ 246
initialize: function(url, options) { 247
this.transport = Ajax.getTransport(); 248
this.setOptions(options); 249
250
try { 251
if (this.options.method == 'get') 252
url += '?' + this.options.parameters + '&_='; 253
254
/** 255
* 此处好像强制使用了异步方式,而不是依照 this.options.asynchronous 的值 256
*/ 257
this.transport.open(this.options.method, url, true); 258
259
/** 260
* 这里提供了 XmlHttp 传输过程中每个步骤的回调函数 261
*/ 262
if (this.options.asynchronous) { 263
this.transport.onreadystatechange = this.onStateChange.bind(this); 264
setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); 265
} 266
267
this.transport.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 268
this.transport.setRequestHeader('X-Prototype-Version', Prototype.Version); 269

270
if (this.options.method == 'post') { 271
this.transport.setRequestHeader('Connection', 'close'); 272
this.transport.setRequestHeader('Content-type', 273
'application/x-www-form-urlencoded'); 274
} 275
276
this.transport.send(this.options.method == 'post' ? 277
this.options.parameters + '&_=' : null); 278
279
} catch (e) { 280
} 281
}, 282
283
onStateChange: function() { 284
var readyState = this.transport.readyState; 285
/** 286
* 如果不是 Loading 状态,就调用回调函数 287
*/ 288
if (readyState != 1) 289
this.respondToReadyState(this.transport.readyState); 290
}, 291
292
/** 293
* 回调函数定义在 this.options 属性中,比如: 294
var option = { 295
onLoaded : function(req) {
}; 296

297
} 298
new Ajax.Request(url, option); 299
*/ 300
respondToReadyState: function(readyState) { 301
var event = Ajax.Request.Events[readyState]; 302
(this.options['on' + event] || Ajax.emptyFunction)(this.transport); 303
} 304
}); 305

306
/** 307
* Ajax.Updater 用于绑定一个html元素与 XmlHttp调用的返回值。类似与 buffalo 的 bind。 308
* 如果 options 中有 insertion(from dom.js) 对象的话, insertion 能提供更多的插入控制。 309
*/ 310
Ajax.Updater = Class.create(); 311
Ajax.Updater.prototype = (new Ajax.Base()).extend({ 312
initialize: function(container, url, options) { 313
this.container = $(container); 314
this.setOptions(options); 315
316
if (this.options.asynchronous) { 317
this.onComplete = this.options.onComplete; 318
this.options.onComplete = this.updateContent.bind(this); 319
} 320
321
this.request = new Ajax.Request(url, this.options); 322
323
if (!this.options.asynchronous) 324
this.updateContent(); 325
}, 326
327
updateContent: function() { 328
if (this.options.insertion) { 329
new this.options.insertion(this.container, 330
this.request.transport.responseText); 331
} else { 332
this.container.innerHTML = this.request.transport.responseText; 333
} 334

335
if (this.onComplete) { 336
setTimeout((function() {this.onComplete(this.request)}).bind(this), 10); 337
} 338
} 339
}); 340

341

342
/** 343
* 针对 页面元素对象 的工具类,提供一些简单静态方法 344
*/ 345
var Field = { 346
/** 347
* 清除参数引用对象的值 348
*/ 349
clear: function() { 350
for (var i = 0; i < arguments.length; i++) 351
$(arguments[i]).value = ''; 352
}, 353

354
/** 355
* 使参数引用对象获取焦点 356
*/ 357
focus: function(element) { 358
$(element).focus(); 359
}, 360
361
/** 362
* 判断参数引用对象值是否为空,如为空,返回false, 反之true 363
*/ 364
present: function() { 365
for (var i = 0; i < arguments.length; i++) 366
if ($(arguments[i]).value == '') return false; 367
return true; 368
}, 369
370
/** 371
* 使选中参数引用对象 372
*/ 373
select: function(element) { 374
$(element).select(); 375
}, 376

377
/** 378
* 使参数引用对象处于可编辑状态 379
*/ 380
activate: function(element) { 381
$(element).focus(); 382
$(element).select(); 383
} 384
} 385

386
/*--------------------------------------------------------------------------*/ 387

388
/** 389
* 表单工具类 390
*/ 391
var Form = { 392
/** 393
* 将表单元素序列化后的值组合成 QueryString 的形式 394
*/ 395
serialize: function(form) { 396
var elements = Form.getElements($(form)); 397
var queryComponents = new Array(); 398
399
for (var i = 0; i < elements.length; i++) { 400
var queryComponent = Form.Element.serialize(elements[i]); 401
if (queryComponent) 402
queryComponents.push(queryComponent); 403
} 404
405
return queryComponents.join('&'); 406
}, 407
408
/** 409
* 得到表单的所有元素对象 410
*/ 411
getElements: function(form) { 412
form = $(form); 413
var elements = new Array(); 414

415
for (tagName in Form.Element.Serializers) { 416
var tagElements = form.getElementsByTagName(tagName); 417
for (var j = 0; j < tagElements.length; j++) 418
elements.push(tagElements[j]); 419
} 420
return elements; 421
}, 422
423
/** 424
* 将指定表单的元素置于不可用状态 425
*/ 426
disable: function(form) { 427
var elements = Form.getElements(form); 428
for (var i = 0; i < elements.length; i++) { 429
var element = elements[i]; 430
element.blur(); 431
element.disable = 'true'; 432
} 433
}, 434

435
/** 436
* 使表单的第一个非 hidden 类型而且处于可用状态的元素获得焦点 437
*/ 438
focusFirstElement: function(form) { 439
form = $(form); 440
var elements = Form.getElements(form); 441
for (var i = 0; i < elements.length; i++) { 442
var element = elements[i]; 443
if (element.type != 'hidden' && !element.disabled) { 444
Field.activate(element); 445
break; 446
} 447
} 448
}, 449

450
/* 451
* 重置表单 452
*/ 453
reset: function(form) { 454
$(form).reset(); 455
} 456
} 457

458
/** 459
* 表单元素工具类 460
*/ 461
Form.Element = { 462
/** 463
* 返回表单元素的值先序列化再进行 URL 编码后的值 464
*/ 465
serialize: function(element) { 466
element = $(element); 467
var method = element.tagName.toLowerCase(); 468
var parameter = Form.Element.Serializers[method](element); 469
470
if (parameter) 471
return encodeURIComponent(parameter[0]) + '=' + 472
encodeURIComponent(parameter[1]); 473
}, 474
475
/** 476
* 返回表单元素序列化后的值 477
*/ 478
getValue: function(element) { 479
element = $(element); 480
var method = element.tagName.toLowerCase(); 481
var parameter = Form.Element.Serializers[method](element); 482
483
if (parameter) 484
return parameter[1]; 485
} 486
} 487

488
/** 489
* prototype 的所谓序列化其实就是将表单的名字和值组合成一个数组 490
*/ 491
Form.Element.Serializers = { 492
input: function(element) { 493
switch (element.type.toLowerCase()) { 494
case 'hidden': 495
case 'password': 496
case 'text': 497
return Form.Element.Serializers.textarea(element); 498
case 'checkbox': 499
case 'radio': 500
return Form.Element.Serializers.inputSelector(element); 501
} 502
return false; 503
}, 504
505
inputSelector: function(element) { 506
if (element.checked) 507
return [element.name, element.value]; 508
}, 509

510
textarea: function(element) { 511
return [element.name, element.value]; 512
}, 513

514
/** 515
* 看样子,也不支持多选框(select-multiple) 516
*/ 517
select: function(element) { 518
var index = element.selectedIndex; 519
var value = element.options[index].value || element.options[index].text; 520
return [element.name, (index >= 0) ? value : '']; 521
} 522
} 523

524
/*--------------------------------------------------------------------------*/ 525

526
/** 527
* Form.Element.getValue 也许会经常用到,所以做了一个快捷引用 528
*/ 529
var $F = Form.Element.getValue; 530

531
/*--------------------------------------------------------------------------*/ 532

533
/** 534
* Abstract.TimedObserver 也没有用 Class.create() 来创建,和Ajax.Base 意图应该一样 535
* Abstract.TimedObserver 顾名思义,是套用Observer设计模式来跟踪指定表单元素, 536
* 当表单元素的值发生变化的时候,就执行回调函数 537
* 538
* 我想 Observer 与注册onchange事件相似,不同点在于 onchange 事件是在元素失去焦点的时候才激发。 539
* 同样的与 onpropertychange 事件也相似,不过它只关注表单元素的值的变化,而且提供timeout的控制。 540
* 541
* 除此之外,Observer 的好处大概就在与更面向对象,另外可以动态的更换回调函数,这就比注册事件要灵活一些。 542
* Observer 应该可以胜任动态数据校验,或者多个关联下拉选项列表的连动等等 543
* 544
*/ 545
Abstract.TimedObserver = function() {} 546

547
/** 548
* 这个设计和 PeriodicalExecuter 一样,bind 方法是实现的核心 549
*/ 550
Abstract.TimedObserver.prototype = { 551
initialize: function(element, frequency, callback) { 552
this.frequency = frequency; 553
this.element = $(element); 554
this.callback = callback; 555
556
this.lastValue = this.getValue(); 557
this.registerCallback(); 558
}, 559
560
registerCallback: function() { 561
setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); 562
}, 563
564
onTimerEvent: function() { 565
var value = this.getValue(); 566
if (this.lastValue != value) { 567
this.callback(this.element, value); 568
this.lastValue = value; 569
} 570
571
this.registerCallback(); 572
} 573
} 574

575
/** 576
* Form.Element.Observer 和 Form.Observer 其实是一样的 577
* 注意 Form.Observer 并不是用来跟踪整个表单的,我想大概只是为了减少书写(这是Ruby的一个设计原则) 578
*/ 579
Form.Element.Observer = Class.create(); 580
Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ 581
getValue: function() { 582
return Form.Element.getValue(this.element); 583
} 584
}); 585

586
Form.Observer = Class.create(); 587
Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ 588
getValue: function() { 589
return Form.serialize(this.element); 590
} 591
}); 592

593

594
/** 595
* 根据 class attribute 的名字得到对象数组,支持 multiple class 596
* 597
*/ 598
document.getElementsByClassName = function(className) { 599
var children = document.getElementsByTagName('*') || document.all; 600
var elements = new Array(); 601
602
for (var i = 0; i < children.length; i++) { 603
var child = children[i]; 604
var classNames = child.className.split(' '); 605
for (var j = 0; j < classNames.length; j++) { 606
if (classNames[j] == className) { 607
elements.push(child); 608
break; 609
} 610
} 611
} 612
613
return elements; 614
} 615

616
/*--------------------------------------------------------------------------*/ 617

618
/** 619
* Element 就象一个 java 的工具类,主要用来 隐藏/显示/销除 对象,以及获取对象的简单属性。 620
* 621
*/ 622
var Element = { 623
toggle: function() { 624
for (var i = 0; i < arguments.length; i++) { 625
var element = $(arguments[i]); 626
element.style.display = 627
(element.style.display == 'none' ? '' : 'none'); 628
} 629
}, 630

631
hide: function() { 632
for (var i = 0; i < arguments.length; i++) { 633
var element = $(arguments[i]); 634
element.style.display = 'none'; 635
} 636
}, 637

638
show: function() { 639
for (var i = 0; i < arguments.length; i++) { 640
var element = $(arguments[i]); 641
element.style.display = ''; 642
} 643
}, 644

645
remove: function(element) { 646
element = $(element); 647
element.parentNode.removeChild(element); 648
}, 649
650
getHeight: function(element) { 651
element = $(element); 652
return element.offsetHeight; 653
} 654
} 655

656
/** 657
* 为 Element.toggle 做了一个符号连接,大概是兼容性的考虑 658
*/ 659
var Toggle = new Object(); 660
Toggle.display = Element.toggle; 661

662
/*--------------------------------------------------------------------------*/ 663

664
/** 665
* 动态插入内容的实现,MS的Jscript实现中对象有一个 insertAdjacentHTML 方法(http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/insertadjacenthtml.asp) 666
* 这里算是一个对象形式的封装。 667
*/ 668
Abstract.Insertion = function(adjacency) { 669
this.adjacency = adjacency; 670
} 671

672
Abstract.Insertion.prototype = { 673
initialize: function(element, content) { 674
this.element = $(element); 675
this.content = content; 676
677
if (this.adjacency && this.element.insertAdjacentHTML) { 678
this.element.insertAdjacentHTML(this.adjacency, this.content); 679
} else { 680
/** 681
* gecko 不支持 insertAdjacentHTML 方法,但可以用如下代码代替 682
*/ 683
this.range = this.element.ownerDocument.createRange(); 684
/** 685
* 如果定义了 initializeRange 方法,则实行,这里相当与定义了一个抽象的 initializeRange 方法 686
*/ 687
if (this.initializeRange) this.initializeRange(); 688
this.fragment = this.range.createContextualFragment(this.content); 689

690
/** 691
* insertContent 也是一个抽象方法,子类必须实现 692
*/ 693
this.insertContent(); 694
} 695
} 696
} 697

698
/** 699
* prototype 加深了我的体会,就是写js 如何去遵循 Don’t Repeat Yourself (DRY) 原则 700
* 上文中 Abstract.Insertion 算是一个抽象类,定义了名为 initializeRange 的一个抽象方法 701
* var Insertion = new Object() 建立一个命名空间 702
* Insertion.Before|Top|Bottom|After 就象是四个java中的四个静态内部类,而它们分别继承于Abstract.Insertion,并实现了initializeRange方法。 703
*/ 704
var Insertion = new Object(); 705

706
Insertion.Before = Class.create(); 707
Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ 708
initializeRange: function() { 709
this.range.setStartBefore(this.element); 710
}, 711
712
/** 713
* 将内容插入到指定节点的前面, 与指定节点同级 714
*/ 715
insertContent: function() { 716
this.element.parentNode.insertBefore(this.fragment, this.element); 717
} 718
}); 719

720
Insertion.Top = Class.create(); 721
Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ 722
initializeRange: function() { 723
this.range.selectNodeContents(this.element); 724
this.range.collapse(true); 725
}, 726
727
/** 728
* 将内容插入到指定节点的第一个子节点前,于是内容变为该节点的第一个子节点 729
*/ 730
insertContent: function() { 731
this.element.insertBefore(this.fragment, this.element.firstChild); 732
} 733
}); 734

735
Insertion.Bottom = Class.create(); 736
Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ 737
initializeRange: function() { 738
this.range.selectNodeContents(this.element); 739
this.range.collapse(this.element); 740
}, 741
742
/** 743
* 将内容插入到指定节点的最后,于是内容变为该节点的最后一个子节点 744
*/ 745
insertContent: function() { 746
this.element.appendChild(this.fragment); 747
} 748
}); 749

750

751
Insertion.After = Class.create(); 752
Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ 753
initializeRange: function() { 754
this.range.setStartAfter(this.element); 755
}, 756

757
/** 758
* 将内容插入到指定节点的后面, 与指定节点同级 759
*/ 760
insertContent: function() { 761
this.element.parentNode.insertBefore(this.fragment, 762
this.element.nextSibling); 763
} 764
}); 765


create:
浙公网安备 33010602011771号