js创建对象的几种方式(工厂模式、构造函数模式、原型模式)

1.工厂模式

  考虑到在 ECMAScript 中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节,如下面的例子所示:

  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('Grey',27,'Doctor');

  函数 createPerson()能够根据接受的参数来构建一个包含所有必要信息的 Person 对象。可以无数次地调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。工厂模式虽然解决了创建\多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。 

  主要好处就是可以消除对象间的耦合,通过使用工程方法而不是new关键字。将所有实例化的代码集中在一个位置防止代码重复。

  工厂模式解决了重复实例化的问题 ,但还有一个问题,那就是识别问题,因为根本无法 搞清楚他们到底是哪个对象的实例。

2.构造函数模式

  ECMAScript中的构造函数可用来创建特定类型的对象,像Array和Object这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而定义自定义对象的属性和方法。使用构造函数的方法,既解决了重复实例化的问题,又解决了对象识别的问题。例如,可以使用构造函数模式将前面的例子重写如下:

  function Person(name,age,job){

    this.name = name;

    this.age = age;

    this.job = job;

    this.sayName = function(){

      alert(this.name);

    }

  }

  var person1 = new Person("Nicholas", 29, "Software Engineer"); 

  var person2 = new Person('Grey',27,'Doctor');

  Person()中的代码除了与 createPerson()中相同的部分外,还存在以下不同之处:

     没有显式地创建对象;
     直接将属性和方法赋给了 this 对象;
     没有 return 语句。

  按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。

  要创建 Person 的新实例,必须使用 new 操作符。以这种方式调用构造函数实际上会经历以下 4个步骤:

    (1) 创建一个新对象;
    (2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
    (3) 执行构造函数中的代码(为这个新对象添加属性);
    (4) 返回新对象。

  在前面例子的最后,person1 和 person2 分别保存着 Person 的一个不同的实例。这两个对象都有一个 constructor(构造函数)属性,该属性指向 Person,如下所示。

    alert(person1.constructor == Person); //true
    alert(person2.constructor == Person); //true

  对象的 constructor 属性最初是用来标识对象类型的。但是,提到检测对象类型,还是 instanceof 操作符要更可靠一些。我们在这个例子中创建的所有对象既是 Object 的实例,同时也是 Person的实例,这一点通过 instanceof 操作符可以得到验证。

    alert(person1 instanceof Object); //true
    alert(person1 instanceof Person); //true
    alert(person2 instanceof Object); //true
    alert(person2 instanceof Person); //true

  创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型;而这正是构造函数模式胜过工厂模式的地方。

  在这个例子中,person1 和 person2 之所以同时是 Object 的实例,是因为所有对象均继承自 Object.

  前面例子中定义的 Person()函数可以通过下列任何一种方式来调用。

    // 当作构造函数使用
    var person = new Person("Nicholas", 29, "Software Engineer");
    person.sayName(); //"Nicholas"
    // 作为普通函数调用
    Person("Greg", 27, "Doctor"); // 添加到 window
    window.sayName(); //"Greg"
    // 在另一个对象的作用域中调用
    var o = new Object();
    Person.call(o, "Kristen", 25, "Nurse");
    o.sayName(); //"Kristen"

  这个例子中的前两行代码展示了构造函数的典型用法,即使用 new 操作符来创建一个新对象。

  接下来的两行代码展示了不使用new操作符调用Person()会出现什么结果:属性和方法都被添加给window对象了。

  当在全局作用域中调用一个函数时,this 对象总是指向 Global 对象(在浏览器中就是 window 对象)。

  因此,在调用完函数之后,可以通过 window 对象来调用 sayName()方法,并且还返回了"Greg"。最后,也可以使用 call()(或者 apply())在某个特殊对象的作用域中调用Person()函数。这里是在对象o 的作用域中调用的,因此调用后o 就拥有了所有属性和sayName()方法。

 3.原型模式

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

  如果按字面意思来理解,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。例如:

  function Person(){

  }

  Person.prototype.name = 'Nicholas';

  Person.prototype.age = 29;

  Person.prototype.job = 'Software Engineer';

  Person.prototype.sayName = function(){

    alert(this.name);

  }

  var person1 = new Person();

  person1.sayName();//"Nicholas"

  

  var person2 = new Person(); 

  person2.sayName(); //"Nicholas" 

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

  在此,我们将 sayName()方法和所有属性直接添加到了 Person 的 prototype 属性中,构造函数变成了空函数。

  即使如此,也仍然可以通过调用构造函数来创建新对象,而且新对象还会具有相同的属性和方法。但与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的。换句话说,person1 和 person2 访问的都是同一组属性和同一个 sayName()函数。要理解原型模式的工作原理,必须先理解 ECMAScript 中原型对象的性质。

 

posted @ 2017-10-15 11:11 清源如风 阅读(...) 评论(...) 编辑 收藏