创建对象的几种模式(一)
  • 工厂模式

    我们很多时候会使用对象字面量来创建单个对象,但是这个方式有比较明显的缺点,创建类似对象是会产生很多重复代码,比如:

    var person1 = {
      name: "nicolas",
      age: 29,
      job: "software engineer"
    };
    
    var person2 = {
      name: "greg",
      age: 27,
      job: "doctor"
    }

    每次创建一个新对象就需要重新输入每一个属性,为了解决这个问题,我们开始使用工厂模式的一种变体,使用一个函数来封装以特定接口创建对象的细节,如下:

    function createPerson(name, age, job) {
      var o = new Object();
      o.name = name;
      o.age = age;
      o.job = job;
      o.sayName = function(){
        alert(this.name);
      }
      return o;
    }
    
    var person1 = createPerson("nicolas", 29, "software enginner");
    var person2 = createPerson("greg", 27, "doctor");

    这个函数可以接收一些参数来构建一个包含必要信息的对象,可以无数次调用这个函数,而且每次都会返回一个包含三个属性一个方法的对象,工厂模式虽然解决了创建多个相似对象的问题,但是却没有解决对象识别的问题,person1和person2之间并没有内在的联系。因此我们引入了构造函数模式。

  • 构造函数模式

    构造函数也是函数,只是通过new操作符来调用,并且在内部直接将属性和方法绑定到this变量上,我们可以把之前的例子重写如下:

    function Person(name, age, job) {
      this.name = name;
      this.age = age;
      this.job = job;
      this.sayName = function(){
        alert(this.name);
      }
    }
    
    var person1 = new Person("nicolas", 29, "software engineer");
    var person2 = new Person("greg", 27, "doctor");

    我们可以看到构造函数中并没有显示的创建对象,也不存在return语句,同时按照惯例一般构造函数的函数名Person首字母使用大写。当使用new操作符调用时,实际上经历了以下四个过程:

    1. 创建一个新对象;
    2. 将构造函数的this赋给新对象;
    3. 为这个新对象添加属性和方法;
    4. 返回新对象。

    最后person1和person2都保存着Person的一个不同的实例,同时他们都一个constructor属性,指向Person

    构造函数虽然好,但是也有一个主要的缺点,那就是每个内部的同样方法都要在内存中重新创建一遍,比如上面的例子里的sayName方法,实际上每次定义一个函数都相当于每次都实例化了一个函数对象,他们在内存中保存于不同的地址,可以用以下代码证明不同实例上的同名函数其实并不相等:

    alert(person1.sayName == person2.sayName); // false

    其实大可以把函数定义转移到构造函数外部来解决这个问题:

    function Person(name, age, job) {
      this.name = name;
      this.age = age;
      this.job = job;
      this.sayName = sayName;
      }
    }
    
    function sayName() {
      alert(this.name);
    }
    
    var person1 = new Person("nicolas", 29, "software engineer");
    var person2 = new Person("greg", 27, "doctor");

    这样以来,person1和person2就共享了全局作用域中的同一个sayName函数,但是新问题又来了:如果对象需要定义很多方法,那么就要定义很多个全局函数,那也就丝毫没有封装性可言了,好在,这些问题我们可以通过原型模式来解决。