许多OO语言都支持二种继承方式:
接口继承和实现继承。
接口继承只继承方法签名,而实现继承则继承实际的方法。
由于JavaScript函数没有签名,在ECMAScript中无法实现接
口继承。
ECMAScript只支持实现继承。
JavaScript中的继承主要是依靠原型链来实现的。
实现继承:
1:原型链
思想:利用原型让一个类型继承另一个引用类型的属性和方法。
每个构造函数都有一个原型对象,原型对象都包含一个指向构造
函数的指针,而实例都包含一个指向原型对象的内部指针。那么,
如果我们让原型对象等于另外一个类型的实例,结果怎么样?
可以很容易的感触,此时的原型对象将包含一个指向另外一个原
型的指针,相应地,另外一个原型中也包含一个指向另一个构
造函数的指针。
假如,另一个原型又是另一个类型的实例,那个上述关系依然成立。
这就是所谓的原型链的基本概念。
并将该实例赋值给SubType.prototype实现的。
实现的本质是重写原型对象。
这个例子的实例以及构造函数和原型之间的关系如图:
很难为超类型传递参数。
由于这些原因,实践中很少单独使用原型链。
2:借用构造函数(伪造对象或经典继承)
思想:在子类型构造函数的内部调用超类型构造函数(
函数只不过是在特定环境中执行代码的对象,因此可以
通过apply()和call()方法也可以在新创建的对象上执行
构造函数
)。
下调用了SuperType构造函数,就会在新的SubType对象上执行
SuperType()函数中定义的所有的对象初始化代码,结果,
SubType的每个实例就都具有自己的colors属性的副本了。
优点:
相对于原型链而言,借用构造函数有一个很大的优势,即可以
在子类型构造函数中向超类型构造函数传递参数。
缺点:
如果仅仅是借用构造函数,那么方法都在函数中定义,
方法共享也就无从谈起。而且在超类型原型中定义的方法,
对于子类型是不可见的。由于存在这些问题,借用构造函数
很少单独使用。
3:组合继承
是将原型链和借用构造函数的技术组合到一块的模式。
思想:使用原型链实现对原型属性和方法继承,而通过借用构造
函数来实现实例属性的继承。
优点:
避免了原型链和借用构造函数的缺陷,成为JavaScript
中最常用的继承模式。instanceof 和isProprotypeOf()
也能够用于识别基于组合继承创建对象。
缺点:
最大的问题就是无论在什么情况下,都会调用二次超类的
构造函数:一次在创建子类型原型的时候,另一次是在子类
型构造函数内部。子类型最终会包含曹类型对象的全部实例
属性,但是不得不在调用类型构造函数时重写这些属性。
第一次调用SuperType构造函数时,SubType.prototype会得到
二个属性:name和colors;它们都是SuperType的实例属性,
只不过位于SubType的原型中。当第二次调用SubType构造函数时,
又会调用一次SuperType构造函数,这次又在新的对象上创建实例
属性name和colors。最终,这二个属性就屏蔽了原型中二个同名的
属性。
图:
在object()函数内部,先创建一个临时的构造函数,然后将
传入的对象作为这个构造函数的原型,最后返回这个临时类型
的一个新的实例。从本质上讲,object()对传入其中的对象执行
了一次浅复制。
5:寄生式继承
思想:创建一个仅用于封装继承过程的函数,该函数
在内部以某种方式来增强对象。
寄生式继承也是一种有用的模式。
前面继承模式使用的object()函数不是必需的;任何
可以返回新的对象的函数都是适用于这个模式。
不可以做到函数复用。
6:寄生组合式继承
思想:通过借用构造函数来继承属性,通过原型链的形式来继承
方法,不必为了指定子类型的原型而调用超类型的构造函数,
我们所需要的无非就是超类型原型的一个副本而已。
本质上,就是使用寄生式继承来继承超类型的原型,然后
再将结果指定给子类型的原型。寄生组合继承基本模式:
只调用一次SuperType,并且避免了在SubType.prototype上面创建
不必要的,多余的属性。同时,原型链还可以保持不变,
可以使用instance和isPrototypeOf().这个是最理想的继承范式。
如图:
接口继承和实现继承。
接口继承只继承方法签名,而实现继承则继承实际的方法。
由于JavaScript函数没有签名,在ECMAScript中无法实现接
口继承。
ECMAScript只支持实现继承。
JavaScript中的继承主要是依靠原型链来实现的。
实现继承:
1:原型链
思想:利用原型让一个类型继承另一个引用类型的属性和方法。
每个构造函数都有一个原型对象,原型对象都包含一个指向构造
函数的指针,而实例都包含一个指向原型对象的内部指针。那么,
如果我们让原型对象等于另外一个类型的实例,结果怎么样?
可以很容易的感触,此时的原型对象将包含一个指向另外一个原
型的指针,相应地,另外一个原型中也包含一个指向另一个构
造函数的指针。
假如,另一个原型又是另一个类型的实例,那个上述关系依然成立。
这就是所谓的原型链的基本概念。
实现运行模式的基本模式为:
function SuperType () {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
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
SubType继承了SuperType,而继承是通过创建SuperType的实例,
并将该实例赋值给SubType.prototype实现的。
实现的本质是重写原型对象。
这个例子的实例以及构造函数和原型之间的关系如图:
事实上:上面图片的还少了一个环。所有的应用类型都是默认
继承Object,所有函数的默认原型都是Object的实例,因此默认
原型都会包含一个内部指针,指向Object.prototype。
完整的原型链为:
原型链存在的问题:
原型链虽然很强大,但是存在一些问题,最主要的问题是包含
引用类型的原型。
引用类型的原型属性会被所有的实例共享;这个也是为什么
要在构造函数中,而不是在原型对象中定义属性的原因。
在通过原型来实现继承时,原型实际上会变成另一个类型的实例。
于是,原先的实例属性也就变成了现在的原型属性了。
原型链虽然很强大,但是存在一些问题,最主要的问题是包含
引用类型的原型。
引用类型的原型属性会被所有的实例共享;这个也是为什么
要在构造函数中,而不是在原型对象中定义属性的原因。
在通过原型来实现继承时,原型实际上会变成另一个类型的实例。
于是,原先的实例属性也就变成了现在的原型属性了。
function SuperType () {
this.colors = ["red","blue","green"];
}
function SubType () {}
//继承了SubType
SubType.prototype = new SuperType ();
var instance1 = new SubType ();
instance1.colors.push ("black");
alert (instance1.colors);//"red,blue,green,black"
var instance2 = new SubType ();
alert (instance2.colors);//"red,blue,green,black"
另外一个存在的问题为:
很难为超类型传递参数。
由于这些原因,实践中很少单独使用原型链。
2:借用构造函数(伪造对象或经典继承)
思想:在子类型构造函数的内部调用超类型构造函数(
函数只不过是在特定环境中执行代码的对象,因此可以
通过apply()和call()方法也可以在新创建的对象上执行
构造函数
)。
function SuperType () {
this.colors = ["red","blue","green"];
}
function SubType () {
//继承了SuperType
superType.call (this);
}
var instance1 = new SubType ();
instance1.colors.push ("black");
alert (instanc1.colors);//"red,blue,green,black"
var instance2 = new SubType ();
alert (instance2.colors); //"red,blue,green"
通过使用call()方法或者apply()方法,在新创建的SubType实例
下调用了SuperType构造函数,就会在新的SubType对象上执行
SuperType()函数中定义的所有的对象初始化代码,结果,
SubType的每个实例就都具有自己的colors属性的副本了。
优点:
相对于原型链而言,借用构造函数有一个很大的优势,即可以
在子类型构造函数中向超类型构造函数传递参数。
function SuperType (name) {
this.name = name;
}
function SubType (name,age) {
//继承了SuperType,同时还传递了参数
SuperType.call (this,name);
this.age = age;
}
var instance = new SubType ("a",17);
alert (instance.name);//"a"
alert (instance.age);//17
缺点:
如果仅仅是借用构造函数,那么方法都在函数中定义,
方法共享也就无从谈起。而且在超类型原型中定义的方法,
对于子类型是不可见的。由于存在这些问题,借用构造函数
很少单独使用。
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);
};
var instance1 = new SubType ("a",17);
instance1.colors.push ("black");
alert (instance1.colors);//"red,blue,green,black"
instance1.sayName ();//"a"
instance1.sayAge ();17
var instance2 = new SubType ("b",21);
alert (instance2.colors);//"red,blue,green"
instance2.sayName();//"b"
instance2.sayAge ();//21
优点:
避免了原型链和借用构造函数的缺陷,成为JavaScript
中最常用的继承模式。instanceof 和isProprotypeOf()
也能够用于识别基于组合继承创建对象。
缺点:
最大的问题就是无论在什么情况下,都会调用二次超类的
构造函数:一次在创建子类型原型的时候,另一次是在子类
型构造函数内部。子类型最终会包含曹类型对象的全部实例
属性,但是不得不在调用类型构造函数时重写这些属性。
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);//第二次调用SuperType()
this.age = age;
}
//继承方法
SubType.prototype = new SuperType ();//第一次调用SuperType()
SubType.prototype.sayAge = function () {
alert (this.age);
};
第一次调用SuperType构造函数时,SubType.prototype会得到
二个属性:name和colors;它们都是SuperType的实例属性,
只不过位于SubType的原型中。当第二次调用SubType构造函数时,
又会调用一次SuperType构造函数,这次又在新的对象上创建实例
属性name和colors。最终,这二个属性就屏蔽了原型中二个同名的
属性。
图:
4:原型式继承
思想:借助原型可以基于已有的对象创建新对象,同时还不必
因此创建自定义类型。
function object (O) {
function F(){}
F.prototype = o;
return new F ();
}
在object()函数内部,先创建一个临时的构造函数,然后将
传入的对象作为这个构造函数的原型,最后返回这个临时类型
的一个新的实例。从本质上讲,object()对传入其中的对象执行
了一次浅复制。
var person = {
name:"zhang",
friends:["Tom","Lucy"]
};
var anotherPerson = object (person);
anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("lily");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "renjiahui";
yetAnotherPerson.friends.push("Bob");
alert (person.friends);//"Tom,Lucy,lily,Bob"
5:寄生式继承
思想:创建一个仅用于封装继承过程的函数,该函数
在内部以某种方式来增强对象。
function createAnother (original) {
var clone = object (original);//object函数就是4中的函数
clone.sayHi = function () {
alert ("hi");
}
return clone;
}
在主要考虑对象而不是自定义类型和构造函数的情况下,
寄生式继承也是一种有用的模式。
前面继承模式使用的object()函数不是必需的;任何
可以返回新的对象的函数都是适用于这个模式。
不可以做到函数复用。
6:寄生组合式继承
思想:通过借用构造函数来继承属性,通过原型链的形式来继承
方法,不必为了指定子类型的原型而调用超类型的构造函数,
我们所需要的无非就是超类型原型的一个副本而已。
本质上,就是使用寄生式继承来继承超类型的原型,然后
再将结果指定给子类型的原型。寄生组合继承基本模式:
function object (o) {
function F(){}
F.prototype = o;
return new F ();
}
function inheritPrototype (subType,superType) {
var prototype = object (superType.prototype);//创建对象
prototype.constructor = subType;
subType.prototype = prototype;
}
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;
}
inheritPrototype (SubType,SuperType);
SubType.prototype.sayAge = function () {
alert (this.age);
};
var instance = new SubType ("a",17);
instance.sayName ();
只调用一次SuperType,并且避免了在SubType.prototype上面创建
不必要的,多余的属性。同时,原型链还可以保持不变,
可以使用instance和isPrototypeOf().这个是最理想的继承范式。
如图: