Javascript 面向对象的程序设计(原型链与继承)

继承

原型链

讲原型的时候提到过继承,设计原型的初衷就是为了继承,原型链是实现继承的主要方法。
那什么是原型链,还记得之前提到过的作用域链吗,它表示标识符在环境中的查找顺序,原型链与作用域链相似,它表示属性或方法在实例和构造函数间的追溯顺序,看一个例子:

function Father() {
}
Father.prototype.familyName = "Zhao";
function Child() {
}
var father = new Father();
Child.prototype = father;
var me = new Child();
me.familyName;
// "Zhao"


当我们在me实例上访问familyName属性时,搜索过程会从原型链的末端开始逐步向上,即:
me实例 → Child原型 → Father原型

默认的原型

由于所有引用类型都继承了Object 因此所有引用类型原型链的顶端都是Object.prototype,因此所有自定义类型都会继承toString() valueOf()等默认方法。

确定原型和实例的关系
  1. instanceof
    用这个操作符测试实例和原型链上出现的构造函数,即返回true
  2. isPropertyOf
    用这个方法测试原型链中出现过的原型,即返回true
谨慎地定义方法

在上面的例子中,我们有一步替换原型对象的操作:

Child.prototype = father;

很多时候,我们想要在子类型中添加一些超类型没有的方法,应该放在替换原型对象之后。

不能使用对象字面量

通过原型链实现继承时,不能使用对象字面量来重写原型,因此这样做会切断子类型与超类型之间的联系。

原型链的问题

在上述例子中,Child构造函数的原型是Father的实例father,那么father实例的属性就变成Child的原型属性,接下来在Child的所有实例,均会共享father实例的属性,我们修改一下上述例子:

function Father() {
    this.hobbies = [];
}
function Child() {
}
Child.prototype = new Father();
var me = new Child();
var sister = new Child();
me.hobbies.push("dancing");
sister.hobbies;
// ["dancing"]

me修改Child原型上的hobbies属性会影响到sister访问该属性时获得的值,注意,当我们直接在实例对象上对某个属性赋值时,我们相当于修改或添加实例中的某个属性(同访问不一样,不遵循原型链),比如:

me.hobbies = [];
sister.hobbies;
//["dancing"]

上述例子中,直接在me实例中添加属性hobbies,因此没有影响sister

借用构造函数

即在子类型的构造函数调用超类型的构造函数,举一个例子:

function Father(givenName) {
    this.givenName = givenName;
}
function Child(givenName) {
    Father.call(this, givenName)
}
var child = new Child("Xianshu");
child.givenName
// "Xianshu"
  1. 私有属性
    使用这种方式,可以使每个实例从超类型中继承的属性私有化,一个实例更改属性值不会影响到其他属性,举一个例子:
var child1 = new Child("Sue")
var child2 = new Child("Jane")
child1.givenName
// "Sue"
child2.givenName
// "Jane"
  1. 传递参数
    通过这种方式,子类型可以在调用超类型构造函数时向其传参。

  2. 结构构造函数的问题
    首先没办法在实例间复用函数,比如:

function Father(givenName) {
    this.givenName = givenName;
    this.cook = function() {
        return "delicious food";
    }
}
function Child(givenName) {
    Father.call(this, givenName)
}
var child1 = new Child("Sue")
var child2 = new Child("Jane")
child1.cook === child2.cook
// false

上述例子中,可以看出,两个实例的cook方法引用的不是同一块内存地址,说明cook被实例化了多次,这显然是冗余的。
其次,超类型的原型中定义的方法对于子类型来说也是不可见的。

组合继承

使用原型链来实现方法的继承,使用构造函数实现属性的继承。比如:

function Father(givenName) {
    this.givenName = givenName;
}
Father.prototype.cook = function() {
    return "delicious food";
}
function Child(givenName) {
    Father.call(this, givenName)
}
Child.prototype = new Father();
Child.prototype.constructor = Child;
var child = new Child("Sue")
child.cook();
// "delicious food"

在创建Child构造函数时,首先继承超类型Father中的属性,然后创建一个Father实例,将其赋值给Child的原型,使其继承Father定义在原型中的方法,这里需要注意两点:

  1. new Father()中的givenName属性不会覆盖child中的givenName属性,这是由标识符在实例及其原型链上的搜索顺序决定的
  2. 需要修正Child.prototype.constructor属性

原型式继承

无需构造函数的一种继承方式,在一个对象的基础上创建出一个新对象:

function object(o) {
    function F(){}
    F.prototype = o;
    return new F();
}
var person = { age : 18, hobbies: [] };
var anotherPerson = object(person);
anotherPerson.age;
// 18

上述例子中,person不是构造函数,只是一个普通的对象,anotherPerson继承了它的属性,需要注意的是,继承到的属性是在实例间共享的。

anotherPerson.hobbies.push("dancing");
anotherPerson.age = 19;
var anotherPerson2 = object(person);
anotherPerson2.age; // 18
anotherPerson2.hobbies; // ["dancing"]

寄生式继承

寄生式继承封装了如下过程:

  1. 调用一个能够返回新对象的函数
  2. 以某种方式来增强返回的对象
  3. 返回对象
function printBook(original) {
    var book = object(original);
    book.auther = "Sue";
    return book;
}
var book = {
    name: "travel notes",
    type: "travel"
}
var travelBook = printBook(book);
travelBook.name
// "travel notes"
travelBook.auther
// "Sue"

上述例子中,travelBook不继承了book中的属性,而且还具有自己的属性。需要注意的是,使用寄生式继承,在第二步中添加的属性或方法不能在实例间复用。

寄生组合式继承

还记得我们在组合继承中提到的注意事项第一条吗...为什么Child原型中以及child实例中都包含givenName属性,这是因为我们调用了两次Father构造函数,第一次将属性赋予child实例,第二次是将Father的实例赋值给Child的原型,虽然第二次调用添加的givenName属性并没有影响到child.givenName,但两次调用毕竟是冗余的,况且Child只需要继承Father的原型,而Father的实例包含了额外的属性。寄生组合方式就是为了解决上述的问题:

function Father(givenName) {
    this.givenName = givenName;
}
Father.prototype.cook = function() {
    return "delicious food";
}
function Child(givenName) {
    Father.call(this, givenName)
}
function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype); 
    prototype.constructor = subType;
    subType.prototype = prototype; 
}
inheritPrototype(Child, Father);
var child = new Child("Sue")
undefined
child.givenName
// "Sue"
"givenName" in Child.prototype
// false
child.cook()
// "delicious food"
child instanceof Father
// true
Father.prototype.isPrototypeOf(child)
// true

Child使用寄生组合的方式继承了Father,可以调用Father原型中的方法,同时,没有留下Father“私有”属性的痕迹,instanceof()isPrototypeOf()可以正常使用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,763评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,728评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,834评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,707评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,576评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,429评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,826评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,474评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,762评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,792评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,582评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,424评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,845评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,059评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,361评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,826评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,038评论 2 338

推荐阅读更多精彩内容