prototype

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > prototype >

[转]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. 

阅读全文