对象可以通过两种形式定义:声明(文字)形式和构造形式
1)对象字面量:
var obj = {
key:value
//...
}
2)构造函数
var obj = new Object();
obj.key = value;
虽然Object构造函数或对象字面量可以用来创建单个对象,但这些方式有个明显的缺点:一个接口创建很多对象,会造成大量的重复代码。为解决这个问题,开始使用工厂模式的一种变体。
工厂模式
function createPerson(name,age,sex){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.sex = sex;
obj.sayName = function(){
return this.name;
}
return obj;
}
var per1 = createPerson('Rose',18,'female');
per1.sayName();//Rose
var per2 = createPerson('Jack',19,'male');
per2.sayName();//Jack
工厂模式虽然解决了多个相似对象的问题,但未解决对象识别的问题(即:怎知一个对象的类型)。
构造函数
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
this.sayName = function(){
return this.name;
}
}
var per1 = new Person('Rose',18,'female');
per1.sayName();//Rose
var per2= new Person('Jack',19,'male');
per2.sayName();//Jack
与工厂模式的不同之处:
1)没有显式地创建对象;
2)直接将属性和方法赋给了this对象;
3)没有return语句返回对象。
根据惯例,构造函数有几点特点:1)构造函数以大写字母开头,非构造函数以小写字母开头;2)创建构造函数的新实例时,必须使用new操作符。
使用构造函数的主要问题是:同样的操作,但每个方法都需在实例上创建一遍
通过把函数转移到构造函数外,来解决这个问题
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
this.sayName = sayName;
}
function sayName(){
return this.name;
}
var per1 = new Person('Rose',18,'female');
per1.sayName();//Rose
var per2= new Person('Jack',19,'male');
per2.sayName();//Jack
我们将sayName属性设置成全局的sayName函数,若对象定义了多个方法,那么我们将定义多个全局函数,就无封装性可言了,因此我们使用原型模式解决问题。
原型模式
function Person(){}
Person.prototype.name = 'Rose';
Person.prototype.age = 18;
Person.prototype.sex = 'female';
Person.prototype.sayName = function(){
return this.name;
}
var per1 = new Person();
per1.sayName();//Rose
var per2 = new Person();
per2.sayName();//ROse
console.log(per1.sayName() === per2.sayName());//true,新对象的这些属性和方法是由所有实例共享的。
更简单的原型语法:用一个包含所有属性和方法的对象字面量来重写整个原型对象
function Person(){}
Person.prototype = {
constructor: Person,
name: 'Rose',
age: 18,
sex: 'female',
sayName: function(){
return this.name;
}
}
var per1 = new Person();
per1.sayName();//Rose
var per2 = new Person();
per2.sayName();//Rose
注:将Person.prototype设置为等于一个以对象字面量形式创建的新对象,最终结果不变,但constructor属性不再指向Person,本质上完全重写了默认的prototype对象,因此constructor属性也变成了新对象的constructor,即指向Object构造函数。若constructor的值很重要,可以特意将它设置回适当的值,如:constructor: Person
原型模式最大的问题就是由其共享特性造成的,对于包含引用类型的属性来说,问题就更加明显了。
function Person(){}
Person.prototype = {
constructor: Person,
name: 'Rose',
age: 18,
sex: 'female',
friends: ['Jack','Joe'],
sayName: function(){
return this.name;
}
}
var per1 = new Person();
per1.friends.push('John');
var per2 = new Person();
console.log(per1.friends);//["Jack", "Joe", "John"]
console.log(per2.friends);//["Jack", "Joe", "John"]
console.log(per1.friends === per2.friends);//true
由于friends数组属性存在于Person.prototype属性中,而非per1中,所以在per1中修改friends数组的属性值,也同样会反应在per2中,这并不是我们想要的结果,这就是我们很少看到单独使用原型模型的问题所在。
组合使用构造函数和原型模式
构造函数模式定义实例属性,原型模式定义共享方法和共享属性
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
this.friends = ['JJ','Joe'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
return this.name;
}
}
var per1 = new Person('Rose',18,'female');
per1.friends.push('John');
var per2 = new Person('Jack',19,'male');
console.log(per1.friends);//["JJ", "Joe", "John"]
console.log(per2.friends);//["JJ", "Joe"]
console.log(per1.friends === per2.friends);//false
console.log(per1.sayName === per2.sayName);//true
构造函数和原型模式混成的模式,是ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法。
动态原型模式
在其他OO语言经验的开发人员在看到独立的构造函数和原型时,很可能会感到困惑。动态原型模式致力于解决这个问题,它将所有信息都封装在构造函数中。
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
if(typeof this.sayName != 'function'){
Person.prototype.sayName = function(){
return this.name;
}
}
}
var per1 = new Person('Rose',18,'female');
per1.sayName();
使用动态原型模式时,不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。
寄生构造函数模式
基本思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
function Person(name,age,sex){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.sex = sex;
obj.sayName = function(){
return this.name;
}
return obj;
}
var per1 = new Person('Rose',18,'female');
per1.sayName();
除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式与工厂模式一模一样。
注:返回的对象域构造函数或者与构造函数的原型属性之间没有关系,即:构造函数返回的对象与在构造函数外部创建的对象没有什么不同,因此,不能依赖 instanceof 操作符来确定对象类型。
稳妥构造函数模式
稳妥对象:指没有公共属性,而且其方法也不引用this对象(禁止使用new和this)。
function Person(name,age,sex){
var obj = new Object();//创建要返回的对象
obj.name = name;//定义私有变量
obj.age = age;
obj.sex = sex;
obj.sayName = function(){//创建方法
return this.name;
}
return obj;//返回对象
}
var per1 = Person('Rose',18,'female');
per1.sayName();//Rose
per1就是一个稳妥对象,除了sayName方法,没有其它途径去获取Person的name属性。
注:与寄生构造函数模式类似,使用稳妥构造函数创建的对象与构造函数没有什么关系,因此使用instanceof操作符对这种对象也没有意义。