面向对象编程强调的是数据和操作数据的行为本质上是互相关联的,因此好的设计就是把数据以及和它相关的行为封装起来。
举例来说,用来表示一个单词或者短语的一串字符通常被称为字符串。字符是数据。但关心的往往不是数据是什么,而是可以对数据做什么,所以可以应用在这种数据上的行为(计算长度、添加数据等等)都被设计成String类的方法。
所有的字符串都是String类的一个实例,包含字符数据和我们可以应用在数据上的函数。还可以使用类对数据结构进行分类,可以把任意数据结构看作范围更广的定义的一种特例。
汽车(Car)可以被看作交通工具(Vehicle)的一种特例,后者是更广泛的类。
Vehicle的定义可能包括推进器(比如引擎)、载人能力等,这些都是Vehicle的行为。对Vehicle中定义的是所有类型的交通工具都包含的东西。
对不同的交通工具重复定义“载人能力”是没有 意义的。相反,只需在Vehicle中定义一次,定义Car时,只有声明它继承了Vehicle的这个基础定义就行。Car的定义就是对通用Vehicle定义的特殊化。
这就是类、继承和实例化。类的另一个核心概念是多态:弗雷的通用行为可以被字类用更特殊的行为重写。实际上,相对多态允许我们重写行为中引用基础行为。
构造函数:类实例是由一个特殊的类方法构造的,这个方法命通常和雷明相同,被称为构造函数.思考下列伪代码
class CoolGuy {
specialTrick = nothing
CoolGuy( trick ) {
specialTrick = trick
} showOff() {
output( "Here's my trick: ", specialTrick)
}
}
可以调用类构造函数来生成一个CoolGuy实例:
Joe = new CoolGuy('Hi') ; Joe.showOff()// HI
1、类的继承
在面向类的语言中,先定义一个类,然后定义一个继承前者的类。后者通常被称为‘字类’,前者通常被称为‘父类’。父类和字类并不是实例。
思考下面关于类继承的伪代码:
class Vehicle{
engines = 1
ignition(){
output("Turning on my engine.");
}
drive(){
ignition();
output("Steering and moving forward!")
}
}
class Car inherits Vehicle{
wheels = 4
drive(){
inherited : drive()
output("Rolling on all ", wheels, " wheels!")
}
}
class SpeedBoat inherits Vehicle{
engines = 2
ignition(){
output("Turning on my ", engines, " engines.")
}
pilot(){
inherited : drive()
output("Speeding through the water with ease!")
}
}
Car重写了继承父类的drive()方法,但之后Car调用了inherited:drive()方法,这表明Car可以引用继承来的原始drive()方法。pilot()方法同样引用了原始drive()方法。这个技术被称为多态。在本例中,更好的说法是相对多态。
在许多语言中可以使用super来代替本例中的inherited:,它的含义是”超类“,表示当前类的父类/祖先类。
2、混入
在继承或者实例化时,JavaScript的对象机制并不会自动执行复制行为。简单来说,JavaScript中只有对象并不存在可以被实例化的类。一个对象并不会被复制到其他对象,它们会被关联起来。由于在其他语言中类表现出来的都是复制行为,因此JavaScript开发者也想出一个方法来模拟类的复制行为,就是混入。
2.1、显示混入
回顾前面提到的Vehicle和Car。由于JavaScript不会自动实现Vehicle到Car的复制行为,所以要手动实现复制功能。这个功能在许多库和框架中被称为extend(),为了方便我们称之为mixin()。
// 非常简单的 mixin(..) 例子 :
function mixin(sourceObj, targetObj){
for (var key in sourceObj){
// 只会在不存在的情况下复制
if (!(key in targetObj)) {
targetObj[key] = sourceObj[key];
}
}
return targetObj;
}
var Vehicle ={
engines : 1,
ignition : function (){
console.log("Turning on my engine.");
},
drive : function (){
this.ignition();
console.log("Steering and moving forward!");
}
};
var Car = mixin(Vehicle,{
wheels : 4,
drive : function (){
Vehicle.drive.call(this);
console.log(
"Rolling on all " + this.wheels + " wheels!");
}
} );
现在Car就有一份Vehicle属性和函数的副本。从技术角度来说,函数实际上没有被复制,复制的是函数引用。
这条语句:Vehicle.drive.call(this)。这就是显示多态。JavaScript(在ES6之前)并没有相对多态机制。所以由于Car和Vehicle中都有drive()函数,为了指明调用对象,必须使用绝对引用。
现在来分析mixin()原理。它会遍历sourceObj的属性,如果targetObj没有这个属性就会进行复制。复制操作完成后,Car就和Vehicle分离了,向Car中添加属性不会影响Vehicle,反之亦然。
2.2、寄生继承:是显示混入模式的一种变体,既是显式的又是隐式的。
下面是它的工作原理:
//“传统的 JavaScript 类” Vehicle
function Vehicle(){
this.engines = 1;
}
Vehicle.prototype.ignition = function (){
console.log("Turning on my engine.");
};
Vehicle.prototype.drive = function (){
this.ignition();
console.log("Steering and moving forward!");
}; //“寄生类” Car
function Car(){
// 首先, car 是一个 Vehicle
var car = new Vehicle();
// 接着我们对 car 进行定制
car.wheels = 4;
// 保存到 Vehicle::drive() 的特殊引用
var vehDrive = car.drive;
// 重写 Vehicle::drive()
car.drive = function (){
vehDrive.call(this);
console.log(
"Rolling on all " + this.wheels + " wheels!");
return car;
}
var myCar = new Car();
myCar.drive();
// Turning on my engine.
//Steering and moving forward
2.3、隐式混入
思考下列代码:
var Something ={
cool : function ()
{
this.greeting = "Hello World";
this.count = this.count ? this.count + 1 : 1;
}
};
Something.cool();
Something.greeting; // "Hello World"
Something.count; // 1
var Another ={
cool : function () {
// 隐式把 Something 混入 Another
Something.cool.call(this);
}
};
Another.cool();
Another.greeting; // "Hello World"
Another.count; // 1(count 不是共享状态)
通过在构造函数调用或者方法调用中使用Something.cool.call(this),实际上”借用“了函数Something.cool()并在Another的上下文中调用了它。最终结果是Something.cool()中的赋值操作都会应用在Another对象上而不是Something对象上。因此我们把Something的行为”混入“到了Another中。