创建对象的方法(构造模式)

1、工厂模式
由于ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节。

 1 function createPerson( name, age, job ){
 2     var o = new Object();
 3     o.name = name;
 4     o.age = age;
 5     o.job = job;
 6 
 7     o.sayName = function(){
 8         alert( this.name );
 9     };
10 
11     return o;
12 }

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

 

2、构造函数模式
ECMAScript 中的构造函数可以用来创建特定类型的对象。

 1 function Person( name, age, job ){
 2     this.name = name;
 3     this.age = age;
 4     this.job = job;
 5 
 6     this.sayName = function(){
 7         alert( this.name );
 8     };
 9 }
10 
11 var person1 = new Person( "Nicholas", 29, "Software Engineer" );
12 var person2 = new Person( "Greg", 27, "Doctor" );

在上面这个例子中,person1和person2分别保存中Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person。

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

 

1)将构造函数当做函数
构造函数与其他函数的唯一区别,就在于调用它们的方法不同。不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要通过new操作符来调用,那它就可以作为构造函数。而任何函数,如果不是通过new操作符来调用,那它 跟普普通函数也不会有什么两样。

 1 //当做构造函数使用
 2 var person = new Person( "Nicholas", 29, "Software Engineer" );
 3 person.sayName();     //"Nicholas"
 4 
 5 //作为普通函数调用
 6 Person( "Greg", 27, "Doctor" );
 7 window.sayName();        //"Greg"
 8 
 9 //在另一个对象的作用域中调用
10 var o = new Object();
11 Person.call( o, "Kristen", 25, "Nurse" );
12 o.sayName();     //"Kristen"

 

2)构造函数的问题
使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。在前面的例子中,person1和person2都有一个额名为sayName()的方法,但是那两个方法并不是同一个Function的实例。

在ECMAScript中函数是对象,因此每定义一个函数,也就是实例化了一个对象。

而创建两个完成同样任务的Function实例的确没有必要。况且有this对象在,根本不用在执行代码前就把函数绑定到特定的对象上,可以通过这样的形式定义:

 1 function Person( name, age, job ){
 2     this.name = name;
 3     this.age = age;
 4     this.job = job;
 5 
 6     this.sayName = sayName;
 7 }
 8 
 9 function sayName(){
10     alert( this.name );
11 }

 

如此一来,就可以将sayName()函数的定义转移到构造函数外部。而在构造函数内部,我们将sayName属性设置成全局的sayName函数。这样的话,由于sayName包含的是一个指向函数的指针,因此person1和person2对象就可以共享在全局作用域中定义的同一个sayName()函数。

这样做解决了两个函数做同一件事的问题,但是新的问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。而更重要的是:如果对象需要定义很多方法,那么就需要定义很多个全局函数,这样一来,我们自定义的这个引用类型就毫无封装性可言了。

这些问题可以通过使用原型模式来解决。


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

使用原型的好处是可以让所有对象实例共享它所包含的属性和方法。

 

 1 function Person(){
 2 }
 3 
 4 Person.prototype.name = "Nicholas";
 5 Person.prototype.age = 29;
 6 Person.prototype.job = "Software Engineer";
 7 Person.prototype.sayName = function(){
 8     alert( this.name );
 9 };
10 
11 var person1 = new Person();
12 person1.sayName();        //"Nicholas"
13 
14 var person2 = new Person();
15 person2.sayName();        //"Nicholas"
16 
17 alert( person1.sayName == person2.sayName );        //true

 

1)理解原型
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性。

在默认情况下,所有prototype属性都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。

每当代码读取某个对象的某个属性时,都会执行一搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。

虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。

如果我们在实例中添加了一个属性,而该属性与实例中的一个属性同名,那么就会在实例中创建该属性,该属性将会屏蔽原型中的那个属性。

即使是将属性设置为null,也只是在实例中的属性值为null。

不过,使用delete操作符可以完全删除实例属性,从而能够重新访问原型中的属性。


使用hasOwnProperty() 方法可以检测一个属性是存在于实例中,还是存在与原型中。这个方法只在给定属性存在于对象实例中时,才会返回true。


2)原型与in操作符
in操作符会在通过对象能够访问给定属性时返回true,无论该属性是存在于实例中还是原型中。


3)更简单的原型语法

function Person(){
    
}

Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",

    syaName : function(){
        alert( this.name );
    }
};

使用这个方式添加原型属性实际上相当于重新创建了一个原型对象。因此,constructor属性不再指向Person了。而是指向了Object构造函数。因此,尽管使用instanceof操作符还能够返回正确的结果,但是通过constructor已经无法确定对象的类型了。

var person = new Person();

alert( person instanceOf Object );        //true
alert( person instanceOf Person );        //true
alert( person.constructor == Person );        //false
alert( person.constructor == Object );        //true

因此需要在prototype中重新为constructor指定值:constructor : Person

 

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

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

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 );
    }
};

 

5、动态原型模式
有着其他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 );
        };
    }
}

其中if区块中的语句只会在构造函数初次调用时执行。伺候原型已经完成初始化,不需要再做修改了。

if语句检查的可以使初始化之后应该存在的任何属性或方法,不需要使用一大堆的if语句检查每个属性和每个方法,只需要检查其中的一个即可。

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

 

 

posted @ 2013-02-01 22:33  向往天空的鱼  阅读(1886)  评论(2编辑  收藏  举报