早期的javascript人员经常使用的模式;即创建一个最简单的一个Object实例;
var person=new Object();
person.name="Nicholas";
person.age=22;
person.job="SoftWare Engineer";
person.sayName=function(){
alert(this.name);
}
person.sayName();
以上创建了一个名为Person的对象,并为它添加了三个属性和一个方法,但是这种方式的缺点:使用同一个接口创建很多对象,会产生大量的重复代码
为了解决这个问题,人们开始使用工厂模式;
javascript中的工厂模式
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("greg",22,"doctor");
person1.sayName();
工厂模式的缺点;工厂模式虽然解决了创建多个相似对象的问题,但是却没有解决对象识别的问题;(因为返回的是object对象,而不是一个具体的对象,即无法将它的实例标识为一种特定的类型)
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){alert(this.name);};
}
var person1=new Person("test1",12,"test job");
var person2=new Person("test2",16,"test job too");
person1.sayName();
person2.sayName();
//person1和person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person
//用instranceof 来检测对象类型
alert(person1.constructor==person2.constructor);
alert(person1 instanceof Object);
alert(person1 instanceof Person);
alert(person1.sayName==person2.sayName);//由此可以得出每个Person实例都包含不同的function实例
缺点;每个方法都要在实例上重新创建一遍;
为了解决这个问题,把函数移到了构造函数的外部;
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayHello=sayHello;
}
function sayHello(){ alert(this.name);}
var person1=new Person("test1",12,"test job");
var person2=new Person("test2",16,"test job too");
alert(person1.sayHello==person2.sayHello);//true 由于sayHello保护的是一个指向函数的指针,因此它们共享了全局作用域中定义的同一个
这样做确实解决了两个函数做同一件事情的问题,可是新的问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。
如果对象需要定义很多的方法,那么就要定义多个全局函数,也就是自定义的引用类型没有封装性可言,这些问题可以同构原型模式来解决。
调用构造函数的方式
//当做构造函数使用
var person=new Person("hello",22,"software engineer");//new创建了一个新的对象
person.sayName();
//作为普通函数调用
Person("hello orgin",23,"software test");
window.sayName();//当在全局作用域中调用一个函数时,this对象总是指向Global对象(浏览器中就是window对象)
//在另外一个对象的作用域中调用
var o=new Object();
Person.call(o,"first call",25,"teacher");//使用call()或者apply()在某个特殊对象的作用域中调用Person()对象
o.sayName();
原型模式
使用原型对象的好处是可以让所有的实例共享它所包含的属性和方法,无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象,在默认情况下,所以的原型对象都会自动获得一个constructor属性,这个熟悉包含一个指向prototype属性在所函数的指针。
创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性
function Student(){}
Student.prototype.name="alice test";
Student.prototype.age=22;
Student.prototype.sayName=function(){ alert(this.name);};
var student1=new Student();
student1.sayName();
var student2=new Student();
student2.sayName();
alert(student1.sayName==student2.sayName);
每当代码读取某个对象的某个属性是,都会执行一次搜索,搜索首先会从对象实例本身开始,如果在实例中找到具有给定名字的属性,则返回该属性,如果没有找到,则继续搜索指针指向的原型对象。
当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存同名的属性,即添加的属性只会阻止我们访问原型中的那个属性,而不是修改那个属性。
可以用delete操作符完全删除实例属性。
hasOwnProperty()方法可以检测一个属性是否存在实例中。
in操作符会在通过对象能够访问给定的属性时返回true。
var student1=new Student();
var student2=new Student();
student1.name="alice test also;";
alert(student1.name);//alice test also;-->来自实例
alert(student2.name);//alice test-->来自原型
delete student1.name;//删除来自实例的属性
alert(student1.name)//来自原型
alert(student1.hasOwnProperty("name"));
alert("name" in student1);
原型模式也不是没有缺点,原型模式的最大问题是由其共享的本质所导致的,对于包含引用类型的值的属性来说,问题就比较突出,因为改变一个实例的引用,所有的都改变了。所以创建自定义类型的最常见的方式,就是组合使用构造函数模式和原型模式,构造函数用于定义实例属性,而原型模式用于定义方法和共享的属性。
function Teacher(name,age){
this.name=name;
this.age=age;
this.friends=["alice","stone"];
}
Teacher.prototype={
constructor:Teacher,//如果不重新设置此属性,则为Object
sayName:function(){
alert(this.name);
};
};
var teacher1=new Teacher("hello",22);
var teacher2=new Teacher("Greg",25);
teacher1.friends.push("Van");
alert(teacher1.friends);//"alice","stone","Van"
alert(teacher2.friends);//"alice","stone"
alert(teacher1.friends===teacher2.friends);//false
alert(teacher1.name===teacher2.name);//true