前言
? 大家好,我是南木元元,热爱技术和分享,欢迎大家交流,一起学习进步!
? 个人主页:南木元元
在 JavaScript 中,继承是一个重要的知识点,上篇文章中我们已经了解了原型和原型链的概念,本文就来介绍一下js中实现继承的几种方式。
目录
继承
实现继承的方法
1.原型链继承
2.借用构造函数继承
3.组合继承
4.原型式继承
5.寄生式继承
6.寄生组合式继承
7.class继承
结语
继承
继承,简单来讲就是让子类能够访问到父类的属性和方法,继承的主要作用就是实现代码的重用。
在JavaScript中,主要通过原型链来实现继承。我们重温一下构造函数、原型和实例的关系:
每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个指针指向原型。
实现继承的方法
1.原型链继承
原型链继承的基本思想:让子类构造函数的原型指向父类的实例。
function Parent () { this.name = 'kevin';}Parent.prototype.getName = function () { console.log(this.name);}function Child () {}//让子类原型指向父类实例Child.prototype = new Parent();var child1 = new Child();console.log(child1.getName()) // kevin
缺点是引⽤类型的属性被所有实例共享,并且创建子类型实例时,不能向父类型传参。
2.借用构造函数继承
基本思想:在子类构造函数中调用父类构造函数,使用call将父对象的构造函数绑定在子对象上。
function Parent () { this.names = ['kevin', 'daisy'];}function Child () { // 调用父类构造函数 Parent.call(this);}var child1 = new Child();child1.names.push('yayu');console.log(child1.names); // ["kevin", "daisy", "yayu"]var child2 = new Child();console.log(child2.names); // ["kevin", "daisy"]
解决了原型链继承不能传参问题和父类原型共享问题,但是无法实现函数方法的复用,方法都在构造函数中定义,每次创建实例都会创建一遍方法,方法本质上都变成了实例自己的方法,不是公共的方法。
3.组合继承
基本思想:将原型链和借用构造函数组合起来使用。使用原型链实现对原型方法的继承,通过借用构造函数的方式来实现属性的继承。
function Parent (name) { this.name = name; this.colors = ['red', 'blue', 'green'];}Parent.prototype.getName = function () { console.log(this.name)}function Child (name, age) { Parent.call(this, name); this.age = age;}Child.prototype = new Parent();Child.prototype.constructor = Child;var child1 = new Child('kevin', '18');child1.colors.push('black');console.log(child1.name); // kevinconsole.log(child1.age); // 18console.log(child1.colors); // ["red", "blue", "green", "black"]var child2 = new Child('daisy', '20');console.log(child2.name); // daisyconsole.log(child2.age); // 20console.log(child2.colors); // ["red", "blue", "green"]
这种方式的优点:既实现了函数复用,又保证每个实例都有自己的属性。
缺点:调用了两次父类的构造函数,造成了子类的原型中多了很多不必要的属性。
4.原型式继承
基本思想:基于已有的对象来创建新的对象。
// 利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型function createObj(o) { function F() {} F.prototype = o; return new F();}var person = { name: 'kevin', friends: ['daisy', 'kelly']}var person1 = createObj(person);var person2 = createObj(person);person1.name = 'person1';console.log(person2.name); // kevinperson1.friends.push('taylor');console.log(person2.friends); // ["daisy", "kelly", "taylor"]
缺点是引⽤类型的属性被所有实例共享。
ES5中存在Object.create()
的方法,能够代替上面的createObj方法。
5.寄生式继承
寄生式继承与原型式继承很接近,它的思想就是在原型式继承的基础上以某种方式增强对象,然后返回这个对象。
function createObj (o) { let clone = Object.create(o); clone.sayName = function () { console.log("hi"); } return clone;}
6.寄生组合式继承
组合继承的缺点就是调用了2次父构造方法。寄生组合式继承就是改造组合式继承,使用父类的原型的副本来作为子类的原型,这样就只调用一次父构造函数,避免了创建不必要的属性。
function Parent (name) { this.name = name; this.colors = ['red', 'blue', 'green'];}Parent.prototype.getName = function () { console.log(this.name)}function Child (name, age) { Parent.call(this, name); this.age = age;}Child.prototype = Object.create(Parent.prototype);Child.prototype.constructor = Child;//把子类的构造指向子类本身var child1 = new Child('kevin', '18');console.log(child1.colors);//[ 'red', 'blue', 'green' ]console.log(child1.getName());//kevin
7.class继承
在 ES6 中,可以使用 class 去实现继承。使用 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 super 。
class Parent { constructor(name) { this.name = name;} getName() { console.log(this.name);}}class Child extends Parent { constructor(name, age) { super(name); //使用this之前必须先调用super() this.age = age;}}// 测试let child = new Child("kevin", 18);console.log(child.name); // kevinconsole.log(child.age); // 18child.getName(); // kevin
注意:在 JS 中并不存在类, class 只是语法糖,本质还是函数。
class Person {}Person instanceof Function // true
class实现的继承本质上是寄生组合式继承。下面是使用babel将ES6代码编译成ES5后的代码:
function _possibleConstructorReturn(self, call) { // ... return call && (typeof call === 'object' || typeof call === 'function') ? call : self;}function _inherits(subClass, superClass) { // ... //看到没有 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var Parent = function Parent() { // 验证是否是 Parent 构造出来的 this _classCallCheck(this, Parent);};var Child = (function (_Parent) { _inherits(Child, _Parent); function Child() { _classCallCheck(this, Child); return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments)); } return Child;}(Parent));
核心是_inherits函数,可以看到它采用的依然也是寄生组合继承方式。不过这里加了一个Object.setPrototypeOf(subClass, superClass),这是用来干啥的呢?
答案是用来继承父类的静态方法。这也是原来的继承方式疏忽掉的地方。
ES5 继承和 ES6 继承的区别ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上 Parent.call(this)
ES6 的继承不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
结语
?如果此文对你有帮助的话,欢迎?关注、?点赞、⭐收藏、✍️评论,支持一下博主~