对象的继承

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-07-29

类式继承

function SuperClass () {}
function SubClass() {
  this.name = "wangcai";
  this.sayHi = function(){
    console.log("wangwang")
  }
}

SubClass.prototype = new SuperClass();
var s = new SubClass();

// 需要注意的是,s的constructor属性是指向SuperClass的

类式继承的问题

使用类式继承,**子类的原型实际上变成了父类型的实例。原先父类的实例属性也就顺理成章地变成了子类的原型属性了。**此时,原型对象的缺点一样存在,即子类的一个实例对原型属性做出了改动,则会影响到其他所有实例。

第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

有鉴于这两个问题,实践中很少会单独使用原型链。

构造函数继承

function SuperClass(id) {
  this.books = ['JavaScript', 'Java', 'CSS']
  this.id = id;
}

SuperClass.prototype.showBooks = function () {
  console.log(this.books);
}

function SubClass(id) {
  SuperClass.call(this, id)
}

测试代码如下:

var instance1 = new SubClass(10);
var instance2 = new SubClass(11);
instance1.books.push('html')
console.log(instance1.books); // [ 'JavaScript', 'Java', 'CSS', 'html' ]
console.log(instance2.books); // [ 'JavaScript', 'Java', 'CSS' ]
instance1.showBooks();  // Error

构造函数继承的问题:由于这种方法没有涉及到原型,所以父类的原型方法不会被子类继承。

组合继承

组合继承避免了类式继承和构造函数继承的缺陷,融合了它们的优点,成为JavaScript 中最常用的继承模式。而且,instanceofisPrototypeOf()也能够用于识别基于组合继承创建的对象。

function SuperClass(name){
  this.colors = ["red", "blue", "green"];
  this.name = name;
}

SuperClass.prototype.sayHello = function () {
  console.log('hello')
}

function SubClass(){
  //构造函数继承属性,同时还传递了参数
  SuperClass.call(this, "Nicholas");
  //实例属性
  this.age = 29;
}
// 原型链继承方法 所有的实例将访问同一个方法 达到复用的效果 
// 而且也可以在实例中重写这个方法,而不影响到其他实例
SubClass.prototype = new SuperClass();
var instance = new SubClass();
instance.sayHello()  // hello

组合继承的问题:使用组合继承会将父类构造函数调用两次,一次是构造函数继承调用的SuperClass.call(this),一次是类式继承时调用 new SuperClass(),所以这不是最完美的方式。

原型式继承

function inheritObj(o) {
  function F() {};
  F.prototype = o;
  return new F();
}

var a = { age: 1 };
var b = inheritObj(a);
console.log(b.__proto__ === a); // true

这是对类式继承的一个封装,其中的过度对象就相当于是类式继承中的子类,只不过在原型式中作为一个过度对象出现的,目的是为了创建要返回的新的实例化对象。

好处是,由于过度类的构造函数无内容,所以开销比较小,使用也比较方便。当然如果你感觉有必要可以加F过度类缓存起来,不必每次创建一个新的过度类,可以直接使用 Object.create()

// Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
var a = { age: 1 };
var b = Object.create(a);
console.log(b.__proto__ === a); // true

缺点是,跟类式继承一样,父类对象中值类型的属性被复制,引用类型的属性被公用。

var book = {
  name: 'js book',
  alikeBook: ['css book', 'html book']
}

var b1 = inheritObj(book);
b1.name = 'java book';
b1.alikeBook.push("python book");

var b2 = inheritObj(book);
b2.name = 'flash book';
b2.alikeBook.push("as book");

console.log(b1.name); // java book
console.log(b1.alikeBook); // [ 'css book', 'html book', 'python book', 'as book' ]
console.log(b2.name); // flash book
console.log(b1.alikeBook); // [ 'css book', 'html book', 'python book', 'as book' ]

原型式继承的问题:依然拥有类式继承的弊病,共享引用类型的值。

寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增加对象,在返回这个对象。

var book = {
  name: 'js book',
  alikeBook: ['css book', 'html book']
}

function createBook(obj) {
  var o = Object.create(obj);
  o.sayName = function () {
    console.log(this.name);
  }
  return o;
}

测试代码如下:

var b1 = createBook(book);
b1.name = 'java book';
b1.alikeBook.push("python book");

var b2 = createBook(book);
b2.name = 'flash book';
b2.alikeBook.push("as book");

console.log(b1.name); // java book
console.log(b1.alikeBook); // [ 'css book', 'html book', 'python book', 'as book' ]
console.log(b2.name); // flash book
console.log(b1.alikeBook); // [ 'css book', 'html book', 'python book', 'as book' ]

b1.sayName(); // java book
b2.sayName(); // flash book

返回的新对象不仅具有book的所有属性和方法,而且还有“自己”的sayName()方法。

在主要考虑为增强对象(为对象添加属性和方法)时,寄生式继承也是一种有用的模式。

寄生式继承的问题

  • 不能做到函数复用而降低效率。

  • 本质上还是原型继承,虽然返回的是新对象,但是还是会有原型继承的弊病,在访问自身没有而原对象有的引用属性时,共享原对象的引用属性的值。

寄生组合式继承

// 改造寄生式继承
function inheritPrototype(subClass, superClass){
  var prototype = Object.create(superClass.prototype);
  prototype.constructor = subClass;
  subClass.prototype = prototype;
}

1.第一步是创建超类型原型的一个副本。

2.第二步是为创建的副本添加constructor 属性,从而弥补因重写原型而失去的默认的constructor属性。

3.最后一步,将新创建的对象(即副本)赋值给子类型的原型。(解决函数复用的问题)

// 寄生组合式继承
function SuperClass(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}

SuperClass.prototype.sayName = function() {
  console.log(this.name);
};

function SubClass(name, age){
  SuperClass.call(this, name);
  this.age = age;
}

inheritPrototype(SubClass, SuperClass);

SubClass.prototype.sayAge = function(){
  console.log(this.age);
};

测试代码如下:

var instance1 = new SubClass("jack", 18)
var instance2 = new SubClass("andy", 20)
instance1.colors.push("black")

console.log(instance1.colors); // [ 'red', 'blue', 'green', 'black' ]
console.log(instance2.colors); // [ 'red', 'blue', 'green' ]
instance1.sayAge(); // 18 调用子类自己的方法 
instance1.sayName(); // jack 调用继承过来的父类的方法 

通过构造函数继承属性,通过寄生式继承继承父类的原型,因为我们要继承的仅仅是父类的原型,所以不需要再调用父类的构造函数,并且我们在构造函数继承中已经调用了父类的构造函数,也能做到向父类的构造函数中传递参数,而且寄生式继承还能增加这个被赋值的副本原型对象(修正constructor指向)。