对象字面量
var person = {
name:"aa",
age:20,
job:"student",
sayName:function(){
alert(this.name);
}
}
工厂模式
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("aa",20,"student");
var person2 = createPerson("bb",22,"teacher");
构造函数模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("aa",20,"student");
var person2 = new Person("bb",22,"teacher");
构造函数始终应以大写字母开头,非构造函数则以小写字母开头。
要创建Person的新实例,必须用new操作符。
person1和person2都有一个constructor(构造函数)属性,该属性指向Person。
person1.constructor == Person; //true
优于工厂模式:可以将他的实例表示为一种特定的类型而非只是Object。
person1 instanceof Person //true
构造函数与其它函数的区别在于调用方式不同。任何函数通过new操作符调用都会作为构造函数,反之不通过new操作符调用那么就是普通函数。
//当做构造函数
var person1 = new Person("aa",20,"student");
person1.sayName(); //aa
//作为普通函数
Person("ee",20,"student");//添加到window
window.sayName();//ee
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o,"cc",34,"mother");
o.sayName(); //cc
作为普通函数调用时,在全局作用域中调用的函数this指向window对象。
构造函数缺点:每个方法都会在每个实例上重新创造一遍。不同实例上的同名函数是不相等的。
原型模式
原型对象:包含所有实例共享的属性和方法。
function Person(){
}
Person.prototype.name = "aa";
Person.prototype.age = 20;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"aa"
var person2 = new Person();
person2.sayName(); //"aa"
person1.sayName == person2.sayName; //true
上例中所有实例访问同一组属性与方法。
检测是否是实例的原型对象:
Person.prototype.isPrototypeOf(person1); //true
Object.getPrototypeOf(person1) == Person.prototype; //true
Object.getPrototypeOf方便地取得一个对象的原型。
搜索属性:
当代码读取某个对象的某个属性时,都会执行一次搜索。
对象实例可以访问原型中的值,但不能重写。若实例中添加了一个与原型中某一个属性同名的属性,则该属性会屏蔽原型中的同名属性:
function Person(){
}
Person.prototype.name = "aa";
Person.prototype.age = 20;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"aa"
var person2 = new Person();
person2.sayName(); //"aa"
person1.name = "bb" ;
person1.name; //"bb"---来自实例
person2.name; //"aa"---来自原型
使用delete操作符可以删除实例属性:
person1.name = "bb" ;
person1.name; //"bb"---来自实例
person2.name; //"aa"---来自原型
delete person1.name;
person1.name; //"aa" ---来自原型
hasOwnProperty:检测一个属性存在于实例还是存在于原型。
person1.hasOwnProperty("name"); //false
person1.name = "bb" ;
person1.name; //"bb"---来自实例
person1.hasOwnProperty("name"); //true
delete person1.name;
person1.name; //"aa" ---来自原型
person1.hasOwnProperty("name"); //false
单独使用in操作符:对象能访问给定属性时返回true,无论此属性是来自实例还是原型。
function Person(){
}
Person.prototype.name = "aa";
Person.prototype.age = 20;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.hasOwnProperty("name"); //false
"name" in person1 //true
person1.name = "bb" ;
person1.name; //"bb"---来自实例
person1.hasOwnProperty("name"); //true
"name" in person1 //true
因此利用hasOwnProperty与in可以判断一个属性是来自实例还是原型。
function hasPrototypeProperty(object,name){
return !object.hasOwnPeoperty(name)&&(name in object);
}
使用for-in时,返回的是对象能访问的,可枚举的属性,其中包括来自实例的也包括来自原型的。
Object.keys( )方法可返回对象上所有可枚举的实例属性:
function Person(){
}
Person.prototype.name = "aa";
Person.prototype.age = 20;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var keys = Object.keys(Person.prototype); //["name", "age", "job", "sayName"]
var person1 = new Person();
var keys = Object.keys(person1 );//[]
person1.name = "bb";
person1.age = 23;
var keys = Object.keys(person1 );//["name", "age"]
获取所有实例属性而无论其是否可枚举:Object.getOwnPropertyNames( )
var keys = Object.getOwnPropertyNames(Person.prototype);//["constructor", "name", "age", "job", "sayName"]
结果中包含了不可枚举的constructor属性。
Object.keys与Object.getOwnPropertyNames均可替代for-in。
更简单的原型语法
function Person(){
}
Person.prototype = {
name:"aa",
age:20,
job:"student",
sayName:function(){
alert(this.name);
}
}
但是在这种语法下,本质上是完全重写了默认的prototype对象,因此constructor属性也就不再指向Person而是Object。
若constructor属性十分重要,则可以像下面这样:
function Person(){
}
Person.prototype = {
constructor:Person,
name:"aa",
age:20,
job:"student",
sayName:function(){
alert(this.name);
}
}
而此时由用户重设的constructor属性已经变成了可枚举的了。
由于在原型中查找值的过程是一次搜索,因此对原型对象所做的任何修改都能够立即从实例上反映出来,即使是先创建实例后修改原型。
function Person(){}
var friend = new Person();
Person.prototype.sayHi =function(){
alert("Hi");
}
friend.sayHi(); //Hi
由于实例和原型间是松散连接关系,其间不过是一个指针,因此通过上面的搜索属性就可以在原型中找到新的方法。
但是如果重写整个原型对象的话(如上面的更简单的原型语法),由于调用构造函数时会为实例添加一个指向最初原型的指针,如果把原型指向另一个对象就等于切断了新原型与已经存在的实例之间联系,也切断了构造函数与旧原型的联系。已经存在的实例引用的仍是最初的原型。
function Person(){}
var friend = new Person();
Person.prototype = {
constructor:Person,
name:"aa",
age:20,
job:"student",
sayName:function(){
alert(this.name);
}
}
friend.sayName(); //error
下图展示了上列过程:
原生对象如Object,Array,String等都是在其构造函数的原型上定义了方法。我们也可以用此方法定义新方法:
String.prototype.startWith = function(text){
return this.indexOf(text)==0;
}
var msg = "hello world";
alert(msg.startWith("hello")); //true
原型对象也有缺点:
1.省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都会取得相同的属性值。
2.原型中的属性是被多实例共享的,对于基本值属性还好,因为可以同名重写,但对于包含引用类型值的属性来说,就会有如下问题:
function Person(){
}
Person.prototype = {
constructor:Person,
name:"aa",
age:20,
job:"student",
friends:["bb","cc"],
sayName:function(){
alert(this.name);
}
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("dd");
person1.friends;//"bb,cc,dd"
person2.friends;//"bb,cc,dd"
person1.friends == person2.friends; //true
组合使用构造函数模式与原型模式
创建自定义类型的最常见方式,构造函数用于定义实例属性,原型用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性同时又共享者对方法的引用,最大限度地节省了内存。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["bb","cc"];
}
Person.prototype = {
constructor:Person,
sayName:function(){
alert(this.name);
}
}
var person1 = new Person("alice",30,"students");
var person2 = new Person("bob",34,"ceo");
person1.friends.push("dd");
person1.friends;//"bb,cc,dd"
person2.friends;//"bb,cc"
person1.friends == person2.friends; //false
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("alice",30,"students");
friend.sayName(); //alice
if检测那段代码只会在初次调用构造函数时执行,此后原型已经完成初始化,之后调用构造函数创建的实例都会有sayname属性,不会走if里面的语句。if语句检查的可以是初始化之后应该存在的属性或方法。
使用动态函数模型时不能使用对象字面量重写原型,否则会切断现有实例与新原型之间的联系。