6.2.1 工厂模式
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("Aska",3,"Musician");
var person2 = createPerson("Yang",4,"Poet");
//from page 144
工厂模式解决了创建多个相似对象的问题,但是没有解决对象识别的问题(即怎样知道一个对象的类型)。
6.2.2 构造函数模式
function Person(name,age,job){
var o = new Object();
this.name = name;
this.age = age;
this.job = job;
this.sayName = function (){
alert(this.name);
};
}
var person1 = new Person("Aska",3,"Musician");
var person2 = new Person("Yang",4,"Poet");
//from page 145
与工厂模式不同的是:
① 没有显示地创建对象 ② 直接将属性和方法赋给了this对象 ③ 没有return语句
构造函数始终以大写字母开头,非构造函数以小写字母开头,所以以上是大写字母开头;
这种方式调用构造函数实际上会经历一下步骤:
① 创建一个新对象;
② 将构造函数的作用域赋值给新对象(因此this就指向了这个新对象);
③ 执行构造函数中的代码;
④ 返回新对象。
person1和person2两个对象都有一个constructor(构造函数)属性,该属性指向Person,如下
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
检测对象类型 :instanceof()操作符
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
1.将构造函数当作函数
构造函数与其他函数的唯一区别,在于调用他们的方式不同。
只要通过new操作符来调用,那它就可以作为构造函数;
function Person(name,age,job){
var o = new Object();
this.name = name;
this.age = age;
this.job = job;
this.sayName = function (){
alert(this.name);
};
}
//当作构造函数使用
var person = new Person("Nicholas", 29, "Engineer");
person.sayName(); //Nicholas
//作为普通函数调用
Person("Greg",27,"Doctor");
window.sayName(); //Greg
//在另一个对象的作用域调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); //Kristen
2.构造函数的问题
好用,但缺点在于每个方法都要在每个实例上重新创建一遍。之前的person1和person2都有一个名为sayName()的方法,但那两个方法不是同一个Function的实例。ECMAScript中函数是对象,每定义一个函数,就实例化一个对象。
以上问题的解决方法:
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("Nicholas", 29, "Enginner");
var person2 = new Person("Greg", 27, "Doctor");
//from page 147
但这个方法也有新问题:在全局作用域中定于的函数实际上只能被某个对象调用,这让全局作用域名捕其实,并且,如果对象需要定义很多方法,那么就要定义很多个全局函数。
6.2.3 原型模式
每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Enginner";
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
//from page 147
1.理解对象原型
创建一个新函数,就会为函数创建一个prototype属性,这个属性指向函数的原型对象。原型对象会自动获取一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在的函数指针。
创建一个新实例后,该实例包含一个[[Prototype]]指针,指向后遭函数的原型对象。但脚本没有标准的方式访问[[Prototype]]。
但是可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
ECMAScript5增加的新方法:Object.getPrototype方法,返回[[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 = "Software Enginner";
Person.prototype.sayName = function (){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); //"Greg" 来自实例
alert(person2.name); //"Nicholas" 来自原型
//from page 149
但是可以通过delete操作符删除实例属性:
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Enginner";
Person.prototype.sayName = function (){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); //"Greg" 来自实例
alert(person2.name); //"Nicholas" 来自原型
delete person1.name;
alert(person1.name); //"Nicholas" 来自原型
hasOwnProperty()方法可以检测属性存在于实例还是原型中,属性存在于实例中才会返回true;
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Enginner";
Person.prototype.sayName = function (){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
person1.name = "Greg";
alert(person1.name); //"Greg" 来自实例
alert(person1.hasOwnProperty("name")); //true
alert(person2.name); //"Nicholas" 来自原型
alert(person2.hasOwnProperty("name")); //false
delete person1.name;
alert(person1.name); //"Nicholas" 来自原型
alert(person1.hasOwnProperty("name")); //false
//from page 151
2.原型与in操作符
单独使用和for-in循环中使用。
① 单独使用:in操作符会在通过对象能够访问给定属性时返回true,无论该属性在实例中还是原型中。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Enginner";
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
//from page 152
可以和hasOwnProperty()一起用,可以确定该属性到底存在于对象中还是原型中;
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
}
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Enginner";
Person.prototype.sayName = function (){
alert(this.name);
};
var person = new Person();
alert(hasPrototypeProperty(person, "name")); //true 存在于原型中
person.name="Greg";
alert(hasPrototypeProperty(person, "name")); //false 存在于实例中
存在于原型中返回true,存在于实例中返回false
② for-in循环中使用:返回所有通过对象访问的,可枚举的属性,包括实例中和原型中的属性。
有bug,IE中由于其实现认为原型方法被打上值为false的[[Enumerable]]标记,因此会跳过该属性,这个bug会影响默认不可枚举的所有属性和方法;
因此要取得对象上所有可枚举的实例属性,可以使用ECMAScript5的Object.keys()方法。这个方法接收一个对象作为参数,返回包含所有可枚举属性的字符串数组。
function Person(){
}
Person.prototype.name = "Aska";
Person.prototype.age = 3;
Person.prototype.job = "Musical poet";
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 = "Yang";
p1.age = 4;
var p1keys = Object.keys(p1);
alert(p1keys); //name,age
//from page 154
想得到所有属性,无论他是否可以枚举:
function Person(){
}
Person.prototype.name = "Aska";
Person.prototype.age = 3;
Person.prototype.job = "Musical poet";
Person.prototype.sayName = function (){
alert(this.name);
}
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //constructor,name,age,job,sayName
3.更简单的原型语法
用一个包含所有属性和方法的对象字面量来重写整个原型对象,如下:
function Person(){
}
Person.prototype = {
name : "Aska",
age : 3,
job : "Musical poet",
sayName : function (){
alert(this.name);
}
};
//from page 154
这样的语法相当于完全重写了 默认的prototype对象,因此constructor属性也变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数,如果constructor很重要,可以这样:
function Person(){
}
Person.prototype = {
constructor : Person,
name : "Aska",
age : 3,
job : "Musical poet",
sayName : function (){
alert(this.name);
}
};
4.原型的动态性
我们对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建了实例后修改原型也是如此。
但是如果重写整个原型对象就不一样了。调用构造函数时会为实例添加一个指向最初原型的[[Prototype]]指针,而把原型修改为另一个对象就等于切断了构造函数与最初原型之间的联系,记住:实例中的指针指向原型,而不是构造函数。
function Person(){
}
var friend = new Person();
Person.prototype = {
constructor : Person,
name : "Aska",
age : 3,
job : "Musical poet",
sayName : function (){
alert(this.name);
}
};
friend.sayName(); //error
//from page 156
5.原生对象的原型
所有原生的引用类型(Object、Array、String等等)都在其构造函数的原型上定义了方法。
例如,在Array.prototype中可以找到sort()方法,
alert(typeof Array.prototype.sort); //function
alert(typeof String.prototype.substring); //function
还可以给原型方法:
String.prototype.startsWith = function (text){
return this.indexOf(text) == 0;
}
但是,不推荐在产品化过程中修改原生对象的原型!
6.原型对象的问题
原型模式做大的问题是:引用类型值的属性,如下:
function Person(){
}
Person.prototype = {
constructor : Person,
name : "Aska",
age : 3,
job : "Musical poet",
alias : ["Pigeon King","Grassland"],
sayName : function (){
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.alias.push("Musical poet");
alert(person1.alias); //Pigeon King,Grassland,Musical poet
alert(person2.alias); //Pigeon King,Grassland,Musical poet
alert(person1.alias == person2.alias); //true
所以很少人单独使用原型模式。
6.2.4 组合使用构造函数模式和原型模式
创建自定义类型的最常见方式,就是组合使用构造函数模式和原型模式。
构造函数模式用于定义实例属性;原型模式用于定义方法和共享属性。这样实例会有自己的实例属性副本,同时又共享着对方法的引用,最大限度的节省了内存。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["as", "ka"];
}
Person.prototype = {
constructor : Person,
sayName : function (){
alert(this.name);
}
};
var person1 = new Person("Aska", 3, "Singer");
var person2 = new Person("Yang", 4, "Poet");
person1.friends.push("Yang");
alert(person1.friends); //as,ka,Yang
alert(person2.friends); //as,ka
alert(person1.friends == person2.friends); //false
alert(person1.sayName == person2.sayName); //true
//from page 159
6.2.5 动态原型模式
把所有信息封装在了构造函数中,通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。
也就是说,可以通过检查每个应该存在的方法是否有效,来决定是否需要初始化原型:
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("Aska", 3, "Singer");
person.sayName(); //Aska
//from page 159
6.2.6 寄生构造函数模式
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("Aska", 3, "Singer");
person.sayName(); //Aska
这个模式可以在特殊情况下用来为对象创建构造函数,假设创建一个具有额外方法的特殊数组。因为不能直接修改Array构造函数,因此可以使用这个模式:
function SpecialArray(){
var values = new Array();
values.push.apply(values,arguments);
values.toPipedString = function (){
return this.join("|");
};
return values;
}
var colors = new SpecialArray("red","blue","green");
alert(colors.toPipedString()); //red|blue|green
需注意:首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同;可以使用其他模式的情况下,建议不使用这种模式
6.2.7 稳妥构造函数模式
稳妥对象指的是没有公共属性,而且其方法也不引用this的对象;
适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。
function Person(name, age, job){
var o = new Object();
o.sayName = function (){
alert(name);
};
return o;
}
var person = Person("Aska",3,"Singer");
person.sayName(); //Aska
这种模式创建的对象中,除了使用sayName()方法之外,没有其他方法访问name的值。