
变量
Bad:
const yyyymmdstr = moment().format("YYYY/MM/DD");
Good:
const currentDate = moment().format("YYYY/MM/DD");
Bad:
// What the heck is 86400000 for?setTimeout(blastOff, 86400000);
Good:
// Declare them as capitalized named constants.const MILLISECONDS_IN_A_DAY = 86400000;setTimeout(blastOff, MILLISECONDS_IN_A_DAY);
Bad:
const address = "One Infinite Loop, Cupertino 95014";const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;saveCityZipCode(address.match(cityZipCodeRegex)[1],address.match(cityZipCodeRegex)[2]);
Good:
const address = "One Infinite Loop, Cupertino 95014";const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;const [, city, zipCode] = address.match(cityZipCodeRegex) || [];saveCityZipCode(city, zipCode);
Bad:
const Car = {carMake: "Honda",carModel: "Accord",carColor: "Blue"};function paintCar(car) {car.carColor = "Red";}
Good:
const Car = {make: "Honda",model: "Accord",color: "Blue"};function paintCar(car) {car.color = "Red";}
Bad:
function createMicrobrewery(name) {const breweryName = name || "Hipster Brew Co.";// ...}
Good:
function createMicrobrewery(name = "Hipster Brew Co.") {// ...}
函数
一个或两个参数是理想的情况,如果可能的话应该避免三个。
应该整合除此之外的任何东西。
通常,如果你有两个以上的参数,那么你的函数试图做太多(秉承一个函数做一件事)。
Bad:
function createMenu(title, body, buttonText, cancellable) {// ...}
Good:
function createMenu({ title, body, buttonText, cancellable }) {// ...}createMenu({title: "Foo",body: "Bar",buttonText: "Baz",cancellable: true});
Bad:
function emailClients(clients) {clients.forEach(client => {const clientRecord = database.lookup(client);if (clientRecord.isActive()) {email(client);}});}
Good:
function emailActiveClients(clients) {clients.filter(isActiveClient).forEach(email);}function isActiveClient(client) {const clientRecord = database.lookup(client);return clientRecord.isActive();}
Bad:
function emailClients(clients) {clients.forEach(client => {const clientRecord = database.lookup(client);if (clientRecord.isActive()) {email(client);}});}
Good:
function emailActiveClients(clients) {clients.filter(isActiveClient).forEach(email);}function isActiveClient(client) {const clientRecord = database.lookup(client);return clientRecord.isActive();}
Bad:
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:
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);});}
Bad:
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:
const menuConfig = {title: "Order",// User did not include 'body' keybuttonText: "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:
if (fsm.state === "fetching" && isEmpty(listNode)) {// ...}
Good:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === "fetching" && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
Bad:
// 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:
for (let i = 0; i < list.length; i++) {
// ...
}
Bad:
function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");
Good:
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");
对象和数据结构
Bad:
function makeBankAccount() {
// ...
return {
balance: 0
// ...
};
}
const account = makeBankAccount();
account.balance = 100;
Good:
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:
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:
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
这里我觉得用原型链也不错,存在疑惑。
Bad:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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);
});
});
错误捕获
Bad:
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}
Good:
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!
}
Bad:
getdata()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
console.log(error);
});
Good:
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!
});
