JS 没有类的概念,那 JS 中的继承是怎么实现的呢?
其实在 JS 中,继承全靠原型链。
一,原型链继承
function Person() {
this.name = 'person'
this.cxk = ['sing', 'dance']
}
Person.prototype.getName = function() {
return this.name
}
function Man() {
this.age = 12
}
// 把父类的一个实例对象赋给子类的原型对象
Man.prototype = new Person()
let man1 = new Man()
let man2 = new Man()
man1.cxk.push('rap')
console.log(man1.age) // 12
console.log(man1.name) // person
console.log(man1.getName()) // person
console.log(man1.cxk) // sing, dance, rap
console.log(man2.cxk) // sing, dance, rap
原理:
- 把父类的一个实例对象赋给子类的原型对象;
优点:
- 子类可以访问父类原型对象上的方法和属性;
缺点:
-
无法传参;
-
只能继承一个父对象,无法实现多继承;
-
父类原型对象上的方法和属性、构造函数中的复杂数据类型的方法和属性,子类会共享使用;
为了解决子类会共享使用父类的某些方法或属性这个问题,我们还可以选择使用第二种继承方式。
二,构造函数继承
function Person1(name) {
this.name = name || 'person'
this.arr = [7, 8, 9]
}
function Person2() {
this.gender = '男'
}
Person1.prototype.getName = function() {
return this.name
}
function Man(name, gender) {
//通过 call 改变 this 作用域
Person1.call(this, name)
Person2.call(this, gender)
this.age = 12
}
var man1 = new Man('cxk')
var man2 = new Man('cxk')
man1.arr.push(10)
console.log(man1.name) // cxk
console.log(man1.gender) // 男
console.log(man1.getName) // undefined
console.log(man1.arr) // 7, 8, 9, 10
console.log(man2.arr) // 7, 8, 9
原理:
- 通过 call 改变了 this 作用域(apply 当然也可以);
优点:
-
支持传参;
-
可以实现多继承;
-
可以继承父类自身的方法和属性,并且复杂数据类型的数据也是独立继承的;
缺点:
-
不能继承父类原型对象上的方法和属性;
第一种方式可以继承父类原型对象上的方法和属性,可以继承父类构造函数自身的方法和属性但是却是共享继承,而第二种方式不可以继承父类原型对象上的方法和属性但可以独立继承父类构造函数的方法和属性,我们把这两种方式联合起来放在一块看看:
三,组合继承
function Person(name) {
this.name = name || 'person'
this.arr = [7, 8, 9]
}
Person.prototype.list = [1, 2, 3]
Person.prototype.getName = function() {
return this.name
}
function Man(name) {
// 第一次
Person.call(this, name)
this.age = 12
}
// 第二次
Man.prototype = new Person()
let man1 = new Man('cxk')
let man2 = new Man('cxk')
console.log(man1.name) // cxk
man1.arr.push(10)
console.log(man1.arr) // 7, 8, 9, 10
console.log(man2.arr) // 7, 8, 9
man1.list.push(4)
console.log(man1.list) // 1, 2, 3, 4
console.log(man2.list) // 1, 2, 3, 4
原理:
- 原型链继承 + 构造函数继承;
优点:
-
支持传参;
-
可以实现多继承;
-
可以继承父类构造函数的方法和属性,也可以继承父类原型对象上的方法和函数;
-
父类构造函数的方法和属性不会共享;
缺点:
-
执行了两次继承父类构造函数方法和属性的操作,会造成一点点性能丢失;
为了解决这个问题,我们可以使用第四种继承方式来替代原型链继承方式。
四,寄生继承
function Person(name) {
this.name = name || 'person'
}
Person.prototype.getName = function() {
return 'hello world'
}
function Man(age) {
this.age= age || 12
}
Man.prototype = Object.create(Person.prototype)
let man1 = new Man()
console.log(man1.getName()) // hello world
原理:
- 使用 ES5 新增的 Object.create() 方法,通过在不调用 new 关键字的情况下使子类继承父类的原型对象;
特点:
- 子类只继承父类原型对象上的方法和属性,并且为独立继承,子类之间的数据不会相互影响;
五,寄生组合继承
function Person(name) {
this.name = name || 'person'
}
Person.prototype.getName = function() {
return this.name
}
function Man(age) {
Person.call(this, name)
this.age = age || 12
}
Man.prototype = Object.create(Person.prototype)
let man1 = new Man('cxk')
console.log(man1.name) // person
console.log(man1.getName()) // person
原理:
- 寄生继承 + 构造函数继承;
优点:
-
可以实现多继承;
-
支持传参;
-
子类继承的所有父类的方法和属性,都是独立继承的不会相互影响;
六,ES6 继承
class Person {
constructor(PersonName) {
this.PersonName = PersonName
}
hello() {
console.log('JS')
}
}
class Man extends Person {
constructor(name, PersonName) {
super(PersonName)
this.name = name
}
hello() {
console.log('Node')
}
}
let man1 = new Man('cxk', 'basketball')
console.log(man1.name) // cxk
console.log(man1.PersonName) // basketball
ES6 通过 extends关键字实现继承。
在 constructor 函数外部定义的方法或属性,相当于是在原型对象上添加的方法或属性。而在 constructor 内部定义的方法和属性,就相当于是在构造函数里添加的方法或属性。
子类需要在 constructor 函数内部第一行通过 super() 来继承父类 constructor 函数内部定义的方法和属性。
ok完结,撒花 🌸✨🌼🌻🌺