创建自定义对象最简单的方式就是创建一个object对象,然后再给它添加属性和方法。
var person = {
name : "lin",
age : = 21,
job : "student",
sayName = function(){
alert(this.name);
}
};
虽然object构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量重复的代码。
工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程。
function createPerson(name,age,job){
var a = new Object();
a.name = "lin";
a.age = 21;
a.job = "student";
a.sayName = function(){
alert(this.name);
};
return a;
}
var person1 = createPerson("sam",23,"teacher");
ECMASscript中的构造函数可用来创建特定类型的对象,像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。使用构造函数模式重写之前的例子如下:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("jun",24,"engineer");
上面的Person()中的代码除了与createPerson()中相同的部分外,还存在着不同:没有显式地创建对象;直接将属性和方法赋给了this对象;没有return语句;
构造函数的缺点:每个方法都要在每个实例上重新创建一次。
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,prototype是通过调用构造函数而创建的对象实例的原型对象。使用原型对象的好处是让所有的对象实例共享它所包含的属性和方法。
function Person(){}
Person.prototype.name = "lin";
Person.prototype.age = 29;
Person.prototype.job = "teacher";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"lin"
var person2 = new Person();
person2.sayName(); //"lin"
alert(person1.sayName == person2.sayName); //true
无论什么时候,只要创建了一个新函数,机会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有的原型对象都会自动获得一个constructor属性,这个属性指向prototype属性所在函数的指针。
虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。可以通过在实例中添加一个属性,该属性与实例原型中的一个属性同名,那么该属性将会屏蔽原型中的那个属性。
function Person(){}
Person.prototype.name = "lin";
Person.prototype.age = 23;
Person.prototype.job = "engineer":
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "sam":
alert(person1.name); //"sam" 来自实例
alert(person2.name); //"lin" 来自原型
//使用hasOwnProperty()方法检测一个属性是在实例中还是原型中
alert(person1.hasOwnProperty("name")); //false
alert(person2.hasOwnProperty("name")); //true
创建自定义类型最常见的就是组合使用构造函数模式和原型模式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。使得每个实例都会有自己的一份实例属性的副本,同时又共享着对方法对使用,最大限度节省了内存,而且这种混合模式还支持向构造函数传递参数。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["lin","xin"];
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
};
}
}
var person1 = new Person("jun",24,"teacher");
var person2 = new Person("sam",28,"student");
person1.friends.push("mo");
alert(person1.friends); //"lin,xin,mo"
alert(person2.friends); //"lin,xin"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象内部都指针。
基本概念:让原型对象等于另一个类型都实例,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针,假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了原型链。
//实现原型链的基本模式
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
实现继承的本质是重写原型对象,代之以一个新类型的实例。原来存在于SuperType的实例中的所有属性和方法,现在也存在于SubType中。
我们知道,所有引用类型都默认继承了Object,这个继承也是通过原型链实现的。所有函数的默认原型是Object,因此默认原型都会包含一个内部指针,指向Object.prototype。这也是所有自定义类型都会继承toString()、valueOf()的根本原因。
原型链的问题:包含引用值类型的原型属性会被所有实例共享,这也是为什么在构造函数中而不是原型对象中定义属性的原因,在通过原型来实现继承时,原型会变成另一个类型的实例,原先的实例属性就变成了现在的原型属性。
在解决原型中包含引用值类型所带来问题的过程中,开发人员开始使用一种借用构造函数的技术。这种技术的基本思想是在子类型构造函数的内部调用超类型的方法构造函数。
function SuperType(){
this.colors = ["red","blue","green"];
}
function SubType(){
//继承SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("pink");
alert(instance1.colors); //"red,blue,green,pink"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
使用apply()和call()方法可以在新创建的对象上执行构造函数。