创建对象
工厂模式
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("Nicholas", 29, "teacher");
var person2 = createPerson("Greg", 27, "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("Nicholas", 29, "teacher");
var person2 = new Person("Greg", 27, "Doctor");
1) person1 和person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性最初用来标识对象类型。
alert(person1.constructor == Person); //true constructor属性指向Person
alert(person2.constructor == Person); //true constructor属性指向Person
2) person1 和person2(创建的所有对象)既是Object的实例,也是Person的实例:
alert(person1 instanceof Object); //true
alert(person2 instanceof Person); //true
alert(person1 instanceof Object); //true
alert(person2 instanceof Person); //true
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型(优于工厂模式):
1) 将构造函数当作函数:任何函数只要通过new操作符调用,它就可以作为构造函数(区别于普通函数)
//当构造函数使用
var person = new Person("Nicholas", 29, "teacher");
person.sayName( ); //"Nicholas"
//当普通函数调用
Person("Greg", 27, "Doctor"); //全局作用域中调用函数,指向Global对象(浏览器中即为window对象)
window.sayName( ); // "Greg"
//在另一个对象作用域中调用
var o = new Object( );
Person.call(o, "Kristen", 25, "Nurse");
o.sayName( ); //"Kristen"
2) 构造函数的问题:每当使用一次构造函数,函数中每个方法都要在每个实例上重新创建一遍(不同实例上的同名函数不相等,有各自不同的作用域链和标识符解析)
alert(person1.sayName == person2.sayName); //false
如要保证两者相等,则可以将函数定义转移到构造函数外部(全局作用域),这样会导致问题:1)全局作用域中定义函数实际上职能被某个对象调用;2) 如果对象需要定义方法很多,则此类全局函数数量增多,导致引用类型没有封装性;
原型模式
每个创建的函数都有prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象包含可以由特定类型的所有实例共享的属性和方法。(即prototype通过调用构造函数而创建的那个对象实例的原型对象)
使用原型对象优点:可以让所有对象实例共享它所包含的属性和方法(不必在构造函数中定义对象实例信息,将这些信息直接添加到原型对象中)。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "teacher";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); // "Nicholas"
var person2 = new Person();
person2.sayName(); // "Nicholas"
alert(person1.sayName == person2.sayName); //true
原型对象的性质:
1) 理解原型对象
1. 只要创建了新函数,就会为该函数创建一个prototype属性,这个内部属性是一个指针,叫[[Prototype]]。
2. 所有原型对象都会自动获得constructor(构造函数)属性,属性包含指向prototype属性所在函数指针。
3. 创建自定义构造函数后,其原型对象默认只会取得constructor属性,其他方法都是从Object继承而来。
isPrototype( ):确定对象是否存在指向原型对象的指针:
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(persom2)); //true
Object.getPrototypeOf( ):返回[[Prototype]]的值:
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
可以通过对象实例访问保存在原型中的值,不能通过对象实例重写原型中的值:
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "teacher";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
person1.name = "Greg";
alert(person1.name); //"Greg" 来自实例
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1); //true
alert(person2.name); //"Nicholas" 来自原型
alert(person2.hasOwnProperty("name")); //false
alert("name" in person2); //true
delete person1.name;
alert(person1.name); //"Nicholas" 来自原型
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
//hasOwnProperty( ): 检测一个属性是存在于实例中,还是存在于原型中(只在给定属性存在于对象实例中,才返回true)
//in操作符:通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
2) 更简单的原型语法
function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job : "teacher",
sayName : function () {
alert(this.name);
}
};
此时,constructor属性指向Object构造函数,不再指向Person函数,除非在内部为constructor设置为Person。
3) 原型动态性
调用构造函数会为实例添加一个指向最初原型的指针[[prototype]],如把原型修改为另一个对象等于切断了构造函数与最初原型之间的联系。
实例中的指针仅指向原型,不指向构造函数。
function Person() {
}
var friend = new Person( );
Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 29,
job : "teacher",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error friend指向的原型中不包含以该名字命名的属性
4) 原生对象的原型
所有原型引用类型(Object, Array, String, 等等)都在其构造函数的原型上定义了方法:
alert(typeof Array.prototype.sort); //"function"
alert(typeof String.prototype.substring); //"function"
通过原生对象的原型,不仅可以取得所有默认方法的引用,也可以定义新方法。可以像修改自定义对象的原型一样修改原生对象的原型,可以随时添加方法:
String.prototype.startsWith = function (text) {
return this.indexOf(text) == 0;
};
var msg = "Hello world!";
alert(msg.startsWith("Hello")); // ture
备注: 不推荐产品化程序中修改原生对象原型。如果因某个实现中缺少某个方法,就在原生对象的原型中添加这个方法,那么当在另一个支持该方法中运行代码时,就可能会导致命名冲突。而且,这样做也可能会意外地重写原生方法。
5) 原型对象的问题
- 省略了为构造函数传递初始化参数环节,所有实例在默认情况下都将取得相同的属性值。
- 所有属性是被很多实例共享,对于包含引用类型值的属性来说问题突出:
function Person() {
}
Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 29,
job : "teacher",
friends : ["Shelby", "Court"],
sayName : function () {
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friend.push("Van");
alert(person1.friends); //"Shelby, Court, Van"
alert(person2.friends); //"Shelby, Court, Van"
alert(person1.friends === person2.friends); //true
}
//所有实例共享一个数组,不能满足每个单独实例有属于自己的全部属性
构造函数模式和原型模式组合
构造函数模式用于定义实例属性,原型模式用于定义方法和共享属性:
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Count"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "teacher");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby, Count, Van"
alert(person2.friends); //"Shelby, Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.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 friend = new Person("Nicholas", 29, "teacher");
friend.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 0;
}
var friend = new Person("Nicholas", 29, "teacher");
friend.sayName( ); //"Nicholas"
除了使用new操作符并把使用的包装函数叫做构造函数外,这个模式跟工厂模式一模一样。
说明: 首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。建议,在其他模式可选的情况下不要使用这种模式
稳妥构造函数模式
稳妥对象指的是没有公共属性,而且其方法也不引用this对象。稳妥对象最适合在一些安全环境中(这些环境中会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。
与寄生构造函数类似,但有两点不同:
1) 新创建对象的实例方法不引用this;
2) 不使用new操作符调用构造函数
function Person(name, age, job){
//创建要返回的对象
var o = new Object( );
//可以在这定义私有变量和函数
//添加方法
o.sayName = function( ){
alert(name);
};
//返回对象
return o;
}
在这种模式下除了使用sayName()方法外,没有其他办法访问name的值。
var friend = Person("Nicholas", 29, "teacher");
friend.sayName(); //"Nicholas"