您的位置:

反思原型链

一、原型链基础

JavaScript的原型链是一种从对象到另一个对象的委托关系。它在一个对象的属性找不到时,会沿着原型链往上找指定名称的属性或方法。我们来看一个例子:

function Animal(name){
  this.name = name;
}

Animal.prototype.getName = function(){
  return this.name;
}

function Dog(name){
  this.name = name;
}

Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;

var dog = new Dog('Henry');
console.log(dog.getName()); //输出:Henry

在此代码中,我们定义了Animal和Dog两个构造函数,并且让Dog的原型指向Animal的实例。当我们调用dog.getName()时,JavaScript引擎会在dog对象上查找getName方法,没有找到时会沿着原型链往上找Animal.prototype上的getName方法。

二、改变原型链

在上一个例子中,我们让Dog的原型指向了Animal的实例,在Dog.prototype上定义getName方法。这种方式同样有一些问题:

function Animal(name){
  this.name = name;
}

Animal.prototype.getName = function(){
  return this.name;
}

function Dog(name){
  this.name = name;
}

Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
Dog.prototype.getName = function(){
  return 'My name is ' + this.name;
}

var dog = new Dog('Henry');
console.log(dog.getName()); //输出:My name is Henry

var animal = new Animal('Oscar');
console.log(animal.getName()); //输出:Oscar

在此代码中,我们在Dog.prototype上定义了一个新的getName方法,这会影响到它的父对象Animal.prototype上的getName方法,而且animal对象也会受到影响。

为了避免这种相互污染的问题,我们可以使用Object.create()方法,创建一个新的对象作为原型,并将其赋值给Dog.prototype。

function Animal(name){
  this.name = name;
}

Animal.prototype.getName = function(){
  return this.name;
}

function Dog(name){
  this.name = name;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.getName = function(){
  return 'My name is ' + this.name;
}

var dog = new Dog('Henry');
console.log(dog.getName()); //输出:My name is Henry

var animal = new Animal('Oscar');
console.log(animal.getName()); //输出:Oscar

三、继承的多种方式

除了使用原型链实现继承之外,我们还可以使用其他几种方式:构造函数、组合继承、寄生构造函数和组合继承。

1. 构造函数

构造函数是最基本的一种继承方式。它有一些局限性,例如无法访问父对象的属性和方法,但在一些情况下,由于其简单易用,还是有着很大的用武之地。

function Animal(name){
  this.name = name;
  this.getName = function(){
    return this.name;
  }
}

function Dog(name, type){
  this.type = type;
  this.getType = function(){
    return this.type;
  }

  Animal.call(this, name);
}

var dog = new Dog('Henry', 'Poodle');
console.log(dog.getName()); //输出:Henry
console.log(dog.getType()); //输出:Poodle

2. 组合继承

组合继承是指同时采用构造函数和原型链的继承方式。使用该方法,可以避免构造函数和原型链各自的局限性和缺陷。

function Animal(name){
  this.name = name;
}

Animal.prototype.getName = function(){
  return this.name;
}

function Dog(name, type){
  this.type = type;

  Animal.call(this, name);
}

Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
Dog.prototype.getType = function(){
  return this.type;
}

var dog = new Dog('Henry', 'Poodle');
console.log(dog.getName()); //输出:Henry
console.log(dog.getType()); //输出:Poodle

3. 寄生构造函数

寄生构造函数是指在另一个构造函数的基础上增加一些方法或属性,从而达到继承的目的。与传统的构造函数继承相比,寄生构造函数继承的优势在于可以使用闭包,对属性和方法进行封装,从而达到不污染父对象的目的。

function Animal(name){
  this.name = name;
}

Animal.prototype.getName = function(){
  return this.name;
}

function Dog(name, type){
  var self = new Animal(name);

  self.type = type;
  self.getType = function(){
    return this.type;
  }

  return self;
}

var dog = new Dog('Henry', 'Poodle');
console.log(dog.getName()); //输出:Henry
console.log(dog.getType()); //输出:Poodle

4. 组合继承

寄生组合继承也是一种很好的继承方式,它通过借用构造函数继承父对象的属性和方法,和通过原型链继承父对象的原型,达到继承的目的。

function Animal(name){
  this.name = name;
}

Animal.prototype.getName = function(){
  return this.name;
}

function Dog(name, type){
  Animal.call(this, name);
  this.type = type;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.getType = function(){
  return this.type;
}

var dog = new Dog('Henry', 'Poodle');
console.log(dog.getName()); //输出:Henry
console.log(dog.getType()); //输出:Poodle