关于「原型和继承」一般在面试时问到的比较多,其实在实际开发中我们也经常用到,只是没有发现。为了打破「纵使相逢应不识」的尴尬局面,本文将会梳理原型和继承的概念,并向大家展示它们的实际用处。
原型
JS 没有复制机制,于是 JS 会在两个对象之间创建一个关联,然后通过委托访问另一个对象。
比如,有一个对象 child 它的原型是 parent,那么:
- child 有一个 __ proto __ 属性,指向 parent 对象;
- child 的构造函数 CreateChild 有一个 prototype 属性,也指向 parent。 同样的,parent 也会有 __ proto __ 属性,指向它的原型,这样一级一级的就构成原型链,直到 object 对象的原型(null)。
使用原型
原型的作用最主要的一点是数据共享,创建对象的时候,我们会把公共的方法和属性挂载到原型上,避免资源浪费。
方式一
给函数 prototype 属性赋值对象自面量,来设定 foo 对象的原型
var foo = function() {
this.a: 'hello';
}
foo.prototype = {
add: (x, y) => x + y;
}
var bar = new foo();
假如给 bar prototype 添加 属性,foo 也会增加
bar.prototype = {
substract: (x, y) => x-y
}

方式二
使用 IIFE 来赋值,可以封装私有属性和变量,通过 return 的形式暴露方法名称,以达到 public/private 的效果
Calculator.prototype = function () {
add = function (x, y) {
return x + y;
},
subtract = function (x, y) {
return x - y;
}
return {
add: add,
subtract: subtract
}
} ();
var zsh = new Calculator()
zsh.add(11, 3) // 14
继承
原型继承的模型就是 JavaScript 实现继承的原理。我们前面说过,JS 中通过委托的方式来访问其他对象,这样我们就不用单独实现某个方法,从而实现继承某个属性。
实现继承的方式
// 实现一,原型链继承
// 缺陷:实例共享一个原型,属性若发生改变,会影响其他
function parent2() {
this.name = 'parent2';
this.play = [1, 2, 3];
}
function child2() {
this.type = 'child2';
}
child2.prototype = new parent2(); // 原型链的桥接
var instance = new child2;
instance.play.push(4);
alert(instance.play); // [1, 2, 3, 4]
var instance2 = new child2;
alert(instance2.play); // [1, 2, 3, 4]
// 实现二,构造函数继承
// 优点:属性互相隔离;可以传递参数
// 缺陷:父类的方法没有被共享,通过 call 复写了原型方法,子类无法访问和继承,造成内存浪费
function parent1() {
this.name = 'parent1';
this.toString = '333';
}
function child1() {
parent1.call(this); // 在未来每个子类实例时,调用超类的构造函数,每个子类都有自己的 play 副本
this.type = 'child1';
}
console.log('new child1 :>> ', new child1());
// 实现三,组合继承
// 使用原型继承和构造函数继承的方式组合,子类既拥有自己属性,也可以共享方法
function parent() {
this.colors = ['red', 'blue']
}
function child() {
parent.call(this)
}
child.prototype = new parent();
child.prototype.constructor = child;
var c1 = new child()
c1.colors.push('green'); // ["red", "blue", "green"]
var c2 = new child() // c2.colors 仍为["red", "blue"]
// 实现四,寄生组合继承
function parent3() {
this.name = 'parent2';
this.play = [1, 2, 3];
}
function child3() {
parent3.call(this); // 通过显示绑定,访问到 parent 中的属性
this.type = 'child3';
}
child3.prototype = Object.create(parent3.prototype); // 不产生原型链桥接
// 因为 child3 的原型指向了另一个原型 -- parent3 ,而这个原型的 constructor 指向是 parent3
child3.prototype.constructor = child3; // 因此要修正 child3 的构造函数,
console.log('new child3() :>> ', new child3());
// 实现五,ES6 class
class parent4 {
constructor() {}
name() {
return 'lee';
}
}
class child4 extends parent4 {
constructor() {
super();
}
}
const c4 = new child4();
console.log('c4 :>> ', c4.name());
上面介绍了 5 种实现继承的方式,是本文主要的内容,实现继承,会涉及到原型、new、原型覆盖与共享等细节问题。代码中也有详细注释,一定要亲自动手试试,体会才更加深刻!