浅谈“类”

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容