①使用Object构造函数创建单个对象
var person = new Object();
person.name = "Mike";
person.age = 29;
②使用对象字面量表示法,是对象定义的一种简写形式,比较受青睐。
var person = {
name : "Mike", //注意这里的逗号
age : 29
}; //注意这里的分号
也可以这样:
var person = {}; //与 new Object()相同
person.name = "Mike";
person.age = 29;
{name:"Mike",age:29}就是一个对象, 可以作为函数的参数传递。在通过对象字面量定义对象时,实际上不会调用Object构造函数。
但是使用以上两个方法使用一个接口创建很多对象,会产生大量的重复代码。
③工厂模式
用函数来封装以特定接口创建对象的细节。
function createPerson(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name);
}
return o;
}
var person1 = creatPerson("Mike",26);
var person2 = creatPerson("Jack",29);
工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎么知道一个对象的类型)
④构造函数模式
创建自定义的构造函数,从而自定义对象类型的属性和方法。
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name);
}
}
var person1 = new Person("Mike",29);
var person2 = new Person("Jack",26);
所有对象既是Object的实例,也是Person的实例。
使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。在前面的例子中,person1和person2都有一个名为sayName()的方法,但由于函数也是对象,因此每定义一个函数,也就是实例化了一个对象。然而,创建两个完成同样任务的Function实例的确没有必要;况且有this对象在,根本不用再执行代码前就把函数绑定到特定对象上门。因此,可以像下面这样,通过把函数定义转移到构造函数外部来解决这个问题。
function Person(name, age){
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
但是这种方式也有缺点,如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。
这些问题可以通过原型模式解决。
⑤原型模式
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以“由特定类型的所有实例”共享的属性和方法。如果按照字面意思来理解,prototype就是通过调用构造函数(说明要定义构造函数)而创建的那个对象实例的原型对象(这句话读起来有点绕口,简单说,prototype是原型对象,那它是谁的原型对象呢,是通过调用构造函数而创建的那个对象的原型对象)。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中(即把这些共享的属性和方法都放到prototype对象里)。
如下所示:
function Person(){
} //构造函数
Person.prototype.name = "Mike";
Person.prototype.age = 29;
Person.prototype.sayName = function(){
alert(this.name);
};
新对象的这些属性和方法是有所有实例共享的,person1 和person2都会由原型对象给他复制过去属性和方法。
a.理解原型对象
在默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针(意思即是constructor指针指向prototype所在函数,比如Person.prototype.constructor指向Person,也就是它的构造函数是Person)。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性,ECMA-262第5版称这个指针[[Prototype]]),指向构造函数的原型对象(即Person.prototype)。要明确的真正重要的一点是,实例内部的[[Prototype]]属性是连接实例与构造函数的原型对象之间,不是实例与构造函数之间。
Person的每个实例person1和person2都包含一个内部属性,该属性仅仅指向了Person.prototype;换句话说过,他们与构造函数没有直接的关系。此外,需要格外注意的是,虽然这两个实例都不包含属性和方法,但我们却可以调用person1.sayName()。这是通过查找对象属性的过程来实现的。
搜索过程:
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有跟定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。也就是说,在我们调用person1.sayName()的时候,会先后执行搜索过程两次。首先解析器会问:“实例person1有sayName属性吗” 答:没有。然后继续搜索,问“person1的原型对象有sayName属性吗?”答:有。于是就读取那个保存在原型对象中的函数。当我们调用person2.sayName()时,重现相同的搜索过程。这就是对象实例共享原型所保存的属性和方法的基本原理。
b.更简单的原型语法
因为prototype指向的是一个对象,因此可以用对象字面法写
function Person(){
}
Person.prototype = {
name : "Jack",
age : 28,
sayName : function(){
alert(this.name);
}
};
因为这个prototype指向的原型对象改变成了对象字面量的对象,这个对象由Object构造函数构造的,因此它的constructor属性是Object。
但是这种模式有个最大的问题,问题由其共享的本性所导致。原型中所有属性是被很多实例共享的,这种共享对于函数非常适合。对于那些包含基本值的属性也说得过去,然而对于包含引用类型值的属性来说,问题比较突出。比如:
function Person(){
}
Person.prototype = {
constructor : Person,
name : "Jack",
age : 29,
friends : ["Bob","Mary"],
sayName : function(){
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Li Lei"; //这句话的意思是在person1实例中添加name属性
person1.friends.push("John"); //这句话改变了prototype数组对象
alert(person2.friends); //输出 Bob,Mary,John
person1.friends = ["Han Mei","Gee"]; //这句话的意思是在person1实例中添加friends属性
alert(person2.friends); //输出 Bob,Mary,John 因为prototype中的对象并未改变
person1对数组对象进行了修改,因为共享,person2.friends也会改变。假如我们的初衷就是像这样在所有的实例共享一个数组(共享这一个数组对象,如果对这个对象进行修改,则全部都修改了),那么这样的结果令人满意。但是,实例一般都要有属于自己的全部属性的,因此我们很少看到有人单独使用原型模式。
⑥组合使用构造函数模式和原型模式(推荐☆☆☆☆☆)
创建自定义类型的最常见方式,就是组合使用构造函数模式和原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。
function Person(name,age){
this.name = name;
this.age = age;
this.friends = ["Bob","John"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Greg",29);
var person2 = new Person("Hanry",26);
person1.friends.push("Mike");
alert(person1.friends); //Bob,John,Mike
alert(person2.friends); //Bob,John
这种模式是用来定义引用类型的一种默认方式,使用最广泛、认同度最高。
参考自:《JavaScript高级程序设计》