原型链继承
原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针。例如:
function Parent1() {
this.name = 'parent1';
this.play = [1, 2, 3]
}
function Child1() {
this.type = 'child2';
}
Child1.prototype = new Parent1();
console.log(new Child1());上面的代码其实有一个潜在的问题,例如:
var s1 = new Child1();
var s2 = new Child1();
s1.play.push(4);
console.log(s1.play);
console.log(s2.play);执行结果如下:
当我修改了s1的play属性的时候,s2的play属性也跟着变了,因为两个实例使用的是同一个原型对象。它们的内存空间是共享的,当一个发生变化的时候,另外一个也随之进行了变化,这就是使用原型链继承方式的一个缺点。
构造函数继承(借助 call)
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';
}
<span class="hljs-comment">// 第一次调用 Parent3()</span>
<span class="hljs-title class_">Child3</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Parent3</span>();
<span class="hljs-comment">// 手动挂上构造器,指向自己的构造函数</span>
<span class="hljs-title class_">Child3</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>.<span class="hljs-property">constructor</span> = <span class="hljs-title class_">Child3</span>;
<span class="hljs-keyword">var</span> s3 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Child3</span>();
<span class="hljs-keyword">var</span> s4 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Child3</span>();
s3.<span class="hljs-property">play</span>.<span class="hljs-title function_">push</span>(<span class="hljs-number">4</span>);
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(s3.<span class="hljs-property">play</span>); <span class="hljs-comment">// 不互相影响</span>
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(s4.<span class="hljs-property">play</span>);
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(s3.<span class="hljs-title function_">getName</span>()); <span class="hljs-comment">// 正常输出'parent3'</span>
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(s4.<span class="hljs-title function_">getName</span>()); <span class="hljs-comment">// 正常输出'parent3'</span></pre><p>结果如下:</p><p></p><p>之前方法一和方法二的问题都得以解决,但是这里又增加了一个新问题:通过注释我们可以看到 Parent3 执行了两次,第一次是改变Child3 的 prototype 的时候,第二次是通过 call 方法调用 Parent3 的时候,那么 Parent3 多构造一次就多进行了一次性能开销。</p><h2 id="item-4">原型式继承</h2><p>ES5 里面的 Object.create 方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。</p><div class="widget-codetool" style="display: none;">
<div class="widget-codetool--inner">
<button type="button" class="btn btn-dark rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="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);
console.log(person4.name === person4.getName());
console.log(person5.name);
console.log(person4.friends);
console.log(person5.friends);" aria-label="复制" data-bs-original-title="复制">
<i class="far fa-copy"></i>
</button>
</div>
let parent4 = {
name: "parent4",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
<span class="hljs-keyword">let</span> person4 = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">create</span>(parent4);
person4.<span class="hljs-property">name</span> = <span class="hljs-string">"tom"</span>;
person4.<span class="hljs-property">friends</span>.<span class="hljs-title function_">push</span>(<span class="hljs-string">"jerry"</span>);
<span class="hljs-keyword">let</span> person5 = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">create</span>(parent4);
person5.<span class="hljs-property">friends</span>.<span class="hljs-title function_">push</span>(<span class="hljs-string">"lucy"</span>);
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(person4.<span class="hljs-property">name</span>);
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(person4.<span class="hljs-property">name</span> === person4.<span class="hljs-title function_">getName</span>());
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(person5.<span class="hljs-property">name</span>);
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(person4.<span class="hljs-property">friends</span>);
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(person5.<span class="hljs-property">friends</span>);</pre><p>执行结果如下:</p><p></p><p>通过 Object.create 这个方法可以实现普通对象的继承,不仅仅能继承属性,同样也可以继承 getName 的方法。前三个输出都是正常的,最后两个输出结果一致是因为Object.create 方法是可以为一些对象实现浅拷贝的,那么关于这种继承方式的缺点也很明显,多个实例的引用类型属性指向相同的内存。</p><h2 id="item-5">寄生式继承</h2><p>使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。</p><p>虽然其优缺点和原型式继承一样,但是对于普通对象的继承方式来说,寄生式继承相比于原型式继承,还是在父类基础上添加了更多的方法。实现如下:</p><div class="widget-codetool" style="display: none;">
<div class="widget-codetool--inner">
<button type="button" class="btn btn-dark rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
function clone(original) {
let clone = Object.create(original);
clone.getFriends = function() {
return this.friends;
};
return clone;
}
let person5 = clone(parent5);
console.log(person5.getName());
console.log(person5.getFriends());" aria-label="复制" data-bs-original-title="复制">
<i class="far fa-copy"></i>
</button>
</div>
let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
<span class="hljs-keyword">function</span> <span class="hljs-title function_">clone</span>(<span class="hljs-params">original</span>) {
<span class="hljs-keyword">let</span> clone = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">create</span>(original);
clone.<span class="hljs-property">getFriends</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">friends</span>;
};
<span class="hljs-keyword">return</span> clone;
}
<span class="hljs-keyword">let</span> person5 = <span class="hljs-title function_">clone</span>(parent5);
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(person5.<span class="hljs-title function_">getName</span>());
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(person5.<span class="hljs-title function_">getFriends</span>());</pre><p>输出结果如下:</p><p></p><p>从最后的输出结果中可以看到,person5 通过 clone 的方法,增加了 getFriends 的方法,从而使 person5 这个普通对象在继承过程中又增加了一个方法,这样的继承方式就是寄生式继承。</p><h2 id="item-6">寄生组合式继承</h2><p>结合第四种中提及的继承方式,解决普通对象的继承问题的 Object.create 方法,我们在前面这几种继承方式的优缺点基础上进行改造,得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式,代码如下:</p><div class="widget-codetool" style="display: none;">
<div class="widget-codetool--inner">
<button type="button" class="btn btn-dark rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="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);
console.log(person6.getName());
console.log(person6.getFriends());" aria-label="复制" data-bs-original-title="复制">
<i class="far fa-copy"></i>
</button>
</div>
function clone (parent, child) {
// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
<span class="hljs-keyword">function</span> <span class="hljs-title function_">Parent6</span>(<span class="hljs-params"></span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">name</span> = <span class="hljs-string">'parent6'</span>;
<span class="hljs-variable language_">this</span>.<span class="hljs-property">play</span> = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>];
}
<span class="hljs-title class_">Parent6</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>.<span class="hljs-property">getName</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) {
<span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">name</span>;
}
<span class="hljs-keyword">function</span> <span class="hljs-title function_">Child6</span>(<span class="hljs-params"></span>) {
<span class="hljs-title class_">Parent6</span>.<span class="hljs-title function_">call</span>(<span class="hljs-variable language_">this</span>);
<span class="hljs-variable language_">this</span>.<span class="hljs-property">friends</span> = <span class="hljs-string">'child5'</span>;
}
<span class="hljs-title function_">clone</span>(<span class="hljs-title class_">Parent6</span>, <span class="hljs-title class_">Child6</span>);
<span class="hljs-title class_">Child6</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>.<span class="hljs-property">getFriends</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) {
<span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">friends</span>;
}
<span class="hljs-keyword">let</span> person6 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Child6</span>();
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(person6);
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(person6.<span class="hljs-title function_">getName</span>());
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(person6.<span class="hljs-title function_">getFriends</span>());</pre><p>执行结果如下:</p><p></p><p>这种寄生组合式继承方式,基本可以解决前几种继承方式的缺点,较好地实现了继承想要的结果,同时也减少了构造次数,减少了性能的开销。整体看下来,这六种继承方式中,寄生组合式继承是这六种里面最优的继承方式。</p><h2 id="item-7">ES6的extends关键字实现逻辑</h2><p>ES6提供了extends语法糖,使用关键字很容易实现JavaScript的继承,先看一下extends使用方法。</p><div class="widget-codetool" style="display: none;">
<div class="widget-codetool--inner">
<button type="button" class="btn btn-dark rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="class Person {
constructor(name) {
this.name = name
}
// 原型方法
// 即 Person.prototype.getName = function() { }
// 下面可以简写为 getName() {...}
getName = function () {
console.log('Person:', this.name)
}
}
class Gamer extends Person {
constructor(name, age) {
// 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
super(name)
this.age = age
}
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法" aria-label="复制" data-bs-original-title="复制">
<i class="far fa-copy"></i>
</button>
</div>
class Person {
constructor(name) {
this.name = name
}
// 原型方法
// 即 Person.prototype.getName = function() { }
// 下面可以简写为 getName() {...}
getName = function () {
console.log('Person:', this.name)
}
}
class Gamer extends Person {
constructor(name, age) {
// 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
super(name)
this.age = age
}
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法
使用babel将ES6 的代码编译成 ES5,代码如下: