ES6:类

引用:http://www.tuicool.com/articles/BFf6jiB

从本质上说,ES6的classes主要是给创建老式构造函数提供了一种更加方便的语法,并不是什么新魔法 —— Axel Rauschmayer,Exploring ES6作者
从功能上来讲,class声明就是一个语法糖,它只是比我们之前一直使用的基于原型的行为委托功能更强大一点。本文将从新语法与原型的关系入手,仔细研究ES2015的class关键字。文中将提及以下内容:

  • 定义与实例化类;
  • 使用extends创建子类;
  • 子类中super语句的调用;
  • 以及重要的标记方法(symbol method)的例子。

在此过程中,我们将特别注意 class 声明语法从本质上是如何映射到基于原型代码的。

退一步说:Classes不是什么

JavaScript的『类』与Java、Python或者其他你可能用过的面向对象语言中的类不同。其实后者可能称作面向『类』的语言更为准确一些。
在传统的面向类的语言中,我们创建的类是对象的模板。需要一个新对象时,我们实例化这个类,这一步操作告诉语言引擎将这个类的方法和属性复制到一个新实体上,这个实体称作实例。实例是我们自己的对象,且在实例化之后与父类毫无内在联系。
而JavaScript没有这样的复制机制。在JavaScript中『实例化』一个类创建了一个新对象,但这个新对象却不独立于它的父类。
正相反,它创建了一个与原型相连接的对象。即使是在实例化之后,对于原型的修改也会传递到实例化的新对象去。
原型本身就是一个无比强大的设计模式。有许多使用了原型的技术模仿了传统类的机制,class便为这些技术提供了简洁的语法。
总而言之:

  • JavaScript不存在Java和其他面向对象语言中的类概念;
  • JavaScript 的class很大程度上只是原型继承的语法糖,与传统的类继承有很大的不同。

类基础:声明与表达式

我们使用class 关键字创建类,关键字之后是变量标识符,最后是一个称作类主体的代码块。这种写法称作类的声明。没有使用extends关键字的类声明被称作基类:

"use strict";

// Food 是一个基类
class Food {

    constructor (name, protein, carbs, fat) {
        this.name = name;
        this.protein = protein;
        this.carbs = carbs;
        this.fat = fat;
    }

    toString () {
        return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`
    }

    print () {
        console.log( this.toString() );
    }
}

const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);

chicken_breast.print(); // 'Chicken Breast | 26g P :: 0g C :: 3.5g F'
console.log(chicken_breast.protein); // 26 (LINE A)

需要注意到以下事情:

  • 类只能包含方法定义,不能有数据属性;
  • 定义方法时,可以使用简写方法定义;
  • 与创建对象不同,我们不能在类主体中使用逗号分隔方法定义;
  • 我们可以在实例化对象上直接引用类的属性(如 LINE A)。

类有一个独有的特性,就是 contructor 构造方法。在构造方法中我们可以初始化对象的属性。
构造方法的定义并不是必须的。如果不写构造方法,引擎会为我们插入一个空的构造方法

"use strict";

class NoConstructor {
    /* JavaScript 会插入这样的代码:
     constructor () { }
    */
}

const nemo = new NoConstructor(); // 能工作,但没啥意思

将一个类赋值给一个变量的形式叫类表达式,这种写法可以替代上面的语法形式:

"use strict";

// 这是一个匿名类表达式,在类主体中我们不能通过名称引用它
const Food = class {
    // 和上面一样的类定义……
}

// 这是一个命名类表达式,在类主体中我们可以通过名称引用它
const Food = class FoodClass {
    // 和上面一样的类定义……

    //  添加一个新方法,证明我们可以通过内部名称引用 FoodClass……        
    printMacronutrients () {
        console.log(`${FoodClass.name} | ${FoodClass.protein} g P :: ${FoodClass.carbs} g C :: ${FoodClass.fat} g F`)
    }
}

const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);
chicken_breast.printMacronutrients(); // 'Chicken Breast | 26g P :: 0g C :: 3.5g F'

// 但是不能在外部引用
try {
    console.log(FoodClass.protein); // 引用错误
} catch (err) {
    // pass
}

这一行为与匿名函数与命名函数表达式很类似。

使用extends创建子类以及使用super调用

使用extends创建的类被称作子类,或派生类。这一用法简单明了,我们直接在上面的例子中构建:

"use strict";

// FatFreeFood 是一个派生类
class FatFreeFood extends Food {

    constructor (name, protein, carbs) {
        super(name, protein, carbs, 0);
    }

    print () {
        super.print();
        console.log(`Would you look at that -- ${this.name} has no fat!`);
    }

}

const fat_free_yogurt = new FatFreeFood('Greek Yogurt', 16, 12);
fat_free_yogurt.print(); // 'Greek Yogurt | 26g P :: 16g C :: 0g F  /  Would you look at that -- Greek Yogurt has no fat!'

派生类拥有我们上文讨论的一切有关基类的特性,另外还有如下几点新特点:

  • 子类使用class关键字声明,之后紧跟一个标识符,然后使用extend关键字,最后写一个任意表达式。这个表达式通常来讲就是个标识符,但理论上也可以是函数。
  • 如果你的派生类需要引用它的父类,可以使用super关键字。
  • 一个派生类不能有一个空的构造函数。即使这个构造函数就是调用了一下super(),你也得把它显式的写出来。但派生类却可以没有构造函数。
  • 在派生类的构造函数中,必须先调用super,才能使用this关键字(译者注:仅在构造函数中是这样,在其他方法中可以直接使用this)。
    在JavaScript中仅有两个super关键字的使用场景:
  • 在子类构造函数中调用。如果初始化派生类是需要使用父类的构造函数,我们可以在子类的构造函数中调用super(parentConstructorParams),传递任意需要的参数。
  • 引用父类的方法。在常规方法定义中,派生类可以使用点运算符来引用父类的方法:super.methodName。
    我们的 FatFreeFood 演示了这两种情况:
  • 在构造函数中,我们简单的调用了super,并将脂肪的量传入为0。
  • 在我们的print方法中,我们先调用了super.print,之后才添加了其他的逻辑。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容

  • 面向对象的语言都有一个类的概念,通过类可以创建多个具有相同方法和属性的对象,ES6之前并没有类的概念,在ES6中引...
    Erric_Zhang阅读 1,093评论 1 4
  • class的基本用法 概述 JavaScript语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子: ...
    呼呼哥阅读 4,062评论 3 11
  • 假如我们想要创建一个经典的面向对象设计示例:Circle类。想象一下我们正在为一个简单的Canvas库编写这个Ci...
    糖心m阅读 378评论 0 2
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,531评论 18 399
  • 获取信息 落榜的,在读的,泸职院,品牌营销知晓度。 途径:QQ,熟人,招人。运营好QQ,裂变,增强粘性。网络搜索 ...
    问学行者阅读 149评论 0 0