浅谈“类”

面向对象编程强调的是数据和操作数据的行为本质上是互相关联的,因此好的设计就是把数据以及和它相关的行为封装起来。

举例来说,用来表示一个单词或者短语的一串字符通常被称为字符串。字符是数据。但关心的往往不是数据是什么,而是可以对数据做什么,所以可以应用在这种数据上的行为(计算长度、添加数据等等)都被设计成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中。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容