工厂模式抽象了创建具体对象的过程,函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person对象,可以无数次的调用这个函数,每次都会返回一个包含三个属性一个方法的对象。虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(不知道对象类型)。
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('Tom', 28, 'teacher');
var person2 = createPerson('Jack', 32, 'Doctor');
创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name)
};
}
var person1 = new Person('Tom', 28, 'teacher');
var person2 = new Person('Jack', 32, 'Doctor');
与函数createPerson()的不同之处:
借鉴于其他OO语言,构造函数都应该以大写字母开头,而飞构造函数以小写字母开头。构造函数本身也是函数,只不过可以用来创建对象。要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数会经历以下步骤:
person1和person2分别保存着Person的一个不同实例,这两个对象都有一个construtor(构造函数)属性,该属性都指向Person。
只要通过new操作符来调用就可以作为构造函数;如果不通过new操作符来调用,那就和普通 函数一样。
//当作构造函数使用
var person = new Person('Jack', 32, 'Doctor');
person.sayName();//'Jack'
//作为普通函数调用
Person('Jack', 32, 'Doctor');//添加到window
window.sayName();//'Jack'
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o, 'Jack', 32, 'Doctor');
o.sayName(); //'Jack'
使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。在person1和person2中都有一个sayName()的方法,但是两个方法不是同一个Function的实例。
alert(person1.sayName()==person2.sayName()); //false
可以通过把函数定义转移到构造函数外部来解决这个问题。
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('Tom', 28, 'teacher');
var person2 = new Person('Jack', 32, 'Doctor');
这样又会有新的问题:如果对象需要定义很多方法,那么就要定义很多个全局函数,这样就没有封装性可言了。
我们创建的每个函数都有一个prototype(原型)属性,在这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享他所包含的属性和方法。
function Person(){}
Person.prototype.name = 'Jack';
Person.prototype.age = 23;
Person.prototype.job = 'Doctor';
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //'Jack'
var person2 = new Person();
person2.sayName(); //'Jack'
alert(person1.sayName == person2.sayName); //true
只要创建了一个新函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。Person.prototype.constructor指向Person。
当代码读取某个对象的某个属性时,首先搜索对象实例本身,如果找到了该属性,则返回该属性的值;如果没有找到,则继续搜索指针直线搞得原型对象,在原型对象中查找。当我们在调用person1.sayName()时会执行两次搜索。虽然可以通过对象实例访问保存在原型中的值,但却不能用过对象实例重写原型中的值。
function Person(){}
Person.prototype.name = 'Jack';
Person.prototype.age = 23;
Person.prototype.job = 'Doctor';
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = 'Tom';
alert(person1.name); //'Tom'
alert(person2.name); //'Jack'
在实例对象中添加一个与原型对象同名的属性时,会屏蔽原型对象中保存的属性,但不会修改那个属性。
使用对象字面量形式创建新对象。
function Person(){}
Person.prototype = {
name:'Jack',
age:22,
job:'Doctor',
sayName:function(){
alert(this.name);
}
}
var person = new Person()
alert(person instanceof Object); //true
alert(person instanceof Person); //true
alert(person.constructor == Person); //false
alert(person.constructor == Object); //true
此时,constructor属性是Object而不是Person。
function Person(){}
Person.prototype = {
constructor:Person,
name:'Jack',
age:22,
job:'Doctor',
sayName:function(){
alert(this.name);
}
}
function Person(){}
var person = new Person();
Person.prototype.sayHi = function(){
alert('hi');
};
person.sayHi(); //'hi'
function Person(){}
var person = new Person();
Person.prototype = {
constructor:Person,
name:'Jack',
age:22,
job:'Doctor',
sayName:function(){
alert(this.name);
}
}
person.sayName(); //error
由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来—即使是先创建了实例后修改原型也是一样。但是第二段代码重写了整个原型对象,切断了构造函数与最初原型之间的联系。实例中的指针仅指向原型,而不指向构造函数。
原型模式省略了为构造函数传递初始化参数这一环节,所有实例都将取得相同的属性,原型中所有属性都是被很多实例共享的,可以通过在实例上添加一个同名属性,隐藏原型中对应的属性值,但是对于包含引用类型值的属性问题就很突出。
function Person(){}
Person.prototype = {
constructor:Person,
name:'Jack',
age:22,
friends:['Mary','Jay'],
job:'Doctor',
sayName:function(){
alert(this.name);
}
}
var person1 = new Person()
var person2 = new Person();
person1.friends.push('Van');
alert(person1.friends); //'Mary,Jay,Van'
alert(person2.friends); //'Mary,Jay,Van'
alert(person1.friends === person2.friends); //true
动态原型模式把所有的信息都封装在了构造函数中,而通过在构造函数中初始化原型,又保持了同事使用构造函数和原型的优点。
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)
}
}
}
var person = new Person('Jack', 32, 'Doctor');
person.sayName(); // 'Jack'
寄生构造函数模式是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后返回新创建的对象。
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 person = new Person('Jack', 32, 'Doctor');
person.sayName(); // 'Jack'
出了使用new操作符之外,这个模式跟工厂模式是一模一样的,可以在特殊的情况下用来为对象创建构造函数。
function SpacialArray(){
//创建数组
var values = new Array();
//添加值
values.push.apply(values,arguments);
//添加方法
values.toPipedString = function(){
return this.join('|')
}
return values
}
var colors = new SpacialArray('red','yellow','green');
colors.toPipedString(); // 'red|yellow|green'
返回的对象与构造函数或者构造函数的原型属性之间没有关系,也就是说,构造函数返回的对象与在函数外部创建的对象没有什么不同,因此不能依赖instanceof操作符来确定对象类型。
稳妥构造函数模式遵循与寄生构造函数类似的模式,但是有两点不同:一是新创建对象的实例方法不引用this;二是不适用new操作符调用构造函数。
function Person(name,age,job){
var o = new Object();
o.sayName = function() {
alert(this.name);
};
return o;
}
var person = new Person('Jack', 32, 'Doctor');
person.sayName(); // 'Jack'
这种模式除了使用sayName()方法之外,没有其他办法访问name的值,这样变量person中保存的是一个稳妥对象,使得它非常适合在某些安全执行环境下使用。