基于原型
这里的基本概念是动态可变对象。转换(完整转换,不仅包括值,还包括特性)和动态语言有直接关系。下面这样的对象可以独立存储他们所有的特性(属性,方法)而不需要的类。
复制代码 代码如下:
object = {a: 10, b: 20, c: 30, method: fn};
object.a; // 10
object.c; // 30
object.method();
此外,由于动态的,他们可以很容易地改变(添加,删除,修改)自己的特性:
复制代码 代码如下:
object.method5 = function () {...}; // 添加新方法
object.d = 40; // 添加新属性 "d"
delete object.c; // 删除属性 "с"
object.a = 100; // 修改属性 "а"
// 结果是: object: {a: 100, b: 20, d: 40, method: fn, method5: fn};
也就是说,在赋值的时候,如果某些特性不存在,则创建它并且将赋值与它进行初始化,如果它存在,就只是更新。
在这种情况下,代码重用不是通过扩展类来实现的,(请注意,我们没有说类没办法改变,因为这里根本没有类的概念),而是通过原型来实现的。
原型是一个对象,它是用来作为其他对象的原始copy,或者如果一些对象没有自己的必要特性,原型可以作为这些对象的一个委托而当成辅助对象。
基于委托
任何对象都可以被用来作为另一个对象的原型对象,因为对象可以很容易地在运行时改变它的原型动态。
注意,目前我们正在考虑的是概论而不是具体实现,当我们在ECMAScript中讨论具体实现时,我们将看到他们自身的一些特点。
例(伪代码):
复制代码 代码如下:
x = {a: 10, b: 20};
y = {a: 40, c: 50};
y.[[Prototype]] = x; // x是y的原型
y.a; // 40, 自身特性
y.c; // 50, 也是自身特性
y.b; // 20 – 从原型中获取: y.b (no) -> y.[[Prototype]].b (yes): 20
delete y.a; // 删除自身的"а"
y.a; // 10 – 从原型中获取
z = {a: 100, e: 50}
y.[[Prototype]] = z; // 将y的原型修改为z
y.a; // 100 – 从原型z中获取
y.e // 50, 也是从从原型z中获取
z.q = 200 // 添加新属性到原型上
y.q // 修改也适用于y
这个例子展示了原型作为辅助对象属性的重要功能和机制,就像是要自己的属性一下,和自身属性相比,这些属性是委托属性。这个机制被称为委托,并且基于它的原型模型是一个委托的原型(或基于委托的原型 ) 。引用的机制在这里称为发送信息到对象上,如果这个对象得不到响应就会委托给原型来查找(要求它尝试响应消息)。
在这种情况下的代码重用被称为基于委托的继承或基于原型的继承。由于任何对象可以当成原型,也就是说原型也可以有自己的原型。 这些原型连接在一起形成一个所谓的原型链。 链也像静态类中分层次的,但是它可以很容易地重新排列,改变层次和结构。
复制代码 代码如下:
x = {a: 10}
y = {b: 20}
y.[[Prototype]] = x
z = {c: 30}
z.[[Prototype]] = y
z.a // 10
// z.a 在原型链里查到:
// z.a (no) ->
// z.[[Prototype]].a (no) ->
// z.[[Prototype]].[[Prototype]].a (yes): 10
如果一个对象和它的原型链不能响应消息发送,该对象可以激活相应的系统信号,可能是由原型链上其它的委托进行处理。
该系统信号,在许多实现里都是可用的,包括基于括动态类的系统:Smalltalk中的#doesNotUnderstand,Ruby中的method_missing;Python中的__getattr__,PHP中的__call;和ECMAScript中的__noSuchMethod__实现,等等。
例(SpiderMonkey的ECMAScript的实现):
复制代码 代码如下:
var object = {
// catch住不能响应消息的系统信号
__noSuchMethod__: function (name, args) {
alert([name, args]);
if (name == 'test') {
return '.test() method is handled';
}
return delegate[name].apply(this, args);
}
};
var delegate = {
square: function (a) {
return a * a;
}
};
alert(object.square(10)); // 100
alert(object.test()); // .test() method is handled