Object构造函数或对象字面量可以用来创建单个对象,但是有明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。
因此,可以考虑使用设计模式来创建对象。
工厂模式
抽象了创建具体对象的过程。
在ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以待定接口创建对象的细节。
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 p1 = createPerson("wonder",23,"student");
缺点:虽然解决了创建多个相似对象的问题,但没有解决对象识别的问题(即怎样知道一个对象的类型)。
构造函数模式
创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName=function(){
alert(this.name);
};
}
var p1 = new Person("wonder",23,"student");
与工厂模式的不同:
- 没有显示地创建对象;
- 直接将属性和方法赋给this对象;
- 没有return语句
与工厂模式相比的优点:
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型。
注意:构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。为了区别其它函数:因为构造函数本身也是函数,只不过可以用来创建对象。
要创建Person的新实例,必须使用new操作符。
检测对象类型,用instanceof操作符比较可靠。
alert(p1 instanceof Object); //true
alert(p1 instanceof Person); //true
1、将构造函数当做函数
构造函数与其他函数的唯一区别:调用它们的方式不同。
任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那它跟普通函数不会有什么两样。
//构造函数使用
var person = new Person("wonder",23,"student");
person.sayName(); //"wonder"
//普通函数调用
Person("wonder",23,"student"); //添加到window
window.sayName(); //"wonder
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o,"Abby",25,"Nurse");
o.sayName(); //"Abby"
2、构造函数的问题
使用构造函数的缺点:每个方法都要在每个实例上重新创建一遍。因此不同实例上的同名函数是不相等的。
var p1 = new Person("wonder",23,"student");
var p2 = new Person("Abby",25,"Nurse");
alert(p1.sayName == p2.sayName); //false
但是,创建两个完成同样任务的Function实例没有必要。因此可以通过把函数定义转移到构造函数外部来解决这个问题:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName= sayName;
}
function sayName(){
alert(this.name);
}
但是这样也有缺点:若对象需要定义很多方法,那就要定义很多个全局函数,因此这个自定义的引用类型就没有封装性可言了。
原型模式可以解决这个问题。
原型模式
创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
优点:可以让所有对象实例共享它所包含的属性和方法。
function Person(){
}
Person.prototype.name = "wonder";
Person.prototype.age = 23;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var p1 = new Person();
p1.sayName(); //"student"
var p2 = new Person();
p2.sayName(); //"student"
alert(p1.sayName == p2.sayName); //true
不能通过对象实例重写原型中的值。如果在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那在实例中间就会创建该属性,该属性将会屏蔽原型中的那个属性。
function Person(){
}
Person.prototype.name = "wonder";
Person.prototype.age = 23;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var p1 = new Person();
var p2 = new Person();
p1.name = "Greg";
alert(p1.name); //"Greg"——来自实例
alert(p2.name); //"wonder"——来自原型
不过,使用delete操作符可以完全删除实例属性,从而能够重新访问原型中的属性。
function Person(){
}
Person.prototype.name = "wonder";
Person.prototype.age = 23;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var p1 = new Person();
var p2 = new Person();
p1.name = "Greg";
alert(p1.name); //"Greg"——来自实例
alert(p2.name); //"wonder"——来自原型
delete p1.name;
alert(p1.name); //"wonder"——来自原型
1、方法isPrototypeOf()可以确定对象之间是否存在关系。如果[[Prototype]]指向调用isPrototypeOf()方法的对象,则方法返回true。
alert(Person.prototype.isPrototypeOf(p1)); //true
alert(Person.prototype.isPrototypeOf(p2)); //true
2、hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法只在给定属性存在于对象实例中时,才会返回true。
function Person(){
}
Person.prototype.name = "wonder";
Person.prototype.age = 23;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var p1 = new Person();
var p2 = new Person();
alert(p1.hasOwnProperty("name")); //false
p1.name = "Greg";
alert(p1.name); //"Greg"——来自实例
alert(p1.hasOwnProperty("name")); //true
delete p1.name;
alert(p1.hasOwnProperty("name")); //false
in操作符
in除了在for-in循环中使用之外,还能单独使用。单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
function Person(){
}
Person.prototype.name = "wonder";
Person.prototype.age = 23;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var p1 = new Person();
var p2 = new Person();
alert(p1.hasOwnProperty("name")); //false
alert("name" in p1); //true
p1.name = "Greg";
alert(p1.name); //"Greg"——来自实例
alert(p1.hasOwnProperty("name")); //true
alert("name" in p1); //true
delete p1.name;
alert(p1.name); //"wonder"——来自原型
alert(p1.hasOwnProperty("name")); //false
alert("name" in p1); //true
同时使用in操作符和hasOwnProperty()方法,就可以确定属性是存在于对象中,还是存在于原型中:
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name) && (name in object);
}
Object.keys()
用途:可以获取对象上所有可枚举的实例属性。
param:一个对象
return:包含所有可枚举属性的字符串数组
支持:IE9+、Firefox 4+、Safari 5+、Opera 12+ 和 Chrome
function Person(){
}
Person.prototype.name = "wonder";
Person.prototype.age = 23;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"
Object.getOwnPropertyNames()
用途:得到所有实例属性,无论是否可枚举。
param:一个对象
return:包含所有实例属性的字符串数组
支持:IE9+、Firefox 4+、Safari 5+、Opera 12+ 和 Chrome
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"
简化原型语法
function Person() {
}
Person.prototype = {
name:'wonder',
age:23,
job:'student',
sayName:function(){
alert(this.name);
}
};
这样的语法,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。
var friend = new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true
如果constructor的值很重要,则可以特意设置回适当的值。但是会导致它的[[Enumerable]]特性被设置为true。默认情况下,原生的constructor属性是不可枚举的,因此可以用Object.defineProperty()。
function Person() {
}
Person.prototype = {
constructor:Person,
name:'wonder',
age:23,
job:'student',
sayName:function(){
alert(this.name);
}
};
//重设构造函数,只适用于ECMAScript 5兼容的浏览器。
Object.defineProperty(Person.propotype,"constructor",{
enumerable:false,
value:Person
});
重写原型对象会切断现有原型与任何之前已经存在的对象实例之间的联系。
function Person() {
}
var friend = new Person();
Person.prototype = {
name:'wonder',
age:23,
job:'student',
sayName:function(){
alert(this.name);
}
};
friend.sayName(); //error
原型模式的缺点
1.省略了为构造函数传递初始化参数环节,所有实例在默认情况下都将取得相同的属性值。
2.原型中所有属性被很多实例共享,因此对于包含引用类型值的属性来说,会产生问题:
function Person(){
}
Person.prototype = {
constructor:Person,
name:'wonder',
age:23,
job:'student',
friends:["Abby","Court"],
sayName:function(){
alert(this.name);
}
};
var p1 = new Person();
var p2 = new Person();
p1.friends.push("Lucy");
alert(p1.friends); //"Abby,Court,Lucy"
alert(p2.friends); //"Abby,Court,Lucy"
alert(p1.friends == p2.friends); //true
组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。
组这种模式是用来定义引用类型的一种默认模式。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends=["Abby","Court"];
}
Person.prototype = {
constructor:Person,
sayName:function(){
alert(this.name);
}
};
var p1 = new Person("Wonder",23,"student");
var p2 = new Person("Lucy",29,"nurse");
p1.friends.push("Van");
alert(p1.friends); //"Abby,Court,Van"
alert(p2.friends); //"Abby,Court"
alert(p1.friends == p2.friends); //false
alert(p1.sayName == p2.sayName); //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 p1 = new Person("Wonder",23,"student");
p1.sayName();
寄生构造函数模式
基本思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
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 p1 = new Person("Wonder",23,"student");
p1.sayName();
稳妥构造函数模式
稳妥对象——指的是没有公共属性,而且其方法也不引用this的对象。
试用:最适合在一些安全的环境中(这些环境中会禁止试用this和new),或者在防止数据被其他应用程序改动时试用。
稳妥构造函数与寄生构造函数有类似的模式,但有两点不同:
- 新创建对象的实例方法不引用this
- 不适用new操作符调用构造函数
function Person(name,age,job){
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
alert(name);
}
return o;
}
var p1 = new Person("Wonder",23,"student");
p1.sayName();
这样变量p1中保存的是一个稳妥对象,除了调用sayName()方法外,没有别的方式可以访问其数据成员。