ECMAScript 的继承方式

在面向对象编程中,支持两种继承:接口继承和实现继承。前者只继承方法签名,后者继承实际的方法。接口继承在 ECMAScript 中是不可能的,因为函数没有签名。实现继承是 ECMAScript 唯一支持的继承方式,而这主要是通过原型链实现的。

一、原型链继承

在ECMAScript中,原型链作为 ECMAScript 的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。

什么是原型链呢???重温一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个_proto_针指向原型。如果一个原型是另一个类型的实例,那就意味着这个原型本身有一个_proto_指针指向另一个原型。这样由 _proto_ 串起的路径就是一条原型链。

如图

通过原型链继承的方式,Dog可以继承Animal构造函数上的所有属性

// 创建Animal
function Animal() {
  this.name = 'animal';
}
Animal.prototype.getAnimalName = function () {
  console.log(this.name);
}
// 创建Dog
function Dog() {
  this.name = 'dog';
}
// Dog继承自Animal  将Animal的实例赋值给Dog的原型对象,相当于将Animal的实例中的__proto__赋值给了Dog的原型对象
// 如此 Dog原型对象 就能通过 Animal 对象的实例中的[[prototype]](__proto__) 来访问到 Animal原型对象 中的属性和方法了。
Dog.prototype = new Animal();
Dog.prototype.getDogName = function () {
  console.log(this.name);
}
var d1 = new Dog();
d1.getAnimalName()
d1.getDogName()

注意:1.实际上,原型链中还有一环。默认情况下,所有引用类型都继承自 Object,这也是通过原型链实现的。任何函数的默认原型都是一个 Object 的实例,这意味着这个实例有一个内部指针指向Object.prototype。这也是为什么自定义类型能够继承包括 toString()、valueOf()在内的所有默认方法的原因。因此上面的图一层继承关系。

2.子类有时候需要覆盖父类的方法,或者增加父类没有的方法。为此,这些方法必须在原型赋值之后再添加到原型上。来看下面的例子:

 

function Animal() {
  this.name = 'animal';
}
Animal.prototype.getAnimalName = function () {
  console.log(this.name);
}
// 创建Animal的实例
var a1 = new Animal()
a1.getAnimalName(); //animal
function Dog() {
  this.name = 'dog';
}
Dog.prototype = new Animal();
// 新方法
Dog.prototype.getDogName = function () {
  console.log(this.name);
}
// 覆盖父类已有的方法
Dog.prototype.getAnimalName = function () {
  console.log('我覆盖了父类的方法');
}
var d1 = new Dog();
d1.getAnimalName(); // 我覆盖了父类的方法
d1.getDogName();//dog

3.以对象字面量方式创建原型方法会破坏之前的原型链,因为这相当于重写了原型链。

function Animal() {
  this.name = 'animal';
}
Animal.prototype.getAnimalName = function () {
  console.log(this.name);
};
function Dog() {
  this.name = 'dog';
}
// 继承
Dog.prototype = new Animal()
//使用字面量
Dog.prototype = {
  getDogName() {
    console.log(this.name);
  },
  someOtherMethod() {
    return false;
  }
};
var d1 = new Dog();
d1.getAnimalName(); // 出错!

在这段代码中,子类的原型在被赋值为 Animal 的实例后,又被通过字面量的赋值方式将它变成一个Object 的实例,而不再是 Animal 的实例。因此之前的原型链就断了,Dog和 Animal 之间也没有关系了。

原型链继承的缺点:

1.当原型链中包含引用类型的时候,会在所有实例间共享

2.原型链的第二个问题是,子类型在实例化时不能给父类型的构造函数传参。我们无法在不影响所有对象实例的情况下把参数传进父类的构造函数。

二、盗用构造函数继承(伪造对象、经典继承)

该方法是在子类构造函数中调用父类构造函数。 因为函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()和 call()方法以新创建的对象为上下文执行构造函数。 该方式解决了原型链继承中出现引用类型不能在实例间共享的问题,也解决了原型链上不能传参的问题。

function Animal(name) {
  this.name = name;
}
Animal.prototype.getAnimalName = function () {
  console.log(this.name);
};
function Dog() {
  // 继承 Animal 并传参
  Animal.call(this, "zhangsan");
  // 实例属性
  this.age = 29;
}
var d = new Dog();
console.log(d.name); // zhangsan
console.log(d.age); // 29
d.getAnimalName()//报错,经典继承方法子类也不能访问父类原型上定义的方法

通过使用 call()(或 apply())方法,Animal构造函数在为 Dog 的实例创建的新对象的上下文中执行了。这相当于新的 Dog 对象上运行了Animal()函数中的所有初始化代码。结果就是每个实例都会有自己的 categorys 属性。

缺点:1.必须在构造函数中声明方法,不能重用

           2.子类不能访问父类上面的原型属性和方法,因为子类的实例只继承了父类的实例属性/方  法,没有继承父类的构造函数的原型对象中的属性/方法。

           3.每个子类的实例都持有父类的实例方法的副本,浪费内存,影响性能,而且无法实现父类的实例方法的复用。

三、组合继承(以上两种方式的组合)

该方式将以上两者的优点集中了起来。基本的思路是使用原型链继承原型对象中的属性/方法,而通过构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

function Animal(name) {
  this.name = name;
  this.categorys = ["cat", "rabbit"];
}
Animal.prototype.sayName = function () {
  console.log(this.name);
};
function Dog(name, age) {
  // 继承实例属性
  Animal.call(this, name);
  this.age = age;
}
// 继承原型对象中方法
Dog.prototype = new Animal();
Dog.prototype.sayAge = function () {
  console.log(this.age);
};
var d1 = new Dog("zhangsan", 29);
d1.categorys.push("dog");
console.log(d1.categorys); // [ 'cat', 'rabbit', 'dog' ]
d1.sayName(); // zhangsan
d1.sayAge(); // 29 
var d2 = new Dog("lisi", 27);
console.log(d2.categorys); // [ 'cat', 'rabbit' ]
d2.sayName(); // lisi
d2.sayAge(); // 27

在这个例子中,Animal 构造函数定义了两个属性,name 和 categorys,而它的原型上也定义了一个方法叫 sayName()。Dog 构造函数调用了 Animal 构造函数,传入了 name 参数,然后又定义了自己的属性 age。此外,Dog.prototype 也被赋值为 Animal 的实例。原型赋值之后,又在这个原型上添加了新方法 sayAge()。这样,就可以创建两个 Dog 实例(d1、d2),让这两个实例都有自己的属性,包括 categorys,同时还共享相同的方法。

组合继承结合了原型链和经典继承函数的有点,是 JavaScript 中使用最多的继承模式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值