Prototype framework
定义类和继承
在prototype的早期版本中, 这个框架来为类创建基本支持: Class.create()
方法。
直到现在,只有以这种方式定义类的功能,称为方法构造函数自动初始化: initialize
。
Prototype 1.6.0 现在支持类模块继承, 相比较之前的版本有了很大提高;你可以比
之前更加轻松的创建各种类。
类的创建基础依然是 Class.create()
方法. 随着新版本的发布,你的基于类的代码将继
续按照以前的方式工作;
唯一的区别是你不需要直接与对象原型打交道 或者 通过使用 Object.extend()
来复制对象属性.
Example
让我们比较新旧方式中定义类和继承的区别:
/** 废弃的语法 **/
var Person = Class.create();
Person.prototype = {
initialize: function(name) {
this.name = name;
},
say: function(message) {
return this.name + ': ' + message;
}
};
var guy = new Person('Miro');
guy.say('hi');
// -> "Miro: hi"
var Pirate = Class.create();
// 继承Person类:
Pirate.prototype = Object.extend(new Person(), {
// 重新定义say方法,之前的父类方法被覆盖
say: function(message) {
return this.name + ': ' + message + ', yarr!';
}
});
var john = new Pirate('Long John');
john.say('ahoy matey');
// -> "Long John: ahoy matey, yarr!"
观察到直接与对象原型交互 和使用Object.extend
笨拙的技术 。
并且同时使用Pirate
重新定义Person的say()方法。
This has changed for the better. Compare the above with:
/** 新的语法 **/
// 属性被直接传递给create方法
var Person = Class.create({
initialize: function(name) {
this.name = name;
},
say: function(message) {
return this.name + ': ' + message;
}
});
// 当实现子类的时候,指定你需要继承的父类
var Pirate = Class.create(Person, {
//重新定义speak方法,这感觉跟java的super调用父类构造函数一样~ ~
say: function($super, message) {
return $super(message) + ', yarr!';
}
});
var john = new Pirate('Long John');
john.say('ahoy matey');
// -> "Long John: ahoy matey, yarr!"
这里有个明显的有点就是可以调用被继承的父类方法。
如何糅合进组件中
你目前已经看到了Class.create
的使用.
var Pirate = Class.create(Person, { /* instance methods */ });
但是事实上, Class.create 接受任意数量的参数
,第一个参数是被继承的对象。
其他的参数是将被添加到子类中的方法实例; 其实他们内部最终调用的是addMethods
方法(如下)。
这能被便利的被糅合进如下的组件中:
// 定义一个组件
var Vulnerable = {
wound: function(hp) {
this.health -= hp;
if (this.health < 0) this.kill();
},
kill: function() {
this.dead = true;
}
};
var Person = Class.create(Vulnerable, {
initialize: function() {
this.health = 100;
this.dead = false;
}
});
var bruce = new Person;
bruce.wound(55);
bruce.health; //-> 45
方法定义中的$super
参数
当你想重写父类中的方法, 但是仍然想要调用父类中的方法, 你需要一个对父类的引用。
你能够获得这个引用当通过定义那些方法并且在第一个位置拥有首参: $super。
Prototype 将检测到这个参数 并且 使那个重写的方法可以继承父类通过传递$super参数。
但是 Pirate#say方法依然期望获得一个单一的参数
;
这些实现细节不会影响你跟其他对象的交互。
关于编程语言类的继承
通常我们将基于类的继承和原型的继承区分开来, 后者特定于 JavaScript。
尽管即将到来的JavaScript 2.0将会支持真正的类定义,
但是目前浏览器支持的js版本仍然局限于原型继承。
原型是js中的一个非常有用的特性, 但是当你真正创建对象的时候原型仍然非常冗余。
这是我们模拟实现基于类的继承的原因(像在ruby语言中一样) 。
这有一定的影响, 例如,在php中 你能够如下定义初始化变量:
class Logger {
public $log = array();
function write($message) {
$this->log[] = $message;
}
}
$logger = new Logger;
$logger->write('foo');
$logger->write('bar');
$logger->log; // -> ['foo', 'bar']
我们可以尝试在Prototype中做同样的工作 :
var Logger = Class.create({
initialize: function() { },
log: [],
write: function(message) {
this.log.push(message);
}
});
var logger = new Logger;
logger.log; // -> []
logger.write('foo');
logger.write('bar');
logger.log; // -> ['foo', 'bar']
以上的程序有效. 但是如果我们创建另外一个Logger对象呢?
var logger2 = new Logger;
logger2.log; // -> ['foo', 'bar']
// ... log
应当是空的对象才对!
你可以看到,新对象中的log数组本来应该是空的,但是依然拥有和第一个对象的相同数组。
事实上,所有的 logger对象分享相同的array对象,因为以上是引用拷贝, 不是值拷贝。
正确的方式是在 initialize方法中初始化你的变量:
var Logger = Class.create({
initialize: function() {
// 正确的方式:
this.log = [];
},
write: function(message) {
this.log.push(message);
}
});
定义类的方法
没有添加 类方法 的特别方式在prototype1.6.0中。 简单的定义它们在你的现有类中:
Pirate.allHandsOnDeck = function(n) {
var voices = [];
n.times(function(i) {
voices.push(new Pirate('Sea dog').say(i + 1));
});
return voices;
}
Pirate.allHandsOnDeck(3);
// -> ["Sea dog: 1, yarr!", "Sea dog: 2, yarr!", "Sea dog: 3, yarr!"]
如果你想同时定义几个方法, 简单的用 Object.extend
即可:
Object.extend(Pirate, {
song: function(pirates) { ... },
sail: function(crew) { ... },
booze: ['grog', 'rum']
});
特殊的类属性
Prototype 1.6.0 定义了2个特别的类属性: subclasses
和 superclass。
见名知意:它们分别 保持对当前类的父类和子类的引用。
Person.superclass
// -> null
Person.subclasses.length
// -> 1
Person.subclasses.first() == Pirate
// -> true
Pirate.superclass == Person
// -> true
Captain.superclass == Pirate
// -> true
Captain.superclass == Person
// -> false
Class#addMethods() 为类添加方法
假设您有一个已经定义的类你想添加额外的方法。 自然的你想要这些方法在你的子类中立即生效。
这已经通过在原型链中注入属性实现了, 最有效的方式是通过调用 Class#addMethods方法
:
var john = new Pirate('Long John');
john.sleep();
// -> ERROR: sleep不是一个方法
// every person should be able to sleep, not just pirates!
Person.addMethods({
sleep: function() {
return this.say('ZzZ');
}
});
john.sleep();
// -> "Long John: ZzZ, yarr!"
那个sleep
方法 立即生效,不仅在new Person实例上, 并且在他的子类 和 已经存在当前内存中的实例上。
###下班回家 2012-7-27 Translationed by 刘明海