说一下js中继承的前世今身

1,048 阅读4分钟

1. 构造函数继承(Call式继承)

借助call函数,继承的是属性(或者使用apply函数)

参数特别多的话就要使用apply+arguments(里面的Person)

 function Person(name,age){
     this.name = name
     this.age =age
 }
 function Star(name,age){
     /* this.name = '张三'
     this.age =23 */
     Person.call(this, name,age)// 这里后面的name和age传给了Person方法(是Star的实参,外面不传实参,直接在这里写实参也可以)
     // Person.apply(this, [name, age])
     // Person中的this指向call后面括号里的this,而括号里的this指向new Star创建的实例
 }
 const s =new Star()

方法不能往构造函数上面挂,存在内存的浪费,往原型对象上挂载(多个实例访问的是同一个原型对象)

 // 如果是下面这种
 function Person(name,age){
  this.name = name
  this.age =age
  this.say=function(){ console.log('11') }
 }
 const p1 = Person(xx,xx)
 const p2 = Person(yy,yy)
 //这样p1和p2实例上面的say方法不相同,被重新创建了一份
 
 // 如果是下面这样,那么重新new的实例指向的say是同一个地址,性能提高
 Person.prototype.say=...

子类继承父类

2. 原型继承

通过改造原型链去继承父类构造函数原型上的方法

call/apply不会访问原型,只会继承属性,实例才会访问原型上的属性或者方法

 function Person(name,age){
     this.name = name
     this.age =age
 }
 Person.prototype,say = function(){
     console.log('hello world')
 }
 function Star(name,age){
 
 }

// 原型继承 => 继承的是方法
 // 直接把Star的原型指向Person的实例
 Star.prototype = new Person()
 
 // 误区  不能 Star.prototype = Person.prototype
 // 这样的话,Star的原型指向了Person的原型,这样给Star的原型添加方法会影响Person的原型,二者指向同一地址

 // 同时这样继承了Person的实例之后,发现Star.prototype.constructor指向了Star。(实际上是p1.constructor,实例没有去找原型的,就是p1.___proto__.constructor,注意p1 = new Person())
 // 还需要
 Star.prototype.constructor = Star
 
 const zs = new Star('尼古拉斯', 30)
 // 这样才能使用
 Star.prototype.constructor ===Star// true
 zs.constructor ===Star//true
 zs.__proto__.constructor ===Star//true

说明:

虽然原型继承也继承了父类的属性,但是这里不会传递参数,而且子类Star构造函数可以随意给自己的prototype添加属性和方法而覆盖父类传过来的静态属性,原型继承主要继承的就是父类的方法,通过原型链找到父类的方法

3. 组合继承

属性会使用this挂载到构造函数上,方法会放在构造函数的原型上

 function Person(name,age){
     this.name = name
     this.age =age
 }
 Person.prototype.say = function(){
     console.log('hello world')
 }
 function Star(name,age){
 Person.call(this, name, age)
 }
 
 // 原型继承 => 继承的是方法
 // 直接把Star的原型指向Person的实例
 Star.prototype = new Person()
 Star.prototype.constructor = Star

4. 寄生组合继承(优化原型继承)--常用

之前原型继承中把父类构造函数的静态属性也继承过来了,但是根本就不需要new的这一过程,只想继承父类构造函数的原型上的方法。

利用Object.create()方法来进行优化,省去了不必要的new操作

  1. Object.create(xx)会创建一个新对象
  2. 并将这个新对象对象的__proto__指向传入的参数对象xx

可以这样改造

 function Person(name,age){
     this.name = name
     this.age =age
 }
 Person.prototype.say = function(){
     console.log('hello world')
 }
 function Star(name,age){
     this.hobby='唱歌'
 
 }
 
 // Star.prototype = new Person()
 Star.prototype = Object.create(Person.prototype)
 Star.prototype.constructor = Star
 
 const zs = new Star('尼古拉斯', 30)

5. ES6的extends继承

先介绍下class

// 原来写法
/* function Person(name, age){
    this.name = name
    this.age = age
}
Person.prototype.sayhi=function(){}
Person.prototype.jump=function(){} */

// class关键字创建两个类,可以直接new
// 好处:构造函数的属性和方法比较分散,需要分开写,而class创建的类是一个整体

// 人类
// 注意person后面没有小括号
class Person{
// constructor类似于之前的构造函数
constructor(name, age){
    // 1.实例属性/方法
        this.name = name
        this.age = age
        
    //### 写在这里的可以接受constructor的参数
    this.state ={
        age: 18
    }
    }
    
	// 1.实例属性/方法,等价于###处的代码。写在这里的无法接收参数(不要用const声明)
	state = {
        age:18
    }
	handldClick = () => {}

// 固定写法,函数之间没有逗号,不能改成function或者箭头函数
// 底层:这两个方法加在了Person的prototype上
// 2.原型方法(es6不能定义原型属性,原型属性本来就应该不一样)
    sayhi(){
        console.log('你好')
    }
    jump(){
        console.log('会跳')
    }

	// 3.静态属性/方法
	static version = 888
	static eat(){
        console.log('eat')
    }
}

// 老师类
class Teacher{
    constructor(name, age, lesson){
        this.name = name
        this.age = age
        this.lesson = lesson
    }
    sayhi(){
        console.log('你好')
    }
    jump(){
        console.log('会跳')
    }
}
const p =new Person('zs', 20)
const t =new Teacher('li', 20, 'en')
console.log(p, t)

我们发现t实例和p实例出现了重复的属性和方法。借助extends和super关键字来完成继承

注意这里底层原理是寄生式组合继承

class Person{
 constructor(name, age){
         this.name = name
         this.age = age
     }
     sayhi(){
         console.log('你好')
     }
     jump(){
         console.log('会跳')
     }
 }
 
 class Teacher extends Person{
     // 不写constructor,默认会调用call继承父类Person的name和age属性
     teach(){
         console.log('会教书')
     }
 }
 const t = new Teacher()

 // 老师类
 class Teacher extends Person{
     constructor(name, age, lesson){
         // super关键字继承父类构造函数的属性(进行实例的属性初始化)
         /* 注意: 
         1.super关键字必须要有
         2.顺序不能反了,必须先继承父类(先super),在写自己的属性
         */
         super(name, age)
         this.lesson = lesson
     }
     teach(){
         console.log('会教书')
     }
 }

做了三件事

  1. Person.call(this, name, age)借调父类属性
  2. Teacher.prototype = Object.create(Person.prototype)借调父类方法
  3. Teacher.prototype.constructor = Teacher将原型的constructor指回自身构造函数