JavaScript 中的六种继承方式

简介: 继承的操作需要有一个父类,这里使用构造函数外加原型来创建一个

直接进入主题:


继承的操作需要有一个父类,这里使用构造函数外加原型来创建一个:


// super
function Person(name){
    this.name = name;
}
Person.prototype.job = 'frontend';
Person.prototype.sayHello = function() {
    console.log('Hello '+this.name);
}
var person = new Person('jia ming');
person.sayHello(); // Hello jia ming


原型链继承


// 原型链继承
function Child() {
    this.name = 'child';
}
Child.prototype = new Person();
var child = new Child();
console.log(child.job); // frontend
// instanceof 判断元素是否在另一个元素的原型链上
// child是Person类的实例
console.log(child instanceof Person); // true
复制代码


关键点:子类原型等于父类的实例Child.prototype = new Person()


原型链的详细讲解自己之前有一篇文章说到深入理解原型对象和原型链


特点


  1. 实例可继承的属性有:实例的构造函数的属性,父类构造函数的属性,父类原型上的属性。(新实例不会继承父类实例的属性)


注意事项


  1. 新实例无法向父类构造函数传参


  1. 继承单一


  1. 所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改)


借用构造函数


// 借用构造函继承
function Child() {
    Person.call(this, 'reng');
}
var child = new Child();
console.log(child.name); // reng
console.log(child instanceof Person); // false
child.sayHello(); // 报错,继承不了父类原型上的东西


关键点:用callapply将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))Person.call(this, 'reng')


针对call, apply, bind的使用,之前有篇文章谈谈JavaScript中的call、apply和bind提到。


特点


  1. 只继承了父类构造函数的属性,没有继承父类原型的属性


  1. 解决了原型链继承的注意事项(缺点)1,2,3


  1. 可以继承多个构造函数的属性(call可以多个)


  1. 在子实例中可以向父实例传参


注意事项


  1. 只能继承父类构造函数的属性


  1. 无法实现构造函数的复用。(每次用每次都要重新调用)


  1. 每个新实例都有构造函数的副本,臃肿


组合继承


组合继承是原型链继承和借用构造函数继承的组合。


// 组合继承
function Child(name) {
    Person.call(this, name);
}
Child.prototype = new Person();
var child = new Child('jia');
child.sayHello(); // Hello jia
console.log(child instanceof Person); // true


关键点:结合了两种模式的优点--向父类传参(call)和复用(prototype)


特点


  1. 可以继承父类原型上的属性,可以传参,可复用


  1. 每个新实例引入的构造函数属性是私有的


注意事项


  1. 调用了两次父类的构造函数(耗内存)


  1. 子类的构造函数会代替原型上的那个父类构造函数(call相当于拿到了父类构造函数的副本)


原型式继承


// 先封装一个函数容器,用来承载继承的原型和输出对象
function object(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}
var super0 = new Person();
var super1 = object(super0);
console.log(super1 instanceof Person); // true
console.log(super1.job); // frontend


关键点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了可以随意增添属性的实例或对象。

Object.create()就是这个原理。


特点


  1. 类似于复制一个对象,用函数来包装


注意事项


  1. 所有的实例都会继承原型上的属性


  1. 无法实现复用。(新实例属性都是后面添加的)


**Object.create()方法规范了原型式继承。**这个方法接收两个参数,一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。


// 传一个参数的时候
var anotherPerson = Object.create(new Person());
console.log(anotherPerson.job); // frontend
console.log(anotherPerson instanceof Person); // true


// 传两个参数的时候
var anotherPerson = Object.create(new Person(), {
    name: {
        value: 'come on'
    }
});
anotherPerson.sayHello(); // Hello come on


寄生式继承


function object(obj) {
    function F(){}
    F.prototype = obj;
    return new F();
}
var sup = new Person();
// 以上是原型式继承,给原型式继承再套个壳子传递参数
function subobject(obj) {
    var sub = object(obj);
    sub.name = 'ming';
    return sub;
}
var sup2 = subobject(sup);
// 这个函数经过声明后就成了可增添属性的对象
console.log(sup2.name); // 'ming'
console.log(sup2 instanceof Person); // true


关键点:就是给原型式继承外面套个壳子。


特点


  1. 没有创建自定义类型,因为只是套了个壳子,返回对象,这个函数顺理成章就成了创建的新对象。


注意事项


  1. 没用到原型,无法复用


寄生组合继承


它跟组合继承一样,都比较常用。


寄生:在函数内返回对象然后调用


组合


  1. 函数的原型等于另一个实例


  1. 在函数中用apply或call引入另一个构造函数,可传参


// 寄生
function object(obj) {
    function F(){}
    F.prototype = obj;
    return new F();
}
// object是F实例的另一种表示方法
var obj = object(Person.prototype);
// obj实例(F实例)的原型继承了父类函数的原型
// 上述更像是原型链继承,只不过只继承了原型属性
// 组合
function Sub() {
    this.age = 100;
    Person.call(this); // 这个继承了父类构造函数的属性
} // 解决了组合式两次调用构造函数属性的特点
// 重点
Sub.prototype = obj;
console.log(Sub.prototype.constructor); // Person
obj.constructor = Sub; // 一定要修复实例
console.log(Sub.prototype.constructor); // Sub
var sub1 = new Sub();
// Sub实例就继承了构造函数属性,父类实例,object的函数属性
console.log(sub1.job); // frontend
console.log(sub1 instanceof Person); // true


重点:修复了组合继承的问题


在上面的问题中,你可能发现了这么一个注释obj.constructor = Sub; // 一定要修复实例。为什么要修正子类的构造函数的指向呢?


因为在不修正这个指向的时候,在获取构造函数返回的时候,在调用同名属性或方法取值上可能造成混乱。比如下面:


function Car() { }
Car.prototype.orderOneLikeThis = function() {  // Clone producing function
    return new this.constructor();
}
Car.prototype.advertise = function () {
    console.log("I am a generic car.");
}
function BMW() { }
BMW.prototype = Object.create(Car.prototype);
BMW.prototype.constructor = BMW;              // Resetting the constructor property
BMW.prototype.advertise = function () {
    console.log("I am BMW with lots of uber features.");
}
var x5 = new BMW();
var myNewToy = x5.orderOneLikeThis();
myNewToy.advertise(); // => "I am BMW ..." if `BMW.prototype.constructor = BMW;` is not 
                      // commented; "I am a generic car." otherwise.



相关文章
|
1天前
|
JavaScript 前端开发
JavaScript 继承的方式和优缺点
JavaScript 继承的方式和优缺点
15 0
|
1天前
|
JavaScript 前端开发 Java
深入JS面向对象(原型-继承)(三)
深入JS面向对象(原型-继承)
31 0
|
1天前
|
JavaScript 前端开发 Java
深入JS面向对象(原型-继承)(一)
深入JS面向对象(原型-继承)
32 0
|
1天前
|
JavaScript 前端开发
js开发:请解释原型继承和类继承的区别。
JavaScript中的原型继承和类继承用于共享对象属性和方法。原型继承利用原型链查找属性,节省内存但不支持私有成员。类继承通过ES6的class和extends实现,支持私有成员但占用更多内存。两者各有优势,适用于不同场景。
22 0
|
1天前
|
JavaScript
|
1天前
|
JavaScript 前端开发
原型继承在 JavaScript 中是如何工作
原型继承在 JavaScript 中是如何工作
21 0
|
1天前
|
JavaScript 前端开发
JavaScript 中最常用的继承方式
【5月更文挑战第9天】JavaScript中的继承有多种实现方式:1) 原型链继承利用原型查找,但属性共享可能引发问题;2) 借用构造函数避免共享,但方法复用不佳;3) 组合继承结合两者优点,是最常用的方式;4) ES6的class继承,是语法糖,仍基于原型链,提供更直观的面向对象编程体验。
17 1
|
1天前
|
设计模式 JavaScript 前端开发
在JavaScript中,继承是一个重要的概念
【5月更文挑战第9天】JavaScript继承有优点和缺点。优点包括代码复用、扩展性和层次结构清晰。缺点涉及深继承导致的复杂性、紧耦合、单一继承限制、隐藏父类方法以及可能的性能问题。在使用时需谨慎,并考虑其他设计模式。
15 2
|
1天前
|
JavaScript 前端开发 开发者
JavaScript 继承的方式和优缺点
JavaScript 继承的方式和优缺点
12 0
|
1天前
|
JavaScript 前端开发
JavaScript 继承的方式和优缺点
JavaScript 继承的方式和优缺点