#变量

  • 使用有意义且能发音的单词

Bad:
1
const yyyymmdstr = moment().format("YYYY/MM/DD");
Good:
1
const currentDate = moment().format("YYYY/MM/DD");
  • 使用可以搜索的单词

Bad:
1
2
// What the heck is 86400000 for?
setTimeout(blastOff, 86400000);
Good:
1
2
3
4
// Declare them as capitalized named constants.
const MILLISECONDS_IN_A_DAY = 86400000;

setTimeout(blastOff, MILLISECONDS_IN_A_DAY);
  • 使用解释说明的单词

Bad:
1
2
3
4
5
6
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(
address.match(cityZipCodeRegex)[1],
address.match(cityZipCodeRegex)[2]
);
Good:
1
2
3
4
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);
  • 不要增加多余的上下文

Bad:
1
2
3
4
5
6
7
8
9
const Car = {
carMake: "Honda",
carModel: "Accord",
carColor: "Blue"
};

function paintCar(car) {
car.carColor = "Red";
}
Good:
1
2
3
4
5
6
7
8
9
const Car = {
make: "Honda",
model: "Accord",
color: "Blue"
};

function paintCar(car) {
car.color = "Red";
}
  • 使用默认参数

Bad:
1
2
3
4
function createMicrobrewery(name) {
const breweryName = name || "Hipster Brew Co.";
// ...
}
Good:
1
2
3
function createMicrobrewery(name = "Hipster Brew Co.") {
// ...
}

函数

  • 参数(最好不超过两个)

一个或两个参数是理想的情况,如果可能的话应该避免三个。
应该整合除此之外的任何东西。
通常,如果你有两个以上的参数,那么你的函数试图做太多(秉承一个函数做一件事)。

Bad:
1
2
3
function createMenu(title, body, buttonText, cancellable) {
// ...
}
Good:
1
2
3
4
5
6
7
8
9
10
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}

createMenu({
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
});
  • 一个函数应该只做一件事

Bad:

1
2
3
4
5
6
7
8
function emailClients(clients) {
clients.forEach(client => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}

Good:

1
2
3
4
5
6
7
8
function emailActiveClients(clients) {
clients.filter(isActiveClient).forEach(email);
}

function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
  • 函数的名字就是她做的事

Bad:

1
2
3
4
5
6
7
8
function emailClients(clients) {
clients.forEach(client => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}

Good:

1
2
3
4
5
6
7
8
function emailActiveClients(clients) {
clients.filter(isActiveClient).forEach(email);
}

function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
  • 删除重复的代码(提升代码的复用性)

Bad:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function showDeveloperList(developers) {
developers.forEach(developer => {
const expectedSalary = developer.calculateExpectedSalary();
const experience = developer.getExperience();
const githubLink = developer.getGithubLink();
const data = {
expectedSalary,
experience,
githubLink
};

render(data);
});
}

function showManagerList(managers) {
managers.forEach(manager => {
const expectedSalary = manager.calculateExpectedSalary();
const experience = manager.getExperience();
const portfolio = manager.getMBAProjects();
const data = {
expectedSalary,
experience,
portfolio
};

render(data);
});
}

Good:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function showEmployeeList(employees) {
employees.forEach(employee => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();

const data = {
expectedSalary,
experience
};

switch (employee.type) {
case "manager":
data.portfolio = employee.getMBAProjects();
break;
case "developer":
data.githubLink = employee.getGithubLink();
break;
}

render(data);
});
}
  • 使用Object.assign来设置对象的默认值

Bad:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const menuConfig = {
title: null,
body: "Bar",
buttonText: null,
cancellable: true
};

function createMenu(config) {
config.title = config.title || "Foo";
config.body = config.body || "Bar";
config.buttonText = config.buttonText || "Baz";
config.cancellable =
config.cancellable !== undefined ? config.cancellable : true;
}

createMenu(menuConfig);

Good:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const menuConfig = {
title: "Order",
// User did not include 'body' key
buttonText: "Send",
cancellable: true
};

function createMenu(config) {
config = Object.assign(
{
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
},
config
);

// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}

createMenu(menuConfig);
  • 封装条件

Bad:

1
2
3
if (fsm.state === "fetching" && isEmpty(listNode)) {
// ...
}

Good:

1
2
3
4
5
6
7
function shouldShowSpinner(fsm, listNode) {
return fsm.state === "fetching" && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
  • 不需要过度优化

Bad:

1
2
3
4
5
// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
for (let i = 0, len = list.length; i < len; i++) {
// ...
}

Good:

1
2
3
for (let i = 0; i < list.length; i++) {
// ...
}
  • 删除废弃的代码

Bad:

1
2
3
4
5
6
7
8
9
10
function oldRequestModule(url) {
// ...
}

function newRequestModule(url) {
// ...
}

const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");

Good:

1
2
3
4
5
6
function newRequestModule(url) {
// ...
}

const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");

对象和数据结构

  • 使用 getter 和 setter

Bad:

1
2
3
4
5
6
7
8
9
10
11
function makeBankAccount() {
// ...

return {
balance: 0
// ...
};
}

const account = makeBankAccount();
account.balance = 100;

Good:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function makeBankAccount() {
// this one is private
let balance = 0;

// a "getter", made public via the returned object below
function getBalance() {
return balance;
}

// a "setter", made public via the returned object below
function setBalance(amount) {
// ... validate before updating the balance
balance = amount;
}

return {
// ...
getBalance,
setBalance
};
}

const account = makeBankAccount();
account.setBalance(100);
  • 使对象具有私有成员

Bad:

1
2
3
4
5
6
7
8
9
10
11
12
const Employee = function(name) {
this.name = name;
};

Employee.prototype.getName = function getName() {
return this.name;
};

const employee = new Employee("John Doe");
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined

Good:

1
2
3
4
5
6
7
8
9
10
11
12
function makeEmployee(name) {
return {
getName() {
return name;
}
};
}

const employee = makeEmployee("John Doe");
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe

这里我觉得用原型链也不错,存在疑惑。

  • 使用class

Bad:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const Animal = function(age) {
if (!(this instanceof Animal)) {
throw new Error("Instantiate Animal with `new`");
}

this.age = age;
};

Animal.prototype.move = function move() {};

const Mammal = function(age, furColor) {
if (!(this instanceof Mammal)) {
throw new Error("Instantiate Mammal with `new`");
}

Animal.call(this, age);
this.furColor = furColor;
};

Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function liveBirth() {};

const Human = function(age, furColor, languageSpoken) {
if (!(this instanceof Human)) {
throw new Error("Instantiate Human with `new`");
}

Mammal.call(this, age, furColor);
this.languageSpoken = languageSpoken;
};

Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function speak() {};

Good:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Animal {
constructor(age) {
this.age = age;
}

move() {
/* ... */
}
}

class Mammal extends Animal {
constructor(age, furColor) {
super(age);
this.furColor = furColor;
}

liveBirth() {
/* ... */
}
}

class Human extends Mammal {
constructor(age, furColor, languageSpoken) {
super(age, furColor);
this.languageSpoken = languageSpoken;
}

speak() {
/* ... */
}
}

class虽然看起来比原型链更简洁,但是不懂原型链何来 class

  • 使用链式方法

Bad:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Car {
constructor(make, model, color) {
this.make = make;
this.model = model;
this.color = color;
}

setMake(make) {
this.make = make;
}

setModel(model) {
this.model = model;
}

setColor(color) {
this.color = color;
}

save() {
console.log(this.make, this.model, this.color);
}
}

const car = new Car("Ford", "F-150", "red");
car.setColor("pink");
car.save();

Good:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Car {
constructor(make, model, color) {
this.make = make;
this.model = model;
this.color = color;
}

setMake(make) {
this.make = make;
// NOTE: Returning this for chaining
return this;
}

setModel(model) {
this.model = model;
// NOTE: Returning this for chaining
return this;
}

setColor(color) {
this.color = color;
// NOTE: Returning this for chaining
return this;
}

save() {
console.log(this.make, this.model, this.color);
// NOTE: Returning this for chaining
return this;
}
}

const car = new Car("Ford", "F-150", "red").setColor("pink").save();
  • 更多时候我们想用的是组合而不是继承

Bad:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}

// ...
}

// Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee
class EmployeeTaxData extends Employee {
constructor(ssn, salary) {
super();
this.ssn = ssn;
this.salary = salary;
}

// ...
}

Good:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class EmployeeTaxData {
constructor(ssn, salary) {
this.ssn = ssn;
this.salary = salary;
}

// ...
}

class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}

setTaxData(ssn, salary) {
this.taxData = new EmployeeTaxData(ssn, salary);
}
// ...
}
  • 接口隔离原则

Bad:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class DOMTraverser {
constructor(settings) {
this.settings = settings;
this.setup();
}

setup() {
this.rootNode = this.settings.rootNode;
this.animationModule.setup();
}

traverse() {
// ...
}
}

const $ = new DOMTraverser({
rootNode: document.getElementsByTagName("body"),
animationModule() {} // Most of the time, we won't need to animate when traversing.
// ...
});

Good:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class DOMTraverser {
constructor(settings) {
this.settings = settings;
this.options = settings.options;
this.setup();
}

setup() {
this.rootNode = this.settings.rootNode;
this.setupOptions();
}

setupOptions() {
if (this.options.animationModule) {
// ...
}
}

traverse() {
// ...
}
}

const $ = new DOMTraverser({
rootNode: document.getElementsByTagName("body"),
options: {
animationModule() {}
}
});

测试

  • 每个测试的单一原则

Bad:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import assert from "assert";

describe("MakeMomentJSGreatAgain", () => {
it("handles date boundaries", () => {
let date;

date = new MakeMomentJSGreatAgain("1/1/2015");
date.addDays(30);
assert.equal("1/31/2015", date);

date = new MakeMomentJSGreatAgain("2/1/2016");
date.addDays(28);
assert.equal("02/29/2016", date);

date = new MakeMomentJSGreatAgain("2/1/2015");
date.addDays(28);
assert.equal("03/01/2015", date);
});
});

Good:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import assert from "assert";

describe("MakeMomentJSGreatAgain", () => {
it("handles 30-day months", () => {
const date = new MakeMomentJSGreatAgain("1/1/2015");
date.addDays(30);
assert.equal("1/31/2015", date);
});

it("handles leap year", () => {
const date = new MakeMomentJSGreatAgain("2/1/2016");
date.addDays(28);
assert.equal("02/29/2016", date);
});

it("handles non-leap year", () => {
const date = new MakeMomentJSGreatAgain("2/1/2015");
date.addDays(28);
assert.equal("03/01/2015", date);
});
});

错误捕获

  • try…catch

Bad:

1
2
3
4
5
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}

Good:

1
2
3
4
5
6
7
8
9
10
11
try {
functionThatMightThrow();
} catch (error) {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
}
  • promise也一样

Bad:

1
2
3
4
5
6
7
getdata()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
console.log(error);
});

Good:

1
2
3
4
5
6
7
8
9
10
11
12
13
getdata()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
});