We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
面试中我们经常会被问到继承,希望通过此文,你能彻底搞懂 JavaScript 中的继承原理。
ES6 以前,JavaScript 中的继承不像其它 oo 语言一样,用特定 class 去实现,它是由构造函数和原型去模拟,下面我们会介绍几种常见的继承方法以及对应的优点和不足。
比如我有一个构造函数,这个构造函数的实例有一个内部指针[[Prototype]]指向构造函数的原型,然后这个构造函数的原型又是另一个构造函数的实例,也就是说这个构造函数原型有一个内部指针[[Prototype]]指向另一个构造函数的原型,如此下去,就构成了一条原型链。那用原型链实现继承用代码表示出来就是这样:
function Parent () { this.name = 'Twittytop'; } Parent.prototype.getName = function () { return this.name; } function Child () { this.age = 29; } // 继承 Child.prototype = new Parent(); var ins = new Child(); console.log(ins.getName()); // Twittytop
这样原来在 Parent 上的属性都变成了 Child.prototype 上的属性。
第一:共享问题
当 Parent 上包含有引用属性时,就出出现问题,比如:
function Parent () { this.friends = ['Jack', 'Tom']; } function Child () { this.age = 29; } // 继承 Child.prototype = new Parent(); var ins1 = new Child(); ins1.friends.push('Bob'); var ins2 = new Child(); console.log(ins2.friends); // ["Jack", "Tom", "Bob"]
因为继承之后变成了 Child 的原型属性,所以所有 Child 的实例都指向的是同一个 friends,当其中一个实例修改了这个值之后,变化就会反映到所有实例上。
第二: 传参问题
Child 在实例化是没法向 Parent 传参,当 Parent 依赖外部传参时,就会导致问题。
function Parent (name) { this.name = name; } Parent.prototype.getName = function () { return this.name; } function Child (name, age) { // 继承 Parent.call(this, name); this.age = age; } var ins = new Child('Twittytop', 29); console.log(ins.name); // Twittytop console.log(ins.getName); // undefined
可以看到,盗用构造函数的优点是能传递参数,问题是它只能继承实例属性,不能继承原型属性。
既然原型链和盗用构造函数继承都有各自的缺点,那我们能不能把这两者结合起来呢?这就是组合继承。
function Parent (name) { this.name = name; } Parent.prototype.getName = function () { return this.name; } function Child (name, age) { // 继承实例属性 Parent.call(this, name); this.age = age; } // 继承原型属性 Child.prototype = new Parent(); var ins = new Child('Twittytop', 29); console.log(ins.name); // Twittytop console.log(ins.getName()); // Twittytop
组合继承弥补了原型链和盗用构造函数的不足,能同时继承实例属性和原型属性,但它的缺点是会调用两次父类构造函数。一次是在 Child 构造函数中执行 Parent.call,一次是在实例化 Parent 时。这样就会导致 Child 的不仅自身实例上有 name 属性,原型上也有 name 属性,导致了不必要的多余继承。用图表示如下:
function Parent (name) { this.name = 'Twittytop'; } Parent.prototype.getName = function () { return this.name; } function Child (age) { this.age = age; } // 继承原型属性 Child.prototype = Object.create(Parent.prototype); var ins = new Child(29); console.log(ins.getName);
原型式继承只继承了原型上的属性,没有继承实例属性,相比原型链继承更干净,它没有把父类的实例属性继承到自身的原型上面,当然,它和原型链一样,也会有引用属性的共享问题。
寄生式继承是建立在原型式继承基础上的,寄生式继承用代码表达出来是这样:
function inherit (Parent) { let pro = Object.create(Parent.prototype); pro.myMethod = function () {}; return pro; }
它相比原型式继承多了添加一些自己的属性和方法。
寄生式组合继承综合了盗用构造函数和寄生式继承,它使用盗用构造函数继承实例属性,使用寄生式继承继承原型属性。
function inherit (Child, Parent) { let pro = Object.create(Parent.prototype); pro.constructor = Child; // 将constructor重新指回Child Child.prototype = pro; } function Parent (name) { this.name = name; } Parent.prototype.getName = function () { return this.name; } function Child (name, age) { // 继承实例属性 Parent.call(this, name); this.age = age; } // 继承原型属性 inherit(Child, Parent) var ins = new Child('Twittytop', 29); console.log(ins.name); // Twittytop console.log(ins.getName()); // Twittytop
寄生式组合继承吸取了盗用构造函数和寄生式继承的优点,又没有组合继承中调用父类构造函数两次的不足,是ES5 实现继承的最佳模式。
关于 ES6 的继承,这里就不介绍了,它本质是上述继承的语法糖而已。
JavaScript 继承独特的地方就是它的原型,如果这篇文章能让你对 JavaScript 继承有进一步的了解,那将是我最大的欣慰。如果有错误或者有疑问的地方,也欢迎交流讨论。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
ES6 以前,JavaScript 中的继承不像其它 oo 语言一样,用特定 class 去实现,它是由构造函数和原型去模拟,下面我们会介绍几种常见的继承方法以及对应的优点和不足。
原型链
什么是原型链?
比如我有一个构造函数,这个构造函数的实例有一个内部指针[[Prototype]]指向构造函数的原型,然后这个构造函数的原型又是另一个构造函数的实例,也就是说这个构造函数原型有一个内部指针[[Prototype]]指向另一个构造函数的原型,如此下去,就构成了一条原型链。那用原型链实现继承用代码表示出来就是这样:
这样原来在 Parent 上的属性都变成了 Child.prototype 上的属性。
问题
第一:共享问题
当 Parent 上包含有引用属性时,就出出现问题,比如:
因为继承之后变成了 Child 的原型属性,所以所有 Child 的实例都指向的是同一个 friends,当其中一个实例修改了这个值之后,变化就会反映到所有实例上。
第二: 传参问题
Child 在实例化是没法向 Parent 传参,当 Parent 依赖外部传参时,就会导致问题。
盗用构造函数
可以看到,盗用构造函数的优点是能传递参数,问题是它只能继承实例属性,不能继承原型属性。
组合继承
既然原型链和盗用构造函数继承都有各自的缺点,那我们能不能把这两者结合起来呢?这就是组合继承。
组合继承弥补了原型链和盗用构造函数的不足,能同时继承实例属性和原型属性,但它的缺点是会调用两次父类构造函数。一次是在 Child 构造函数中执行 Parent.call,一次是在实例化 Parent 时。这样就会导致 Child 的不仅自身实例上有 name 属性,原型上也有 name 属性,导致了不必要的多余继承。用图表示如下:
原型式继承
原型式继承只继承了原型上的属性,没有继承实例属性,相比原型链继承更干净,它没有把父类的实例属性继承到自身的原型上面,当然,它和原型链一样,也会有引用属性的共享问题。
寄生式继承
寄生式继承是建立在原型式继承基础上的,寄生式继承用代码表达出来是这样:
它相比原型式继承多了添加一些自己的属性和方法。
寄生式组合继承
寄生式组合继承综合了盗用构造函数和寄生式继承,它使用盗用构造函数继承实例属性,使用寄生式继承继承原型属性。
寄生式组合继承吸取了盗用构造函数和寄生式继承的优点,又没有组合继承中调用父类构造函数两次的不足,是ES5 实现继承的最佳模式。
关于 ES6 的继承,这里就不介绍了,它本质是上述继承的语法糖而已。
写在后面
JavaScript 继承独特的地方就是它的原型,如果这篇文章能让你对 JavaScript 继承有进一步的了解,那将是我最大的欣慰。如果有错误或者有疑问的地方,也欢迎交流讨论。
The text was updated successfully, but these errors were encountered: