JS 基础--原型与继承

285 阅读3分钟

关于「原型和继承」一般在面试时问到的比较多,其实在实际开发中我们也经常用到,只是没有发现。为了打破「纵使相逢应不识」的尴尬局面,本文将会梳理原型和继承的概念,并向大家展示它们的实际用处。

原型

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、原型覆盖与共享等细节问题。代码中也有详细注释,一定要亲自动手试试,体会才更加深刻!