创建对象
我们都知道面向对象都有类的概念,而 ES5 里面却没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。
在一开始的时候,我们可以通过原生 Object 或者对象字面量的方式来创建对象,例如
var person1 = {
name: "steven",
age: 21
};
var person2 = {...};
var person3 = {...};
可是当我们使用同一个接口创建对象,就会产生大量的重复代码。为了解决这个问题,人们开始使用工厂模式的一种变体。
工厂模式
function createPerson(name, age) {
var o = new Object();
o.name = name;
o.age = age;
return o
}
var person1 = createPerson("Steven", 21);
var person2 = createPerson(...);
这个方式解决了重复代码的问题,却没有解决对象识别的类型(即怎样知道一个对象的类型)。随着 JavaScript 的发展,出现了构造函数模式。
构造函数模式
function Person(name, age){
this.name = name;
this.age = age;
this.sayName = function() {
alert(this.name);
};
}
var person1 = new Person("Steven", 21);
var person2 = new Person(...);
比起工厂模式,构造函数模式具有以下特点:
- 没有显示创建对象
- 直接将属性和方法赋给 this 对象
- 没有 return 语句
其中调用构造函数会经历以下步骤:
- 创建一个新对象
- 将构造函数的作用域赋给了新对象(因此 this 就指向了这个对象)
- 执行构造函数中的代码
- 返回新对象
除此之外,创建自定义的构造函数意味着将来可以将它的实例标示为一种特定的类型,而正是构造函数模式胜过工厂模式的地方。
alert(person1.constructor == Person); // True
alert(person2.constructor == Person); // True
注意:按照惯例,构造函数始终应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。
然而我们注意到,每个方法都要在每个实例重新创建一遍,然而这些方法其实都是公有的。因此,我们可以采用原型模式。
原型模式
我们知道,我们创建的每一个函数都有一个 prototype (原型)属性。使用原型对象的好处是,所有对象实例可以共享它所包含的属性和方法。
function Person(){}
Person.prototype.name = "Steven";
Person.prototype.age = 21;
Person.prototype.sayName = function() {
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
然而如果把属性放在原型链上会导致两个问题:
- 无法初始化参数,导致所有实例取得相同的属性值
- 原型中所有的属性会被实例共享
为了解决上面问题,可以把构造函数和原型模式结合起来,这样即可以利用构造函数初始化参数并保存不同属性,也可以引用同样的方法。
组合使用
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.constructor = Person;
Person.prototype.sayName = function() {
alert(this.name);
};
var person1 = new Person("Steven", 21);
var person2 = new Person(...);
目前在 ES5 中,这种构造函数与原型混成的模式,是使用最广泛和认同度最高的创建自定义类型的方法。