原型对象的问题
1>.它省略了构造函数传递初始化参数这一环节,结果所有实例默认情况下都将取得相同的属性值。
2>.原型模式的最大问题是由其共享的本性所导致的。
原型中所有属性是被很多实例所共享的,这种共享对于函数非常合适,对于那些包含基本值的属性倒也说的过去,通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然而对于包含引用类型值的属性来说,问题就比较突出了。
function Person(){} Person.prototype={ name:"mike", age:29, friends:["john","jerge"], job:"engineer", sayName:function(){ alert(this.name); } }; var person=new Person(); var person2=new Person(); person.friends.push("sari"); alert(person.friends);//john,jerge,sari alert(person2.friends);//john,jerge,sari alert(person.friends===person2.friends);//true;
//原型属性共享了
组合使用构造函数模式和原型模式
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.friends=["jerge","winter"]; } Person.prototype={ constructor:Person, sayName:function(){ alert(this.name); } }; var person=new Person("mike",29,"engineer"); var person2=new Person("john",22,"dc"); person.friends.push("sari"); alert(person.friends);//jerge,winter,sari alert(person2.friends);//jerge,winter alert(person.friends===person2.friends);//false; alert(person.sayName===person2.sayName);//true;
//这个例子中,所有实例的属性都在构造函数中定义的,所有实例共享的属性constructor和方法sayName()都是在原型中定义的。此时再修改person.friends将不会再英雄person2.friends.因为它们引用了不同的数组
动态原型模式
可以通过检测某个应该存在的方法是否有效,来决定是否需要初始化原型。
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("mike",29,"engineer"); person.sayName();
//这里只在sayName()方法不存在的情况下,才会将它添加到原型中。这端代码只会在初次调用构造函数时才会执行,此后,原型已经完成初始化。也可以用instanceof操作符确定它的类型。
寄生构造函数模式
function create(name,age,sex){ var o=new Object(); o.name=name; o.age=age; o.sex=sex; o.sayName=function(){ alert(this.name); }; return o; } var person=new create("mike",29,"man");//与工厂模式的区别就在这,寄生构造函数模式使用new 操作符并把使用的包装函数叫做构造函数之外 person.sayName();//mike
构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个return语句,可以重写调用构造函数时返回的值。
这个模式可以在特殊的情况下来为对象创建构造函数,假如我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,因此可以使用这个模式。
function SpecialArray(){ //创建数组 var values=new Array(); //添加至 values.push.apply(values,arguments); //添加方法 values.toPipeString=function(){ return this.join("|"); }; //返回数组 return values; } var colors=new SpecialArray("red","blue","green"); alert(colors.toPipeString());//red|blue|green
//在以上这个函数内部,首先创建了一个数组,然后push()方法(用构造函数接收到的所有参数)初始化了数组的值。然后,又给数组实例添加了一个toPipeString()方法,该方法返回以竖线分隔的数组值。返回数组的形式。
关于寄生构造函数模式,首先返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与构造函数外部创建的对象没有什么不同。为此,不能依赖Instanceof操作符来确定对象类型。在使用其他模式的情况下,不要使用这种模式。
稳妥构造函数模式
所谓稳妥模式,指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new)。稳妥构造函数遵循与寄生构造函数类似的模式,但有2点不同,一是新创建的对象的实例方法不引用this;二是不使用new操作符调用构造函数。
function Person(name,age,job){ //创建要返回的对象 var o=new Object(); //可以在这里定义私有变量和函数 //添加方法 o.sayName=function(){ alert(name); }; //返回对象 return o; } var friend=Person("mike",29,"engineer"); friend.sayName();//mike
//这样,变量friend中保存的是一个稳妥对象,除了sayName()外,没有别的方式可以访问其数据成员。
与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,因此instanceof操作符对这种对象也没有意义。
继承
许多面向对象语言都支持2种继承方式,接口继承和实现继承。
接口继承只继承方法签名,而实现继承则继续实际的方法。
由于js函数没有签名,所以无法实现接口继承。es只支持实现继承,主要是依靠原型链来实现的。
原型链
其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
实现原型链有一种基本模式:
function SuperType(){ this.prototype=true; } SuperType.prototype.getSuperValue=function(){ return this.prototype; }; function SubType(){ this.Subprototype=false; }//继承了SuperType SubType.prototype=new SuperType(); SubType.prototype.getSubValue=function(){ return this.subproperty; }; var instance=new SubType(); alert(instance.getSuperValue());//true
以上代码定义了2个类型:SuperType和SubType。每个类型分别有一个属性和一个方法。它们的主要区别是SubType继承了SuperType,而继承是通过创建SuperType的实例,并将该实例赋给SubType。prototype实现的。实现的本质是重写原型对象,代之以一个新类型的实例。原来存在于SuperType的实例中的所有属性和方法,现在也存在于SubType.prototype中。在确立了继承关系后,我们给SubType.prototype添加了一个方法,这样就在继承了SuperType的属性和方法的基础上有添加了一个新方法。