继承的本质是原型链
实现继承的几种方式
JS继承方式
一、借助构造函数实现继承
原理:用 apply、call 修改函数运行的上下文,通过这种调用把父级即 Parent1 构造函数在子函数就是说子类函数里面执行的话同时修改父级构造函数this执行,也就是指向了child1实例化的对象引用,修改this指向,导致父类执行时属性都会挂在child1 子类上去
缺点:
这种方式的继承,父类原型对象(prototype)上的方法(属性)没有被子类继承,只实现部分继承,没有实现真正继承,能继承构造函数上的方法,但是不能继承父类原型对象的方法(属性)
1 function Parent1 () { 2 this.name = 'parent1' 3 } 4 5 // Parent1.prototype.say = function () { 6 // } 7 8 function Child1 () { 9 Parent1.call(this) //用apply 也可以, 10 11 this.type = 'child1' 12 } 13 14 // console.log(new Child1, new Child1().say())
二、 借助原型链实现继承
原理:每个构造函数都有prototype 属性,子类.prototype 是一个对象,这个对象可以任意赋值,这个时候赋值给父类 Parent2 一个实例,当实例化子类Child2时,有 new Child2().__proto__, new Child2().__proto__ === Child2.prototype,所以有 new Child2().__proto__.name 这个属性,值是: 'parent2'
缺点:原型链中的原型对象是共有的,实例改变其值,其他实例对应的属性也会改变
1 function Parent2 () { 2 this.name = 'parent2' 3 this.play = [1, 2, 3] 4 } 5 6 function Child2 () { 7 this.type = 'child2' 8 } 9 Child2.prototype = new Parent2() 10 console.log(new Child2) 11 // console.log(new Child2().__proto__ === Child2.prototype) 12 // console.log(new Child2().__proto__.name) 13 14 var s1 = new Child2() 15 var s2 = new Child2() 16 console.log(s1.play, s2.play) 17 s1.play.push(4)
三、 组合方式
原理:结合了构造函数和原型链实现继承的组合方式
缺点:父级的构造函数执行了两次,一次是 new 实例化子类的时候,一次是Child3.prototype又执行了一次,没有必要执行两次,并把父类的constructor 也继承了
1 function Parent3 () { 2 this.name = 'Parent3' 3 this.play = [1, 2, 3] 4 } 5 6 function Child3 () { 7 Parent3.call(this) 8 this.type = 'child3' 9 } 10 Child3.prototype = new Parent3() 11 var s3 = new Child3() 12 var s4 = new Child3() 13 s3.play.push(4) 14 console.log(s3.play, s4.play)
四、 组合继承的优化1
这种方式的优点,父类只执行一次,在实例化子类的时候执行了一次,原型对象上只是简单的引用,js上的数值有引用类型和值类型,这里都是对象,所以都是引用类型,所以这一块不会再执行父级的构造函数
缺点:把 父类的 constructor 也继承了,Child4.prototype = Parent4.prototype, prototype里有 constructor,子类的constructor 是从父类的constructor继承的
1 function Parent4 () { 2 this.name = 'Parent4' 3 this.play = [1, 2, 3] 4 } 5 6 function Child4 () { 7 Parent4.call(this) 8 this.type = 'child4' 9 } 10 Child4.prototype = Parent4.prototype //js上的数值类型有引用类型和值类型,这里都是对象,所以都是引用类型,所以这一块不会再执行父级的构造函数 11 var s5 = new Child4() 12 var s6 = new Child4() 13 // s5.play.push(4) 14 console.log(s5, s6) 15 // console.log(s5.play, s6.play) 16 console.log(s5 instanceof Child4, s5 instanceof Parent4) 17 console.log(s5.constructor)
五、组合继承的优化2
原理: 通过 Object.create 方法创建一个中间对象,它创建该对象的原型对象就是参数,然后把子类的构造函数赋值为该子类
1 function Parent5 () { 2 this.name = 'Parent5' 3 this.play = [1, 2, 3] 4 } 5 6 function Child5 () { 7 Parent5.call(this) 8 this.type = 'child5' 9 } 10 Child5.prototype = Object.create(Parent4.prototype) // __proto__; 通过 Object.create 方法创建一个中间对象,它创建该对象的原型对象就是参数,然后把子类的构造函数赋值为该子类 11 Child5.prototype.constructor = Child5 12 var s7 = new Child5() 13 console.log(s7 instanceof Child5, s7 instanceof Parent5) // true, true 14 console.log(s7.constructor)
ES6 class 继承方式
1、子类继承父类要用 extends,子类里面要用 supper()
1 // 父类 2 class People { 3 constructor(name) { 4 this.name = name 5 } 6 eat() { 7 console.log(`${this.name} eat something`) 8 } 9 } 10 11 // 子类 12 class Student extends People { 13 constructor(name, number) { 14 super(name) 15 this.number = number 16 } 17 sayHi() { 18 console.log(`姓名 ${this.name} 学号 ${this.number}`) 19 } 20 } 21 22 // 子类 23 class Teacher extends People { 24 constructor(name, major) { 25 super(name) 26 this.major = major 27 } 28 teach() { 29 console.log(`${this.name} 教授 ${this.major}`) 30 } 31 } 32 33 34 const xialuo = new Student('夏洛', 100) 35 console.log(xialuo.name) 36 console.log(xialuo.number) 37 xialuo.sayHi() 38 xialuo.eat() 39 40 console.log(xialuo.hasOwnProperty('name')) // true 41 console.log(xialuo.hasOwnProperty('sayHi')) //false 42 console.log(xialuo.hasOwnProperty('eat')) //false 43 44 // 实例 45 const wanglaoshi = new Teacher('王老师', '语文') 46 console.log(wanglaoshi.name) 47 console.log(wanglaoshi.major) 48 wanglaoshi.teach() 49 wanglaoshi.eat()
2、super关键字
super这个关键字,既可以当作函数使用,也可以当作对象使用。当作函数使用时,super代表父类的构造函数,并在子类中执行Parent.apply(this),从而将父类实例对象的属性和方法,添加到子类的this上面。以下三点需要特别注意:
1)子类必须在constructor方法中调用super方法,如果子类没有定义constructor方法,constructor方法以及其内部的super方法会被默认添加。
2)在子类的constructor方法中,只有调用super之后,才可以使用this关键字,否则会报错。
3)super作为对象时,在子类中指向父类的原型对象。即super=Parent.prototype。
3、static关键字
在一个方法前加上关键字static,就表示该方法不会被实例继承,但是父类的静态方法,会被子类继承。
1 // 父类 2 class People { 3 constructor(name) { 4 this.name = name 5 } 6 static showName(name) { 7 console.log(name) 8 } 9 eat() { 10 console.log(`${this.name} eat something`) 11 } 12 } 13 14 // 子类 15 class Student extends People { } 16 17 Student.showName('zhangsan') // zhangsan 18 19 var p = new People() 20 p.showName('zhangsan') //Uncaught TypeError: p.showName is not a function