继承可以是的子类具有父类的各种属性和方法,从而不在需要编写相同的代码,在子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。
JavaScript实现继承主要有以下几种方式
- 原型链继承
- 构造函数继承
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
原型链继承
原型链继承是比较常见的继承方式之一,其中设计的构造函数、原型和实例,三者之间存在一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针
function Parent(){ this.name = 'parent1' this.play = [1,2,3] } function Child(){ this.type = 'child2' } Child1.prototype = new Parent(); console.log(new Child())
上述代码看似没有问题,实际存在潜在问题
var s1 = new Child2(); var s2 = new Child2(); s1.play.push(4); console.log(s1.play,s2.play)
改变s1的play属性,会发现s2也跟着发生变化了,这是因为两个实例使用的是同一个原型对象,内存空间是共享的。
构造函数继承
借助call 调用Parent函数, 父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法,相比第一种原型链继承方式,父类的引用属性不会被共享,优化了第一种继承方式的弊端,但是只能继承父类的实例属性和方法,不能继承原型属性或者方法。
function Parent(){ this.name = 'parent1'; } Parent.prototype.getName = function() { return this.name; } function Child(){ Parent.call(this); this.type = 'child' } let child = new Child(); console.log(child); //Child { name: 'parent1', type: 'child' } console.log(child.getName());//报错child.getName is not a function
组合继承
组合继承是将原型链继承和构造函数继承组合起来,这种方式看起来就没什么问题,方式一和方式二的问题都解决了,但是从上面代码我们也可以看到 Parent3
执行了两次,造成了多构造一次的性能开销
function Parent3 () { this.name = 'parent3'; this.play = [1, 2, 3]; } Parent3.prototype.getName = function () { return this.name; } function Child3() { // 第二次调用 Parent3() Parent3.call(this); this.type = 'child3'; } // 第一次调用 Parent3() Child3.prototype = new Parent3(); // 手动挂上构造器,指向自己的构造函数 Child3.prototype.constructor = Child3; var s3 = new Child3(); var s4 = new Child3(); s3.play.push(4); console.log(s3.play, s4.play); // 不互相影响 console.log(s3.getName()); // 正常输出'parent3' console.log(s4.getName()); // 正常输出'parent3'
原型式继承
这里主要借助Object.create
方法实现普通对象的继承,这种继承方式的缺点也很明显,因为Object.create
方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能。
let parent4 = { name: "parent4", friends: ["p1", "p2", "p3"], getName: function() { return this.name; } }; let person4 = Object.create(parent4); person4.name = "tom"; person4.friends.push("jerry"); let person5 = Object.create(parent4); person5.friends.push("lucy"); console.log(person4.name); // tom console.log(person4.name === person4.getName()); // true console.log(person5.name); // parent4 console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"] console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]
寄生组合式继承
寄生组合式继承,借助解决普通对象的继承问题的 Object.create
方法,在亲全面几种继承方式的优缺点基础上进行改造,这也是所有继承方式里面相对最优的继承方式
function clone (parent, child) { // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程 child.prototype = Object.create(parent.prototype); child.prototype.constructor = child; } function Parent6() { this.name = 'parent6'; this.play = [1, 2, 3]; } Parent6.prototype.getName = function () { return this.name; } function Child6() { Parent6.call(this); this.friends = 'child5'; } clone(Parent6, Child6); Child6.prototype.getFriends = function () { return this.friends; } let person6 = new Child6(); console.log(person6); //{friends:"child5",name:"child5",play:[1,2,3],__proto__:Parent6} console.log(person6.getName()); // parent6 console.log(person6.getFriends()); // child5
总结
通过Object.create
来划分不同的继承方式,最后的寄生式组合继承方式是通过组合继承改造之后的最优继承方式,而 extends
的语法糖和寄生组合继承的方式基本类似