关于JS继承的几种实现方式

204 阅读4分钟

继承

原型链继承

基本思路:利用原型让一个引用类型继承另一个引用类型的属性和方法

原型和构造函数和实例之间的关系

  1. 每个构造函数都有一个原型对象
  2. 原型对象都有一个指针指向构造函数
  3. 实例都包含一个指向原型对象的内部指针
function Father (){
    this.name = "Father"
    this.friends = ["Jack","May"]
}
Father.prototype.sayName = function(){
    console.log(this.name)
}
function Son (){
    this.name = "Son"
}
//子类的原型指向父类的实例
Son.prototype = new Father()
Son.prototype.sayName = function(){
    console.log(this.name)
}
const testSon = new Son()
testSon.sayName()//"Son"
console.log(testSon.friends)//"Jack","May"
testSon.friends.push("Sonfriend-Jone")
const testSon1 = new Son()
console.log(testSon.friends)//"Jack","May","Sonfriend-Jone"
console.log(testSon1.friends)//"Jack","May","Sonfriend-Jone"

原型链继承存在的问题:

  1. 所有实例都共享一个原型,导致引用类型的数据会被共享,实例修改的引用数据来自原型,一旦修改,改的是原型中的引用数据,会导致其他对象也会受到牵连,也就是说,失去了私有的来自继承的变量
  2. 创建子类的时候,不能向父类去传参

借用构造函数继承

基本思路:在子类中去调用父类的构造函数,通过apply和call在将来新建的对象上执行构造函数

function Father(){
    this.friends = ["Jack-F","May-F"]
}
function Son(){
    //继承了Father的属性
    Father.call(this)
}

const testSon = new Son()
testSon.friends.push("Jone-S")
console.log(testSon.friends)//"Jack-F","May-F","Jone-S"
const testSon2 = new Son()
console.log(testSon2.friends) //"Jack-F","May-F"

此时可以看到,引用类型形成了自己的私有变量

另外还可以使用apply,向父类构造函数进行传参

function Father(name){
	this.name = name
}
function Son(){
	Father.call(this,"Son1")
	this.age = 18
}
const testSon = new Son()
console.log(testSon.name,testSon.age)//Son1 18

但是这种模式也有问题,它仅仅是借壳生产,在原型层面是并没有让Son和Father进行任何的联系,这就意味着我们不能像原型继承一样,对一些可以提到公共领域(原型)的方法进行复用,方法只能在构造函数中定义

组合式继承

基本思路:结合原型链继承和借用构造函数继承,使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现自己的私有属性

function Father(name){
	this.name = name
    this.friends = ["Jack-F","May-F"]
}
Father.prototype.sayName = function (){
    console.log(this.name)
}
function Son(name,age){
    Father.call(this,name)
    this.age = age
}
//通过原型建立继承关系
Son.prototype = new Father()
console.log(Son.prototype.constructor)//此时构造函数改变了!!!!!
Son.prototype.constructor = Son//所以我们要把构造函数改回来
//添加公用方法
Son.prototype.sayAge = function(){
    console.log(this.age)
}
const son1 = new Son("son1",16)
son1.friends.push("Mike-S1")
console.log(son1.friends)
son1.sayName()
son1.sayAge()

const son2 = new Son("son2",17)
console.log(son2.friends)
son2.sayName()
son2.sayAge()

原型式继承

基本思路:假设我已经有了一个对象,那么我可以通过这个对象来构建一个新对象,并且让他人我做爸爸

//o为已有对象
function Object(o){
    function F(){}
    F.prototype = o
    return new F()
}

const father = {
    name:"Jack",
    friends:["May-F","Mike-F"]
}

const son = Object(father)
console.log(son.name)//Jack
console.log(son.friends) //"May-F","Mike-F"
son.name = "pekey"
son.friends.push("Rob-S1")
console.log(son.name)//pekey
console.log(son.friends) //"May-F","Mike-F","Rob"

const son2 = Object(father)
son2.name="Lida"
son2.friends.push("James-S2")
console.log(son2.name)//Lida
console.log(son2.friends)//"May-F","Mike-F","Rob","James-S2"

可以使用Object.create来代替上述的object

可以看到我们利用father对象来构造出一个son对象,而且实现了原型继承,但是对于引用类型也是会出现没有形成私有的情况

寄生式继承

基本思路:利用原型式继承生成一个下级对象,然后让该对象去拥有属于自己的方法和属性

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

const father = {
    name:"Jack",
    friends:["May-F","Mike-F"]
}
//这一步相当于让原型式继承出来的函数包装自己的属性和方法
function create(o){
    const clone = Object(o)
    clone.say = function(){
        console.log("Hi")
    }
    return clone
}

const son = create(father)
son.say()

寄生组合式继承

由于组合式继承调用了两次父类构造函数,第一次是在创建子类原型的时候,第二次在子内借用构造函数的时候,有没有什么方法只调用一次?

在第一次调用时,我们要指定子类的原型,所以调用父类构造函数生成一个实例对象,这一步时可以省略的

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

function inherit(Son,Father){
    const prototype = Object(Father.prototype)//既然我们使用的是父类的原型对象,那么我们没有必要使用实例化对象,直接拿原型对象来构造子类的实例对象
    prototype.constructor = Son
    Son.prototype = prototype
}

function Father(name){
    this.name = name
    this.friends = ["Jack-F","May-F"]
    this.home = "A street"
}

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

function Son(name,age){
    Father.call(this,name)
    this.age = age
}

inherit(Son,Father)

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

const testSon1 = new Son("James",18)
testSon1.sayName()
testSon1.sayAge()
console.log(testSon1.friends)
testSon1.friends.push("Linda-S1")
testSon1.home = "B street"
console.log(testSon1.home)

const testSon2 = new Son("Jimy",17)
testSon2.sayName()
testSon2.sayAge()
console.log(testSon1.friends)
console.log(testSon2.home)