JavaScript创建对象的方式

一、工厂模式

  工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程。

   考虑到在 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("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
console.log(person1);
console.log(person2);

  运行结果如下图所示:

  函数 createPerson()能够根据接受的参数来构建一个包含所有必要信息的 Person 对象。

  可以无数次地调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。

  工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

二、构造函数模式

  ECMAScript 中的构造函数可用来创建特定类型的对象,像 Object 和 Array 这样的原生构造函数,在运行时会自动出现在执行环境中。

  此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。

   可以使用构造函数模式将前面的例子重写如下:

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("Greg", 27, "Doctor");
console.log(person1);
console.log(person2);

  运行结果如下图所示:

    在这个例子中,Person()函数取代了 createPerson()函数。

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

     1.没有显式地创建对象;

     2.直接将属性和方法赋给了 this 对象;

     3.没有 return 语句。 

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

  (1) 创建一个新对象;

  (2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);

  (3) 执行构造函数中的代码(为这个新对象添加属性);

  (4) 返回新对象。

  构造函数与其他函数的唯一区别,就在于调用它们的方式不同。

  不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。

  任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数。

  而任何函数,如果不通过 new 操作符来调用,那它跟普通函数也不会有什么两样。

  使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。

三、原型模式

  我们创建的每个函数都有一个 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()函数。 

   缺:原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。 但是实例一般都是要有属于自己的全部属性,实力属性不能共享。 

四、 组合使用构造函数模式和原型模式

  创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。

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

  结果,每个实例都会有自己的一份实例属性的副本, 但同时又共享着对方法的引用,最大限度地节省了内存。

  另外,这种混成模式还支持向构造函数传递参数,可谓是集两种模式之长。

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name);
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
console.log(person1.friends);    //"Shelby,Count,Van"
console.log(person2.friends);    //"Shelby,Count"
console.log(person1.friends === person2.friends);  //false
console.log(person1.sayName === person2.sayName);  //true

   在这个例子中,实例属性都是在构造函数中定义的,而由所有实例共享的属性 constructor 和方法 sayName()则是在原型中定义的。

  而修改了 person1.friends(向其中添加一个新字符串),并不会影响到 person2.friends,因为它们分别引用了不同的数组。

五、动态原型模式

  有其他 OO 语言经验的开发人员在看到独立的构造函数和原型时,很可能会感到非常困惑。动态原型模式正是致力于解决这个问题的一个方案。

  动态原型模式把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

function Person(name, age, job){
//属性
    this.name = name;
    this.age = age;
    this.job = job;
//方法
    if (typeof this.sayName != "function"){
        Person.prototype.sayName = function(){
            alert(this.name);
        };
    }
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

   这里只在 sayName()方法不存在的情况下,才会将它添加到原型中。

  这段代码只会在初次调用构造函数时才会执行。此后,原型已经完成初始化,不需要再做什么修改了。

  不过要记住,这里对原型所做的修改,能够立即在所有实例中得到反映。

六、class模式

  ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        console.log(this.x + ', ' + this.y );
    }
}
let point = new Point(1,2);
point.toString();//结果为1,2

  上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。

  也就是说,ES5 的构造函数Point,对应 ES6 的Point类的构造方法。

  Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。

原文地址:https://www.cnblogs.com/gg-qq/p/10669717.html