A mostly reasonable approach to JavaScript
一个最讲道理的JavaScript方法论
Commas 逗号
[20.1] Leading commas: Nope. eslint:
comma-style
jscs:requireCommaBeforeLineBreak
一定不要前导逗号。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// bad
const story = [
once
, upon
, aTime
];
// good
const story = [
once,
upon,
aTime,
];
// bad
const hero = {
firstName: 'Ada'
, lastName: 'Lovelace'
, birthYear: 1815
, superPower: 'computers'
};
// good
const hero = {
firstName: 'Ada',
lastName: 'Lovelace',
birthYear: 1815,
superPower: 'computers',
};[20.2] Additional trailing comma: Yup. eslint:
comma-dangle
jscs:requireTrailingComma
额外的尾拖逗号,是的。Why? This leads to cleaner git diffs. Also, transpilers like Babel will remove the additional trailing comma in the transpiled code which means you don’t have to worry about the trailing comma problem in legacy browsers.
为什么?这可以让 git diffs 更加清楚。另外,编译器如 Babel 会移除多于的尾拖逗号,你不用担心古老浏览器的问题。1
2
3
4
5
6
7
8
9
10
11
12
13
14// bad - git diff without trailing comma
const hero = {
firstName: 'Florence',
- lastName: 'Nightingale'
+ lastName: 'Nightingale',
+ inventorOf: ['coxcomb chart', 'modern nursing']
};
// good - git diff with trailing comma
const hero = {
firstName: 'Florence',
lastName: 'Nightingale',
+ inventorOf: ['coxcomb chart', 'modern nursing'],
};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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72// bad
const hero = {
firstName: 'Dana',
lastName: 'Scully'
};
const heroes = [
'Batman',
'Superman'
];
// good
const hero = {
firstName: 'Dana',
lastName: 'Scully',
};
const heroes = [
'Batman',
'Superman',
];
// bad
function createHero(
firstName,
lastName,
inventorOf
) {
// does nothing
}
// good
function createHero(
firstName,
lastName,
inventorOf,
) {
// does nothing
}
// good (note that a comma must not appear after a "rest" element)
// 注意逗号不应该出现在剩余参数里。
function createHero(
firstName,
lastName,
inventorOf,
...heroArgs
) {
// does nothing
}
// bad
createHero(
firstName,
lastName,
inventorOf
);
// good
createHero(
firstName,
lastName,
inventorOf,
);
// good (note that a comma must not appear after a "rest" element)
createHero(
firstName,
lastName,
inventorOf,
...heroArgs
);
Semicolons 分号
[21.1] Yup. eslint:
semi
jscs:requireSemicolons
需要。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// bad
(function () {
const name = 'Skywalker'
return name
})()
// good
(function () {
const name = 'Skywalker';
return name;
}());
// good, but legacy (guards against the function becoming an argument when two files with IIFEs are concatenated)
// 好,但是是遗产,保证函数不会变成一个参数,当两个文件立即调用函数的时候。
;((() => {
const name = 'Skywalker';
return name;
})());
Type Casting & Coercion 类型适配&强制分配
[22.1] Perform type coercion at the beginning of the statement.
执行强制类型在句子一开始的地方[22.2] Strings:
1
2
3
4
5
6
7
8
9
10// => this.reviewScore = 9;
// bad
const totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf() 调用this.reviewScore.valueOf
// bad
const totalScore = this.reviewScore.toString(); // isn’t guaranteed to return a string 不保证返回字符串
// good
const totalScore = String(this.reviewScore);[22.3] Numbers: Use
Number
for type casting andparseInt
always with a radix for parsing strings. eslint:radix
数字:使用Number
来做类型适配,使用parseInt
带上进制来做字符串的转换。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const inputValue = '4';
// bad
const val = new Number(inputValue);
// bad
const val = +inputValue;
// bad
const val = inputValue >> 0;
// bad
const val = parseInt(inputValue);
// good
const val = Number(inputValue);
// good
const val = parseInt(inputValue, 10);[22.4] If for whatever reason you are doing something wild and
parseInt
is your bottleneck and need to use Bitshift for performance reasons, leave a comment explaining why and what you’re doing.
无论什么原因,parseInt 成为你的瓶颈,出于性能考虑你需要用位运算,记得留一段注释解释你 TM 干了啥。1
2
3
4
5
6
7// good
/**
* parseInt was the reason my code was slow.
* Bitshifting the String to coerce it to a
* Number made it a lot faster.
*/
const val = inputValue >> 0;22.5 Note: Be careful when using bitshift operations. Numbers are represented as 64-bit values, but bitshift operations always return a 32-bit integer (source). Bitshift can lead to unexpected behavior for integer values larger than 32 bits. Discussion. Largest signed 32-bit Int is 2,147,483,647:
使用位运算的时候需要十分小心。数字表示成64bit 的值,但是位运算总是返回一个32bit 的整数,大于32bit 的数字位运算可能导致预期之外的行为。最大的32进制整数是 2,147,483,647:1
2
32147483647 >> 0; // => 2147483647
2147483648 >> 0; // => -2147483648
2147483649 >> 0; // => -2147483647[22.6] Booleans:
1
2
3
4
5
6
7
8
9
10const age = 0;
// bad
const hasAge = new Boolean(age);
// good
const hasAge = Boolean(age);
// best
const hasAge = !!age;
Naming Conventions 命名约定
[23.1] Avoid single letter names. Be descriptive with your naming. eslint:
id-length
避免单个字母的名字。名字应该有描述性。1
2
3
4
5
6
7
8
9// bad
function q() {
// ...
}
// good
function query() {
// ...
}[23.2] Use camelCase when naming objects, functions, and instances. eslint:
camelcase
jscs:requireCamelCaseOrUpperCaseIdentifiers
使用小驼峰来命名对象、函数、实例。1
2
3
4
5
6
7
8// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}
// good
const thisIsMyObject = {};
function thisIsMyFunction() {}[23.3] Use PascalCase only when naming constructors or classes. eslint:
new-cap
jscs:requireCapitalizedConstructors
只有命名类和构造器的时候使用大驼峰。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// bad
function user(options) {
this.name = options.name;
}
const bad = new user({
name: 'nope',
});
// good
class User {
constructor(options) {
this.name = options.name;
}
}
const good = new User({
name: 'yup',
});[23.4] Do not use trailing or leading underscores. eslint:
no-underscore-dangle
jscs:disallowDanglingUnderscores
不要用前导下划线。Why? JavaScript does not have the concept of privacy in terms of properties or methods. Although a leading underscore is a common convention to mean “private”, in fact, these properties are fully public, and as such, are part of your public API contract. This convention might lead developers to wrongly think that a change won’t count as breaking, or that tests aren’t needed. tl;dr: if you want something to be “private”, it must not be observably present.
为什么?JavaScript 没有私有属性或者方法的概念。尽管前导下划线是一个常用来表达私有的约定,事实上,这些属性全是公开的,是你的 api 协议的一部分。这个约定可能导致开发者错误认为改变一下没关系,或者不需要测试。如果你需要什么真的私有的东西,它必须不显著的出现。1
2
3
4
5
6
7// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';
this._firstName = 'Panda';
// good
this.firstName = 'Panda';[23.5] Don’t save references to
this
. Use arrow functions or Function#bind. jscs:disallowNodeTypes
不要保存指向 this 的引用。使用箭头函数或者 Function.bind1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// bad
function foo() {
const self = this;
return function () {
console.log(self);
};
}
// bad
function foo() {
const that = this;
return function () {
console.log(that);
};
}
// good
function foo() {
return () => {
console.log(this);
};
}[23.6] A base filename should exactly match the name of its default export.
一个级别的文件名应该精确的和默认导入类同名。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// file 1 contents
class CheckBox {
// ...
}
export default CheckBox;
// file 2 contents
export default function fortyTwo() { return 42; }
// file 3 contents
export default function insideDirectory() {}
// in some other file
// bad
import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename
import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export
import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export
// bad
import CheckBox from './check_box'; // PascalCase import/export, snake_case filename
import forty_two from './forty_two'; // snake_case import/filename, camelCase export
import inside_directory from './inside_directory'; // snake_case import, camelCase export
import index from './inside_directory/index'; // requiring the index file explicitly
import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly
// good
import CheckBox from './CheckBox'; // PascalCase export/import/filename
import fortyTwo from './fortyTwo'; // camelCase export/import/filename
import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index"
// ^ supports both insideDirectory.js and insideDirectory/index.js[23.7] Use camelCase when you export-default a function. Your filename should be identical to your function’s name.
需要导出默认函数的时候使用小驼峰。你的名字应该作为你函数的标识名。1
2
3
4
5function makeStyleGuide() {
// ...
}
export default makeStyleGuide;[23.8] Use PascalCase when you export a constructor / class / singleton / function library / bare object.
导出构造器、类、单例、函数库、空对象的时候,使用大驼峰。1
2
3
4
5
6const AirbnbStyleGuide = {
es6: {
},
};
export default AirbnbStyleGuide;[23.9] Acronyms and initialisms should always be all capitalized, or all lowercased.
首字母缩写需要全部大写或者全部小写。Why? Names are for readability, not to appease a computer algorithm.
为什么?命名是为了可读性,而不是为了满足算法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// bad
import SmsContainer from './containers/SmsContainer';
// bad
const HttpRequests = [
// ...
];
// good
import SMSContainer from './containers/SMSContainer';
// good
const HTTPRequests = [
// ...
];
// best
import TextMessageContainer from './containers/TextMessageContainer';
// best
const Requests = [
// ...
];
Accessors 存取器
[24.1] Accessor functions for properties are not required.
属性存取函数没必要。[24.2] Do not use JavaScript getters/setters as they cause unexpected side effects and are harder to test, maintain, and reason about. Instead, if you do make accessor functions, use getVal() and setVal(‘hello’).
不要用 JavaScript 的getters/setters,它们可能产生难以测试、修理、推断的副作用。用 getVal() 和 setVal(‘hello’) 来做一个存储器。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// bad
class Dragon {
get age() {
// ...
}
set age(value) {
// ...
}
}
// good
class Dragon {
getAge() {
// ...
}
setAge(value) {
// ...
}
}[24.3] If the property/method is a
boolean
, useisVal()
orhasVal()
.
如果属性或者方法是一个布尔值,使用isVal()
或hasVal()
。1
2
3
4
5
6
7
8
9// bad
if (!dragon.age()) {
return false;
}
// good
if (!dragon.hasAge()) {
return false;
}[24.4] It’s okay to create get() and set() functions, but be consistent.
用 get set 也没关系,但是请保持一致性。1
2
3
4
5
6
7
8
9
10
11
12
13
14class Jedi {
constructor(options = {}) {
const lightsaber = options.lightsaber || 'blue';
this.set('lightsaber', lightsaber);
}
set(key, val) {
this[key] = val;
}
get(key) {
return this[key];
}
}
Events 事件
[25.1] When attaching data payloads to events (whether DOM events or something more proprietary like Backbone events), pass a hash instead of a raw value. This allows a subsequent contributor to add more data to the event payload without finding and updating every handler for the event. For example, instead of:
当荷载带着数据到event之后。传递了一个哈希值而不是原始值。这允许后来的贡献者加上更多的数据到荷载里,同时不需要找到更新每一个处理者。1
2
3
4
5
6
7
8// bad
$(this).trigger('listingUpdated', listing.id);
// ...
$(this).on('listingUpdated', (e, listingId) => {
// do something with listingId
});prefer:
1
2
3
4
5
6
7
8// good
$(this).trigger('listingUpdated', { listingId: listing.id });
// ...
$(this).on('listingUpdated', (e, data) => {
// do something with data.listingId
});
j Query
[26.1] Prefix jQuery object variables with a
$
. jscs:requireDollarBeforejQueryAssignment
jQuery对象用美元符号开头更好。1
2
3
4
5
6
7
8// bad
const sidebar = $('.sidebar');
// good
const $sidebar = $('.sidebar');
// good
const $sidebarBtn = $('.sidebar-btn');[26.2] Cache jQuery lookups.
缓存查找器。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// bad
function setSidebar() {
$('.sidebar').hide();
// ...
$('.sidebar').css({
'background-color': 'pink',
});
}
// good
function setSidebar() {
const $sidebar = $('.sidebar');
$sidebar.hide();
// ...
$sidebar.css({
'background-color': 'pink',
});
}[26.3] For DOM queries use Cascading
$('.sidebar ul')
or parent > child$('.sidebar > ul')
. jsPerf
dom 查询使用串联之类的选择器。[26.4] Use
find
with scoped jQuery object queries.
已经选了对象查询的 jQuery 使用 find 。1
2
3
4
5
6
7
8
9
10
11
12
13
14// bad
$('ul', '.sidebar').hide();
// bad
$('.sidebar').find('ul').hide();
// good
$('.sidebar ul').hide();
// good
$('.sidebar > ul').hide();
// good
$sidebar.find('ul').hide();
ECMAScript 5 Compatibility ES5兼容性
- [27.1] Refer to Kangax’s ES5 compatibility table.
ECMAScript 6+ (ES 2015+) Styles
28.2 Do not use TC39 proposals that have not reached stage 3.
Why? They are not finalized, and they are subject to change or to be withdrawn entirely. We want to use JavaScript, and proposals are not JavaScript yet.
Testing
[29.1] Yup.
1
2
3function foo() {
return true;
}[29.2] No, but seriously:
- Whichever testing framework you use, you should be writing tests!
- 不管用什么框架,都应该写测试。
- Strive to write many small pure functions, and minimize where mutations occur.
- 努力写很多小的纯函数,最小化变化。
- Be cautious about stubs and mocks - they can make your tests more brittle.
- 小心使用存档和原型,让测试十分易碎。
- We primarily use
mocha
at Airbnb.tape
is also used occasionally for small, separate modules. - Airbnb 的框架 mocha
- 100% test coverage is a good goal to strive for, even if it’s not always practical to reach it.
- 追求100%的测试率是个好目标,但有时候难以实现。
- Whenever you fix a bug, write a regression test. A bug fixed without a regression test is almost certainly going to break again in the future.
- 无论什么时候修了一个 bug,应该加一个回归测试,没有回归测试的 bug fix 有很大概率将来又坏掉。
Performance 性能
- On Layout & Web Performance
- String vs Array Concat
- Try/Catch Cost In a Loop
- Bang Function
- jQuery Find vs Context, Selector
- innerHTML vs textContent for script text
- Long String Concatenation
- Are Javascript functions like
map()
,reduce()
, andfilter()
optimized for traversing arrays? - Loading…
Resources 资源
Learning ES6
- Draft ECMA 2015 (ES6) Spec
- ExploringJS
- ES6 Compatibility Table
- Comprehensive Overview of ES6 Features
Read This
Tools
- Code Style Linters
- ESlint - Airbnb Style .eslintrc
- JSHint - Airbnb Style .jshintrc
- JSCS - Airbnb Style Preset (Deprecated, please use ESlint)
- Neutrino preset - neutrino-preset-airbnb-base
Other Style Guides
- Google JavaScript Style Guide
- jQuery Core Style Guidelines
- Principles of Writing Consistent, Idiomatic JavaScript
Other Styles
- Naming this in nested functions - Christian Johansen
- Conditional Callbacks - Ross Allen
- Popular JavaScript Coding Conventions on GitHub - JeongHoon Byun
- Multiple var statements in JavaScript, not superfluous - Ben Alman
Further Reading
- Understanding JavaScript Closures - Angus Croll
- Basic JavaScript for the impatient programmer - Dr. Axel Rauschmayer
- You Might Not Need jQuery - Zack Bloom & Adam Schwartz
- ES6 Features - Luke Hoban
- Frontend Guidelines - Benjamin De Cock
Books
- JavaScript: The Good Parts - Douglas Crockford
- JavaScript Patterns - Stoyan Stefanov
- Pro JavaScript Design Patterns - Ross Harmes and Dustin Diaz
- High Performance Web Sites: Essential Knowledge for Front-End Engineers - Steve Souders
- Maintainable JavaScript - Nicholas C. Zakas
- JavaScript Web Applications - Alex MacCaw
- Pro JavaScript Techniques - John Resig
- Smashing Node.js: JavaScript Everywhere - Guillermo Rauch
- Secrets of the JavaScript Ninja - John Resig and Bear Bibeault
- Human JavaScript - Henrik Joreteg
- Superhero.js - Kim Joar Bekkelund, Mads Mobæk, & Olav Bjorkoy
- JSBooks - Julien Bouquillon
- Third Party JavaScript - Ben Vinegar and Anton Kovalyov
- Effective JavaScript: 68 Specific Ways to Harness the Power of JavaScript - David Herman
- Eloquent JavaScript - Marijn Haverbeke
- You Don’t Know JS: ES6 & Beyond - Kyle Simpson
Blogs
- JavaScript Weekly
- JavaScript, JavaScript…
- Bocoup Weblog
- Adequately Good
- NCZOnline
- Perfection Kills
- Ben Alman
- Dmitry Baranovskiy
- Dustin Diaz
- nettuts
Podcasts