首页 > 代码库 > js面向对象程序设计之构造函数

js面向对象程序设计之构造函数

再上一篇的开头说了创建对象的两种方式,一种是Object构造函数的方式,一种是对象字面量的方法。但这些方式创建多个对象的时候都会产生大量的重复代码。经过技术的进步也演化出来许多的创建对象的模式。本章会介绍 工厂模式,原型模式,构造函数模式和构造函数与原型模式的混合使用。

1,工厂模式

工厂模式是一个比较广为人知的模式,这种模式将细节抽象出来。代码如下

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("ds",12,"dada");
var person2=createPerson("ds2",122,"dada2");

给他所需要的材料,他就会返回一个对象。解决了大量重复代码的问题。可是又有一个问题,就是每个返回的对象都是Object。每个对象的的类型部分却别开来。所以有个新的模式来解决这个问题。

2,构造函数模式

废话不多说,还是先上代码吧。

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=function(){
        alert(this.name)
    }
}

var person1=new Person("ds",12,"dada");
var person2=new Person("ds2",122,"dada2");

第二种的使用了new的关键字。实际上经历了4步。1,创建了一个新对象。2,将构造函数的作用域赋给创建的变量。3,执行代码。4,返回新的对象。

person1和person2是不同的实例。两个对像有一个Constructor(构造函数)指向Person的函数本身。而Constructor就是用来表示对象的类型。这也是构造函数模式和工厂模式的不同。而且想Object和Array的就是js中原生的构造函数。创建自定义的构造函数意味着可以为他的实例标识为一种特定的类型。

其实构造函数也是函数知识调用的方式不同而已。构造函数也可以普通的方式调用。

var p=new Object();
Person.call(p,"sss",22,"222");

但是构造函数并不是没有问题,比如说上面的那个SayName(),其实person1和person2的SayName其实并不是同一个Funcition的实例。也就是说每new一个对象,SayName本身的方法也被new了一次。所有实例的SayName都是独立的并没有指向同一个方法。解决的方法也有可以将方法提取到全局变量。

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=sayName;
}

function sayName()
{
    alert(this.name)
}

但是问题又来了,sayName作为全局变量的话却只能被Person调用,有点不太符合全局变量的定义。重要的是如果方法很多的话又要在全局变量中添加许许多多的方法。好在这些问题都可以通过原型模式来解决

3,原型模式

在说原型模型的时候,应该先说明一下prototype(原型)是什么?其实它就是一个指针,指向一个对象。这个对象就是可以给特定类型的所有实例共享的属性和方法。还是看代码吧。

function Person(){
}

Person.prototype.name="shaoqi";
Person.prototype.age=28;
Person.prototype.job="IT";
Person.prototype.sayName=function(){
    alert(this.name);
}

var person1=new Pserson();
person1.sayName();
  技术分享

上图解释了对象,实例,原型和构造函数之间的关系。

在js中,只要创建一个新函数就会为该函数创建一个prototype的属性。指向函数的原型对象。原型对象有都会有个constructor的指针,指向原函数。每个实例也有个指针[[prototype]],指向原型。其实每个实例和构造函数是没有直接关系的,是通过原型将实例和构造函数关联起来。

其实构造函数还有个更简单的写法。

function Person(){

}

Person.prototype={
    name:"shaoqi",
    age:23,
    job:"It",
    SayName:function(){
        alert(this.name);
    }
}

但这样又有个问题,这样就相当与重写了整个prototype,js本身是不会给它生成constructor的。也就是说这个原型对象中没有指向Person的指针。但可以给它显示的给它赋值。

function Person(){

}

Person.prototype={
    constructor:Person,
    name:"shaoqi",
    age:23,
    job:"It",
    SayName:function(){
        alert(this.name);
    }
}

但还有个小问题,就是当这样显示的给原型指定构造函数的话,它的[[Enumerable]]会默认为true,原生的constructor是不能被枚举的。如果一定要兼容ECMAScript5的话可以改写成下面代码

function Person(){

}

Person.prototype={
    name:"shaoqi",
    age:23,
    job:"It",
    SayName:function(){
        alert(this.name);
    }
}

Object.defineProperty(Perosn.prototype,"constructor",{
    enumerable:false,
    value:Person
})

defineProperty这个方法的话在上一遍中有提到过。用来给属性显示的赋值特性的方法。

原型模式还有个动态性,应为本身在实例中它是已指针的形式存在的。可以动态的给原型添加方法和属性。可以动态的添加到实例上面。当然原型对象也不是完美的,也是存在问题的。下面来看一段代码。

function Person(){

}

Person.prototype={
    constructor:Person,
    name:"shaoqi",
    age:23,
    job:"It",
    friends:["11","22","33"],
    SayName:function(){
        alert(this.name);
    }
}

var person1=new Person();
var person2=new Person();

person1.friends.push("44");
alert(person1.friends);
alert(person2.friends);

问题就在于所有的实例(person1,person2)的指针都指向了一个数组。person1对数组操作之后,会改变原型中的数据,那么person2的Friend也就变了。实际实例一般都是要有属于自己的全部属性的。所以这才是很少有人单独使用原型模式的原因。

4,组合使用构造函数和原型模式

其实看到这里,我们会发现原型模式和构造模式的一些问题。构造函数模式是所有的属性都是为每一个实例重新的初始化一个出来。而原型模式则是为所有的实例公用一个属性或者方法。其实两种方法都有点极端,在一个对象中,其实情况是多样的,有些属性需要独立,有些需要共享。所有就有了这种组合模式的出现。可谓是取了两种模式之长。

function Person(name,age,job)
{
    this.name=name;
    this.age=age;
    this.job=job;
    this.friends=["11","22"]
}

Person.prototype={
    constructor:Person,
    sayName:function(){
        alert(this.name);
    }
}

将需要独立出来属性放在构造函数里面进行初始化,然后类似方法的属性则通过原型的方式进行赋值。

这是在ECMAScript5中使用最广泛的创建自定义类型方式。

5,动态原型模式

其实在对象语言中如C#中是没有原型一说的,只有一个构造函数。为了尽量的消除这点差异便又来动态原型模式。它就是将所有的信息都封装到了构造函数中。

function Person(name,age,job)
{
    this.name=name;
    this.age=age;
    this.job=job;
    this.friends=["11","22"];
    if(typeof this.sayName != "function")
    {
        Person.prototype.sayName=function(){
            alert(this.name)
        }
    }
}

不同之处就是在构造函数中有个判断。这个判断保证里面的代码只在初始化的时候运行一遍。这样就可用同一个构造函数来完成组合模式的作用了。

6,寄生模式

这是一个比较少用的模式。其实他和工厂方式没什么太大的区别。看个例子。

function Person(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=new Person("dd",12,"dd");

是不是和工厂模式一毛一样??其实唯一的区别就在于调用的时候,寄生模式使用了new的方法进行初始化。这种模式一般使用在什么场景呢?

假设我们想在原生的Array的基础上改造一下便可以这样

function SpecialArray()
{
    var value=http://www.mamicode.com/new Array();

    value.push.apply(values,arguments);

    value.toPopedString=function(){
        return this.join("|");
    }

    return value;
}

var colors=new SpecialArray("11","2","33");
alert(colors.toPopedString());

在原有的类型上进行改造,可以用到寄生模式,有点类似于c#中的扩展。但是这样产生的实例和构造函数,原型是没有任何关系的。所以建议在可以使用它模式的前提下就不要用这种模式了。

这一篇吧对象的构造函数说完了,也说了原型的原理。下一篇就开始继承和原型链了。

js面向对象程序设计之构造函数