今天我们来讨论下创建对象的几种典型模式
工厂模式
用函数来封装以特定接口创建对象的细节
function creatPerson(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=creatPerson("myy", 24, "web engineer");
var person2=creatPerson("mww", 25, "web engineer");
该模式解决了创建多个相似对象的问题,但没解决对象识别的问题(即怎样知道一个对象的类型)。所以构造函数模式应运而生。
构造函数模式
构造函数分原生的和自定义的,原生的有我们常见的Object, Array等用来创建对象的构造函数,运行时会自动出现在运行环境中,此外,我们也可以自定义构造函数,从而定义自定义对象类型的属性和方法。
function Person(name, age, job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
alert(this.name);
};
}
var person1=new Person("myy", 24, "web engineer");
var person2=new Person("myy", 24, "web engineer");
和工厂模式的区别是:
1.没有显式地创建对象;
2.直接将属性和方法付给了this对象;
3.没有return语句。
person1 和person2 分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person。
创建自定义的构造函数意味着可以把它的实例标识为一种特定的类型。这也是构造函数模式胜过工厂模式的地方。
注意:构造函数本身也是函数,只是调用方式不同而已,它可以被当做构造函数,也可以被当做普通函数,还可以在另一个对象的作用域中调用,如下面例子所示:
var o=new Object();
Person.call(o,"myy", 24, "web engineer");
o.sayName(); //myy
问题:使用构造函数的主要问题是每个方法都要在每个实例上重新创建一遍,然而不同实例的同名函数是不相等的,但创建两个完成同样任务的Function实例没什么必要,这种情况下可以通过把函数定义转移到构造函数外部来解决:
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("myy", 24, "web engineer");
var person2=new Person("myy", 24, "web engineer");
问题:问题又来了, 全局作用域中定义的函数实际上只能被某个对象调用,如果对象需要定义很多方法,就要定义多个全局函数,于是这个自定义的引用类型就没有封装性可言了。于是,原型模式闪亮登场!
原型模式
原型的基本语法
构造函数、原型对象、对象实例的关系
此处调用函数确认关系是否存在
alert(Person.prototype.isPrototypeOf(person1));//true
alert(Person.prototype.isPrototypeOf(person1));//true
Es5增加了一个新方法,返回[[Prototype]]的值,该值可读不可写,实例中如果有属性和原型属性重名,则会屏蔽原型属性,但不会修改原型属性。即使将实例属性设置为null,也不会影响原型属性,除非delete操作符删除实例属性,我们才可以重新访问原型属性。
alert(Object.getPrototypeOf(person1)==Person.prototype);//true
当我们读取实例属性时,搜索先从实例本身开始,如果没有继续搜索原型对象属性。
hasOwnProperty()方法可以检测一个属性存在于实例中(返回true)还是存在于原型中。in 操作符可以检测属性是否存在,所以同时使用这两个操作符可以确定属性是否存在于实例或者原型中,如下所示:
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name)&&(name in object); }
要取得对象上所有可枚举的实例属性,可以使用Object.keys()方法。接收对象为参数,返回 属性数组。
如果要获得所有实例属性,不管是否可枚举,要用Object.getOwnPropertyNames()方法。
注意:ES5中constructor和prototype属性默认不可枚举。
原型的动态性
对原型做的修改可以及时反馈到实例中,即使先创造实例后修改原型的
var friend=new Person();
Person.prototype.sayHi=function(){
alert("hi");}
friend.sayHi(); //"hi"
但如果重写原型对象,情况就不一样了,这就等于切断了构造函数与最初原型之间的联系。
function Person(){
}
var friend =new Person();
Person.prototype={
constructor:Person,
name:myy,
age:24,
jao: web,
sayName:function(){
alert(this.name);}
};
friend.sayName(); //error
整个过程如下图所示:
从图中可以看出:重写原型对象切断了原型和任何之前已经存在的对象实例之间的联系;它们引用的仍然是最初的原型。
原生对象的原型
所有原生引用类型(Object、Array、String等)都在其构造函数的原型上定义了方法。
通过原生对象的原型,不仅可以取得所有默认方法的引用,还可以自定义新方法。
String.prototype.startsWith= function (text){
return this.indexOf(text)==0;}
var msg="hello world";
alert(msg.startsWith("hello")); //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("myy","24","web");
friend.sayName();
代码中if语句部分,只有在sayName方法不存在时才会添加到原型中,这段代码只有在初次调用构造函数时才会执行。此后,原型已经完成初始化,不需要再对它修改了。不过要记住这里对它做的修改能够立刻反映在实例中,所以这种方法很完美。
注意:这种模式不能使用对象字面量重写原型,否则会切断联系。对于这种方式创建的对象,可以使用instanceof操作符确定它的类型。
寄生构造函数模式
前面几种模式都不适用的时候,可以使用这种模式,它的基本思想是创建一个函数,该函数的作用是封装创建对象的代码,然后再返回新创建的对象
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 friend =new Person("myy","24","web");
friend.sayName(); //myy
该模式和构造函数模式很像,构造函数不返回值的情况下,默认返回新对象实例,而此处的return语句重写了返回值。
问题:此模式存在的问题是返回的对象与构造函数或者构造函数的原型属性之间没有关系,因此不能通过instanceof操作符来确定对象类型。
稳妥构造函数模式
稳妥对象:指没有公共属性,而且其方法也不引用this的对象,适合在禁止使用this和new的环境中。
fuction Person(){
//创建要返回的对象
var O=new Object();
//可以在这里定义私有变量和函数
//添加方法
O.sayName=function(){
alert(name);};
//返回对象
return O;
}
var friend =Person("myy", "24","web");
friend.sayName(); //myy
注意:以这种模式创建的对象,除了使用sayName方法之外,没有其他方法访问name的值。即使有其他代码给这个对象添加方法或数据成员,也不可能有别的方法访问传入到构造函数中的原始数据。
继承
继承~~~
接口继承:继承方法签名(函数没有签名,不能接口继承)
实现继承:继承实际的方法
原型链
原型对象等于另一个类型的实例,即原型对象指向另一个原型对象(包含着指向另一个构造函数的指针),这样层层递进,构成了实例与原型的链条,实质是重写原型对象,代之以一个新类型的实例。
代码如下:
function SuperType(){
this.property=true;
}
SuperType.prototype.getSuperValue=function(){
return this.peroperty;
}
function SubType(){
this.subproperty=false;
}
//继承了Supertype
SubType.prototype=new SuperType();
subType.prototype.getSubValue=function(){
return this.subproperty;
}
var instance=new SubType();
alert(instance.getSuperValue());//true
原型链扩展了搜索机制,使得搜索过程沿链向上进行。
测试原型与实例的关系,用instanceof和isPrototype,都返回true
alert(instance instanceof Object);//true
alert(SubType.prototype.isPrototypeOf(instance));//true
注意:
1.给原型添加方法一定要在替换原型的语句之后。
2.通过原型链实现继承时,不能使用对象字面量创建原型方法。会重写原型链,导致继承无效。
原型链的问题:
1.包含引用类型值的原型属性会被所有实例共享,通过对实例一的属性值修改,新建的实例也会反映出来。
2.创建子类型时,不能向超类型的构造函数中传递参数,会影响所有对象实例。
借用构造函数
也称伪造对象和经典继承,即在子类型的构造函数内部调用超类型构造函数。
fucntion subType(){
//继承了superType
SuperType.call(this);
}
优点:可以在子类型构造函数中向超类型构造函数传递参数。
缺点:方法都在构造函数中定义,函数复用无从谈起。而且,在超类型的原型中定义的方法,对子类型不可见,结果所有类型只能用构造函数模式。
组合继承
也称伪经典继承,即将原型链和借用构造函数的技术结合。使用原型链实现对原型属性和方法的继承,使用构造函数实现对实例属性的继承。
function SuperType(name){
this.name =name;
this.colors = ["red","blue","green"];
this.sex = "man"
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
//1.继承属性
SuperType.call(this,name);
this.age = age ;
}
//2.继承方法
SubType.prototype = new SuperType();
// SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nichloas",29);
instance1.colors.push("black");
alert(instance1.colors);
alert(instance1.sex);
instance1.sayName();
instance1.sayAge();
var instance2 = new SubType("Greg",27);
alert(instance2.colors);
alert(instance2.sex);
instance2.sayName();
instance2.sayAge();
这样让两个SubType实例既分别拥有自己的属性,又可以使用相同的方法。
原型式继承
借助原型可以基于原有对象创建新对象,同时还不必因此创建自定义类型。
function object(o){
function F(){}
F.prototype=o;
return new F();}
ES5新增了Object.create()方法,该方法接受两个参数,一个用作新对象原型的对象,另外一个为新对象定义额外属性的对象。
var person={name:"",friend:[]};
var anotherPerson=Object.create(person,{name:{value:"myy"}});
alert(anotherPerson.name); //"myy"
寄生式继承
寄生式继承就是创建一个仅用于封装过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
function createAnother(original){
var clone=object(original);
clone.sayHi=function(){
alert("hi");};
return clone;
}
寄生式继承用于主要考虑对象而不是自定义类型和构造函数的情况下
寄生组合式继承
借用构造函数来继承属性,通过原型链的混成形式来继承方法。也就是不必为了指定子类型的原型而调用超类型的构造函数,我们只需超类型原型的一个副本。
function inheritPrototype(subType,superType){
var prototype=object(superTYpe.prototype);//创建对象,超类型原型的一个副本
prototype.constructor=subType;//增强对象,添加constructor属性,弥补重写原型而失去的默认属性
subType.prototype=prototype;//指定对象,将副本赋值给子类型的原型}
这样就可以使用该函数替换为子类型原型赋值的语句了
subType.prototype=new SuperType();
替换成:
inheritPrototype(subType,SuperType);
该模式只调用了一次SuperType构造函数,原型链保持不变,能够正常使用
instanceof和isPrototypeOf(),是公认的引用类型最理想的继承模式。