在创建单个对象的时候,有两个基本的方法。使用Object构造函数或者使用对象字面量。但假如说我们需要创建许多具有部分相同方法,部分相同属性的一类对象,那么这样一个一个去创建这样一类的对象,效率就十分低下,会堆砌出大量重复的代码。
所以开发人员发明了一种函数,用函数来封装以特定接口创建的对象,例如下面的代码:
<script type="text/javascript">
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("孙悟空",18,"齐天大圣");
var person2=createPerson("猪八戒",28,"天蓬元帅");
</script>
在声明并初始化person的值的时候,函数createPerson运行,函数运行创建了一个对象o,函数运行中,根据传入的参数的不同,对象o的每个属性的值也不同,最后对象o被返回,又赋值给了person,person具有了对象o的所有属性和方法;而这些方法和属性都是在函数createPerson内部封装好的,只需要在每次创建person调用就可以了。这种大量创建对象方法,叫做工厂模式。
工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题,因为在这里所有的对象person是由对象o赋值得到的,而o全部都是原生Object类型。所以我们的需求就由大量创建相似对象,升级为创建大量自定义类型的相似对象:
首先,我们要知道在使用Object构造函数创建单个对象的时候,var obj=new Object(),实际上是将Obejct的作用域给了obj,所以obj才可以继承原生构造函数Object()的属性和方法。所以,我们可以自己定义一个构造函数,然后给构造函数内部添加它自己属性和方法:
<script type="text/javascript">
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
alert(this.name);
};
}
var person1=new Person("孙悟空",18,"齐天大圣");
var person2=new Person("猪八戒",28,"天蓬元帅");
</script>
当我们使用
var person1=new Person("孙悟空",18,"齐天大圣");
创建一个对象的时候,会经历四个步骤:
①会创建一个叫做person1的新对象;
②这个新对象会继承构造函数Person的作用域;(所以接下来函数执行的时候,this会指向person1);
③执行构造函数中的代码;(将属性和方法给this)
④返回新的对象;
构造函数有点奇怪的地方就是它没有return语句? 但是在最后返回一个新的对象给person1,这是我不太明白的地方,应该是与构造函数Person是通过new操作符调用的有关。
还有一点需要记住的是,通过构造函数创建的对象中,都存在着一个属于Person,但是不同的实例。这个实例通过constructor(译:“建造者”)属性,指向Person。
alert(person1.constructor==Person);//true;
并且,由于“继承”的原因,实际上person1,person2不但是Person的实例,而且也是Object的实例;这一点可以通过操作符instanceof(译:实例)来证明(instanceof是操作符,不是属性,使用的时候需要注意);
alert(person1 instanceof Person);//true;
alert(person1 instanceof Object);//true;
var o=new Object();
Person.call(o,"红孩儿",13,"小屁孩");
o.sayName();
同样,如上代码所示,构造函数也可以被其他对象调用,调用结束之后,那个对象就有了该构造函数的所有属性和方法。
构造函数虽然好用,但是同样也有缺点:
①每创建一个对象,就要运行一次构造函数;
②如上例所示,person1和person2都有一个sayName方法,但是这个sayName方法并不是只有一个。如上所说,每创建一个对象就要运行一次构造函数,就要分发一个新的sayName方法给新创建的对象;
所以为了性能,实现相同功能的方法只要存在一个就可以了,可以做出如下改动:
构造函数执行时,不给每个新创建的对象分发方法了,改为分发一个指向sayName函数的指针sayName;
<script type="text/javascript">
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=sayName;
}
function sayName(){
alert(this.name);
}
var person1= new Person("孙悟空",18,"齐天大圣");
var person2=new Person("猪八戒",28,"天蓬元帅");
</script>
但是,尽管这样之后,所有的新创建的对象都共用了一个方法sayName。还是有不好的地方,那就是这个方法我们希望只有Person的实例可以访问,可是这个方法在全局之中,有点不太安全的感觉。而且假如说在JS代码之中还出现了其他与sayName同名的方法,就会导致所有的实例都出错。
所以我们就再次对代码进行优化,引入原型模式:
<script type="text/javascript">
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
Person.prototype.master="唐僧";
Person.prototype.sayName=function(){
alert(this.name);
};
}
var person1= new Person("孙悟空",18,"齐天大圣");
var person2=new Person("猪八戒",28,"天蓬元帅");
alert(person1.master);//唐僧
person2.sayName();//猪八戒
</script>
通过引入原型对象,我们就可以让所有实例继承其构造函数原型对象上的属性和方法;
原型对象:我们创建的每一个函数都有一个原型(prototype)属性,这个属性是一个指针,指向一个对象,这个对象上面所有的方法和属性对由这个函数创建的实例都是开放的。
当通过构造函数创建一个新的对象的时候,这个新的对象会有一个新的实例,实例中会含有一个指针,我们称呼这个指针为[[Prototype]],这个指针我们无法访问,但是在新创建的对象上还存在着另外一个属性_proto_,每个实例正是通过_proto_去访问构造函数的原型对象的:
alert(person1.__proto__==Person.prototype);//true
前面我们说到了每个实例上面都自带一个constructor属性,这个属性都是指向构造函数的。但是其实这个属性并不是在实例上,而是在原型对象上面,代码表示为Person.prototype.constructor==Person。
由于是在原型对象上面,所以每个实例都可以访问这个constructor属性。