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