深入理解javascript中的继承机制(1)原型链继承机制将共有的属性放进原型中

javascript中的继承机制是建立在原型的基础上的,所以必须先对原型有深刻的理解,笔者在之前已经写过关于js原型的文章。

我们都知道,每个函数function都会有一个属性,这个属性就是原型prototype,它是一个引用,它指向一个对象object。当我们使用new操作符调用构造函数,创建一个新对象的时候,这个新对象就会拥有一个指向它的构造函数的原型对象的神秘链接,在浏览器中一般是proto,通常我们也称为它的原型对象。这就可以理解为,new出来的对象继承拥有了了它的构造函数的原型对象,这就隐约有一点继承的概念了。

原型链继承机制

原型链的概念就是多个这样的对象通过proto相互关系起来

Paste_Image.png

上图可以看到,对象A有一系列的属性,但其中的部分属性是藏在原型之中的,而这个原型又指向了对象B,所以对象A间接也拥有了对象B的部分属性,同理,对象B的部分属性是藏在proto中,而proto又指向了对象C. 这样一环一环的往上递加,显然最后会指向Object.prototype,它是所有对象的父对象。

下面我们就通过一个实例来说明,原型链继承机制的实现与原理 我们有三个构造函数,Shape,2DShape, Triangle。 很显然,它们之间的继承关系,应该是Shape,2DShape,Triangle. 下面分别定义三个构造函数:

function Shape(){
    this.name = 'Shape';
    this.toString = function () {
        return this.name;
    };
}

function TwoDShape(){
    this.name = '2D shape';
}

function Triangle(side, height){
    this.name = 'Triangle';
    this.side = side;
    this.height = height;
    this.getArea = function () {
        return this.side * this.height / 2;
    };
}

接下来实现它们之间的原型链继承关系:

TwoDShape.prototype = new Shape();
Triangle.prototype = new TwoDShape();

当我们覆盖原型对象的时候,不要忘了原型的陷阱(读者若不清楚,可以参考笔者介绍原型的博文)

TwoDShape.prototype.constructor = TwoDShape;
Triangle.prototype.constructor = Triangle;

这样我们就实现了原型链的继承关系。 每一个new出来的TwoDShape对象的proto属性都指向一个Shape对象,所以它可以拥有Shape对象的属性和方法,同理,每一个new出来的Triangle对象的proto属性都指向一个TwoDShape对象,而TwoDShape又继承至Shape,所以这样就形成了一个原型链。

下面我们对以上原型链关系进行测试

Paste_Image.png

上图我们可以看到清晰的一个原型链关系。

Paste_Image.png

我们看到实现原型链继承关系之后,my作为子对象,同时都是父对象的一种,这是符合java等语言中继承的概念的。

将共有的属性放进原型中

如上个例子中的,name属性是三中对象共有的,上个例子每个单独的对象都会new出一个name属性,这样就造成了对空间的浪费。 所以我们将name属性移到原型中去

function Shape() {}
Shape.prototype.name = 'Shape';

就不用每次都new出一个name属性,而是共用原型属性里面的name属性。

下面我们就用这种思想完善之前的例子

// constructor
function Shape() {}
// augment prototype
Shape.prototype.name = 'Shape';
Shape.prototype.toString = function () {
    return this.name;
};
// another constructor
function TwoDShape() {}
// take care of inheritance
TwoDShape.prototype = new Shape();
TwoDShape.prototype.constructor = TwoDShape;
// augment prototype
TwoDShape.prototype.name = '2D shape';
function Triangle(side, height) {
    this.side = side;
    this.height = height;
}
// take care of inheritance
Triangle.prototype = new TwoDShape();
Triangle.prototype.constructor = Triangle;
// augment prototype
Triangle.prototype.name = 'Triangle';
Triangle.prototype.getArea = function () {
    return this.side * this.height / 2;
};

Paste_Image.png

将部分共享属性移到原型里去之后,原型链的继承关系如图,对比之前简洁了一些,因为没有多余的重复属性

Paste_Image.png

这里调用toString方法得到相同的结果,但与之前略有不同,这里要多搜索一次,因为toString方法是属于Shape的原型属性里的。于是效率就有所降低。

同时,这种模式还有一个缺陷看下面的例子

Paste_Image.png

我们访问Shape对象的name属性结果显示的确实Triangle,这是为什么呢? 其实很简单,因为我们所有的原型都指向同一个对象,而每个对象的原型属性只是取得了指向唯一的原型对象的指针,所以只要改变了它,所有的都会改变了 因为这句: Triangle.prototype.name = 'Triangle'; 所以会导致Shape,TwoDShape的name属性都都为Triangle。

所以在某些时候,就没法使用这种继承模式,这种将共享的属性移到原型中的模式,会产生子对象覆盖掉父对象共有属性的缺陷。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏吾爱乐享

java之学习Integer类相关面试题

14440
来自专栏鸿的学习笔记

python的对象引用

Every object has an identity, a type and a value. An object’s identity never cha...

12830
来自专栏Java帮帮-微信公众号-技术文章全总结

Java基础-07(01).总结private,this,封装,static,成员方法变量,局部变量匿名对象

1:成员变量和局部变量的区别(理解) (1)在类中的位置不同 成员变量:类中方法外 局部变量:方法定义中或者方法声明上 (2)在内存中的位置不同 成...

36840
来自专栏Java帮帮-微信公众号-技术文章全总结

String中的null,以及String s;等区别详解

1、判断一个引用类型数据是否null。 用==来判断。 2、释放内存,让一个非null的引用类型变量指向null。这样这个对象就不再被任何对象应用了。等待JVM...

37140
来自专栏小古哥的博客园

教程笔记《JavaScript深入浅出》

一、数据类型 javascript是弱数据类型语言,不需要显式的定义类型,一共有如下六种数据类型 五种基本类型:number,string,boolean,nu...

29360
来自专栏架构之路

Java中Class类详解、用法及泛化

Java中Class类及用法 Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识,即所谓的RTTI。这项信息纪录了每个对象所属的...

31960
来自专栏null的专栏

python基础知识——控制语句

控制语句主要有条件语句和循环语句。 一、条件语句 1、if语句 格式 if 表达式: 语句1 else: 语句2 如下面的例子: ...

32560
来自专栏Vamei实验室

Python补充01 序列的方法

在快速教程中,我们了解了最基本的序列(sequence)。回忆一下,序列包含有定值表(tuple)和表(list)。此外,字符串(string)是一种特殊的定值...

19380
来自专栏塔奇克马敲代码

第 15 章 面向对象程序设计

23330
来自专栏Python小屋

Python内置函数使用说明

函数功能简要说明abs(x)返回数字x的绝对值或复数x的模all(iterable)如果对于可迭代对象iterable中所有元素x都有bool(x)为True,...

29340

扫码关注云+社区

领取腾讯云代金券