开篇,原本想大量引用网上对Ext-base.js文件的分析。但是经过大量的阅读国外的书籍发现,原来那人根本不懂什么是js面向对象程序设计。
参考书:
Apress出版的Pro.JavaScript.Design.Patterns
Professional.JavaScript.for.Web.Developers.2nd.Edition.2009
本文想通过一步一步的对javascript语法的进化来最后达到最ext-base.js的解析过程。
1. Javascript中的类
利用对javascript的常识,我认为js的设计应该如下:
/* Anim class. */ var Anim = function() { ... }; Anim.prototype.start = function() { ... }; Anim.prototype.stop = function() { ... }; /* Usage. */ var myAnim = new Anim(); myAnim.start(); ... myAnim.stop();
定义了一个新的类Anim,委派了两个方法给prototype属性。所谓的prototype,我的理解是指针。实际上它和c++,c中指针的运行方法有微小的差别,但大致一样。详情请参考上面的两本书。
如果你喜欢把所有的属性和方法都装在类中。如下:
/* Anim class, with a slightly different syntax for declaring methods. */ var Anim = function() { ... }; Anim.prototype = { start: function() { ... }, stop: function() { ... } };
通过以上的操作,产生一个新的概念:Encapsulation。我的理解:装箱。就是把所有的属性和方法放到类中。
值得谈到的是Javascript中的函数。在javascript中函数就是类。
函数的大部分概念和C++没有什么区别。值得注意的是匿名函数.
<html> <head> <title>String Example</title> <script type="text/javascript"> //在javascript中,函数都最好的对象 //函数的大部分概念和C++没有什么区别。值得注意的是匿名函数 //示例如下: /* An anonymous function, executed immediately. */ (function(){ var foo = 10; var bar = 2; alert(foo * bar); })(); //这个函数的定义和执行并没有分配给任何变量。最后的()执行了这个函数。 //他们是空的,但是并不是这种情况 /* An anonymous function with arguments. */ (function(foo, bar){ alert(foo * bar); })(10, 2); //下例同第一个例子等价。不过将值给了一个内在的变量 /* An anonymous function that returns a value. */ var baz = (function(foo, bar){ return foo * bar; })(10, 2); alert(baz); //匿名函数最有趣的应用时建立闭包。 //闭包是一个受保护的变量空间,这就是说闭包函数可以在他们所定义的任意地方执行。不受函数体影响 /* An anonymous function used as a closure. */ var baz; (function(){ var foo = 10; var bar = 2; baz = function(){ return foo * bar; }; })(); baz(); // baz can access foo and bar, even though it is executed outside of the class </script> </head> <body> <!-- Nothing in the body --> //在javascript中,函数都最好的对象 //函数的大部分概念和C++没有什么区别。值得注意的是匿名函数 //示例如下: /* An anonymous function, executed immediately. */ (function(){ var foo = 10; var bar = 2; alert(foo * bar); })(); //这个函数的定义和执行并没有分配给任何变量。最后的()执行了这个函数。 //他们是空的,但是并不是这种情况 /* An anonymous function with arguments. */ (function(foo, bar){ alert(foo * bar); })(10, 2); //下例同第一个例子等价。不过将值给了一个内在的变量 /* An anonymous function that returns a value. */ var baz = (function(foo, bar){ return foo * bar; })(10, 2); alert(baz); //匿名函数最有趣的应用时建立闭包。 //闭包是一个受保护的变量空间,这就是说闭包函数可以在他们所定义的任意地方执行。不受函数体影响 /* An anonymous function used as a closure. */ var baz; (function(){ var foo = 10; var bar = 2; baz = function(){ return foo * bar; }; })(); baz(); // baz can access foo and bar, even though it is executed outside of the class </body> </html>
如果你使用装箱的写法,可以试试下面一种:
/* Add a method to the Function object that can be used to declare methods. */ Function.prototype.method = function(name, fn) { this.prototype[name] = fn; }; /* Anim class, with methods created using a convenience method. */ var Anim = function() { ... }; Anim.method('start', function() { ... }); Anim.method('stop', function() { ... }); 并修改method,让指针只记住类中各种方法的地址。即只给出链接。 /* This version allows the calls to be chained. */ Function.prototype.method = function(name, fn) { this.prototype[name] = fn; return this; }; /* Anim class, with methods created using a convenience method and chaining. */ var Anim = function() { ... }; Anim.method('start', function() { ... }). method('stop', function() { ... });
在javascript中,所有的东西都是对象,所有的对象都是可变的。这就意味着你可以在类定义好,并实例化以后改变它。
<html> <head> <title>String Example</title> <script type="text/javascript"> //在javascript中,所有的东西都是对象 //所有的对象都是可变的。 //这就意味着你可以在类定义好,并实例化以后改变它。 /* Class Person. */ function Person(name, age){ this.name = name; this.age = age; } Person.prototype = { getName: function(){ return this.name; }, getAge: function(){ return this.age; } } /* Instantiate the class. */ var alice = new Person('Alice', 93); var bill = new Person('Bill', 30); document.write("/* Instantiate the class. */<br/>"); document.write("name: "+alice.name+";her age: "+alice.age+"<br/>"); document.write("name: "+bill.name+";his age: "+bill.age+"<br/>"); /* Modify the class. */ Person.prototype.getGreeting = function(){ return 'Hi ' + this.getName() + '!'; }; document.write("/* Modify the class. */<br/>"); document.write("alice.getGreeting:"+alice.getGreeting()+"<br/>"); /* Modify a specific instance. */ alice.displayGreeting = function(){ alert(this.getGreeting()); } document.write("/* Modify a specific instance. */<br/>alert(getGreeting)"); alice.displayGreeting(); </script> </head> <body> <!-- Nothing in the body --> </body> </html>
这种对象的可变性叫:自我检查introspection.你能实时的检查任何的属性和方法。这正因为对象可以实时改变,javascript才很少进行类型检查。
在javascript中,还有个根深蒂固的概念。闭包Closures。闭包就是在类中定义的函数。由于对象具有可变性,因此我们可以在类体意外去调用和改变类中定义的函数。
<html> <head> <title>String Example</title> <script type="text/javascript"> function foo(){ var a = 10; function bar(){ a *= 2; } bar(); return a; } document.writeln(foo()); </script> </head> <body> <!-- Nothing in the body --> </body> </html>
在javascript中,能够想到的创建对象的样式有三种。完全显示的对象(最简单但只提供公有成员)。使用括号标识方法和属性来区别私有成员(只是伪代码),详情请参考上面的书籍。最后是通过闭包的方法建立真正的私有成员。通过闭包实现私有成员。
有了闭包的特性,是可以实现真实的私有成员和公有成员的。在javascript中没有私有,公有的特性的。但是在实际的程序当中,我们往往要用到这些特性。这也是面向对象的重要部分之一。
<html> <head> <title>String Example</title> <script type="text/javascript"> var Book = function(newIsbn, newTitle, newAuthor){ // implements Publication // Private attributes. var isbn, title, author; // Privileged methods. this.getIsbn = function(){ return isbn; }; this.setIsbn = function(newIsbn){ isbn = newIsbn; }; this.getTitle = function(){ return title; }; this.setTitle = function(newTitle){ title = newTitle || 'No title specified'; }; this.getAuthor = function(){ return author; }; this.setAuthor = function(newAuthor){ author = newAuthor || 'No author specified'; }; // Constructor code. this.setIsbn(newIsbn); this.setTitle(newTitle); this.setAuthor(newAuthor); }; // Public, non-privileged methods. Book.prototype = { display: function(){ return "ISBN:"+this.getIsbn() + "<br/>" +"Title:" + this.getTitle() + "<br/>" +"Author:" + this.getAuthor(); } }; var book = new Book('978-0261103283','pro javascript design pattern','Apress'); document.write(book.display()); //使用了更多的内存 //很难被继承 </script> </head> <body> <!-- Nothing in the body --> </body> </html>
但是这么写的类,虽然是完成了类成员的私有,公有特性。但是这样的方法很难被继承,同时也造成大量的内存冗余。因为javascript中没有资源回收的机制。在类使用完毕之后,存储空间不会随之消失。我们还需要静态方法和真正的动态空间存储的方法。
<html> <head> <title>String Example</title> <script type="text/javascript"> var Book = (function(){ // Private static attributes. var numOfBooks = 0; // Return the constructor. return function(newIsbn, newTitle, newAuthor){ // implements Publication // Private attributes. var isbn, title, author; // Private static method. function checkIsbn(isbn){ if (isbn) { return true; } else { return false; } } // Privileged methods. this.getIsbn = function(){ return isbn; }; this.setIsbn = function(newIsbn){ if (!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.'); isbn = newIsbn; }; this.getTitle = function(){ return title; }; this.setTitle = function(newTitle){ title = newTitle || 'No title specified'; }; this.getAuthor = function(){ return author; }; this.setAuthor = function(newAuthor){ author = newAuthor || 'No author specified'; }; // Constructor code. numOfBooks++; // Keep track of how many Books have been instantiated // with the private static attribute. if (numOfBooks > 50) throw new Error('Book: Only 50 instances of Book can be ' + 'created.'); this.setIsbn(newIsbn); this.setTitle(newTitle); this.setAuthor(newAuthor); } })(); // Public static method. Book.convertToTitle = function(inputString){ return inputString + "[us]"; }; // Public, non-privileged methods. Book.prototype = { display: function(){ return "ISBN:" + this.getIsbn() + "<br/>" + "Title:" + this.getTitle() + "<br/>" + "Author:" + this.getAuthor(); } }; var book = new Book('978-0261103283', Book.convertToTitle('pro javascript design pattern'), 'Apress'); document.write(book.display()); </script> </head> <body> <!-- Nothing in the body --> </body> </html>
静态方法的实现,实际上是利用匿名函数,在内存的堆中申请一片空间。类中只记录这片存储区域的地址。类调用完成,匿名函数随之消失。同时类中申请的静态变量保留了下来。
有了上面面向对象的公有私有成员和方法,对象可变性的概念。来看看js中继承的实现
Javascript的语法提供使用基于对象的继承。我们可以用它来模拟基于类的继承。
<html> <head> <title>String Example</title> <script type="text/javascript"> /* Class Person. */ function Person(name){ this.name = name; } Person.prototype.getName = function(){ return this.name; } /* Class Author. */ //建立一个类,继承于其他类 function Author(name, books){ //首先,建立构造器函数,call父类的构造函数,传递参数 Person.call(this, name); // Call the superclass's constructor in the scope of this. this.books = books; // Add an attribute to Author. } //当你调用new操作符,很多事已经为你准备好。首先一个空的object对象已经建立。 //构造器函数调用空对象的scope chain Author.prototype = new Person(); // Set up the prototype chain. Author.prototype.constructor = Author; // Set the constructor attribute to Author. Author.prototype.getBooks = function(){ // Add a method to Author. return this.books; }; //实例测试 var author = []; author[0] = new Author('Dustin Diaz', ['JavaScript Design Patterns']); author[1] = new Author('Ross Harmes', ['JavaScript Design Patterns']); document.write(author[0].getName()+' ' +author[0].getBooks()+"<br/>"); document.write(author[1].getName()+' ' +author[1].getBooks()); </script> </head> <body> <!-- Nothing in the body --> </body> </html>
尽管代码相当简单,利用prototype这个指针进行地址的链接。但是它却是个很复杂的话题。javascript没有继承关键字。与之替代的,每个object对象都有prototype属性。这个属性指向另一个对象或者null。当对象成员被读取,javascript通过prototype读取该对象。如果它在现在的对象中不存在,javascript会读取每个对象的prototype。直到读到该对象或者null,这和其他的面向对象语言完全不一样。这种方法使得多个子类被实例化的同时,父类也同样被实例化很多次。浪费了内存。在子类实例化的情况下,实例化改变父类,子类会受到很大影响。
基于以上原因,必须要改变继承的方式。
<html> <head> <title>String Example</title> <script type="text/javascript"> /* Extend function, improved. */ function extend(subClass, superClass){ var F = function(){ }; F.prototype = superClass.prototype; subClass.prototype = new F(); subClass.prototype.constructor = subClass; subClass.superclass = superClass.prototype; if (superClass.prototype.constructor == Object.prototype.constructor) { superClass.prototype.constructor = superClass; } } /* Class Person. */ function Person(name){ this.name = name; } Person.prototype.getName = function(){ return this.name; } /* Class Author. */ function Author(name, books){ Author.superclass.constructor.call(this, name); this.books = books; } extend(Author, Person); Author.prototype.getBooks = function(){ return this.books; }; //改变Author类 Author.prototype.getName = function(){ var name = Author.superclass.getName.call(this); return name + ', Author of ' + this.getBooks().join(', '); }; //实例测试 var author = []; author[0] = new Author('Dustin Diaz', ['JavaScript Design Patterns']); author[1] = new Author('Ross Harmes', ['JavaScript Design Patterns']); document.write(author[0].getName() + "<br/>"); document.write(author[1].getName()); </script> </head> <body> <!-- Nothing in the body --> </body> </html>
继承方式已经实现。但是离ext-base.js的目标还差得远。在此,先提出一个概念:Prototypal Inheritance,我的理解为:指针型继承。指针型继承非常不同。我们发现最好的去思考它的方法是忘掉一切关于类和实例化的方法。只考虑对象本身,典型创建对象有两种方法
a.定义对象结构,使用类声明
b.实例化类,并创建对象(网上把它叫做对象直接量)
通过实例化类来创建的对象是拷贝所有实例化的属性,连接所有实例化方法的地址在指针型继承中,这样做的优点是你能很简单的建一个类。这个类可以被其他类重用。
<html> <head> <title>String Example</title> <script type="text/javascript"> //函数与函数间指针的传递 /* Clone function. */ function clone(object){ function F(){ } F.prototype = object; return new F; } /* * 指针型继承Prototypal Inheritance * 指针型继承非常不同。我们发现最好的去思考它的方法是忘掉一切关于类和实例化的方法。只考虑对象本身 * 典型创建对象有两种方法 * a.定义对象结构,使用类声明 * b.实例化类,并创建对象 * 通过实例化类来创建的对象是拷贝所有实例化的属性,连接所有实例化方法的地址 * 在指针型继承中,你能很简单的建一个类。这个类可以被其他类重用 */ /* Person Prototype Object. */ var Person = { name: 'default name', getName: function(){ return this.name; } }; //简单对象的继承和测试 var reader = clone(Person); document.write("<h5 style='color:red'>simple object inherited and testing</h5>"); document.write(reader.getName() + "<br/>"); // This will output 'default name'. reader.name = 'John Smith'; document.write(reader.getName() + "<br/>"); // This will now output 'John Smith'. /* Author Prototype Object. */ var Author = clone(Person); Author.books = []; // Default value. Author.getBooks = function(){ return this.books; } var author = []; author[0] = clone(Author); author[0].name = 'Dustin Diaz'; author[0].books = ['JavaScript Design Patterns']; author[1] = clone(Author); author[1].name = 'Ross Harmes'; author[1].books = ['JavaScript Design Patterns']; document.write("<h5 style='color:red'>Author object inherited and testing</h5>"); document.write(author[0].getName() + ' ' + author[0].getBooks() + "<br/>"); document.write(author[1].getName() + ' ' + author[1].getBooks() + "<br/>"); /* * 异步读写父类成员 * 为了有效的使用指针型继承,我们必须忘掉所有传统的继承方式。这就是所要忘记的方式之一。 * 在传统的继承里,每个Author类都拷贝了books数组。你可以通过author[1].books.push('New Book Title')添加到数组 * 这种方式不能用于指针型继承。因为prototype链起了作用 * clone函数并不是object对象的prototype完全独立的拷贝。 * 当你读取author[1].name,你只是得到从prototype那里保存的连接。并没有得到实例的值 * 当你写一个author[1].name,实际是为author[1]对象添加了一个新的属性 */ var authorClone = clone(Author); document.write("<h5 style='color:red'>Symmetrical Reading and Writing of Inherited Members</h5>"); document.write("Name="+authorClone.getName() + "<br/>"); // Linked to the primative Person.name, which is the string 'default name'. document.write("Books="+authorClone.getBooks() + "<br/>"); authorClone.name = 'new name'; // A new primative is created and added to the // authorClone object itself. document.write("Name="+authorClone.getName() + "<br/>"); // Now linked to the primative authorClone.name, which // is the string 'new name'. authorClone.books.push('new book'); // authorClone.books is linked to the array document.write("Books="+authorClone.getBooks() + "<br/>"); // Author.books. We just modified the // prototype object's default value, and all // other objects that link to it will now // have a new default value there. authorClone.books = []; // A new array is created and added to the authorClone // object itself. authorClone.books.push('a new book to array'); // We are now modifying that new array. document.write("<h5 style='color:red'>Asymmetrical Reading and Writing of Inherited Members</h5>"); document.write(authorClone.getName() + ' ' + authorClone.getBooks() + "<br/>"); </script> </head> <body> <!-- Nothing in the body --> </body> </html>
通过Prototypal Inheritance的指针继承,我们体会到一种设计模式:骨架模式。骨架模式就是基类设置好所有的属性和方法,并赋默认值。子类需要自己的方法可以任意的添加。这里引出一个概念:异步读写父类成员。为了有效的使用指针型继承,我们必须忘掉所有传统的继承方式。这就是所要忘记的方式之一。在传统的继承里,每个Author类都拷贝了books数组。你可以通过author[1].books.push('New Book Title')添加到数组。这种方式不能用于指针型继承。因为prototype链起了作用。 clone函数并不是object对象的prototype完全独立的拷贝。当你读取author[1].name,你只是得到从prototype那里保存的连接。并没有得到实例的值。当你写一个author[1].name,实际是为author[1]对象添加了一个新的属性。通俗的讲,父类给出了骨架,子类去加肉。
但是有时候,指针对象会有子对象包含其中。如果你想重写子对象某一个单一的方法,你必须重建整个对象。这可以通过将子对象复制给空对象实现。但是这要求被克隆的对象必须明确子对象的结构和默认值。为了保持各对象之间的低耦合度,任何复杂的子对象都应该用固定的方法创建。工厂模式因此而产生。
<html> <head> <title>String Example</title> <script type="text/javascript"> /* Clone function. */ function clone(object){ function F(){ } F.prototype = object; return new F; } /* * 有时候,指针对象会有子对象包含其中。 * 如果你想重写子对象某一个单一的方法,你必须重建整个对象 * 这可以通过将子对象复制给空对象实现 * 但是这要求被克隆的对象必须明确子对象的结构和默认值 * 为了保持各对象之间的低耦合度,任何复杂的子对象都应该用固定的方法创建 * 工厂模式因此而产生 */ var CompoundObject = {}; CompoundObject.string1 = 'default value'; CompoundObject.createChildObject = function(){ return { bool: true, num: 10 } }; CompoundObject.childObject = CompoundObject.createChildObject(); var compoundObjectClone = clone(CompoundObject); compoundObjectClone.childObject = CompoundObject.createChildObject(); compoundObjectClone.childObject.num = 5; document.write(compoundObjectClone.string1 + "<br/>"); document.write(compoundObjectClone.childObject.num); </script> </head> <body> <!-- Nothing in the body --> </body> </html>
到此,ext-base.js的所有概念都有了。下面开始具体分析extend类
<html> <head> <title>Extend Example</title> <script type="text/javascript"> //Ext类的第一个细胞原型 //骨架模式。采用Prototypal Inheritance的指针继承方式 //ext类是用隐私的实例化类开始进行构造的。 Ext = { version: '3.0' }; //Ext类之对象拷贝函数。实现对象与对象之间的直接复制。 /* * in这个操作符在IE中无法识别非枚举属性 * 以下非枚举属性toString(),hasOwnProperty() ,propertyIsEnumerable() , toLocaleString() , valueOf() * Ext的解决方法是用overrides重新写了apply这个函数,其中所有toString的方法全部由object类的方法覆盖。 * apply的另一变形是applyif。但是如果某属性存在,则不会被覆盖。 */ Ext.apply = function(o, c, defaults){ // no "this" reference for friendly out of scope calls if (defaults) { Ext.apply(o, defaults); } if (o && c && typeof c == 'object') { for (var p in c) { o[p] = c[p]; } } return o; }; Ext.apply(Ext, { //判断类型v是否是object类 isObject: function(v){ return v && typeof v == "object"; }, //Ext类之继承属性 /* * 闭包: * 1、作为一个函数变量的一个引用 - 当函数返回时,其处于激活状态。 * 2、一个闭包就是当一个函数返回时,一个没有释放资源的栈区。 */ //Ext是个匿名函数 extend: function(){ // inline overrides //这句是将所有的supperclass,subclass,overrides方法拷贝到io类备用。 var io = function(o){ for (var m in o) { this[m] = o[m]; } }; var oc = Object.prototype.constructor; return function(sb, sp, overrides){ //如果传递进来的第二个参数是object。 //拷贝子类到overrides类,如果overrides里面有个constructor属性, //就用overrides的constructor当作子类的构造函数。 //否则,创建个新的function,里面包含一句话,就是"sp.apply(this, arguments);", //这个又是闭包的一个应用,在退出extend方法之后并没有释放局部变量sp的内存空间。 //子类拷贝完成 if (Ext.isObject(sp)) { overrides = sp; sp = sb; sb = overrides.constructor != oc ? overrides.constructor : function(){ sp.apply(this, arguments); }; } //var F=function(){},定义一个空函数,里面没有属性。 //F.prototype=sp.prototype //sbp=new F()把F.prototype也就是sp.prototype里面的东西拷贝到sb.prototype //同时,因为F是个没有任何属性的函数,所以不需要再delete任何东西。 //将超类拷贝到了sbp。 //以父类为骨架,并将更多的方法和属性添加到子类中,代码值得借鉴 var F = function(){ }, sbp, spp = sp.prototype; F.prototype = spp; sbp = sb.prototype = new F(); //sb.prototype.constructor是F(),所以sbp.constructor=sb sbp.constructor = sb; sb.superclass = spp; if (spp.constructor == oc) { spp.constructor = sp; } sb.override = function(o){ Ext.override(sb, o); }; sbp.superclass = sbp.supr = (function(){ return spp; }); sbp.override = io; Ext.override(sb, overrides); sb.extend = function(o){ Ext.extend(sb, o); }; return sb; }; }(), //和apply一样的功能,复制对象。如果子类重写了toString,则覆盖父类。 override: function(origclass, overrides){ if (overrides) { var p = origclass.prototype; Ext.apply(p, overrides); if (Ext.isIE && overrides.toString != origclass.toString) { p.toString = overrides.toString; } } } }); //实例测试 //申请一个新的类S function S(){ } //S中两个变量s,s1 S.prototype.s = "s"; S.prototype.s1 = "s1"; //申请一个类C //C中两个变量c,c1 function C(){ this.c = "c"; this.c1 = "c1"; } //C继承S基类,并重写s1 Ext.extend(C, S, { s1: "by c overload" }); //实例化C var c = new C(); alert(c.s); //s alert(c.s1); //by c overload </script> </head> <body> <!-- Nothing in the body --> </body> </html>
1 楼
laitaogood
2009-09-29
太长了,没看完,但是注释写的非常好,哈哈