javaScript之创建对象模式

ECMA-262把对象定义为:无序属性的集合,其属性可以包含基本值、对象或函数

对象的每个属性或方法都有一个名字,而每个名字都映射到一个值,因此可以把ECMAScript的对象想象成散列表,即一组名值对,其中值可以是数据或函数。

每个对象都是基于一个引用类型创建的。

首先先看看早期创建对象的模式:


1、基于Object对象创建对象,是创建自定义对象最简单方式。

var person=new Object();
person.name="singsong";
person.age=23;
person.info=function(){
   alert("name: "+this.name +"  "+"age: "+this.age);
};

2、用对象字面量创建对象

var person={
name:"singsong",
age:23,
info:function(){
   alert("name: "+this.name +"  "+"age: "+this.age);
}
};

虽然Object构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。解决这问题,可以使用工厂模式的一种变体。


3、工厂模式抽象了创建具体对象的过程,考虑到在ECMAScript中无法创建类,开发人员就发明一种函数,用函数来封装以特定接口创建对象的细节。

function createPerson(name,age)
{
  var o=new Object();
  o.name=name;
  o.age=age;
  o.info=function(){
    alert("name: "+this.name +"  "+"age: "+this.age);
  }
  return o;
}
//产生一个对象。
var person=createPerson("singsong",23);

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎么知道一个对象的类型)。随着javaScript发展,有一种新模式出现了——构造函数模式

4、构造函数模式

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.info = function() {
    alert("name: " + this.name + "  " + "age: " + this.age);
  };
}
var person = new Person("singsong", 23);

构造函数模式与工厂模式的区别:

  • Person()函数取代了createPerson()函数;

  • 没有显式地创建对象(即 var o=new Object(););

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

  • 没有return语句;

  • 构造函数模式,创建Person实例,必须使用new操作符;

注意:函数名Person的首字母是大写的P,按照惯例,构造函数始终应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。这个做法借鉴自其他OO语言,主要是为了区别于ECMAScript中的其他函数,因为构造函数本身也是函数,只不过用来创建对象而已。


以构造函数模式创建的新实例会经历以下4个步骤:

  • 创建一个新对象;

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

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

  • 返回新函数;

回到正题:那么构造函数模式怎么去识别对象的类型?

通过构造函数创建的新实例都有一个constructor属性,该属性指向Person

Alert(person.constructor==Person); //true;

对象的constructor属性最初是用来标识对象类型的。但是,提到检测对象类型,还是instanceof操作符要更可靠一些。person既是Object的实例,同时也是Person的实例。

Alert(person  instanceof  Person); //true;
Alert(person  instanceof  Object); //true;

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


构造函数模式虽然好用,但也并非没有缺点,使用构造函数的主要问题,就是在每一个方法都要在每个势实例上创建一遍,

5、原型模式

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

function Person() {}
Person.prototype.name = "singsong";
Person.prototype.age = 23;
Person.prototype.info = function() {
  alert("name: " + this.name + "  " + "age: " + this.age);
};
var personOne = new Person();
var personTwo= new Person();
personOne.name="Tom";
alert(personOne.name);//Tom 来自实例
alert(personTwo.name);//singsong 来自原型

可用hasOwnPrototype方法可以检测一个属性是存在于实例中,还是存在于原型中。由于这个方法是从Object对象继承过来的,只在给定属性存在于对象实例时,才会返回true

alert(personOne.hasOwnProperty("name"));//来自实例 true alert(personTwo.hasOwnProperty("name"));//来自原型 false
delete personOne.name ;//删除personFive中name属性
alert(personTwo.hasOwnProperty("name"));// false

原型模式缺点:它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。原型中所有属性是被很多实例共享的,这种对于函数非常适合,对那些包含基本值的属性到也说得过去。可以通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然而,对于包含引用类型值的属性,它是指向同一个指针的。

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

此组合模式是创建自定义类型最常见方式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。

  function Person(name, age) {
    this.name = name;
    this.age = age;
  }
  Person.prototype = {
    constructor: Person,
    info: function() {
      alert("name: " + this.name + "  " + "age: " + this.age);
    }
  }
  var person = new Person("singsong", 23);
  person.info();
}

这种构造函数与原型混成的模式,是目前在ECMAScript中使用最广泛,认同度最高的一种创建自定义类型的方法。可以说,这是用来定义引用类型的默认的一种默认模式。


7、动态原型模式

动态原型模式把所有信息都封装在构造函数中。通过在构造函数中初始化原型,有保持了同时使用构造函数和原型的优点。

function Person(name, age) {
  this.name = name;
  this.age = age;
  if (typeof this.info != "function") {
    Person.prototype.info = function() {
      alert("name: " + this.name + "  " + "age: " + this.age);
    }
  };
}
var person = new Person("singsong", 23);
person.info();

使用动态原型模式时,不能使用对象字面量重写原型,如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。

8、寄生构造函数模式

这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后返回新创建的对象。

function Person(name, age) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.info = function() {
      alert("name: " + this.name + "  " + "age: " + this.age);
    };
    return o;
  }
  var person = new Person("singsong", 23);
  person.info();

除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样。构造函数在不返回值得情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个return语句,可以重写调用构造函数时返回的值。

这个模式可以在特殊的情况下用来为对象创建构造函数。如由于不能直接修改原生对象的构造函数,可以借助此模式,重创建构造函数来添加额外方法。

注意:寄生构造函数模式返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。因此,就不能依赖instanceof操作符来确定对象类型了。由于存在此上述问题,建议在可以使用其他模式的情况下,不要使用这种模式。

9、 稳妥构造函数模式

Douglas Crockford发明了javaScript中的稳妥对象(durable objects)这个概念。

所谓稳妥对象就是指没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this new),或者在防止数据被其他应用程序改动时使用。

function Person(name, age) {
    var o = new Object();
    o.info = function() {
      alert("name: " + name + "  " + "age: " + age);
    };
    return o;
  }
  var person = Person("singsong", 23);
  person.info();

与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象鱼构造函数之间也没有什么关系,因此instanceof操作符对这种对象也没有意义。