开篇,原本想大量引用网上对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>