谈谈JS中的继承(一)

134 阅读4分钟

子类的原型对象——类式继承

先看下实现方式

// 声明父类
function SupperClass() {
  this.supperValue = true;
}

// 父类添加共有方法
SupperClass.prototype.getSupperValue = function () {
  return this.supperValue;
};

// 声明子类
function SubClass() {
  this.subValue = false;
}

// 继承父类
SubClass.prototype = new SupperClass();

// 子类添加共有方法
SubClass.prototype.getSubValue = function () {
  return this.subValue;
};

使用一下子类

var ins = new SubClass();
console.log(ins.getSupperValue()); //true
console.log(ins.getSubValue()); // false

子类的原型可以访问父类原型上的属性和方法以及父类构造函数中复制的属性和方法,这就是类式继承的原理

使用intanceof来检测下

console.log(ins instanceof SubClass); // true
console.log(ins instanceof SupperClass); // true

// 🎈🎈🎈
console.log(SubClass instanceof SupperClass); //false

我们说SubClass 继承了 SupperClass 但为啥SubClass instanceof SupperClassfalse呢?

  1. instanceof 是判断前边的对象是否为后边类(对象)的实例,但并不代表着两者有继承关系
  2. 实现的方式是通过将 SupperClass的实例赋值给SubClass的原型prototype所以说是 SubClass.prototype继承了SuperClass

类式继承的缺点

  • 因为子类通过对其原型prototype对父类实例化,继承了父类,所以父类中的共有属性要是引用类型,就会在子类中被所有实例共用,因此一个子类的实例更改了子类原型从父类构造函数中继承的共有属性就会直接影响到其他子类
// 声明父类
function SupperClass() {
  this.books = ["html", "css"];
}

// 声明子类
function SubClass() {}

// 继承父类
SubClass.prototype = new SupperClass();

var ins1 = new SubClass();
var ins2 = new SubClass();

console.log(ins2.books); // ["html", "css"];
ins1.books.push("js");
console.log(ins2.books); // ["html", "css","js"];

ins1的一个无意的修改却伤害了ins2中book的属性,这就很容易埋藏陷阱

  • 由于子类实现的继承是靠其原型prototype对父类的实例化实现的,所以在创建父类的时候,是无法传递传递参数给父类的,所以在初始化父类的时候也无法对父类构造函数内的属性进行初始化
// 声明父类
function SupperClass() {
  this.books = ["html", "css"];
}

// 声明子类
function SubClass() {}

// 继承父类发生在这一步,所以导致了改缺陷的产生🎈🎈🎈🎈🎈🎈🎈
SubClass.prototype = new SupperClass();

var ins1 = new SubClass();
var ins2 = new SubClass();

不知道大家有没有注意到第一个缺陷中只提到的引用类型,有没有一瞬间想过为什么原始值类型不会有这个问题??

// 声明父类
function SupperClass() {
  this.book = "html";
}

// 声明子类
function SubClass() {}

// 继承父类
SubClass.prototype = new SupperClass();

var ins1 = new SubClass();
var ins2 = new SubClass();

console.log(ins1.books); // "html";
console.log(ins2.books); // "html";
ins1.book = "js";
console.log(ins1.books); // "js";
console.log(ins2.books); // "html";

这个取决于我们对原始值类型赋值方式

  • 当通过这样的方式修改属性时,其实相当于对in1实例本身增加了一个book属性,并没有去修改其继承父类的book属性,所以子类在更改原始类型数据时不会对其他子类产生影响

备注

  • 如果对引用类型数据也通过这种赋值方式,也不会对其他子类产生影响
  • 但是这就是潜在的风险,不能容忍必须解决.

创建即继承——构造函数继承

先来看实现

// 声明父类
function SupperClass(id) {
  this.books = ["html","css"];
  this.id = id
}

// 父类声明原型方法
SupperClass.prototype.showBooks = function() {
    return this.books
}

// 声明子类
function SubClass(id) {
    SupperClass.call(this,id)
}

测试一下

// 🎈🎈🎈🎈🎈🎈🎈对构造函数中的属性进行初始化🎈🎈🎈🎈🎈🎈🎈
var ins1 = newSubClass(10)
var ins2 = newSubClass(11)

console.log(ins2.books) // ["html","css"] 注意这里🎈🎈🎈
ins1.books.push("js")
console.log(ins1.books) // ["html","css","js"]
console.log(ins1.id) //  10
console.log(ins2.books) //["html","css"] 是不是没有发生改变😊😊😊😊😊😊 解决了 共有属性污染的问题
console.log(ins2.id) // 11

回想下类式继承的缺点是不是在构造函数继承中得到解决

构造函数继承

  • 核心代码SupperClass.call(this,id)
  • 在执行new SubClass()this绑定到SupperClass中所以子类中是可以继承父类的属性的

缺点

  • 因为没有涉及到父类的prototyoe所以自然无法继承父类prototype中的属性和方法

将优点统一起来——组合继承

看标题是不是猜到的大概,就是将类式继承构造函数继承结合起来

上代码

// 声明父类
function SupperClass(name) {
  this.books = ["html","css"];
  this.name = name
}

// 父类声明原型方法
SupperClass.prototype.getName = function() {
    return this.name
}

// 声明子类
function SubClass(name,time) {
    // 构造函数继承🎈🎈🎈
    SupperClass.call(this,name)
    this.time = time
}

// 类式继承🎈🎈🎈
SubClass.prototype = new SupperClass()

SubClass.prototype.getTime = function () {
    return this.time
}

测试一下

// 🎈🎈🎈🎈🎈🎈🎈对构造函数中的属性进行初始化🎈🎈🎈🎈🎈🎈🎈
var ins1 = newSubClass("ins1","2021")
ins1.books.push("js")
console.log(ins1.books) // ["html","css","js"]
console.log(ins1.getTime) //  "2021"
console.log(ins1.getName) // "ins1"

var ins2 = newSubClass("ins2","2023")
console.log(ins2.books) // ["html","css"] 注意这里🎈🎈🎈
console.log(ins2.getName) // "ins2"
console.log(ins2.getTime) // '2023'

优点

  • 子类继承了父类的构造函数属性以及prototype中方法
  • 子类实例更改继承父类的引用类型数据时,不会对其他子类实例产生影响
  • 子类实例化过程中也可以将参数传递到父类的构造函数中,对属性进行初始化

组合继承是最完美的继承方式吗?

  • 可以发现在构造函数继承时执行了一遍父类的构造函数
  • 实现类式继承时又调用了一遍父类的构造函数
  • 所以父类构造函数调用了两遍,所以它不是最完美的方式

难道还有更好的方式呢?

  • 后边我们慢慢聊