详解JavaScript实现继承的五种经典方式(附图解)
作者:自律版Zz
JavaScript中的继承是一种机制,通过它可以创建一个对象,该对象可以享有另一个对象的属性和方法,本文将详细的为大家介绍实现继承的五种经典方式,感兴趣的小伙伴跟着小编一起来看看吧
前言
- 在 JavaScript 中,实现继承的几种常见方式包括:
- 原型链继承: 此种方式也有两种方式
- 1.1 共享同一个父类实例
- 1.2 绕过父类实例,共享同一个类型原型
- 构造函数继承(借用构造函数)
- 组合继承
- 原型式继承
- ES6 类继承
原型链继承
原型链继承-共享同一个父类实例
这是 JavaScript 最早的继承方式,通过将子类的原型对象指向父类的实例,实现子类继承父类的属性和方法。但它有一个缺点,就是所有子类实例共享同一个父类实例。
function Parent() { this.property = "parentProperty"; } Parent.prototype.say = function () { console.log("Parent say"); }; function Child() { this.childProperty = "childProperty"; } // 修改 Child 的 prototype 属性指向 Parent 实例对象,那么 Child 实例对象的 __proto__ 就会指向其构造函数 Child 的 prototype 属性(即Parent 实例对象) // 修改了 Child.prototype 的指向后,那么原来 Child.prototype 指向的对象由于被没有引用,就会被回收。 Child.prototype = new Parent(); const childInstance = new Child(); console.log(childInstance.property); // 输出 'parentProperty' childInstance.say();
下图是代码图解:
上面的代码看似没有问题,但其实还是存在缺陷:
console.log(Child.prototype.constructor); // 输出:[Function: Parent], 即是 Parent 构造函数,这是由于 Child.prototype 自身没有,就会沿着 __proto__ 寻找,因此找到 Parent。这明显是不对的 console.log(Child.prototype.constructor === Child); // 输出:false, 这明显也是不对的
因此我们需要再修改 Child.prototype
的指向之后(即代码 Child.prototype = new Parent();
),需要同时修改 Child.prototype.constructor
的指向:
Child.prototype = new Parent(); +Child.prototype.constructor = Child;
- 后续代码也是同理!
原型链继承-绕过父类实例,共享同一个父类的原型
function Parent() { this.property = "parentProperty"; } Parent.prototype.say = function () { console.log("Parent say"); }; function Child() { Parent.call(this); this.childProperty = "childProperty"; } // 方式一:直接指向 // Child.prototype.__proto__ = Parent.prototype; // 方式二:使用 Object.create(),这是 es5 的方法 Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; let childInstance = new Child(); console.log(childInstance.property); childInstance.say();
下图是代码图解:
构造函数继承(借用构造函数)
这种方式通过在子类构造函数中调用父类构造函数,实现继承属性。这样每个子类实例都拥有独立的属性副本,但无法继承父类原型上的方法。
function Parent() { this.property = "parentProperty"; } Parent.prototype.say = function () { console.log("Parent say"); }; function Child() { Parent.call(this); this.childProperty = "childProperty"; } const childInstance = new Child(); console.log(childInstance.property); // 输出 'parentProperty' // childInstance.say() // 报错:childInstance.say is not a function
下图是代码图解:
组合继承
组合继承结合了原型链继承和构造函数继承,通过在子类构造函数中调用父类构造函数,然后设置子类的原型为一个父类实例,实现了既能继承属性又能继承方法。
function Parent() { this.property = "parentProperty"; } Parent.prototype.say = function () { console.log("Parent say"); }; function Child() { Parent.call(this); this.childProperty = "childProperty"; } Child.prototype = new Parent(); // 注意:修改其原型对象之后,同时必须得修改 constructor 的指向 Child.prototype.constructor = Child; const childInstance = new Child(); console.log(childInstance.property); // 输出 'parentProperty' childInstance.say();
原型式继承
这种继承方式创建一个临时的构造函数,将这个构造函数的原型指向父构造函数的原型,再将子构造函数的原型指向该构造函数的实例,从而实现继承。
function mockExtend(Parent, Child) { function Fn() {} /** * 1. 修改了 Fn.prototype 的指向后,那么原来的 Fn.prototype 没有被引用,则会被回收 * 2. 那么Fn的实例对象的 __proto__ 就指向其构造函数的 prototype */ Fn.prototype = Parent.prototype; Child.prototype = new Fn(); // 注意:修改了原型对象之后,同时必须得修改 constructor 的指向 Child.prototype.constructor = Child; } // =============================== 使用 ================================== function Parent() { this.property = "parentProperty"; } Parent.prototype.say = function () { console.log("Parent say"); }; function Child() { Parent.call(this); this.childProperty = "childProperty"; } mockExtend(Parent, Child); const childInstance = new Child(); console.log(childInstance.property); childInstance.say();
下图是代码图解:
ES6 类继承
ES6 引入了 class 关键字,使继承更加易读和易用。通过 extends 关键字,一个类可以继承另一个类的属性和方法。
class Parent { constructor() { this.property = "parentProperty"; } say() { console.log("Parent say"); } } class Child extends Parent { constructor() { super(); this.childProperty = "childProperty"; } } const childInstance = new Child(); console.log(childInstance.property); // 输出 'parentProperty' childInstance.say();
到此这篇关于详解JavaScript实现继承的五种经典方式(附图解)的文章就介绍到这了,更多相关JavaScript实现继承方式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!