JS继承的6种方式(非ES6)
本篇博客主要来整合一下JS继承的六种方式及其优缺点。首先我们提供一个父类:
// 父类
function Person(name) {
this.name = name;
this.showName = function () {
console.log(this.name);
}
}
原型链继承
本节中我们使用到的子类如下:
// 子类
function Staff(office) {
this.office = office;
this.showOffice = function(){
console.log(this.office);
}
}
Staff.prototype = new Person('staff');
const a = new Staff('D302');
const b = new Staff('D232');
a.showName(); // satff
a.showOffice(); // D302
b.showName(); // staff
b.showOffice(); // D232
上述语句即为原型链继承,此时,a
与b
有一个共有属性name
,要注意此时name为基本数据类型,而原型链继承构造的子对象之间共享的是引用数据类型,如果我单独对a
的name
属性进行修改,那么b
的name
属性不会因此而改变。举例说明如下:
Staff.prototype = new Person('staff');
const a = new Staff('D302');
const b = new Staff('D232');
a.name = 'change'
a.showName(); // change
b.showName(); // staff
但是,如果Person
对象的name
属性值引用类型的话,那么构造出的子对象的属性就有可能会受到影响,实例如下:
Staff.prototype = new Person([1,2,3]);
const a = new Staff('D302');
const b = new Staff('D232');
a.showName(); // [1,2,3]
b.showName(); // [1,2,3]
a.name.push(4);
a.showName(); // [1,2,3,4]
b.showName(); // [1,2,3,4]
a.name = [1,2]
a.showName(); // [1,2]
b.showName(); // [1,2,3,4]
大家可以看到如上两种情况,我在往name
属性里面push的时候a
和b
的属性值都发生了变化,但是我在将a
的name
属性重新赋值为[1,2]时,b
的name
值并没有发生变化。这是因为进行push
操作之后引用数据类型的地址值并没有发生变化,而b
的name
属性指向的地址与a
的name
属性指向的地址相同,所以a
和b
会同时发生变化。但是我将其重新复制时,a
的name
属性指向的地址发生了变化,b指向的还是原来的地址,所以a
和b
并没有发生同步变化。
优点: 能通过instanceOf
和isPrototypeOf
的检测
缺点:
-
父对象中的属性也变成了子对象的prototype中的公用属性
2. 不能向父类型的构造函数中传递参数 3. 只能继承一个父类
借用构造函数
function Staff(name, office) {
Person.call(this, name);
this.office = office;
this.showOffice = function(){
console.log(this.office);
}
}
const a = new Staff('a', 'D302');
const b = new Staff('b', 'D232');
a.showName(); // a
b.showName(); // b
console.log(a instanceof Person); // false
通过这个实例我们可以看出我们可以向父类传递参数了,并且可以继承多个父类函数,只要调用多次call
函数即可.
优点:
- 可以向父类传递参数
- 可以实现多父类继承
- 父类的属性不会被子类共享
缺点:调用构造函数的话就相当于在每个新建对象内都有一个独立的属性已经函数,Staff
与Person
并不能使用instanceof
方法,两者并不存在指向关系,函数复用并没有派上用场。
组合继承
function Staff(name, office) {
Person.call(this, name);
this.office = office;
this.showOffice = function(){
console.log(this.office);
}
}
Staff.prototype = new Person();
const a = new Staff('a', 'D302');
const b = new Staff('b', 'D232');
a.showName(); // a
b.showName(); // b
console.log(a instanceof Person); // true
这种方法就是对原型链继承和构造函数继承的结合。
优点:
- 继承前两者的优点
- 可以实现对构造函数的复用
缺点:调用了两次父类构造函数,更耗内存
原型式继承
function container(obj){
function F() {}
F.prototype = obj;
return new F();
}
const child = new Person('child');
const staff = container(child);
staff.showName(); // child
这种继承方式其实可以看做是直接复制了父类。
缺点:无法复用
寄生式继承
function container(obj){
function F() {}
F.prototype = obj;
return new F();
}
function Staff(obj, office) {
const sub = container(obj);
sub.office = office;
sub.showOffice = function(){
console.log(this.office);
}
return sub;
}
const child = new Person('child');
const a = Staff(child, 'D232');
const b = Staff(child, 'C433');
a.showName(); // child
a.showOffice(); // D232
b.showName(); // child
b.showOffice(); // C433
这种继承方式就是在原型式继承的基础上又加了一个函数用来新增加属性,解决了原型式继承需要后续增加属性的问题。
寄生组合继承(常用)
寄生组合式继承强化的部分就是在组合继承的基础上减少一次多余的调用父类的构造函数:
function Staff(name, office) {
Person.call(this, name);
this.office = office;
this.showOffice = function () {
console.log(this.office);
}
}
Staff.prototype = Object.create(Person.prototype);
Staff.prototype.constructor = Person;
const a = new Staff('a', 'D232');
const b = new Staff('b', 'C433');
a.showName(); // a
a.showOffice(); // D232
b.showName(); // b
b.showOffice(); // C433