ES6 学习笔记 Part3-3

ES6 学习笔记 Part3-3

这是 ES6 学习笔记记录 Part3-3

学习并节选自 阮一峰 - ECMAScript 6 入门

仅供个人学习记录,原著请访问链接


文章目录

  • ES6 学习笔记 Part3-3
    • 19. Class 的基本语法
      • 19.1 简介
      • 19.2 严格模式
      • 19.3 constructor 方法
      • 19.4 类的实例对象
      • 19.5 Class 表达式
      • 19.6 不存在变量提升
      • 19.7 私有方法和私有属性
      • 19.8 this 的指向
      • 19.9 name 属性
      • 19.10 Class 的取值函数(getter)和存值函数(setter)
      • 19.11 Class 的 Generator 方法
      • 19.12 Class 的静态方法
      • 19.13 Class 的静态属性和实例属性
        • 类的实例属性
        • 类的静态属性
      • 19.14 new.target 属性
    • 20. Class 的继承
      • 20.1 简介
      • 20.2 Object.getPrototypeOf()
      • 20.3 super 关键字
      • 20.4 原生构造函数的继承
      • 20.5 Mixin 模式的实现
    • 21. 修饰器
    • 22. Module 的语法
      • 22.1 概述
      • 22.2 严格模式
      • 22.3 export 命令
      • 22.4 import 命令
      • 22.5 模块的整体加载
      • 22.6 export default 命令
      • 22.7 export 与 import 的复合写法
      • 22.8 跨模块常量
    • 23. 编程风格
      • 23.1 块级作用域
      • 23.2 字符串
      • 23.3 解构赋值
      • 23.4 对象
      • 23.5 数组
      • 23.6 函数
      • 23.7 Map 结构
      • 23.8 Class
      • 23.9 模块
      • 23.10 ESLint 的使用


19. Class 的基本语法

19.1 简介

JavaScript 语言中,生成实例对象的传统方法是通过构造函数:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大。ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

上面的代码用 ES6 的class改写,就是下面这样:

//定义类
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

// constructor方法是构造方法
// 而this关键字则代表实例对象

// Point类除了构造方法,还定义了一个toString方法
// 注意,定义“类”的方法的时候,前面不需要加上function这个关键字
// 另外,方法之间不需要逗号分隔,加了会报错

使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致:

class Bar {
  doStuff() {
    console.log('stuff');
  }
}

var b = new Bar();
b.doStuff() // "stuff"

事实上,类的所有方法都定义在类的prototype属性上面。在类的实例上面调用方法,其实就是调用原型上的方法。另外,类的内部所有定义的方法,都是不可枚举的。

19.2 严格模式

类和模块的内部,默认就是严格模式,只有严格模式可用。

考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。

19.3 constructor 方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加:

class Point {
}

// 等同于
class Point {
  constructor() {}
}

constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象:

class Foo {
  constructor() {
    return Object.create(null);
  }
}

new Foo() instanceof Foo
// false

类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

19.4 类的实例对象

生成类的实例对象的写法,与 ES5 完全一样,也是使用new命令:

class Point {
  // ...
}

// 报错
var point = Point(2, 3);

// 正确
var point = new Point(2, 3);

实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上):

//定义类
class Point {

  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }

}

var point = new Point(2, 3);

point.toString() // (2, 3)

point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true

与 ES5 一样,类的所有实例共享一个原型对象。这也意味着,可以通过实例的__proto__属性为“类”添加方法。

19.5 Class 表达式

与函数一样,类也可以使用表达式的形式定义:

const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};
// 需要注意的是,这个类的名字是 MyClass
// 而不是 Me
// Me只在 Class 的内部代码可用,指代当前类。

19.6 不存在变量提升

类不存在变量提升(hoist),这一点与 ES5 完全不同。

new Foo(); // ReferenceError
class Foo {}

上面代码中,Foo类使用在前,定义在后,这样会报错,因为 ES6 不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义:

{
  let Foo = class {};
  class Bar extends Foo {
  }
}

19.7 私有方法和私有属性

私有是常见需求,但 ES6 不提供,只能通过变通方法模拟实现。

19.8 this 的指向

类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错:

class Logger {
  printName(name = 'there') {
    this.print(`Hello ${name}`);
  }

  print(text) {
    console.log(text);
  }
}

const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined
// printName方法中的this,默认指向Logger类的实例
// 但是,如果将这个方法提取出来单独使用
// this会指向该方法运行时所在的环境
// 因为找不到print方法而导致报错。

19.9 name 属性

由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性:

class Point {}
Point.name // "Point"

name属性总是返回紧跟在class关键字后面的类名。

19.10 Class 的取值函数(getter)和存值函数(setter)

与 ES5 一样,在“类”的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为:

class MyClass {
  constructor() {
    // ...
  }
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'

19.11 Class 的 Generator 方法

如果某个方法之前加上星号(*),就表示该方法是一个 Generator 函数:

class Foo {
  constructor(...args) {
    this.args = args;
  }
  * [Symbol.iterator]() {
    for (let arg of this.args) {
      yield arg;
    }
  }
}

for (let x of new Foo('hello', 'world')) {
  console.log(x);
}
// hello
// world

19.12 Class 的静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”:

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。

父类的静态方法,可以被子类继承。静态方法也是可以从super对象上调用的。

19.13 Class 的静态属性和实例属性

类的实例属性

类的实例属性可以用等式,写入类的定义之中:

class MyClass {
  myProp = 42;

  constructor() {
    console.log(this.myProp); // 42
  }
}

以前,我们定义实例属性,只能写在类的constructor方法里面。有了新的写法以后,可以不在constructor方法里面定义。这种写法比以前更清晰。

类的静态属性

类的静态属性只要在上面的实例属性写法前面,加上static关键字就可以了:

class MyClass {
  static myStaticProp = 42;

  constructor() {
    console.log(MyClass.myStaticProp); // 42
  }
}

同样的,这个新写法大大方便了静态属性的表达。

19.14 new.target 属性

new是从构造函数生成实例对象的命令。ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。


20. Class 的继承

20.1 简介

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多:

class Point {
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。子类必须在constructor方法中调用super方法,否则新建实例时会报错。如果不调用super方法,子类就得不到this对象。

如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

20.2 Object.getPrototypeOf()

Object.getPrototypeOf方法可以用来从子类上获取父类:

Object.getPrototypeOf(ColorPoint) === Point
// true

因此,可以使用这个方法判断,一个类是否继承了另一个类。

20.3 super 关键字

super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。

第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

20.4 原生构造函数的继承

原生构造函数是指语言内置的构造函数,通常用来生成数据结构。ECMAScript 的原生构造函数大致有下面这些:

Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()

ES6 允许继承原生构造函数定义子类,而 ES5 则不可以。

20.5 Mixin 模式的实现

Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。

function mix(...mixins) {
  class Mix {}

  for (let mixin of mixins) {
    copyProperties(Mix.prototype, mixin); // 拷贝实例属性
    copyProperties(Mix.prototype, Reflect.getPrototypeOf(mixin)); // 拷贝原型属性
  }
  return Mix;
}

function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== "constructor"
      && key !== "prototype"
      && key !== "name"
    ) {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}
// 上面代码的mix函数,可以将多个对象合成为一个类
// 使用的时候,只要继承这个类即可

class DistributedEdit extends mix(Loggable, Serializable) {
  // ...
}

21. 修饰器

修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。

基本上,修饰器的行为就是下面这样:

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;

22. Module 的语法

22.1 概述

历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。

在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。

ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。

22.2 严格模式

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";

严格模式主要有以下限制:

  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用with语句
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
  • 不能删除变量delete prop,会报错,只能删除属性delete * global[prop]
  • eval不会在它的外层作用域引入变量
  • evalarguments不能被重新赋值
  • arguments不会自动反映函数参数的变化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局对象
  • 不能使用fn.callerfn.arguments获取函数调用的堆栈
  • 增加了保留字(比如protectedstaticinterface

22.3 export 命令

模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

上面代码保存了用户信息。ES6 将其视为一个模块,里面用export命令对外部输出了三个变量。

export的写法,还有另外一种:

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};
// 应该优先考虑使用这种写法

export命令除了输出变量,还可以输出函数或类(class)。

通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名:

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
// 使用as关键字,重命名了函数v1和v2的对外接口
// 重命名后,v2可以用不同的名字输出两次

需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

// 报错
export 1;

// 报错
var m = 1;
export m;
// 上面两种写法都会报错,因为没有提供对外的接口
// 第一种写法直接输出 1,第二种写法通过变量m,还是直接输出 1
// 1只是一个值,不是接口

// 正确的写法是下面这样
// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};

另外,export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。

最后,export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,import命令也是如此。

22.4 import 命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块:

// main.js
import {firstName, lastName, year} from './profile.js';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

上面代码的import命令,用于加载profile.js文件,并从中输入变量。import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。

如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名:

import { lastName as surname } from './profile.js';

import命令输入的变量都是只读的,因为它的本质是输入接口。

import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。

import {myMethod} from 'util';

注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。

最后,import语句会执行所加载的模块,因此可以有下面的写法。

import 'lodash';

上面代码仅仅执行lodash模块,但是不输入任何值。

如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次:

import 'lodash';
import 'lodash';
// 上面代码加载了两次lodash,但是只会执行一次

import语句是 Singleton 模式:

import { foo } from 'my_module';
import { bar } from 'my_module';

// 等同于
import { foo, bar } from 'my_module';

通过 Babel 转码,CommonJS 模块的require命令和 ES6 模块的import命令,可以写在同一个模块里面,但是最好不要这样做。

22.5 模块的整体加载

除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。

下面是一个circle.js文件:

// circle.js

export function area(radius) {
  return Math.PI * radius * radius;
}

export function circumference(radius) {
  return 2 * Math.PI * radius;
}

现在,加载这个模块。

// main.js

import { area, circumference } from './circle';

console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));

上面写法是逐一指定要加载的方法,整体加载的写法如下:

import * as circle from './circle';

console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

注意,模块整体加载所在的那个对象(上例是circle),应该是可以静态分析的,所以不允许运行时改变。

22.6 export default 命令

为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出:

// export-default.js
export default function () {
  console.log('foo');
}

其他模块加载该模块时,import命令可以为该匿名函数指定任意名字:

// import-default.js
import customName from './export-default';
customName(); // 'foo'

上面代码的import命令,可以用任意名称指向export-default.js输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import命令后面,不使用大括号。

export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default命令。

本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。

22.7 export 与 import 的复合写法

如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起:

export { foo, bar } from 'my_module';

// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };

上面代码中,exportimport语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foobar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foobar

22.8 跨模块常量

const声明的常量只在当前代码块有效。如果想设置跨模块的常量(即跨多个文件)可以采用下面的写法:

// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;

// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3

// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3

23. 编程风格

23.1 块级作用域

建议不再使用var命令,而是使用let命令取代。let完全可以取代var,因为两者语义相同,而且let没有副作用。

letconst之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。

// bad
var a = 1, b = 2, c = 3;

// good
const a = 1;
const b = 2;
const c = 3;

// best
const [a, b, c] = [1, 2, 3];

23.2 字符串

静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号:

// bad
const a = "foobar";
const b = 'foo' + a + 'bar';

// acceptable
const c = `foobar`;

// good
const a = 'foobar';
const b = `foo${a}bar`;

23.3 解构赋值

使用数组成员对变量赋值时,优先使用解构赋值:

const arr = [1, 2, 3, 4];

// bad
const first = arr[0];
const second = arr[1];

// good
const [first, second] = arr;

函数的参数如果是对象的成员,优先使用解构赋值:

// bad
function getFullName(user) {
  const firstName = user.firstName;
  const lastName = user.lastName;
}

// good
function getFullName(obj) {
  const { firstName, lastName } = obj;
}

// best
function getFullName({ firstName, lastName }) {
}

如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序:

// bad
function processInput(input) {
  return [left, right, top, bottom];
}

// good
function processInput(input) {
  return { left, right, top, bottom };
}

const { left, right } = processInput(input);

23.4 对象

单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾:

// bad
const a = { k1: v1, k2: v2, };
const b = {
  k1: v1,
  k2: v2
};

// good
const a = { k1: v1, k2: v2 };
const b = {
  k1: v1,
  k2: v2,
};

对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法:

// bad
const a = {};
a.x = 3;

// if reshape unavoidable
const a = {};
Object.assign(a, { x: 3 });

// good
const a = { x: null };
a.x = 3;

如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义:

// bad
const obj = {
  id: 5,
  name: 'San Francisco',
};
obj[getKey('enabled')] = true;

// good
const obj = {
  id: 5,
  name: 'San Francisco',
  [getKey('enabled')]: true,
};

对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写:

var ref = 'some value';

// bad
const atom = {
  ref: ref,

  value: 1,

  addValue: function (value) {
    return atom.value + value;
  },
};

// good
const atom = {
  ref,

  value: 1,

  addValue(value) {
    return atom.value + value;
  },
};

23.5 数组

使用扩展运算符(…)拷贝数组:

// bad
const len = items.length;
const itemsCopy = [];
let i;

for (i = 0; i < len; i++) {
  itemsCopy[i] = items[i];
}

// good
const itemsCopy = [...items];

使用 Array.from 方法,将类似数组的对象转为数组:

const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);

23.6 函数

立即执行函数可以写成箭头函数的形式:

(() => {
  console.log('Welcome to the Internet.');
})();

那些需要使用函数表达式的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了 this:

// bad
[1, 2, 3].map(function (x) {
  return x * x;
});

// good
[1, 2, 3].map((x) => {
  return x * x;
});

// best
[1, 2, 3].map(x => x * x);

箭头函数取代Function.prototype.bind,不应再用 self/_this/that 绑定 this:

// bad
const self = this;
const boundMethod = function(...params) {
  return method.apply(self, params);
}

// acceptable
const boundMethod = method.bind(this);

// best
const boundMethod = (...params) => method.apply(this, params);

简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。

所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数:

// bad
function divide(a, b, option = false ) {
}

// good
function divide(a, b, { option = false } = {}) {
}

不要在函数体内使用 arguments 变量,使用 rest 运算符(...)代替。:

// bad
function concatenateAll() {
  const args = Array.prototype.slice.call(arguments);
  return args.join('');
}

// good
function concatenateAll(...args) {
  return args.join('');
}
使用默认值语法设置函数参数的默认值。

// bad
function handleThings(opts) {
  opts = opts || {};
}

// good
function handleThings(opts = {}) {
  // ...
}

23.7 Map 结构

注意区分 ObjectMap,只有模拟现实世界的实体对象时,才使用 Object。如果只是需要key: value的数据结构,使用 Map 结构。因为 Map 有内建的遍历机制:

let map = new Map(arr);

for (let key of map.keys()) {
  console.log(key);
}

for (let value of map.values()) {
  console.log(value);
}

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}

23.8 Class

总是用 Class,取代需要 prototype 的操作:

// bad
function Queue(contents = []) {
  this._queue = [...contents];
}
Queue.prototype.pop = function() {
  const value = this._queue[0];
  this._queue.splice(0, 1);
  return value;
}

// good
class Queue {
  constructor(contents = []) {
    this._queue = [...contents];
  }
  pop() {
    const value = this._queue[0];
    this._queue.splice(0, 1);
    return value;
  }
}

使用extends实现继承,不会有破坏instanceof运算的危险:

// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
  Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function() {
  return this._queue[0];
}

// good
class PeekableQueue extends Queue {
  peek() {
    return this._queue[0];
  }
}

23.9 模块

Module 语法是 JavaScript 模块的标准写法,坚持使用这种写法。使用import取代require:

// bad
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;

// good
import { func1, func2 } from 'moduleA';

使用export取代module.exports:

// commonJS的写法
var React = require('react');

var Breadcrumbs = React.createClass({
  render() {
    return <nav />;
  }
});

module.exports = Breadcrumbs;

// ES6的写法
import React from 'react';

class Breadcrumbs extends React.Component {
  render() {
    return <nav />;
  }
};

export default Breadcrumbs;

如果模块只有一个输出值,就使用export default,如果模块有多个输出值,就不使用export defaultexport default与普通的export不要同时使用。

不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default):

// bad
import * as myObject from './importModule';

// good
import myObject from './importModule';

如果模块默认输出一个函数,函数名的首字母应该小写:

function makeStyleGuide() {
}

export default makeStyleGuide;

如果模块默认输出一个对象,对象名的首字母应该大写:

const StyleGuide = {
  es6: {
  }
};

export default StyleGuide;

23.10 ESLint 的使用

ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。

你可能感兴趣的:(Web,JavaScript)