[转]prototype 源码解读 超强推荐
作者:
[转]prototype 源码解读 超强推荐
复制代码 代码如下:
Prototype is a JavaScript framework that aims to ease development of dynamic web applications. Featuring a unique, easy-to-use toolkit for class-driven development and the nicest Ajax library around, Prototype is quickly becoming the codebase of choice for Web 2.0 developers everywhere.Ruby On Rails 中文社区的醒来贴了自己对于prototype的源码解读心得,颇有借鉴意义。
我喜欢Javascript,热衷于 Ajax 应用。我把自己阅读prototype源码的体会写下来,希望对大家重新认识 Javascript 有所帮助。
prototype.js 代码:
复制代码 代码如下:
/**
2
3 * 定义一个全局对象, 属性 Version 在发布的时候会替换为当前版本号
4
5 */
6
7 var Prototype = {
8
9 Version: '@@VERSION@@'
10
11 }
12
13
14 /**
15
16 * 创建一种类型,注意其属性 create 是一个方法,返回一个构造函数。
17
18 * 一般使用如下
19
20 * var X = Class.create(); 返回一个类型,类似于 java 的一个
21
22 * Class实例。
23
24 * 要使用 X 类型,需继续用 new X()来获取一个实例,如同 java 的
25
26 * Class.newInstance()方法。
27
28 *
29
30 * 返回的构造函数会执行名为 initialize 的方法, initialize 是
31
32 * Ruby 对象的构造器方法名字。
33
34 * 此时initialize方法还没有定义,其后的代码中创建新类型时会建立
35
36 * 相应的同名方法。
37
38 *
39
40 * 如果一定要从java上去理解。你可以理解为用Class.create()创建一个
41
42 * 继承java.lang.Class类的类。
43
44 * 当然java不允许这样做,因为Class类是final的
45
46 *
47
48 */
49
50 var Class = {
51
52 create: function() {
53
54 return function() {
55
56 this.initialize.apply(this, arguments);
57
58 }
59
60 }
61
62 }
63
64
65 /**
66
67 * 创建一个对象,从变量名来思考,本意也许是定义一个抽象类,以后创建
68
69 * 新对象都 extend 它。
70
71 * 但从其后代码的应用来看, Abstract 更多是为了保持命名空间清晰的考虑。
72
73 * 也就是说,我们可以给 Abstract 这个对象实例添加新的对象定义。
74
75 *
76
77 * 从java去理解,就是动态给一个对象创建内部类。
78
79 */
80
81 var Abstract = new Object();
82
83
84 /**
85
86 * 获取参数对象的所有属性和方法,有点象多重继承。但是这种继承是动态获得的。
87
88 * 如:
89
90 * var a = new ObjectA(), b = new ObjectB();
91
92 * var c = a.extend(b);
93
94 * 此时 c 对象同时拥有 a 和 b 对象的属性和方法。但是与多重继承不同的是,
95
96 * c instanceof ObjectB 将返回false。
97
98 */
99
100 Object.prototype.extend = function(object) {
101
102 for (property in object) {
103
104 this[property] = object[property];
105
106 }
107
108 return this;
109
110 }
111
112
113 /**
114
115 * 这个方法很有趣,它封装一个javascript函数对象,返回一个新函数对象,新函
116
117 * 数对象的主体和原对象相同,但是bind()方法参数将被用作当前对象的对象。
118
119 * 也就是说新函数中的 this 引用被改变为参数提供的对象。
120
121 * 比如:
122
123 * <input type="text" id="aaa" value="aaa">
124
125 * <input type="text" id="bbb" value="bbb">
126
127 * .................
128
129 * <script>
130
131 * var aaa = document.getElementById("aaa");
132
133 * var bbb = document.getElementById("bbb");
134
135 * aaa.showValue = function() {alert(this.value);}
136
137 * aaa.showValue2 = aaa.showValue.bind(bbb);
138
139 * </script>
140
141 * 那么,调用aaa.showValue 将返回"aaa",
142
143 * 但调用aaa.showValue2 将返回"bbb"。
144
145 *
146
147 * apply 是ie5.5后才出现的新方法(Netscape好像很早就支持了)。
148
149 * 该方法更多的资料参考MSDN
150
151 * http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthApply.asp
152
153 * 还有一个 call 方法,应用起来和 apply 类似。可以一起研究下。
154
155 */
156
157 Function.prototype.bind = function(object) {
158
159 var method = this;
160
161 return function() {
162
163 method.apply(object, arguments);
164
165 }
166
167 }
168
169
170 /**
171
172 * 和bind一样,不过这个方法一般用做html控件对象的事件处理。所以要传递event对象
173
174 * 注意这时候,用到了 Function.call。它与 Function.apply 的不同好像仅仅是对参
175
176 * 数形式的定义。如同 java 两个过载的方法。
177
178 */
179
180 Function.prototype.bindAsEventListener = function(object) {
181
182 var method = this;
183
184 return function(event) {
185
186 method.call(object, event || window.event);
187
188 }
189
190 }
191
192
193 /**
194
195 * 将整数形式RGB颜色值转换为HEX形式
196
197 */
198
199 Number.prototype.toColorPart = function() {
200
201 var digits = this.toString(16);
202
203 if (this < 16) return '0' + digits;
204
205 return digits;
206
207 }
208
209
210 /**
211
212 * 典型 Ruby 风格的函数,将参数中的方法逐个调用,返回第一个成功执行的方法的返回值
213
214 */
215
216 var Try = {
217
218 these: function() {
219
220 var returnValue;
221
222
223 for (var i = 0; i < arguments.length; i++) {
224
225 var lambda = arguments[i];
226
227 try {
228
229 returnValue = lambda();
230
231 break;
232
233 } catch (e) {}
234
235 }
236
237
238 return returnValue;
239
240 }
241
242 }
243
244
245 /*--------------------------------------------------------------------------*/
246
247
248 /**
249
250 * 一个设计精巧的定时执行器
251
252 * 首先由 Class.create() 创建一个 PeriodicalExecuter 类型,
253
254 * 然后用对象直接量的语法形式设置原型。
255
256 *
257
258 * 需要特别说明的是 rgisterCallback 方法,它调用上面定义的函数原型方法bind,
259
260 * 并传递自己为参数。
261
262 * 之所以这样做,是因为 setTimeout 默认总以 window 对象为当前对象,也就是说,
263
264 * 如果 registerCallback 方法定义如下的话:
265
266 * registerCallback: function() {
267
268 * setTimeout(this.onTimerEvent, this.frequency * 1000);
269
270 * }
271
272 * 那么,this.onTimeoutEvent 方法执行失败,因为它无法
273
274 * 访问 this.currentlyExecuting 属性。
275
276 * 而使用了bind以后,该方法才能正确的找到this,
277
278 * 也就是PeriodicalExecuter的当前实例。
279
280 */
281
282 var PeriodicalExecuter = Class.create();
283
284 PeriodicalExecuter.prototype = {
285
286 initialize: function(callback, frequency) {
287
288 this.callback = callback;
289
290 this.frequency = frequency;
291
292 this.currentlyExecuting = false;
293
294
295 this.registerCallback();
296
297 },
298
299
300 registerCallback: function() {
301
302 setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000);
303
304 },
305
306
307 onTimerEvent: function() {
308
309 if (!this.currentlyExecuting) {
310
311 try {
312
313 this.currentlyExecuting = true;
314
315 this.callback();
316
317 } finally {
318
319 this.currentlyExecuting = false;
320
321 }
322
323 }
324
325
326 this.registerCallback();
327
328 }
329
330 }
331
332
333 /*--------------------------------------------------------------------------*/
334
335
336 /**
337
338 * 这个函数就 Ruby 了。我觉得它的作用主要有两个
339
340 * 1. 大概是 document.getElementById(id) 的最简化调用。
341
342 * 比如:$("aaa") 将返回上 aaa 对象
343
344 * 2. 得到对象数组
345
346 * 比如: $("aaa","bbb") 返回一个包括id为
347
348 * "aaa"和"bbb"两个input控件对象的数组。
349
350 */
351
352 function $() {
353
354 var elements = new Array();
355
356
357 for (var i = 0; i < arguments.length; i++) {
358
359 var element = arguments[i];
360
361 if (typeof element == 'string')
362
363 element = document.getElementById(element);
364
365
366 if (arguments.length == 1)
367
368 return element;
369
370
371 elements.push(element);
372
373 }
374
375
376 return elements;
377
378 }
ajax.js 代码:
复制代码 代码如下:
/**
2
3 * 定义 Ajax 对象, 静态方法 getTransport 方法返回一个 XMLHttp 对象
4
5 */
6
7 var Ajax = {
8
9 getTransport: function() {
10
11 return Try.these(
12
13 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
14
15 function() {return new ActiveXObject('Microsoft.XMLHTTP')},
16
17 function() {return new XMLHttpRequest()}
18
19 ) || false;
20
21 },
22
23
24 emptyFunction: function() {}
25
26 }
27
28
29 /**
30
31 * 我以为此时的Ajax对象起到命名空间的作用。
32
33 * Ajax.Base 声明为一个基础对象类型
34
35 * 注意 Ajax.Base 并没有使用 Class.create() 的方式来创建,我想是因为作者并不
36
37 * 希望 Ajax.Base 被库使用者实例化。
38
39 * 作者在其他对象类型的声明中,将会继承于它。
40
41 * 就好像 java 中的私有抽象类
42
43 */
44
45 Ajax.Base = function() {};
46
47 Ajax.Base.prototype = {
48
49 /**
50
51 * extend (见prototype.js中的定义) 的用法真是让人耳目一新
52
53 * options 首先设置默认属性,然后再 extend 参数对象,那么参数对象中也有同名
54
55 * 的属性,那么就覆盖默认属性值。
56
57 * 想想如果我写这样的实现,应该类似如下:
58
59 setOptions: function(options) {
60
61 this.options.methed = options.methed? options.methed : 'post';
62
63 ..........
64
65 }
66
67 我想很多时候,java 限制了 js 的创意。
68
69 */
70
71 setOptions: function(options) {
72
73 this.options = {
74
75 method: 'post',
76
77 asynchronous: true,
78
79 parameters: ''
80
81 }.extend(options || {});
82
83 }
84
85 }
86
87
88
89 /**
90
91 * Ajax.Request 封装 XmlHttp
92
93 */
94
95 Ajax.Request = Class.create();
96
97
98 /**
99
100 * 定义四种事件(状态), 参考
101
102 * http://msdn.microsoft.com/workshop/
103
104 * author/dhtml/reference/properties/readystate_1.asp
105
106 */
107
108 Ajax.Request.Events =
109
110 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
111
112
113 /**
114
115 *
116
117 */
118
119 Ajax.Request.prototype = (new Ajax.Base()).extend({
120
121 initialize: function(url, options) {
122
123 this.transport = Ajax.getTransport();
124
125 this.setOptions(options);
126
127
128 try {
129
130 if (this.options.method == 'get')
131
132 url += '?' + this.options.parameters + '&_=';
133
134
135 /**
136
137 * 此处好像强制使用了异步方式,而不是依照 this.options.asynchronous 的值
138
139 */
140
141 this.transport.open(this.options.method, url, true);
142
143
144 /**
145
146 * 这里提供了 XmlHttp 传输过程中每个步骤的回调函数
147
148 */
149
150 if (this.options.asynchronous) {
151
152 this.transport.onreadystatechange = this.onStateChange.bind(this);
153
154 setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
155
156 }
157
158
159 this.transport.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
160
161 this.transport.setRequestHeader('X-Prototype-Version', Prototype.Version);
162
163
164 if (this.options.method == 'post') {
165
166 this.transport.setRequestHeader('Connection', 'close');
167
168 this.transport.setRequestHeader('Content-type',
169
170 'application/x-www-form-urlencoded');
171
172 }
173
174
175 this.transport.send(this.options.method == 'post' ?
176
177 this.options.parameters + '&_=' : null);
178
179
180 } catch (e) {
181
182 }
183
184 },
185
186
187 onStateChange: function() {
188
189 var readyState = this.transport.readyState;
190
191 /**
192
193 * 如果不是 Loading 状态,就调用回调函数
194
195 */
196
197 if (readyState != 1)
198
199 this.respondToReadyState(this.transport.readyState);
200
201 },
202
203
204 /**
205
206 * 回调函数定义在 this.options 属性中,比如:
207
208 var option = {
209
210 onLoaded : function(req) {...};
211
212 ......
213
214 }
215
216 new Ajax.Request(url, option);
217
218 */
219
220 respondToReadyState: function(readyState) {
221
222 var event = Ajax.Request.Events[readyState];
223
224 (this.options['on' + event] || Ajax.emptyFunction)(this.transport);
225
226 }
227
228 });
229
230
231 /**
232
233 * Ajax.Updater 用于绑定一个html元素与 XmlHttp调用的返回值。
234
235 * 类似与 buffalo 的 bind。
236
237 * 如果 options 中有 insertion(from dom.js) 对象的话,
238
239 * insertion 能提供更多的插入控制。
240
241 */
242
243 Ajax.Updater = Class.create();
244
245 Ajax.Updater.prototype = (new Ajax.Base()).extend({
246
247 initialize: function(container, url, options) {
248
249 this.container = $(container);
250
251 this.setOptions(options);
252
253
254 if (this.options.asynchronous) {
255
256 this.onComplete = this.options.onComplete;
257
258 this.options.onComplete = this.updateContent.bind(this);
259
260 }
261
262
263 this.request = new Ajax.Request(url, this.options);
264
265
266 if (!this.options.asynchronous)
267
268 this.updateContent();
269
270 },
271
272
273 updateContent: function() {
274
275 if (this.options.insertion) {
276
277 new this.options.insertion(this.container,
278
279 this.request.transport.responseText);
280
281 } else {
282
283 this.container.innerHTML = this.request.transport.responseText;
284
285 }
286
287
288 if (this.onComplete) {
289
290 setTimeout((function() {this.onComplete(this.request)}).bind(this), 10);
291
292 }
293
294 }
295
296 });
form.js 代码:
复制代码 代码如下:
/**
2
3 * 针对 页面元素对象 的工具类,提供一些简单静态方法
4
5 */
6
7 var Field = {
8
9 /**
10
11 * 清除参数引用对象的值
12
13 */
14
15 clear: function() {
16
17 for (var i = 0; i < arguments.length; i++)
18
19 $(arguments[i]).value = '';
20
21 },
22
23
24 /**
25
26 * 使参数引用对象获取焦点
27
28 */
29
30 focus: function(element) {
31
32 $(element).focus();
33
34 },
35
36
37 /**
38
39 * 判断参数引用对象值是否为空,如为空,返回false, 反之true
40
41 */
42
43 present: function() {
44
45 for (var i = 0; i < arguments.length; i++)
46
47 if ($(arguments[i]).value == '') return false;
48
49 return true;
50
51 },
52
53
54 /**
55
56 * 使选中参数引用对象
57
58 */
59
60 select: function(element) {
61
62 $(element).select();
63
64 },
65
66
67 /**
68
69 * 使参数引用对象处于可编辑状态
70
71 */
72
73 activate: function(element) {
74
75 $(element).focus();
76
77 $(element).select();
78
79 }
80
81 }
82
83
84 /*-----------------------------------------------------------------*/
85
86
87 /**
88
89 * 表单工具类
90
91 */
92
93 var Form = {
94
95 /**
96
97 * 将表单元素序列化后的值组合成 QueryString 的形式
98
99 */
100
101 serialize: function(form) {
102
103 var elements = Form.getElements($(form));
104
105 var queryComponents = new Array();
106
107
108 for (var i = 0; i < elements.length; i++) {
109
110 var queryComponent = Form.Element.serialize(elements[i]);
111
112 if (queryComponent)
113
114 queryComponents.push(queryComponent);
115
116 }
117
118
119 return queryComponents.join('&');
120
121 },
122
123
124 /**
125
126 * 得到表单的所有元素对象
127
128 */
129
130 getElements: function(form) {
131
132 form = $(form);
133
134 var elements = new Array();
135
136
137 for (tagName in Form.Element.Serializers) {
138
139 var tagElements = form.getElementsByTagName(tagName);
140
141 for (var j = 0; j < tagElements.length; j++)
142
143 elements.push(tagElements[j]);
144
145 }
146
147 return elements;
148
149 },
150
151
152 /**
153
154 * 将指定表单的元素置于不可用状态
155
156 */
157
158 disable: function(form) {
159
160 var elements = Form.getElements(form);
161
162 for (var i = 0; i < elements.length; i++) {
163
164 var element = elements[i];
165
166 element.blur();
167
168 element.disable = 'true';
169
170 }
171
172 },
173
174
175 /**
176
177 * 使表单的第一个非 hidden 类型而且处于可用状态的元素获得焦点
178
179 */
180
181 focusFirstElement: function(form) {
182
183 form = $(form);
184
185 var elements = Form.getElements(form);
186
187 for (var i = 0; i < elements.length; i++) {
188
189 var element = elements[i];
190
191 if (element.type != 'hidden' && !element.disabled) {
192
193 Field.activate(element);
194
195 break;
196
197 }
198
199 }
200
201 },
202
203
204 /*
205
206 * 重置表单
207
208 */
209
210 reset: function(form) {
211
212 $(form).reset();
213
214 }
215
216 }
217
218
219 /**
220
221 * 表单元素工具类
222
223 */
224
225 Form.Element = {
226
227 /**
228
229 * 返回表单元素的值先序列化再进行 URL 编码后的值
230
231 */
232
233 serialize: function(element) {
234
235 element = $(element);
236
237 var method = element.tagName.toLowerCase();
238
239 var parameter = Form.Element.Serializers[method](element);
240
241
242 if (parameter)
243
244 return encodeURIComponent(parameter[0]) + '=' +
245
246 encodeURIComponent(parameter[1]);
247
248 },
249
250
251 /**
252
253 * 返回表单元素序列化后的值
254
255 */
256
257 getValue: function(element) {
258
259 element = $(element);
260
261 var method = element.tagName.toLowerCase();
262
263 var parameter = Form.Element.Serializers[method](element);
264
265
266 if (parameter)
267
268 return parameter[1];
269
270 }
271
272 }
273
274
275 /**
276
277 * prototype 的所谓序列化其实就是将表单的名字和值组合成一个数组
278
279 */
280
281 Form.Element.Serializers = {
282
283 input: function(element) {
284
285 switch (element.type.toLowerCase()) {
286
287 case 'hidden':
288
289 case 'password':
290
291 case 'text':
292
293 return Form.Element.Serializers.textarea(element);
294
295 case 'checkbox':
296
297 case 'radio':
298
299 return Form.Element.Serializers.inputSelector(element);
300
301 }
302
303 return false;
304
305 },
306
307
308 inputSelector: function(element) {
309
310 if (element.checked)
311
312 return [element.name, element.value];
313
314 },
315
316
317 textarea: function(element) {
318
319 return [element.name, element.value];
320
321 },
322
323
324 /**
325
326 * 看样子,也不支持多选框(select-multiple)
327
328 */
329
330 select: function(element) {
331
332 var index = element.selectedIndex;
333
334 var value = element.options[index].value || element.options[index].text;
335
336 return [element.name, (index >= 0) ? value : ''];
337
338 }
339
340 }
341
342
343 /*--------------------------------------------------------------------------*/
344
345
346 /**
347
348 * Form.Element.getValue 也许会经常用到,所以做了一个快捷引用
349
350 */
351
352 var $F = Form.Element.getValue;
353
354
355 /*--------------------------------------------------------------------------*/
356
357
358 /**
359
360 * Abstract.TimedObserver 也没有用 Class.create() 来创建,
361
362 * 和Ajax.Base 意图应该一样
363
364 * Abstract.TimedObserver 顾名思义,
365
366 * 是套用Observer设计模式来跟踪指定表单元素,
367
368 * 当表单元素的值发生变化的时候,就执行回调函数
369
370 *
371
372 * 我想 Observer 与注册onchange事件相似,
373
374 * 不同点在于 onchange 事件是在元素失去焦点
375
376 * 的时候才激发。
377
378 * 同样的与 onpropertychange 事件也相似,
379
380 * 不过它只关注表单元素的值的变化,而且提供timeout的控制。
381
382 *
383
384 * 除此之外,Observer 的好处大概就在与更面向对象,另外可以动态的更换回调函数,
385
386 * 这就比注册事件要灵活一些。
387
388 * Observer 应该可以胜任动态数据校验,或者多个关联下拉选项列表的连动等等
389
390 *
391
392 */
393
394 Abstract.TimedObserver = function() {}
395
396
397 /**
398
399 * 这个设计和 PeriodicalExecuter 一样,bind 方法是实现的核心
400
401 */
402
403 Abstract.TimedObserver.prototype = {
404
405 initialize: function(element, frequency, callback) {
406
407 this.frequency = frequency;
408
409 this.element = $(element);
410
411 this.callback = callback;
412
413
414 this.lastValue = this.getValue();
415
416 this.registerCallback();
417
418 },
419
420
421 registerCallback: function() {
422
423 setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000);
424
425 },
426
427
428 onTimerEvent: function() {
429
430 var value = this.getValue();
431
432 if (this.lastValue != value) {
433
434 this.callback(this.element, value);
435
436 this.lastValue = value;
437
438 }
439
440
441 this.registerCallback();
442
443 }
444
445 }
446
447
448 /**
449
450 * Form.Element.Observer 和 Form.Observer 其实是一样的
451
452 * 注意 Form.Observer 并不是用来跟踪整个表单的,我想大概只是
453
454 * 为了减少书写(这是Ruby的一个设计原则)
455
456 */
457
458 Form.Element.Observer = Class.create();
459
460 Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({
461
462 getValue: function() {
463
464 return Form.Element.getValue(this.element);
465
466 }
467
468 });
469
470
471 Form.Observer = Class.create();
472
473 Form.Observer.prototype = (new Abstract.TimedObserver()).extend({
474
475 getValue: function() {
476
477 return Form.serialize(this.element);
478
479 }
480
481 });
dom.js 代码:
复制代码 代码如下:
/**
2
3 * 根据 class attribute 的名字得到对象数组,支持 multiple class
4
5 *
6
7 */
8
9 document.getElementsByClassName = function(className) {
10
11 var children = document.getElementsByTagName('*') || document.all;
12
13 var elements = new Array();
14
15
16 for (var i = 0; i < children.length; i++) {
17
18 var child = children[i];
19
20 var classNames = child.className.split(' ');
21
22 for (var j = 0; j < classNames.length; j++) {
23
24 if (classNames[j] == className) {
25
26 elements.push(child);
27
28 break;
29
30 }
31
32 }
33
34 }
35
36
37 return elements;
38
39 }
40
41
42 /*--------------------------------------------------------------------------*/
43
44
45 /**
46
47 * Element 就象一个 java 的工具类,主要用来 隐藏/显示/销除 对象,
48
49 * 以及获取对象的简单属性。
50
51 *
52
53 */
54
55 var Element = {
56
57 toggle: function() {
58
59 for (var i = 0; i < arguments.length; i++) {
60
61 var element = $(arguments[i]);
62
63 element.style.display =
64
65 (element.style.display == 'none' ? '' : 'none');
66
67 }
68
69 },
70
71
72 hide: function() {
73
74 for (var i = 0; i < arguments.length; i++) {
75
76 var element = $(arguments[i]);
77
78 element.style.display = 'none';
79
80 }
81
82 },
83
84
85 show: function() {
86
87 for (var i = 0; i < arguments.length; i++) {
88
89 var element = $(arguments[i]);
90
91 element.style.display = '';
92
93 }
94
95 },
96
97
98 remove: function(element) {
99
100 element = $(element);
101
102 element.parentNode.removeChild(element);
103
104 },
105
106
107 getHeight: function(element) {
108
109 element = $(element);
110
111 return element.offsetHeight;
112
113 }
114
115 }
116
117
118 /**
119
120 * 为 Element.toggle 做了一个符号连接,大概是兼容性的考虑
121
122 */
123
124 var Toggle = new Object();
125
126 Toggle.display = Element.toggle;
127
128
129 /*--------------------------------------------------------------------------*/
130
131
132 /**
133
134 * 动态插入内容的实现,MS的Jscript实现中对象有一个 insertAdjacentHTML 方法
135
136 * http://msdn.microsoft.com/workshop/
137
138 * author/dhtml/reference/methods/insertadjacenthtml.asp
139
140 * 这里算是一个对象形式的封装。
141
142 */
143
144 Abstract.Insertion = function(adjacency) {
145
146 this.adjacency = adjacency;
147
148 }
149
150
151 Abstract.Insertion.prototype = {
152
153 initialize: function(element, content) {
154
155 this.element = $(element);
156
157 this.content = content;
158
159
160 if (this.adjacency && this.element.insertAdjacentHTML) {
161
162 this.element.insertAdjacentHTML(this.adjacency, this.content);
163
164 } else {
165
166 /**
167
168 * gecko 不支持 insertAdjacentHTML 方法,但可以用如下代码代替
169
170 */
171
172 this.range = this.element.ownerDocument.createRange();
173
174 /**
175
176 * 如果定义了 initializeRange 方法,则实行,
177
178 * 这里相当与定义了一个抽象的 initializeRange 方法
179
180 */
181
182 if (this.initializeRange) this.initializeRange();
183
184 this.fragment = this.range.createContextualFragment(this.content);
185
186
187 /**
188
189 * insertContent 也是一个抽象方法,子类必须实现
190
191 */
192
193 this.insertContent();
194
195 }
196
197 }
198
199 }
200
201
202 /**
203
204 * prototype 加深了我的体会,就是写js 如何去遵循
205
206 * Don't Repeat Yourself (DRY) 原则
207
208 * 上文中 Abstract.Insertion 算是一个抽象类,
209
210 * 定义了名为 initializeRange 的一个抽象方法
211
212 * var Insertion = new Object() 建立一个命名空间
213
214 * Insertion.Before|Top|Bottom|After 就象是四个java中
215
216 * 的四个静态内部类,而它们分别继承于
217
218 * Abstract.Insertion,并实现了initializeRange方法。
219
220 */
221
222 var Insertion = new Object();
223
224
225 Insertion.Before = Class.create();
226
227 Insertion.Before.prototype =
228
229 (new Abstract.Insertion('beforeBegin')).extend({
230
231 initializeRange: function() {
232
233 this.range.setStartBefore(this.element);
234
235 },
236
237
238 /**
239
240 * 将内容插入到指定节点的前面, 与指定节点同级
241
242 */
243
244 insertContent: function() {
245
246 this.element.parentNode.insertBefore(this.fragment, this.element);
247
248 }
249
250 });
251
252
253 Insertion.Top = Class.create();
254
255 Insertion.Top.prototype =
256
257 (new Abstract.Insertion('afterBegin')).extend({
258
259 initializeRange: function() {
260
261 this.range.selectNodeContents(this.element);
262
263 this.range.collapse(true);
264
265 },
266
267
268 /**
269
270 * 将内容插入到指定节点的第一个子节点前,于是内容变为该节点的第一个子节点
271
272 */
273
274 insertContent: function() {
275
276 this.element.insertBefore(this.fragment, this.element.firstChild);
277
278 }
279
280 });
281
282
283 Insertion.Bottom = Class.create();
284
285 Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({
286
287 initializeRange: function() {
288
289 this.range.selectNodeContents(this.element);
290
291 this.range.collapse(this.element);
292
293 },
294
295
296 /**
297
298 * 将内容插入到指定节点的最后,于是内容变为该节点的最后一个子节点
299
300 */
301
302 insertContent: function() {
303
304 this.element.appendChild(this.fragment);
305
306 }
307
308 });
309
310
311
312 Insertion.After = Class.create();
313
314 Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({
315
316 initializeRange: function() {
317
318 this.range.setStartAfter(this.element);
319
320 },
321
322
323 /**
324
325 * 将内容插入到指定节点的后面, 与指定节点同级
326
327 */
328
329 insertContent: function() {
330
331 this.element.parentNode.insertBefore(this.fragment,
332
333 this.element.nextSibling);
334
335 }
336
337 });
其他代码:
prototype 还有两个源码文件 effects.js compat.js 就不贴出来了。两者并不常用,effects.js 看example 做花哨的效果还不错,不过代码中没有太多新鲜的东西。
需要指出的就是
compat.js 中 Funcation.prototype.apply 的实现有两个错误(应该是拼写错误), 我分别贴出来,大家比较一下就清楚了。
复制代码 代码如下:
/* 这是包含错误的原版本
2
3 if (!Function.prototype.apply) {
4
5 // Based on code from http://www.youngpup.net/
6
7 Function.prototype.apply = function(object, parameters) {
8
9 var parameterStrings = new Array();
10
11 if (!object) object = window;
12
13 if (!parameters) parameters = new Array();
14
15
16 for (var i = 0; i < parameters.length; i++)
17
18 parameterStrings[i] = 'x[' + i + ']'; //Error 1
19
20
21 object.__apply__ = this;
22
23 var result = eval('obj.__apply__(' + //Error 2
24
25 parameterStrings[i].join(', ') + ')');
26
27 object.__apply__ = null;
28
29
30 return result;
31
32 }
33
34 }
35
36 */
37
38
39 if (!Function.prototype.apply) {
40
41 Function.prototype.apply = function(object, parameters) {
42
43 var parameterStrings = new Array();
44
45 if (!object) object = window;
46
47 if (!parameters) parameters = new Array();
48
49
50 for (var i = 0; i < parameters.length; i++)
51
52 parameterStrings[i] = 'parameters[' + i + ']';
53
54
55 object.__apply__ = this;
56
57 var result = eval('object.__apply__(' + parameterStrings.join(', ') + ')');
58
59 object.__apply__ = null;
60
61
62 return result;
63
64 }
65
66 }
接下来是我模仿着编写的一个 Effect 的一个子类,用来实现闪烁的效果。
复制代码 代码如下:
Effect.Blink = Class.create();
2
3 Effect.Blink.prototype = {
4
5 initialize: function(element, frequency) {
6
7 this.element = $(element);
8
9 this.frequency = frequency?frequency:1000;
10
11 this.element.effect_blink = this;
12
13 this.blink();
14
15 },
16
17
18 blink: function() {
19
20 if (this.timer) clearTimeout(this.timer);
21
22 try {
23
24 this.element.style.visibility =
25
26 this.element.style.visibility == 'hidden'?'visible':'hidden';
27
28 } catch (e) {}
29
30 this.timer = setTimeout(this.blink.bind(this), this.frequency);
31
32 }
33
34 };
使用也很简单, 调用 new Effect.Blink(elementId) 就好了。
通过对 prototype 源码的研究,我想我对javascript又有了一点新的体会,而最大的体会就是 《Ajax : A New Approach to Web Applications》文章最后作者对设计人员的建议: to forget what we think we know about the limitations of the Web, and begin to imagine a wider, richer range of possibilities.