Airbnb JavaScript Style Guide()

clean-code-javascript


Array - 数组

  1. ### 使用...来复制数值
  2. // bad
  3. const len = items.length;
  4. const itemsCopy = [];
  5. let i;
  6. for (i = 0; i < len; i += 1) {
  7. itemsCopy[i] = items[i];
  8. }
  9. **********************************************************************
  10. // good
  11. const itemsCopy = [...items];
  12. **********************************************************************
  13. ### 将可迭代的对象转换为数组时,使用...而不是Array.from
  14. const foo = document.querySelectorAll('.foo');
  15. // good
  16. const nodes = Array.from(foo);
  17. **********************************************************************
  18. // best
  19. const nodes = [...foo];
  20. **********************************************************************
  21. ### 使用Array.from来转换类数组对象。
  22. const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };
  23. // bad
  24. const arr = Array.prototype.slice.call(arrLike);
  25. **********************************************************************
  26. // good
  27. const arr = Array.from(arrLike);
  28. **********************************************************************
  29. ### 使用Array.from而非...来迭代, 因为这样可避免产生中间数组。
  30. // bad
  31. const baz = [...foo].map(bar);
  32. **********************************************************************
  33. // good
  34. const baz = Array.from(foo, bar);
  35. **********************************************************************
  36. ### 在数组方法回调中使用return语句,在某些情况下可省略return
  37. // bad - no returned value means `acc` becomes undefined after the first iteration
  38. [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
  39. const flatten = acc.concat(item);
  40. });
  41. **********************************************************************
  42. // good
  43. [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
  44. const flatten = acc.concat(item);
  45. return flatten;
  46. });
  47. **********************************************************************

Destructuring - 解构

  1. ### 在访问和使用对象的多个属性时,请使用对象分解,这样可以减少临时引用。
  2. // bad
  3. function getFullName(user) {
  4. const firstName = user.firstName;
  5. const lastName = user.lastName;
  6. return `${firstName} ${lastName}`;
  7. }
  8. // good
  9. function getFullName(user) {
  10. const { firstName, lastName } = user;
  11. return `${firstName} ${lastName}`;
  12. }
  13. **********************************************************************
  14. // best
  15. function getFullName({ firstName, lastName }) {
  16. return `${firstName} ${lastName}`;
  17. }
  18. **********************************************************************
  19. ### 使用数组解构
  20. const arr = [1, 2, 3, 4];
  21. // bad
  22. const first = arr[0];
  23. const second = arr[1];
  24. **********************************************************************
  25. // good
  26. const [first, second] = arr; //first-1,second-2。
  27. **********************************************************************
  28. ### 用于返回值时使用对象分解,而非数组分解。
  29. // bad
  30. function processInput(input) {
  31. // then a miracle occurs
  32. return [left, right, top, bottom];
  33. }
  34. // the caller needs to think about the order of return data
  35. const [left, __, top] = processInput(input);
  36. **********************************************************************
  37. // good
  38. function processInput(input) {
  39. // then a miracle occurs
  40. return { left, right, top, bottom };
  41. }
  42. // the caller selects only the data they need
  43. const { left, top } = processInput(input); //left, top 对应返回值里的left,top。
  44. **********************************************************************

String - 字符串

  1. ### 使用模板字符串,且避免使用多余空格。
  2. // bad
  3. function sayHi(name) {
  4. return 'How are you, ' + name + '?';
  5. }
  6. // bad
  7. function sayHi(name) {
  8. return ['How are you, ', name, '?'].join();
  9. }
  10. // bad
  11. function sayHi(name) {
  12. return `How are you, ${ name }?`;
  13. }
  14. const [left, __, top] = processInput(input);
  15. **********************************************************************
  16. // good
  17. function sayHi(name) {
  18. return `How are you, ${name}?`;
  19. }
  20. **********************************************************************

Functions - 函数

  1. ### 使用命名函数表达式而不是函数声明
  2. ### 函数声明会提升,容易在定义函数前被调用-不利于可读性。(有利有弊)
  3. // bad
  4. function foo() {
  5. // ...
  6. }
  7. // bad
  8. const foo = function () {
  9. // ...
  10. };
  11. **********************************************************************
  12. // good
  13. // lexical name distinguished from the variable-referenced invocation(s)
  14. const short = function longUniqueMoreDescriptiveLexicalFoo() {
  15. // ...
  16. };
  17. **********************************************************************
  18. ### 函数立即执行时用括号包起来
  19. // immediately-invoked function expression (IIFE)
  20. (function () {
  21. console.log('Welcome to the Internet. Please follow me.');
  22. }());
  23. ### Note: ECMA-262 defines a block as a list of statements.
  24. ### A function declaration is not a statement.
  25. // bad
  26. if (currentUser) {
  27. function test() {
  28. console.log('Nope.');
  29. }
  30. }
  31. **********************************************************************
  32. // good
  33. let test;
  34. if (currentUser) {
  35. test = () => {
  36. console.log('Yup.');
  37. };
  38. }
  39. **********************************************************************
  40. ### 不要使用arguments,而是使用...,这样args是真数组,而不是伪数组。
  41. // bad
  42. function concatenateAll() {
  43. const args = Array.prototype.slice.call(arguments);
  44. return args.join('');
  45. }
  46. **********************************************************************
  47. // good
  48. function concatenateAll(...args) {
  49. return args.join('');
  50. }
  51. **********************************************************************
  52. ### 使用默认参数,而非后期处理。
  53. // really bad
  54. function handleThings(opts) {
  55. // No! We shouldn’t mutate function arguments.
  56. // Double bad: if opts is falsy it'll be set to an object which may
  57. // be what you want but it can introduce subtle bugs.
  58. opts = opts || {};
  59. // ...
  60. }
  61. // still bad
  62. function handleThings(opts) {
  63. if (opts === void 0) {
  64. opts = {};
  65. }
  66. // ...
  67. }
  68. **********************************************************************
  69. // good
  70. function handleThings(opts = {}) {
  71. // ...
  72. }
  73. **********************************************************************
  74. ### 始终将默认参数放最后
  75. // bad
  76. function handleThings(opts = {}, name) {
  77. // ...
  78. }
  79. **********************************************************************
  80. // good
  81. function handleThings(name, opts = {}) {
  82. // ...
  83. }
  84. **********************************************************************
  85. ### 请勿更改参数
  86. // bad
  87. function f1(obj) {
  88. obj.key = 1;
  89. }
  90. **********************************************************************
  91. // good
  92. function f2(obj) {
  93. const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
  94. }
  95. **********************************************************************
  96. ### 请勿重新定义参数
  97. // bad
  98. function f1(a) {
  99. a = 1;
  100. // ...
  101. }
  102. function f2(a) {
  103. if (!a) { a = 1; }
  104. // ...
  105. }
  106. **********************************************************************
  107. // good
  108. function f3(a) {
  109. const b = a || 1;
  110. // ...
  111. }
  112. function f4(a = 1) {
  113. // ...
  114. }
  115. **********************************************************************
  116. ### 使用...来调用可变参数函数。
  117. // bad
  118. const x = [1, 2, 3, 4, 5];
  119. console.log.apply(console, x);
  120. **********************************************************************
  121. // good
  122. const x = [1, 2, 3, 4, 5];
  123. console.log(...x); // 1 2 3 4 5
  124. // good
  125. new Date(...[2016, 8, 5]);
  126. **********************************************************************
  127. ### 函数参数 (理想情况下应不超过 2 个)
  128. ### 应避免三个以上参数的函数。通常情况下,参数超过两个意味着函数功能过于复杂,这时需要重新优化你的函数。
  129. ### 当确实需要多个参数时,大多情况下可以考虑这些参数封装成一个对象。
  130. // bad
  131. function createMenu(title, body, buttonText, cancellable) {
  132. ...
  133. }
  134. **********************************************************************
  135. // good
  136. var menuConfig = {
  137. title: 'Foo',
  138. body: 'Bar',
  139. buttonText: 'Baz',
  140. cancellable: true
  141. }
  142. function createMenu(menuConfig) {
  143. ...
  144. }
  145. **********************************************************************
  146. ### 函数功能的单一性
  147. // bad
  148. function emailClients(clients) {
  149. clients.forEach(client => {
  150. let clientRecord = database.lookup(client);
  151. if (clientRecord.isActive()) {
  152. email(client);
  153. }
  154. });
  155. }
  156. **********************************************************************
  157. // good
  158. function emailClients(clients) {
  159. clients.forEach(client => {
  160. emailClientIfNeeded(client);
  161. });
  162. }
  163. function emailClientIfNeeded(client) {
  164. if (isClientActive(client)) {
  165. email(client);
  166. }
  167. }
  168. function isClientActive(client) {
  169. let clientRecord = database.lookup(client);
  170. return clientRecord.isActive();
  171. }
  172. **********************************************************************
  173. ### 分解代码,移除重复代码。
  174. ### 使用 Object.assign 设置默认对象。
  175. // bad
  176. var menuConfig = {
  177. title: null,
  178. body: 'Bar',
  179. buttonText: null,
  180. cancellable: true
  181. }
  182. function createMenu(config) {
  183. config.title = config.title || 'Foo'
  184. config.body = config.body || 'Bar'
  185. config.buttonText = config.buttonText || 'Baz'
  186. config.cancellable = config.cancellable === undefined ? config.cancellable : true;
  187. }
  188. createMenu(menuConfig);
  189. **********************************************************************
  190. // good
  191. var menuConfig = {
  192. title: 'Order',
  193. // User did not include 'body' key
  194. buttonText: 'Send',
  195. cancellable: true
  196. }
  197. function createMenu(config) {
  198. config = Object.assign({
  199. title: 'Foo',
  200. body: 'Bar',
  201. buttonText: 'Baz',
  202. cancellable: true
  203. }, config);
  204. // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  205. // ...
  206. }
  207. createMenu(menuConfig);
  208. **********************************************************************
  209. ### 不要使用标记(Flag)作为函数参数
  210. // bad
  211. function createFile(name, temp) {
  212. if (temp) {
  213. fs.create('./temp/' + name);
  214. } else {
  215. fs.create(name);
  216. }
  217. }
  218. **********************************************************************
  219. // good
  220. function createTempFile(name) {
  221. fs.create('./temp/' + name);
  222. }
  223. ----------
  224. function createFile(name) {
  225. fs.create(name);
  226. }
  227. **********************************************************************
  228. ### 避免副作用
  229. ### 当函数产生了除了“接受一个值并返回一个结果”之外的行为时,称该函数产生了副作用。
  230. ### 应该将这些需要副作用的功能集中在一起,不要用多个函数/类修改某个文件。
  231. ### 用且只用一个 service 完成这一需求。
  232. // bad
  233. // Global variable referenced by following function.
  234. // If we had another function that used this name, now it'd be an array and it could break it.
  235. var name = 'Ryan McDermott';
  236. function splitIntoFirstAndLastName() {
  237. name = name.split(' ');
  238. }
  239. splitIntoFirstAndLastName();
  240. console.log(name); // ['Ryan', 'McDermott'];
  241. **********************************************************************
  242. // good
  243. function splitIntoFirstAndLastName(name) {
  244. return name.split(' ');
  245. }
  246. var name = 'Ryan McDermott'
  247. var newName = splitIntoFirstAndLastName(name);
  248. console.log(name); // 'Ryan McDermott';
  249. console.log(newName); // ['Ryan', 'McDermott'];
  250. **********************************************************************
  251. ### 封装判断条件
  252. // bad
  253. if (fsm.state === 'fetching' && isEmpty(listNode)) {
  254. /// ...
  255. }
  256. **********************************************************************
  257. // good
  258. function shouldShowSpinner(fsm, listNode) {
  259. return fsm.state === 'fetching' && isEmpty(listNode);
  260. }
  261. if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  262. // ...
  263. }
  264. **********************************************************************
  265. ### 避免“否定情况”的判断。
  266. //bad
  267. function isDOMNodeNotPresent(node) {
  268. // ...
  269. }
  270. if (!isDOMNodeNotPresent(node)) {
  271. // ...
  272. }
  273. **********************************************************************
  274. // good
  275. function isDOMNodePresent(node) {
  276. // ...
  277. }
  278. if (isDOMNodePresent(node)) {
  279. // ...
  280. }
  281. **********************************************************************
  282. ### 避免过度优化
  283. // bad
  284. // 这里使用变量len是因为在老式浏览器中,
  285. // 直接使用正例中的方式会导致每次循环均重复计算list.length的值,
  286. // 而在现代浏览器中会自动完成优化,这一行为是没有必要的。
  287. for (var i = 0, len = list.length; i < len; i++) {
  288. // ...
  289. }
  290. **********************************************************************
  291. // good
  292. for (var i = 0; i < list.length; i++) {
  293. // ...
  294. }
  295. **********************************************************************
  296. ### 当函数间存在相互调用的情况时,应将两者置于较近的位置。
  297. ### 理想情况下,应将调用其他函数的函数写在被调用函数的上方。
  298. // bad
  299. class PerformanceReview {
  300. constructor(employee) {
  301. this.employee = employee;
  302. }
  303. lookupPeers() {
  304. return db.lookup(this.employee, 'peers');
  305. }
  306. lookupMananger() {
  307. return db.lookup(this.employee, 'manager');
  308. }
  309. getPeerReviews() {
  310. let peers = this.lookupPeers();
  311. // ...
  312. }
  313. perfReview() {
  314. getPeerReviews();
  315. getManagerReview();
  316. getSelfReview();
  317. }
  318. getManagerReview() {
  319. let manager = this.lookupManager();
  320. }
  321. getSelfReview() {
  322. // ...
  323. }
  324. }
  325. let review = new PerformanceReview(user);
  326. review.perfReview();
  327. **********************************************************************
  328. // good
  329. class PerformanceReview {
  330. constructor(employee) {
  331. this.employee = employee;
  332. }
  333. perfReview() {
  334. getPeerReviews();
  335. getManagerReview();
  336. getSelfReview();
  337. }
  338. getPeerReviews() {
  339. let peers = this.lookupPeers();
  340. // ...
  341. }
  342. lookupPeers() {
  343. return db.lookup(this.employee, 'peers');
  344. }
  345. getManagerReview() {
  346. let manager = this.lookupManager();
  347. }
  348. lookupMananger() {
  349. return db.lookup(this.employee, 'manager');
  350. }
  351. getSelfReview() {
  352. // ...
  353. }
  354. }
  355. let review = new PerformanceReview(employee);
  356. review.perfReview();
  357. **********************************************************************

Arrow Functions - 箭头函数

  1. ### 省略{}与使用{}。
  2. **********************************************************************
  3. // good
  4. [1, 2, 3].map((number) => `A string containing the ${number + 1}.`);
  5. // good
  6. [1, 2, 3].map((number) => {
  7. const nextNumber = number + 1;
  8. return `A string containing the ${nextNumber}.`;
  9. });
  10. // good
  11. [1, 2, 3].map((number, index) => ({
  12. [index]: number,
  13. }));
  14. **********************************************************************
  15. ### 如果表达式跨越多行,请将其括在括号中以提高可读性。
  16. // bad
  17. ['get', 'post', 'put'].map((httpMethod) => Object.prototype.hasOwnProperty.call(
  18. httpMagicObjectWithAVeryLongName,
  19. httpMethod,
  20. )
  21. );
  22. **********************************************************************
  23. // good
  24. ['get', 'post', 'put'].map((httpMethod) => (
  25. Object.prototype.hasOwnProperty.call(
  26. httpMagicObjectWithAVeryLongName,
  27. httpMethod,
  28. )
  29. ));
  30. **********************************************************************
  31. ### 在参数周围加上括号。
  32. // bad
  33. [1, 2, 3].map(x => x * x);
  34. **********************************************************************
  35. // good
  36. [1, 2, 3].map((x) => x * x);
  37. **********************************************************************
  38. ### 避免将箭头函数语避免将箭头函数语法(=>)与比较运算符(<=,>=)混淆。
  39. // bad
  40. const itemHeight = (item) => item.height >= 256 ? item.largeSize : item.smallSize;
  41. **********************************************************************
  42. // good
  43. const itemHeight = (item) => (item.height <= 256 ? item.largeSize : item.smallSize);
  44. **********************************************************************

Classes & Constructors - 类和构造函数

  1. ### 始终使用class,避免直接使用prototype
  2. // bad
  3. function Queue(contents = []) {
  4. this.queue = [...contents];
  5. }
  6. Queue.prototype.pop = function () {
  7. const value = this.queue[0];
  8. this.queue.splice(0, 1);
  9. return value;
  10. };
  11. **********************************************************************
  12. // good
  13. class Queue {
  14. constructor (contents = []) {
  15. this.queue = [...contents];
  16. }
  17. pop () {
  18. const value = this.queue[0];
  19. this.queue.splice(0, 1);
  20. return value;
  21. }
  22. }
  23. **********************************************************************
  24. ### 使用extends继承。
  25. // bad
  26. const inherits = require('inherits');
  27. function PeekableQueue(contents) {
  28. Queue.apply(this, contents);
  29. }
  30. inherits(PeekableQueue, Queue);
  31. PeekableQueue.prototype.peek = function () {
  32. return this.queue[0];
  33. };
  34. **********************************************************************
  35. // good
  36. class PeekableQueue extends Queue {
  37. peek() {
  38. return this.queue[0];
  39. }
  40. }
  41. **********************************************************************
  42. ### 方法可以返回this以帮助方法链式调用。
  43. ### 注意this,文章https://blog.csdn.net/zhjzls/article/details/80038353
  44. // bad
  45. Jedi.prototype.jump = function () {
  46. this.jumping = true;
  47. return true;
  48. };
  49. Jedi.prototype.setHeight = function (height) {
  50. this.height = height;
  51. };
  52. const luke = new Jedi();
  53. luke.jump(); // => true
  54. luke.setHeight(20); // => undefined
  55. **********************************************************************
  56. // good
  57. class Jedi {
  58. jump() {
  59. this.jumping = true;
  60. return this;
  61. }
  62. setHeight(height) {
  63. this.height = height;
  64. return this;
  65. }
  66. }
  67. const luke = new Jedi();
  68. luke.jump()
  69. .setHeight(20); // important!
  70. **********************************************************************
  71. ### 类的设计原则s
  72. ### 单一职责原则(SRP)
  73. ### 最小化对一个类需要修改的次数是非常有必要的,一个类里不要有太多太杂的功能。
  74. // bad
  75. class UserSettings {
  76. constructor(user) {
  77. this.user = user;
  78. }
  79. changeSettings(settings) {
  80. if (this.verifyCredentials(user)) {
  81. // ...
  82. }
  83. }
  84. verifyCredentials(user) {
  85. // ...
  86. }
  87. }
  88. **********************************************************************
  89. //good
  90. class UserAuth {
  91. constructor(user) {
  92. this.user = user;
  93. }
  94. verifyCredentials() {
  95. // ...
  96. }
  97. }
  98. class UserSettings {
  99. constructor(user) {
  100. this.user = user;
  101. this.auth = new UserAuth(user)
  102. }
  103. changeSettings(settings) {
  104. if (this.auth.verifyCredentials()) {
  105. // ...
  106. }
  107. }
  108. }
  109. **********************************************************************
  110. ### 开/闭原则(OCP)
  111. ### 易于扩展,难于修改。
  112. // bad
  113. class AjaxRequester {
  114. constructor() {
  115. // What if we wanted another HTTP Method, like DELETE? We would have to
  116. // open this file up and modify this and put it in manually.
  117. this.HTTP_METHODS = ['POST', 'PUT', 'GET'];
  118. }
  119. get(url) {
  120. // ...
  121. }
  122. }
  123. **********************************************************************
  124. // good
  125. class AjaxRequester {
  126. constructor() {
  127. this.HTTP_METHODS = ['POST', 'PUT', 'GET'];
  128. }
  129. get(url) {
  130. // ...
  131. }
  132. addHTTPMethod(method) {
  133. this.HTTP_METHODS.push(method);
  134. }
  135. }
  136. **********************************************************************
  137. ### 利斯科夫替代原则 (LSP)
  138. ### 子类对象应该能够替换其超类对象被使用
  139. ### 如果有一个父类和一个子类,当采用子类替换父类时不应该产生错误的结果。
  140. // bad
  141. class Rectangle {
  142. constructor() {
  143. this.width = 0;
  144. this.height = 0;
  145. }
  146. setColor(color) {
  147. // ...
  148. }
  149. render(area) {
  150. // ...
  151. }
  152. setWidth(width) {
  153. this.width = width;
  154. }
  155. setHeight(height) {
  156. this.height = height;
  157. }
  158. getArea() {
  159. return this.width * this.height;
  160. }
  161. }
  162. class Square extends Rectangle {
  163. constructor() {
  164. super();
  165. }
  166. setWidth(width) {
  167. this.width = width;
  168. this.height = width;
  169. }
  170. setHeight(height) {
  171. this.width = height;
  172. this.height = height;
  173. }
  174. }
  175. function renderLargeRectangles(rectangles) {
  176. rectangles.forEach((rectangle) => {
  177. rectangle.setWidth(4);
  178. rectangle.setHeight(5);
  179. let area = rectangle.getArea(); // BAD: Will return 25 for Square. Should be 20.
  180. rectangle.render(area);
  181. })
  182. }
  183. let rectangles = [new Rectangle(), new Rectangle(), new Square()];
  184. renderLargeRectangles(rectangles);
  185. **********************************************************************
  186. // good
  187. class Shape {
  188. constructor() {}
  189. setColor(color) {
  190. // ...
  191. }
  192. render(area) {
  193. // ...
  194. }
  195. }
  196. class Rectangle extends Shape {
  197. constructor() {
  198. super();
  199. this.width = 0;
  200. this.height = 0;
  201. }
  202. setWidth(width) {
  203. this.width = width;
  204. }
  205. setHeight(height) {
  206. this.height = height;
  207. }
  208. getArea() {
  209. return this.width * this.height;
  210. }
  211. }
  212. class Square extends Shape {
  213. constructor() {
  214. super();
  215. this.length = 0;
  216. }
  217. setLength(length) {
  218. this.length = length;
  219. }
  220. getArea() {
  221. return this.length * this.length;
  222. }
  223. }
  224. function renderLargeShapes(shapes) {
  225. shapes.forEach((shape) => {
  226. switch (shape.constructor.name) {
  227. case 'Square':
  228. shape.setLength(5);
  229. case 'Rectangle':
  230. shape.setWidth(4);
  231. shape.setHeight(5);
  232. }
  233. let area = shape.getArea();
  234. shape.render(area);
  235. })
  236. }
  237. let shapes = [new Rectangle(), new Rectangle(), new Square()];
  238. renderLargeShapes(shapes);
  239. **********************************************************************
  240. ### 接口隔离原则 (ISP)
  241. ### 客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。
  242. ### 减少对配置参数数量的需求是有益的。
  243. // bad
  244. class DOMTraverser {
  245. constructor(settings) {
  246. this.settings = settings;
  247. this.setup();
  248. }
  249. setup() {
  250. this.rootNode = this.settings.rootNode;
  251. this.animationModule.setup();
  252. }
  253. traverse() {
  254. // ...
  255. }
  256. }
  257. let $ = new DOMTraverser({
  258. rootNode: document.getElementsByTagName('body'),
  259. animationModule: function() {} // Most of the time, we won't need to animate when traversing.
  260. // ...
  261. });
  262. **********************************************************************
  263. // good
  264. class DOMTraverser {
  265. constructor(settings) {
  266. this.settings = settings;
  267. this.options = settings.options;
  268. this.setup();
  269. }
  270. setup() {
  271. this.rootNode = this.settings.rootNode;
  272. this.setupOptions();
  273. }
  274. setupOptions() {
  275. if (this.options.animationModule) {
  276. // ...
  277. }
  278. }
  279. traverse() {
  280. // ...
  281. }
  282. }
  283. let $ = new DOMTraverser({
  284. rootNode: document.getElementsByTagName('body'),
  285. options: {
  286. animationModule: function() {}
  287. }
  288. });
  289. **********************************************************************
  290. ### 依赖反转原则 (DIP)
  291. ### 两个核心点:高层模块不应该依赖于低层模块。他们都应该依赖于抽象接口。
  292. ### 抽象接口应该脱离具体实现,具体实现应该依赖于抽象接口。
  293. // bad
  294. class InventoryTracker {
  295. constructor(items) {
  296. this.items = items;
  297. // BAD: We have created a dependency on a specific request implementation.
  298. // We should just have requestItems depend on a request method: `request`
  299. this.requester = new InventoryRequester();
  300. }
  301. requestItems() {
  302. this.items.forEach((item) => {
  303. this.requester.requestItem(item);
  304. });
  305. }
  306. }
  307. class InventoryRequester {
  308. constructor() {
  309. this.REQ_METHODS = ['HTTP'];
  310. }
  311. requestItem(item) {
  312. // ...
  313. }
  314. }
  315. let inventoryTracker = new InventoryTracker(['apples', 'bananas']);
  316. inventoryTracker.requestItems();
  317. **********************************************************************
  318. // good
  319. class InventoryTracker {
  320. constructor(items, requester) {
  321. this.items = items;
  322. this.requester = requester;
  323. }
  324. requestItems() {
  325. this.items.forEach((item) => {
  326. this.requester.requestItem(item);
  327. });
  328. }
  329. }
  330. class InventoryRequesterV1 {
  331. constructor() {
  332. this.REQ_METHODS = ['HTTP'];
  333. }
  334. requestItem(item) {
  335. // ...
  336. }
  337. }
  338. class InventoryRequesterV2 {
  339. constructor() {
  340. this.REQ_METHODS = ['WS'];
  341. }
  342. requestItem(item) {
  343. // ...
  344. }
  345. }
  346. // By constructing our dependencies externally and injecting them, we can easily
  347. // substitute our request module for a fancy new one that uses WebSockets.
  348. let inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2());
  349. inventoryTracker.requestItems();
  350. **********************************************************************

Modules - 模块

  1. ### 不要使用通配符导入
  2. ### Why? This makes sure you have a single default export.
  3. // bad
  4. import * as AirbnbStyleGuide from './AirbnbStyleGuide';
  5. **********************************************************************
  6. // good
  7. import AirbnbStyleGuide from './AirbnbStyleGuide';
  8. **********************************************************************
  9. ### 请勿直接在import里直接export
  10. // bad
  11. // filename es6.js. !!! Not AirbnbStyleGuide.js !!!
  12. export { es6 as default } from './AirbnbStyleGuide';
  13. **********************************************************************
  14. // good
  15. // filename es6.js
  16. import { es6 } from './AirbnbStyleGuide';
  17. export default es6;
  18. **********************************************************************
  19. ### 一个地方一个import
  20. // bad
  21. import foo from 'foo';
  22. // … some other imports … //
  23. import { named1, named2 } from 'foo';
  24. **********************************************************************
  25. // good
  26. import foo, { named1, named2 } from 'foo';
  27. // good
  28. import foo, {
  29. named1,
  30. named2,
  31. } from 'foo';
  32. **********************************************************************
  33. ### 避免导出变量,一般来说导出常量。
  34. // bad
  35. let foo = 3;
  36. export { foo };
  37. **********************************************************************
  38. // good
  39. const foo = 3;
  40. export { foo };
  41. **********************************************************************
  42. ### 在具有单个导出的模块中,首选默认导出而不是命名导出。
  43. // bad
  44. export function foo() {}
  45. **********************************************************************
  46. // good
  47. export default function foo() {}
  48. **********************************************************************

Iterators and Generators - 迭代器和生成器

  1. ### 不要是使用for-in or for-of这种迭代器
  2. ### 尽量使用高阶函数来遍历
  3. ### map()/ every()/ filter()/ find()/ findIndex()/ reduce()/ some()/ ... 等遍历数组
  4. ### Object.keys()/ Object.values()/ Object.entries()产生数组,以便可以遍历对象。
  5. const numbers = [1, 2, 3, 4, 5];
  6. // bad
  7. let sum = 0;
  8. for (let num of numbers) {
  9. sum += num;
  10. }
  11. sum === 15;
  12. // good
  13. let sum = 0;
  14. numbers.forEach((num) => {
  15. sum += num;
  16. });
  17. sum === 15;
  18. **********************************************************************
  19. // best (use the functional force)
  20. const sum = numbers.reduce((total, num) => total + num, 0);
  21. sum === 15;
  22. **********************************************************************
  23. // bad
  24. const increasedByOne = [];
  25. for (let i = 0; i < numbers.length; i++) {
  26. increasedByOne.push(numbers[i] + 1);
  27. }
  28. // good
  29. const increasedByOne = [];
  30. numbers.forEach((num) => {
  31. increasedByOne.push(num + 1);
  32. });
  33. **********************************************************************
  34. // best (keeping it functional)
  35. const increasedByOne = numbers.map((num) => num + 1);
  36. **********************************************************************
  37. ### 现在不要使用生成器。

Properties - 属性

  1. ### 访问属性时使用。
  2. ### 使用[]访问变量属性。
  3. ### 计算指数时使用指数运算符 **
  4. // bad
  5. const binary = Math.pow(2, 10);
  6. **********************************************************************
  7. // good
  8. const binary = 2 ** 10;
  9. **********************************************************************

Variables - 变量

  1. ### 使用const or let声明变量。
  2. ### 一个const or let声明一个变量。
  3. ### const写一块,let写一块。
  4. ### 在需要的地方分配变量,但将其放置在合理的位置
  5. ### Whylet并且const是块作用域而不是函数作用域。
  6. // bad - unnecessary function call
  7. function checkName(hasName) {
  8. const name = getName();
  9. if (hasName === 'test') {
  10. return false;
  11. }
  12. if (name === 'test') {
  13. this.setName('');
  14. return false;
  15. }
  16. return name;
  17. }
  18. **********************************************************************
  19. // good
  20. function checkName(hasName) {
  21. if (hasName === 'test') {
  22. return false;
  23. }
  24. const name = getName();
  25. if (name === 'test') {
  26. this.setName('');
  27. return false;
  28. }
  29. return name;
  30. }
  31. **********************************************************************
  32. ### 不要链式声明变量。
  33. ### 可能会隐式创建全局变量。
  34. // bad
  35. (function example() {
  36. // JavaScript interprets this as
  37. // let a = ( b = ( c = 1 ) );
  38. // The let keyword only applies to variable a; variables b and c become
  39. // global variables.
  40. let a = b = c = 1;
  41. }());
  42. console.log(a); // throws ReferenceError
  43. console.log(b); // 1
  44. console.log(c); // 1
  45. **********************************************************************
  46. // good
  47. (function example() {
  48. let a = 1;
  49. let b = a;
  50. let c = a;
  51. }());
  52. console.log(a); // throws ReferenceError
  53. console.log(b); // throws ReferenceError
  54. console.log(c); // throws ReferenceError
  55. **********************************************************************
  56. // the same applies for `const`
  57. ### 不要使用 ++ -- ,而是使用 += -=
  58. ### 不要在声明的 = 后折行,若超过max-len则将 = 后的内容将括号括起来。
  59. ### 使用易于检索的名称
  60. // bad
  61. // 525600 是什么?
  62. for (var i = 0; i < 525600; i++) {
  63. runCronJob();
  64. }
  65. **********************************************************************
  66. // good
  67. // Declare them as capitalized `var` globals.
  68. var MINUTES_IN_A_YEAR = 525600;
  69. for (var i = 0; i < MINUTES_IN_A_YEAR; i++) {
  70. runCronJob();
  71. }
  72. **********************************************************************
  73. ### 避免无意义的条件判断
  74. // bad
  75. function createMicrobrewery(name) {
  76. var breweryName;
  77. if (name) {
  78. breweryName = name;
  79. } else {
  80. breweryName = 'Hipster Brew Co.';
  81. }
  82. }
  83. **********************************************************************
  84. //good
  85. function createMicrobrewery(name) {
  86. var breweryName = name || 'Hipster Brew Co.'
  87. }
  88. **********************************************************************

Hoisting - 提升

  1. ### var 声明提升到当前作用域顶部,赋值不提升。
  2. ### const let 不提升, Temporal Dead Zones (TDZ)。
  3. ### 匿名函数表达式声明提升,赋值不提升。
  4. function example() {
  5. console.log(anonymous); // => undefined
  6. anonymous(); // => TypeError anonymous is not a function
  7. var anonymous = function () { //匿名!!!Attention!!!
  8. console.log('anonymous function expression');
  9. };
  10. }
  11. ### 命名函数表达式声明提升,赋值(函数名或函数体)不提升。
  12. function example() {
  13. console.log(named); // => undefined
  14. named(); // => TypeError named is not a function
  15. superPower(); // => ReferenceError superPower is not defined
  16. var named = function superPower() {
  17. console.log('Flying');
  18. };
  19. }
  20. // the same is true when the function name
  21. // is the same as the variable name.
  22. function example() {
  23. console.log(named); // => undefined
  24. named(); // => TypeError named is not a function
  25. var named = function named() {
  26. console.log('named');
  27. };
  28. }
  29. ### 函数声明整体提升。
  30. function example() {
  31. superPower(); // => Flying
  32. function superPower() {
  33. console.log('Flying');
  34. }
  35. }

Comparison Operators & Equality - 比较运算符和等号

  1. ### 空数组空对象都是true
  2. if ([0] && []) {
  3. // true
  4. // an array (even an empty one) is an object, objects will evaluate to true
  5. }
  6. ### if(value) 将这种快捷方式用于布尔值,数字和字符串进行显示对比
  7. // bad
  8. if (isValid === true) {
  9. // ...
  10. }
  11. **********************************************************************
  12. // good
  13. if (isValid) {
  14. // ...
  15. }
  16. **********************************************************************
  17. // bad
  18. if (name) {
  19. // ...
  20. }
  21. **********************************************************************
  22. // good
  23. if (name !== '') {
  24. // ...
  25. }
  26. **********************************************************************
  27. // bad
  28. if (collection.length) {
  29. // ...
  30. }
  31. **********************************************************************
  32. // good
  33. if (collection.length > 0) {
  34. // ...
  35. }
  36. **********************************************************************
  37. ### 使用switch时,用{}包裹对case的处理。
  38. // bad
  39. switch (foo) {
  40. case 1:
  41. let x = 1;
  42. break;
  43. default:
  44. class C {}
  45. }
  46. **********************************************************************
  47. // good
  48. switch (foo) {
  49. case 1: {
  50. let x = 1;
  51. break;
  52. }
  53. case 4:
  54. bar();
  55. break;
  56. default: {
  57. class C {}
  58. }
  59. }
  60. **********************************************************************
  61. ### 三元运算不嵌套,单行处理。
  62. // bad
  63. const foo = maybe1 > maybe2
  64. ? "bar"
  65. : value1 > value2 ? "baz" : null;
  66. // best
  67. // split into 2 separated ternary expressions
  68. const maybeNull = value1 > value2 ? 'baz' : null;
  69. const foo = maybe1 > maybe2 ? 'bar' : maybeNull;
  70. ### 避免不必要的声明语句。
  71. // bad
  72. const foo = a ? a : b;
  73. const bar = c ? true : false;
  74. const baz = c ? false : true;
  75. **********************************************************************
  76. // good
  77. const foo = a || b;
  78. const bar = !!c;
  79. const baz = !c;
  80. **********************************************************************
  81. ### 混合运算时将优先级模糊的地方使用括号括起来。

WhiteSpace - 空白符

  1. ### 在块和下一条语句之间保留空白行。
  2. // bad
  3. const obj = {
  4. foo() {
  5. },
  6. bar() {
  7. },
  8. };
  9. return obj;
  10. **********************************************************************
  11. // good
  12. const obj = {
  13. foo() {
  14. },
  15. bar() {
  16. },
  17. };
  18. return obj;
  19. **********************************************************************
  20. ### 不要用空行填充块。
  21. // bad
  22. class Foo {
  23. constructor(bar) {
  24. this.bar = bar;
  25. }
  26. }
  27. **********************************************************************
  28. // good
  29. function bar() {
  30. console.log(foo);
  31. }
  32. **********************************************************************
  33. ### 括号内不要使用空格,括号前后空格在不同情况下有不同选择。
  34. // bad
  35. if ( foo ) {
  36. console.log(foo);
  37. }
  38. **********************************************************************
  39. // good
  40. if (foo) {
  41. console.log(foo);
  42. }
  43. **********************************************************************
  44. // bad
  45. function apple (x) {
  46. // ...
  47. }
  48. **********************************************************************
  49. // good
  50. function apple(x) {
  51. // ...
  52. }
  53. **********************************************************************
  54. ### 注意方括号中的空格,括号前后不需要空格,逗号后需要空格,逗号前避免出现空格。
  55. // bad
  56. const foo = [ 1, 2, 3 ];
  57. console.log(foo[ 0 ]);
  58. **********************************************************************
  59. // good
  60. const foo = [1, 2, 3];
  61. console.log(foo[0]);
  62. **********************************************************************
  63. ### 注意花括号中的空格,花括号需要空格,冒号前不需要空格。
  64. // bad
  65. const foo = {clark: 'kent'};
  66. **********************************************************************
  67. // good
  68. const foo = { clark: 'kent' };
  69. **********************************************************************

Comma - 逗号

  1. ### 在尾部添加额外逗号。
  2. ### Why?这使得git diff更干净。同样,像Babel这样的编译器也将删除已编译代码中的多余尾部逗号。
  3. ### Attention!逗号不要在...rest后出现。
  4. // bad
  5. createHero(
  6. firstName,
  7. lastName,
  8. inventorOf
  9. );
  10. **********************************************************************
  11. // good
  12. createHero(
  13. firstName,
  14. lastName,
  15. inventorOf,
  16. );
  17. // good (note that a comma must not appear after a "rest" element)
  18. createHero(
  19. firstName,
  20. lastName,
  21. inventorOf,
  22. ...heroArgs
  23. );
  24. **********************************************************************

Type Casting & Coercion - 类型转换

  1. ### 转换为字符串
  2. // bad
  3. const totalScore = new String(this.reviewScore); // typeof totalScore is "object" not "string"
  4. // bad
  5. const totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf()
  6. // bad
  7. const totalScore = this.reviewScore.toString(); // isn’t guaranteed to return a string
  8. **********************************************************************
  9. // good
  10. const totalScore = String(this.reviewScore);
  11. **********************************************************************
  12. ### 转换为数字
  13. const inputValue = '4';
  14. // bad
  15. const val = new Number(inputValue);
  16. // bad
  17. const val = +inputValue;
  18. // bad
  19. const val = inputValue >> 0;
  20. // bad
  21. const val = parseInt(inputValue);
  22. **********************************************************************
  23. // good
  24. const val = Number(inputValue);
  25. // good
  26. const val = parseInt(inputValue, 10);
  27. **********************************************************************
  28. ### 转为布尔值
  29. const age = 0;
  30. // bad
  31. const hasAge = new Boolean(age);
  32. // good
  33. const hasAge = Boolean(age);
  34. **********************************************************************
  35. // best
  36. const hasAge = !!age;
  37. **********************************************************************

Naming Conventions - 命名规范

  1. ### 在命名对象,函数和实例时,请使用camelCase(驼峰)。
  2. ### 仅在命名构造函数或类时使用PascalCase(大驼峰)。
  3. ### 不要保存对的引用this。使用箭头函数或Functionbind
  4. ### 箭头函数没有this,如果箭头函数里出现了this,此this一定代表外层的this
  5. // bad
  6. function foo() {
  7. const self = this;
  8. return function () {
  9. console.log(self);
  10. };
  11. }
  12. // bad
  13. function foo() {
  14. const that = this;
  15. return function () {
  16. console.log(that);
  17. };
  18. }
  19. **********************************************************************
  20. // good
  21. function foo() {
  22. return () => {
  23. console.log(this);
  24. };
  25. }
  26. **********************************************************************
  27. ### 基本文件名应与默认导出文件名完全匹配。
  28. // file 1 contents
  29. class CheckBox {
  30. // ...
  31. }
  32. export default CheckBox;
  33. // file 2 contents
  34. export default function fortyTwo() { return 42; }
  35. // file 3 contents
  36. export default function insideDirectory() {}
  37. // in some other file
  38. // bad
  39. import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename
  40. import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export
  41. import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export
  42. // bad
  43. import CheckBox from './check_box'; // PascalCase import/export, snake_case filename
  44. import forty_two from './forty_two'; // snake_case import/filename, camelCase export
  45. import inside_directory from './inside_directory'; // snake_case import, camelCase export
  46. import index from './inside_directory/index'; // requiring the index file explicitly
  47. import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly
  48. **********************************************************************
  49. // good
  50. import CheckBox from './CheckBox'; // PascalCase export/import/filename
  51. import fortyTwo from './fortyTwo'; // camelCase export/import/filename
  52. import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index"
  53. // ^ supports both insideDirectory.js and insideDirectory/index.js
  54. **********************************************************************
  55. ### 导出默认功能时,请使用camelCase,文件名应与函数名称相同。
  56. function makeStyleGuide() {
  57. // ...
  58. }
  59. export default makeStyleGuide;
  60. ### 在导出constructor/class/singleton/function library/bare object时使用PascalCase
  61. ### 首字母缩写词和首字母缩写应始终全部大写或全部小写(提高可读性)。
  62. // bad
  63. import SmsContainer from './containers/SmsContainer';
  64. // bad
  65. const HttpRequests = [
  66. // ...
  67. ];
  68. **********************************************************************
  69. // good
  70. import SMSContainer from './containers/SMSContainer';
  71. // good
  72. const HTTPRequests = [
  73. // ...
  74. ];
  75. // also good
  76. const httpRequests = [
  77. // ...
  78. ];
  79. **********************************************************************
  80. // best
  81. import TextMessageContainer from './containers/TextMessageContainer';
  82. // best
  83. const requests = [
  84. // ...
  85. ];
  86. **********************************************************************
  87. ### const在已导出,或嵌套永不更改时变量名能大写。
  88. // bad
  89. const PRIVATE_VARIABLE = 'should not be unnecessarily uppercased within a file';
  90. // bad
  91. export const THING_TO_BE_CHANGED = 'should obviously not be uppercased';
  92. // bad
  93. export let REASSIGNABLE_VARIABLE = 'do not use let with uppercase variables';
  94. // ---
  95. // allowed but does not supply semantic value
  96. export const apiKey = 'SOMEKEY';
  97. **********************************************************************
  98. // better in most cases
  99. export const API_KEY = 'SOMEKEY';
  100. **********************************************************************
  101. // ---
  102. // bad - unnecessarily uppercases key while adding no semantic value
  103. export const MAPPING = {
  104. KEY: 'value'
  105. };
  106. **********************************************************************
  107. // good
  108. export const MAPPING = {
  109. key: 'value'
  110. };
  111. **********************************************************************

Standard Library - 标准库

  1. ### 使用 Number.isNaN 而不是全局的 isNaN
  2. ### 与原有的isNaN()方法不同,不存在隐式的Number()类型转换,非NaN一律返回false
  3. // bad
  4. isNaN('1.2.3'); // true
  5. **********************************************************************
  6. // good
  7. Number.isNaN('1.2.3'); // false,没有隐式转换所以非NaN,返回false。
  8. Number.isNaN(Number('1.2.3')); // true
  9. **********************************************************************
  10. ### 使用 Number.isFinite 而不是全局的 isFinite
  11. ### 与原有的isFinite()方法的不同之处在于,
  12. ### Number.isFinite()方法没有隐式的Number()类型转换,对于非数值一律返回false
  13. // bad
  14. isFinite('2e3'); // true
  15. **********************************************************************
  16. // good
  17. Number.isFinite('2e3'); // false,没有隐式转换,所以返回false。
  18. Number.isFinite(parseInt('2e3', 10)); // true
  19. **********************************************************************

Objects and Data Structures - 对象和数据结构

  1. ### 使用 getters setters
  2. // bad
  3. // 通过引用直接修改值
  4. class BankAccount {
  5. constructor() {
  6. this.balance = 1000;
  7. }
  8. }
  9. let bankAccount = new BankAccount();
  10. // Buy shoes...
  11. bankAccount.balance = bankAccount.balance - 100;
  12. **********************************************************************
  13. // good
  14. // 通过setter修改值
  15. class BankAccount {
  16. constructor() {
  17. this.balance = 1000;
  18. }
  19. // It doesn't have to be prefixed with `get` or `set` to be a getter/setter
  20. withdraw(amount) {
  21. if (verifyAmountCanBeDeducted(amount)) {
  22. this.balance -= amount;
  23. }
  24. }
  25. }
  26. let bankAccount = new BankAccount();
  27. // Buy shoes...
  28. bankAccount.withdraw(100);
  29. **********************************************************************
  30. ### 让对象拥有私有成员
  31. ### 可以通过闭包完成

Concurrency - 并发

  1. ### Promises 替代回调
  2. // bad
  3. require('request').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', function(err, response) {
  4. if (err) {
  5. console.error(err);
  6. }
  7. else {
  8. require('fs').writeFile('article.html', response.body, function(err) {
  9. if (err) {
  10. console.error(err);
  11. } else {
  12. console.log('File written');
  13. }
  14. })
  15. }
  16. })
  17. **********************************************************************
  18. // good
  19. require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
  20. .then(function(response) {
  21. return require('fs-promise').writeFile('article.html', response);
  22. })
  23. .then(function() {
  24. console.log('File written');
  25. })
  26. .catch(function(err) {
  27. console.error(err);
  28. })
  29. **********************************************************************
  30. ### Async/Await 是较 Promises 更好的选择
  31. // bad
  32. require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
  33. .then(function(response) {
  34. return require('fs-promise').writeFile('article.html', response);
  35. })
  36. .then(function() {
  37. console.log('File written');
  38. })
  39. .catch(function(err) {
  40. console.error(err);
  41. })
  42. **********************************************************************
  43. // good
  44. import { get } from "request-promise";
  45. import { writeFile } from "fs-extra";
  46. async function getCleanCodeArticle() {
  47. try {
  48. const body = await get(
  49. "https://en.wikipedia.org/wiki/Robert_Cecil_Martin"
  50. );
  51. await writeFile("article.html", body);
  52. console.log("File written");
  53. } catch (err) {
  54. console.error(err);
  55. }
  56. }
  57. getCleanCodeArticle()
  58. **********************************************************************