ECMAScript中没有类的概念,且它将对象定义为:无序的集合,其属性可包含基本值、对象或者函数
o.name = name;
o.sayName = function(){alert(this.name)};
return o;
this.name = name;
this.sayName = function(){
alert(this.name);
};
构造函数的缺点在于每个方法都要在每个实例上重新创建一遍,于是把函数定义转移到构造函数之外:
this.name = name;
this.sayName = sayName;
function sayName(){
alert(this.name);
alert(this.name);
hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中,若在原型中,返回true
in操作符:
constructor:Person,
name:'a'
constructor:Person,
name:'a'
原型模式的缺点在于:它省略了为构造函数传递初始化参数这一环节,导致所有实例默认情况下将取得相同的属性值;而其最大问题在于其共享的本性。
原型中的所有属性被很多实例共享,对于其他属性还好,但对于包含引用类型值的属性来说,问题比较突出
constructor:Person,
friends:['b','c']
this.name = name;
if (typeof this.sayName != 'function'){
Person.prototype.sayName = function(){
alert(this.name);
var o = new Object();
o.sayName = function(){
alert(this.name);
}
return o;
这种模式可以在特殊情况下用来为对象创建构造函数,假设想创建一个具有额外方法的特殊数组:
var values = new Array();
values.push.apply(values,arguments);
values.toPipedString = function(){
return this.join('|');
};
return values;
ECMAScript只支持实现继承,且其实现继承主要依靠原型链来实现
1 原型链
原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法
原型链有一种基本模式,其代码大致如下:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){return this.property};
function SubType(){this.subproperty = false;}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
}
var instance = new SubType();
以上继承的实现本质是重写原型对象,代之以一个新类型的实例。而这个新类型的实例又指向自己的原型
通过实现原型链,本质上扩展了原型搜索机制,即当以读取模式访问一个实例属性时,首先会在实例中搜索该属性。如果没有找到该属性,则会继承搜索实例的原型。在通过原型链实现继承的情况下搜索过程就得以沿着原型链继续向上。
可以通过两种方式确定原型和实例之间的关系:
第一种方式是使用instanceof操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true
第二种方式是使用isPrototypeOf()方法,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型
原型链实现继承的问题:
最主要的问题来自包含引用类型值的原型,新类的实例会共享该属性;其次是创建子类实例时,不能向超类型的构造函数中传递参数,实际上应该说是没办法在不影响所有对象实例的情况下给超类型的构造函数传递参数。由于以上原因,很少单独使用原型链。
2 借用构造函数
在解决原型中包含引用类型值所带来的问题的过程中,使用了借用构造函数的技术,其基本思想是在子类型构造函数内部调用超类型构造函数:
function SuperType(){
this.colors = ['red','blue','green'];
function SubType(){
SuperType.call(this);
通过使用call()或apply()方法,会在新创建的SubType对象上执行SuperType()函数中定义的所有对象初始化代码。
借用构造函数相对于原型链的优势除了共享属性,还可在子类型构造函数中向超类型构造函数传递参数:
function SuperType(name){
this.name = name;
}
function SubType(){
SuperType.call(this,'a');
this.age = 29;
}
借用构造函数的问题:和构造函数模式一样,方法都在构造函数中定义,利用无从谈起;且超类型的原型中定义的方法对子类型而言不可见。
3 组合继承
使用原型链实现对属性和方法的继承,而通过借用构造函数实现对实例属性的继承
function SuperType(name){
this.name = name;
this.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
alert(this.age);
}
组合继承避免了原型链和借用构造函数的缺陷,且instanceof和isPrototypeOf()也能识别基于组合继承创建的对象
4 原型式继承
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型:
function object(o){
function F(){}
F.prototype = o;
return new F();
}
从本质上讲,object()对传入其中的对象执行了一次浅复制,虽然对于引用类型的属性来说,这种方式并不合适,但在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承完全可以胜任。
5 寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象
function createAnother(original){
var clone = object(original);
clone.sayHi = function(){
alert('Hi');
}
return clone;
}
在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承是一种有用的模式。前面示范继承时使用的object()函数不是必需的,任何能返回新对象的函数都适用于此模式。
使用寄生式继承来为对象添加函数会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。
6 寄生组合式继承
组合继承的最大问题在于任何情况下都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。即虽然子类型最终会包含超类型对象的全部实例属性,但不得不在调用子类型构造函数时重写这些属性。
所谓寄生式组合继承,即通过借用构造函数来继承属性,通过原型链继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需的是超类型原型的一个副本而已。本质上就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
function inheritPrototype(subType,superType){
var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType(name){
this.name = name;
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name){
SuperType.call(this,name);
}
inheritPrototype(SubType,SuperType);
上述模式中,inheritPrototype()函数接收两个参数:子类型构造函数和超类型构造函数。在函数内部,第一先创建超类型原型的一个副本;第二步为创建的副本添加constructor属性;最后将新创建的对象(即副本)赋值给子类型的原型。
这个例子的高效率体现在它只调用了一次SuperType构造函数,且避免了在SubType.prototype上创建不必要、多余的属性。与此同时,原型链还能保持不变,可正常使用instanceof和isPrototypeOf()。
寄生组合式继承可能是引用类型最理想的继承范式。
创建对象
1自定义对象
var person = new Object();
person.name="Nicholas";
person.sayName = function(){alert(this.name);}
但这种方式这种方式会产生大量重复代码
2工厂模式
function createPerson(name){
var o = new Object();
}
工厂模式节省了大量代码,但无法解决识别对象类型的问题
3构造函数模式
function Person(name){
}
Person中的代码与工厂模式比起来没有显式地创建对象,直接将属性和方法赋给了this对象,没有return语句
要创建Person的新实例,必须使用new操作符,且创建过程有4个步骤:创建一个新对象,将构造函数的作用域赋给新对象(因此this指向了这个新对象),执行构造函数中的代码,返回新对象
创建自定义的构造函数意味着可将它的实例标识为一种特定的类型
构造函数与其他函数的唯一区别在于调用它们的方式,任何函数,只要通过new操作符来调用,就可作为构造函数;而任何函数,若不通过new操作符调用,就是普通函数。
var o = new Object();
Person.call(o,'a');
o.sayName(); //a
function Person(name){
}
}
但这种方式的不合理之处在于:在全局作用域中创建的函数只能被某类对象使用,且对于这样自定义的引用类型来说,破坏了封装性。
4 原型模式
原型可以让所有对象实例共享它所包含的属性和方法
function Person(){}
Person.prototype.name = 'a';
Person.prototype.sayName = function(){
}
上图展示了Person构造函数,Person的原型属性以及Person现有的两个实例之间关系
当为对象实例添加一个属性时,它会屏蔽原型对象中保存的同名属性,使用delete操作符可完全删除实例属性,使实例能够重新访问原型属性。
可以单独使用,也可以在for-in循环中使用。单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
使用for-in循环时,返回的是所有能够通过对象访问的,可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏幕了原型中不可枚举属性的实例属性也会在for-in循环中返回,除了IE,这个bug会影响默认不可枚举的所有属性和方法:hasOwnProperty()、propertyIsEnumerable()、toLocaleString()、toString()和valueOf()
更简单的原型语法:
function Person(){}
Person.prototype = {
}
原型的动态性:
对原型对象做的任何修改都能立即从实例上反映出来。若重写了原型对象,则因为调用构造函数会为实例添加一个指向最初原型的_proto_指针,于是相当于切断了构造函数与最初原型的联系,在重写前创建的实例中的指针仍然指向原来的原型
function Person(){}
var person = new Person();
Person.prototype= {
}
person.sayName(); //error
原生对象的原型:
可以像修改自定义对象的原型一样修改原生对象如String,Array等的原型,但不推荐这样做
原型对象的问题:
function Person(){}
Person.prototype= {
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push('d');
则person2中的friends属性也会是同样的值,某种程度上来说,实例没有属于自己的属性了
5 组合构造函数和原型模式
创建构造函数用于定义实例属性,原型模式用于定义方法和共享的属性,这样每个实例都有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存,且这种混合模式支持向构造函数传递参数
6 动态原型模式
动态原型模式把所有信息都封装在构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点
function Person(name){
}
}
}
使用动态原型模式时,不能使用对象直接量重写原型,因为若在已经创建了实例的情况下,这会切断现有实例与新原型之间的联系
7 寄生构造函数模式
在前面的模式不适用的情况下,可使用寄生构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用是封装创建对象的代码,然后返回新创建的对象:
function Person(name){
}
除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式很像
function SpecialArray(){
}
需要说明的是:返回的对象与构造函数或与构造函数的原型属性之间没有关系
继承
}
}