JavaScript学习笔记(三十一) 经典继承模式

使用经典继承得到期望的结果(Expected Outcome When Using Classical Inheritance)

这个经典继承实现目的就是让使用Child()构造函数创造的对象获得从另一个构造函数Parent()而来的属性。

虽然这个讨论是关于经典继承模式(classical patterns),我们避开使用"类"(class),而说长一点的"构造函数"(constructor function 或 constructor),但它是准确的没有歧义的。

一般,当和你的团队交流时尽量避免使用"类"(class)这个词,因为当涉及到JavaScript,这个词可能对不同的人有不同的理解。

这里是一个定义两个构造函数Parent()和Child()的例子:

// the parent constructor
function Parent(name) {
    this.name = name || 'Adam';
}
// adding functionality to the prototype
Parent.prototype.say = function () {
    return this.name;
};
// empty child constructor
function Child(name) {}
// inheritance magic happens here
inherit(Child, Parent);
现在你已经有了parent和child构造函数,一个say()方法被添加到parent构造函数的原型上,然后调用了inherit()方法,这个方法会处理继承(inheritance)。

JavaScript语言并没有提供这个inherit()函数,所以你必须自己实现它。让我们看一些通用的(in a generic way)方法去实现它。

经典模式一默认模式(Classical Pattern #1—The Default Pattern)

通常使用的默认方法是使用Parent()构造函数创建一个对象并且将这个对象赋值给Child()构造函数的prototype(原型)。
这里是第一个可复用的inherit()函数的实现:
function inherit(C, P) {
    C.prototype = new P();
}
记住prototype属性应该指向一个对象而不是一个函数是非常重要的,那么它必须指向父类构造函数的创建的实例(一个对象),而不是父类的构造函数本身,注意new操作符,因为你需要它才能让这种模式行得通。

接下来在你的程序中,当你使用new Child()去创建一个对象时,它将通过prototype(原型)获得来自Parent()的函数,就像下面这个例子一样:
var kid = new Child();
kid.say(); // "Adam"

跟踪原型链(Following the Prototype Chain)

使用这种模式,你获得了自己的属性(添加到this的实例指定的属性,比如name)和原型的属性和方法(比如:say())。
让我们探讨一下原型链在这种继承模式中是如何工作的。为了这个讨论,让我们认为对象时内存中某块区域(blocks),可以包含数据和其他区域的引用。
当你使用new Parent(),你创建了一个这样区域(在图6.2中标记为#2)。它保存了name属性的数据。
如果你试图访问say()方法(比如:通过 new Parent().say() ),虽然#2区域不包含这个方法。但是使用隐藏的连接(link) __proto__ ,指向构造函数Parent()prototype属性, 你能够访问#1对象(Parent.prototype),它确实包含say()方法。
所有的这一切都发生在幕后,你不需要担心它们, 但知道它是如何工作和你正在访问和可能修改的数据在什么地方是非常重要的。记住在这里使用的_proto__ 是为了说明原型链;这个属性在语言自身中是不可以被访问的,尽管它在一些环境中可以被访问(比如:Firefox )。



让我们看一下在调用inherit()后,使用var kid = new Child()创建一个新对象发生了什么。见图6-2:

Child()构造函数是空实现并且没有属性被添加到Child.prototype;因此,使用new Child()创建的对象时相当空的(pretty much empty),除了隐藏的连接__proto__,在这种情况下,__proto__指向在inherit()中new Parent()创建的对象。

现在当我们调用 kid.say()时会发生什么呢?#3对象并没有这么一个方法,所以它使用原型链查找#2对象。#2对象也没有这个方法,所以它顺着原型链到#1对象,#1对象恰巧有这个方法。然后在say()方法里面有一个指向this.name的引用需要被处理。新的一次查找过程又开始了。在这种情况下,this指向的#3对象并没有name属性。#2对象被查找,它有个name属性,值为"Adam"。

最后,让我们再看一下多一步 的操作,比如说我们有这样的代码:
var kid = new Child();
kid.name = "Patrick";
kid.say(); // "Patrick"
图6-3展示的这种情况下原型链的情况:

设置kid.name不会改变#2对象的name属性,但这会直接在#3对象上创建一个自己的属性。当你调用kid.say(),say方法会首先在#3对象中查找,然后#2对象最后查找#1对象,就像前面一样。但这次查找this.name(和kid.name是一样的)是快速的,因为这个属性立刻就在#3对象中被找到。

如果你使用delete kid.name移除了这个新属性,那么#2对象的name属性会被宠幸(shine through)并且会被在连续的查找中被找到。

使用经典模式一的缺点(Drawbacks When Using Pattern #1)

这种模式的一个缺点就是你继承了添加给this自身属性(own properties)和原型属性(prototype properties)。大多数情况下,你不需要自身属性,因为它们有可能对每个实例都是不同的,是不可复用的。 一个使用构造函数的经验就是复用的成员应该被添加到原型中。

使用通用(generic)的inherit()另一个缺点就是它不能让你给child构造函数传递参数,然后child构造函数再传递给parent。
考虑下面这个例子:
var s = new Child('Seth');
s.say(); // "Adam"
这个不是你所期望的,让child函数传递参数给parent构造函数是可以的,但当你每次需要一个新的child你不得不调用做一次继承,这是低效的,因为你最终一次又一次创建了parent对象。









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值