首先我们罗列一下JS中创建对象的几种模式:

  1. 工厂模式
  2. 构造函数模式
  3. 原型模式
  4. 组合使用构造函数模式和原型模式
  5. 动态原型模式
  6. 寄生构造函数模式
  7. 稳妥构造函数模式

当我们在面试的过程中,总是会被问起JavaScript中是如何实现继承的?实现继承的前提是我们需要创建不同的对象,在这不同的对象之间去实现相应的集成关系。那Javascript当中是怎么去创建对象的呢?

如果您阅读过《JavaScript高级程序设计》(以下以《高程》简称)这本书,那么一定了解过文章开头罗列的7种创建对象的模式。当一般我被问及对象创建模式的时候,前四种还能随口说出来,但是后面三种就会记得比较模糊了。再加上如果您阅读了继承相关的章节,你会发现,继承的几种方式与对象创建的几种模式是对应起来的,记忆还可能会混乱。关于继承的几种模式后续文章再详说。

那怎么样才能够准确的说出或者记住这几种创建对象的模式呢?其实我之前的情形基本处于看一遍就会了,然后过一阵就忘了的状态。其原因归结下来,我认为有两点:

  • 不常用
  • 没有真正的理解

接下来针对这几种模式,一步一步来进行梳理,如果真正的了解了每一种模式的优缺点,那么也就知道了它存在的目的。

一、为什么要有工厂模式

创建对象最简单直接的方式:

JS(下文中所有的JavaScript都使用JS来代替)中创建对象最直接的方法莫过于此:

var obj = {};

或者

var obj = new Object();
Init1: 为对象添加属性以及方法
obj.name = 'lee';
obj.sayName = function() {
    console.log(this.name);
}

但是如果需要生成很多具有相同属性和方法的对象,我们需要重复的编写 Init1 步骤的代码。所以我们其实可以创建一个方法用于封装对象初始化的这些步骤,然后再把初始化好的这个对象返回回来。

工厂模式

function createObj(name) {
    var o = new Object();
    o.name = name;
    o.sayName = function() {
        console.log(this.name);
    }
    return o;
}
var obj = createObj('lee');

好处

封装了对象创建以及初始化的细节。可以实现对象属性以及方法属性创建的复用。

问题

以上工厂模式创建对象的方法名,我并没有指定创建相应对象的类型,《高程》中的写法是createPerson。我们大概能猜到这个对象可能是人,因为有name属性,同时还能sayName。那我怎么知道返回的对象就是一个Person类型的呢?所以工厂模式存在对象类型无法识别的问题。

二、为什么要有构造函数模式

好处

构造函数可以用来创建特定类型的对象。

function Person(name) {
    this.name = name;
    this.sayName = function() {
        console.log(this.name);
    }
}
var p1 = new Person('lee');
var p2 = new Person('shao');
  • 没有显式创建对象;
  • 直接将属性和方法赋值给了this对象;
  • 没有renturn语句;
  • 使用new操作符创建对象;
  • 方法名首字母大写;

new 操作符创建对象经历的4个步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋值给新对象(因此this就指向了这个新对象)
  3. 执行构造函数中的代码(为新对象添加属性和方法)
  4. 返回新对象
p1.constructor == Person; // true
p1 instanceof Object; // true
p1 instanceof Person; // true

问题

function Person(name) {
    this.name = name;
    // sayName方法在每一个实例上都会重新创建一遍
    this.sayName = function() {
        console.log(this.name);
    }
}

sayName方法在每一个实例上都会重新创建一遍。不同实例之间无法实现方法的复用。

三、为什么要有原型模式

好处

每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。让所有对象实例共享它所包含的属性和方法

function Person() {}
Person.prototype.name = 'lee';
Person.prototype.sayName = function() {
    console.log(this.name);
}
var p1 = new Person();
var p2 = new Person();
console.log(p1.sayName == p2.sayName); // true

问题

无法为构造函数传递初始化参数;如果原型上的属性是引用类型,则所有实例都是共享的,会导致不同实例之间值变化的相互影响。

function Person() {}
Person.prototype.friends = ['lee'];
var p1 = new Person();
var p2 = new Person();
console.log(p2.friends); // ['lee']
p1.friends.push('van')
console.log(p2.friends); // ['lee', 'van']

四、为什么要有组合使用构造函数模式和原型模式

好处

支持向构造函数传参,构造函数模式定义实例属性,原型模式用于定义方法和共享的属性。

... 待续


饭等米
114 声望13 粉丝

前端新人