JavaScript的原型链

JavaScript的原型链

JavaScript的继承主要是通过原型链实现的,所以理解原型链是掌握JavaScript继承的关键一环。原型链的继承的基本思想是通过原型链继承多个引用类型的属性和方法。

理解原型链

关于原型链的定义与理解:

每个构造函数都有一个原型对象,原型有一个属性指向构造函数,而实例有一个内部指针指向原型。但是如果原型是另外一个类型的实例呢,那么就意味着这个原型本身有一个内部指针指向另外一个原型,相应地另外一个原型也有一个指针指向另一个构造函数。这样实例和原型之间构造了一条原型链。这就是原型链的基本思想构成。

阅读下面的代码,来深入理解原型链:

function SuperType () {
    this.property = true;
}

SuperType.prototype.getSuperValue = function () {
    return this.property;
}

function SubType() {
    this.subproperty = false;
}

// 现在让之类继承父类SuperType

SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function() {
    return this.subproperty;
};

const instance = new SubType();

console.log(instance.getSuperValue());

上面代码定义了两个类:父类SuperType和子类SubType,并且分别定义了一个属性和方法。其中SubType是通过创建SuperType的实例并将其赋值给自己的原型(SubType.prototype)实现了对SuperType的继承。这个赋值重写了SubType最初的原型,并将其替换为SuperType的实例。这就意味着SuperType实例可以访问的属性和方法也会存在于SubType.prototype

在这里插入图片描述

这个例子中实现继承的关键,是SubType没有使用默认原型,而是将其替换成了一个新的对象。这个新的对象恰好是SuperType的实例。这样一来,SubType的实例不仅能从SuperType的实例中继承属性和方法,而且还与SuperType的原型挂上了钩。于是instance(通过内部的[[Prototype]])指向SubType.prototype,而SubType.prototype(作为SuperType的实例又通过内部的[[Prototype]])指向SuperType.prototype。注意,getSuperValue()方法还在SuperType.prototype对象上,而property属性则在SubType.prototype上。这是因为getSuperValue()是一个原型方法,而property是一个实例属性。SubType.prototype现在是SuperType的一个实例,因此property才会存储在它上面。还要注意,由于SubType.prototypeconstructor属性被重写为指向SuperType,所以instance.constructor也指向SuperType.

默认情况下,所有引用类型都继承自Object,这也是通过原型链实现的。任何函数的默认原型都是一个Object的实例,这意味着这个实例有一个内部指针指向Object.prototype。这也是为什么自定义类型能够继承包括toString()valueOf()在内的所有默认方法的原因。SubType继承SuperType,而SuperType继承Object。在调用instance.toString()时,实际上调用的是保存在Object.prototype上的方法。

我们知道,在读取实例上的属性时,首先会在实例上搜索这个属性。如果没找到,则会继承搜索实例的原型。在通过原型链实现继承之后,搜索就可以继承向上,搜索原型的原型。对前面的例子而言,调用instance.getSuperValue()经过了3步搜索:instanceSubType.prototypeSuperType.prototype,最后一步才找到这个方法。对属性和方法的搜索会一直持续到原型链的末端。这就是原型链的向上搜索功能。

原型链与继承

原型与实例的关系可以通过两种方式来确定:instanceofisPrototypeOf

instanceof操作符

使用instanceof操作符,如果一个实例的原型链中出现过相应的构造函数,则instanceof返回true。示例如下:

console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof SubType); // true 

因为instanceObjectSuperTypeSubType,所以使用instanceof操作符返回true

isPrototypeOf方法

使用isPrototypeOf()方法。原型链中的每个原型都可以调用这个方法,如下例所示,只要原型链中包含这个原型,这个方法就返回true

console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true 

原型链继承所面临的问题

新增或重新方法的要点

子类可以增加新的方法或者覆盖父类的方法

// 覆盖父类的方法
SubType.prototype.getSuperValue = function() {
  return false;
};

需要注意的是,我们应该避免使用以对象字面量方式创建原型方法,在会破坏之前的原型链,因为这相当于重写
了原型链。

function SuperType () {
  this.property = true;
}

SuperType.prototype.getSuperValue = function () {
  return this.property;
}

function SubType() {
  this.subproperty = false;
}

// 现在让之类继承父类SuperType

SubType.prototype = new SuperType()

SubType.prototype = {
  getSubValue() {
    return this.subproperty;
  },
  getOtherMethod() {
    return false;
  }
}

在这段代码中,子类的原型在被赋值为SuperType的实例后,又被一个对象字面量覆盖了。覆盖后的原型是一个Object的实例,而不再是SuperType的实例。因此之前的原型链就断了。SubTypeSuperType之间也没有关系了。

原型链继承的引用问题

原型链虽然是实现继承的强大工具,但它也有问题。主要问题出现在原型中包含引用值的时候。前面在谈到原型的问题时也提到过,原型中包含的引用值会在所有实例间共享,这也是为什么属性通常会在构造函数中定义而不会定义在原型上的原因。在使用原型实现继承时,原型实际上变成了另一个类型的实例。这意味着原先的实例属性摇身一变成为了原型属性。

function SuperType() {
 this.colors = ["red", "blue", "green"];
}
function SubType() {}
// 继承 SuperType
SubType.prototype = new SuperType();

let instance1 = new SubType();

instance1.colors.push("black");

console.log(instance1.colors); // "red,blue,green,black"

// 因为colors是因为类型,上一个实例instance1修改了属性,进而影响了第二个实例
let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green,black" 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绝对零度HCL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值