1.1 Kiểu nguyên thủy: Khi bạn truy cập một giá trị kiểu nguyên thủy, bạn làm việc trực tiếp trên giá trị của nó.
string
number
boolean
null
undefined
symbol
const foo = 1;let bar = foo;bar = 9;console.log(foo, bar); // => 1, 9
Sự thiếu hỗ trợ cho các Symbol
không thể được lấp đầy bởi các bộ trợ năng một cách toàn diện, do đó, chúng không nên được sử dụng khi hướng đến các trình duyệt/môi trường không có hỗ trợ sẵn.
1.2 Kiểu phức tạp: Khi bạn truy cập một giá trị kiểu phức tạp, bạn làm việc trên tham chiếu giá trị của nó.
object
array
function
const foo = [1, 2];const bar = foo;bar[0] = 9;console.log(foo[0], bar[0]); // => 9, 9
2.1 Sử dụng const
đối với tất cả các tham chiếu; tránh sử dụng var
. eslint: prefer-const
, no-const-assign
Tại sao? Điều này đảm bảo rằng bạn không thể gán lại các tham chiếu, việc có thể gây ra các lỗi và gây khó khăn cho sự đọc hiểu mã nguồn.
// không tốtvar a = 1;var b = 2;// tốtconst a = 1;const b = 2;
2.3 Lưu ý rằng cả let
và const
đều thuộc phạm vi khối.
// const và let chỉ tồn tại trong phạm vi khối tạo ra chúng.{let a = 1;const b = 1;}console.log(a); // ReferenceErrorconsole.log(b); // ReferenceError
3.1 Sử dụng cú pháp nguyên văn {}
để khởi tạo đối tượng. eslint: no-new-object
// không tốtconst item = new Object();// tốtconst item = {};
3.2 Sử dụng các tên được tính của thuộc tính [key()]
khi tạo các đối tượng có các tên của thuộc tính là động.
Tại sao? Chúng cho phép bạn định nghĩa tất cả các thuộc tính của một đối tượng cùng một chỗ.
function getKey(k) {return `tên của thuộc tính là ${k}`;}// không tốtconst obj = {id: 5,name: 'San Francisco',};obj[getKey('enabled')] = true;// tốtconst obj = {id: 5,name: 'San Francisco',[getKey('enabled')]: true,};
3.3 Sử dụng cú pháp định nghĩa phương thức rút gọn để định nghĩa các phương thức của đối tượng. eslint: object-shorthand
// không tốtconst atom = {value: 1,addValue: function (value) {return atom.value + value;},};// tốtconst atom = {value: 1,addValue(value) {return atom.value + value;},};
3.4 Sử dụng cú pháp định nghĩa thuộc tính rút gọn để định nghĩa các thuộc tính của đối tượng. eslint: object-shorthand
Tại sao? Nó ngắn gọn và súc tích.
const lukeSkywalker = 'Luke Skywalker';// không tốtconst obj = {lukeSkywalker: lukeSkywalker,};// tốtconst obj = {lukeSkywalker,};
3.5 Gom tất cả các thuộc tính rút gọn ở trên cùng khi khai báo đối tượng.
Tại sao? Điều này giúp bạn dễ dàng biết được thuộc tính nào sử dụng cú pháp rút gọn.
const anakinSkywalker = 'Anakin Skywalker';const lukeSkywalker = 'Luke Skywalker';// không tốtconst obj = {episodeOne: 1,twoJediWalkIntoACantina: 2,lukeSkywalker,episodeThree: 3,mayTheFourth: 4,anakinSkywalker,};// tốtconst obj = {lukeSkywalker,anakinSkywalker,episodeOne: 1,twoJediWalkIntoACantina: 2,episodeThree: 3,mayTheFourth: 4,};
3.6 Chỉ sử dụng dấu lược ' '
cho các thuộc tính có định danh không hợp lệ. eslint: quote-props
Tại sao? Nhìn chung, chúng ta sẽ thấy nó dễ đọc hơn nhiều. Nó cải thiện nhấn mạnh cú pháp, và nó cũng giúp việc tối ưu hóa bằng các trình thực thi JS hiệu quả hơn.
// không tốtconst bad = {'foo': 3,'bar': 4,'một-cái-tên': 5,};// tốtconst good = {foo: 3,bar: 4,'một-cái-tên': 5,};
3.7 Không gọi các phương thức Object.prototype
một cách trực tiếp, ví dụ như hasOwnProperty
, propertyIsEnumerable
, và isPrototypeOf
. eslint: no-prototype-builtins
Tại sao? Những phương thức này có thể bị thay thế bởi các thuộc tính của một đối tượng - như
{ hasOwnProperty: false }
- hoặc, đối tượng có thể là một đối tượng rỗng (Object.create(null)
).
// không tốtconsole.log(object.hasOwnProperty(key));// tốtconsole.log(Object.prototype.hasOwnProperty.call(object, key));// tốt nhấtconst has = Object.prototype.hasOwnProperty; // lưu tạm phương thức một lần, dùng cho cả mô-đun./* hoặc */import has from 'has'; // https://www.npmjs.com/package/has// ...console.log(has.call(object, key));
3.8 Ưu tiên sử dụng toán tử liệt kê ...
so với Object.assign
để tạo bản sao nhanh của một đối tượng. Sử dụng toán tử còn-lại ...
để tạo một đối tượng mới với một số thuộc tính đã bị loại bỏ
// rất không tốtconst original = { a: 1, b: 2 };const copy = Object.assign(original, { c: 3 }); // cái này làm biến đổi `original` ಠ_ಠdelete copy.a; // cái này cũng vậy// không tốtconst original = { a: 1, b: 2 };const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }// tốtconst original = { a: 1, b: 2 };const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }const { a, ...noA } = copy; // noA => { b: 2, c: 3 }
4.1 Sử dụng cú pháp nguyên văn []
để khởi tạo mảng. eslint: no-array-constructor
// không tốtconst items = new Array();// tốtconst items = [];
4.2 Sử dụng Array#push, thay vì phép gán, để thêm các mục cho một mảng.
const someStack = [];// không tốtsomeStack[someStack.length] = 'abracadabra';// tốtsomeStack.push('abracadabra');
4.3 Sử dụng toán tử liệt kê ...
để sao nhanh một mảng.
// không tốtconst len = items.length;const itemsCopy = [];let i;for (i = 0; i < len; i += 1) {itemsCopy[i] = items[i];}// tốtconst itemsCopy = [...items];
4.4 Để chuyển đổi một đối tượng khả duyệt thành một mảng, sử dụng toán tử liệt kê ...
thay vì Array.from
.
const foo = document.querySelectorAll('.foo');// tốtconst nodes = Array.from(foo);// tốt nhấtconst nodes = [...foo];
4.5 Sử dụng Array.from
để chuyển đổi một đối tượng giống-mảng thành một mảng.
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };// không tốtconst arr = Array.prototype.slice.call(arrLike);// tốtconst arr = Array.from(arrLike);
4.6 Sử dụng Array.from
, thay vì toán tử liệt kê ...
, để ánh xạ một đối tượng khả duyệt, vì nó không tạo ra một mảng trung gian.
// không tốtconst baz = [...foo].map(bar);// tốtconst baz = Array.from(foo, bar);
4.7 Sử dụng các lệnh return
cho các hàm gọi lại dùng cho các phương thức của mảng. Được phép bỏ qua return
nếu phần thân hàm chỉ gồm một câu lệnh trả về một biểu thức không có hiệu ứng phụ, theo quy tắc 8.2. eslint: array-callback-return
// tốt[1, 2, 3].map((x) => {const y = x + 1;return x * y;});// tốt[1, 2, 3].map(x => x + 1);// không tốt - không có giá trị trả về đồng nghĩa với `acc` sẽ trở thành undefined sau lượt duyệt đầu tiên[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {const flatten = acc.concat(item);});// tốt[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {const flatten = acc.concat(item);return flatten;});// không tốtinbox.filter((msg) => {const { subject, author } = msg;if (subject === 'Con chim nhại') {return author === 'Harper Lee';} else {return false;}});// tốtinbox.filter((msg) => {const { subject, author } = msg;if (subject === 'Con chim nhại') {return author === 'Harper Lee';}return false;});
4.8 Sử dụng dấu ngắt dòng trước và sau các dấu đóng và mở ngoặc vuông nếu một mảng nằm trên nhiều dòng.
// không tốtconst arr = [[0, 1], [2, 3], [4, 5],];const objectInArray = [{id: 1,}, {id: 2,}];const numberInArray = [1, 2,];// tốtconst arr = [[0, 1], [2, 3], [4, 5]];const objectInArray = [{id: 1,},{id: 2,},];const numberInArray = [1,2,];
5.1 Sử dụng trích xuất đối tượng khi truy cập và sử dụng nhiều thuộc tính của một đối tượng. eslint: prefer-destructuring
Tại sao? Trích xuất giúp việc tạo các tham chiếu đến các thuộc tính trở nên dễ dàng hơn.
// không tốtfunction getFullName(user) {const firstName = user.firstName;const lastName = user.lastName;return `${firstName} ${lastName}`;}// tốtfunction getFullName(user) {const { firstName, lastName } = user;return `${firstName} ${lastName}`;}// bestfunction getFullName({ firstName, lastName }) {return `${firstName} ${lastName}`;}
5.2 Hãy sử dụng trích xuất mảng. eslint: prefer-destructuring
const arr = [1, 2, 3, 4];// không tốtconst first = arr[0];const second = arr[1];// tốtconst [first, second] = arr;
5.3 Sử dụng trích xuất đối tượng khi có nhiều giá trị trả về, thay vì trích xuất mảng.
Tại sao? Bạn có thể thêm các thuộc tính mới qua thời gian hay thay đổi thứ tự các thứ mà không lo làm hỏng các phép gọi trước đó.
// không tốtfunction processInput(input) {// khi một phép màu xảy rareturn [left, right, top, bottom];}// người gọi cần nghĩ về thứ tự của giá trị trả vềconst [left, __, top] = processInput(input);// tốtfunction processInput(input) {// khi một phép màu xảy rareturn { left, right, top, bottom };}// người gọi chỉ cần chọn giá trị mà họ muốnconst { left, top } = processInput(input);
6.2 Các chuỗi, dù khiến cho độ dài của dòng lớn hơn 100 ký tự, không nên được viết thành nhiều dòng sử dụng ghép chuỗi.
Tại sao? Các chuỗi bị chia nhỏ rất khó để làm việc cùng và khiến việc tìm kiếm trong mã nguồn trở nên khó hơn.
// không tốtconst errorMessage = 'Đây là một lỗi rất dài mà được ném ra bởi \Người Dơi. Khi bạn ngừng nghĩ về việc tại sao Người Dơi chẳng có liên \quan gì với thứ này, bạn sẽ vẫn chẳng đi đến đâu với \đâu.';// không tốtconst errorMessage = 'Đây là một lỗi rất dài mà được ném ra bởi' +'Người Dơi. Khi bạn ngừng nghĩ về việc tại sao Người Dơi chẳng có liên' +'quan gì với thứ này, bạn sẽ vẫn chẳng đi đến đâu với' +'đâu.';// tốtconst errorMessage = 'Đây là một lỗi rất dài mà được ném ra bởi Người Dơi. Khi bạn ngừng nghĩ về việc tại sao Người Dơi chẳng có liên quan gì với thứ này, bạn sẽ vẫn chẳng đi đến đâu với đâu.';
6.3 Khi xây dựng các chuỗi theo một chu trình, sử dụng mẫu chuỗi thay vì ghép chuỗi. eslint: prefer-template
template-curly-spacing
Tại sao? Các mẫu chuỗi cho bạn một cú pháp súc tích, dễ đọc với các ngắt dòng và các tính năng ghép chuỗi phù hợp.
// không tốtfunction sayHi(name) {return 'Bạn có khỏe không, ' + name + '?';}// không tốtfunction sayHi(name) {return ['Bạn có khỏe không, ', name, '?'].join();}// tốtfunction sayHi(name) {return `Bạn có khỏe không, ${name}?`;}
6.5 Không sử dụng các ký tự thoát trong một chuỗi khi không cần thiết. eslint: no-useless-escape
Tại sao? Các dấu chéo ngược làm giảm tính khả đọc, vì thế chúng chỉ nên xuất hiện khi cần.
// không tốtconst foo = '\'cái này\' \đư\ợc \"cho trong ngoặc\"';// tốtconst foo = '\'cái này\' được "cho trong ngoặc"';const foo = `tên của tôi là '${name}'`;
7.1 Sử dụng biểu thức hàm hữu danh thay vì khai báo hàm. eslint: func-style
Tại sao? Các khai báo hàm đều được kéo lên, đồng nghĩa với việc một hàm rất dễ có khả năng được sử dụng trước cả khi nó được định nghĩa trong tệp. Điều này làm giảm tính khả đọc và khả năng bảo trì. Nếu bạn thấy một hàm đủ lớn hoặc phức tạp đến mức ảnh hưởng đến việc đọc hiểu phần còn lại của tệp thì, có lẽ, nó nên được tách ra thành một mô-đun riêng! Đừng quên đặt tên cho biểu thức một cách rõ ràng, cho dù tên hàm có thể được suy ra từ tên biến chứa hàm đó (thường gặp ở các trình duyệt mới nhất hoặc các trình biên dịch như Babel). Điều này loại bỏ các nhận định liên quan đến ngăn xếp của một lỗi. (Cuộc thảo luận)
// không tốtfunction foo() {// ...}// không tốtconst foo = function () {// ...};// tốt// tên riêng của hàm, phân biệt với tên tham chiếu được gọi khi cần sử dụngconst short = function longUniqueMoreDescriptiveLexicalFoo() {// ...};
7.2 Đặt các biểu thức hàm gọi tức thời trong ngoặc. eslint: wrap-iife
Tại sao? Một biểu thức hàm gọi tức thời mà một đơn vị riêng - đặt nó và dấu ngoặc dùng để gọi nó
()
trong ngoặc để biểu đạt nó một cách rõ ràng. Cũng cần biết là, trong cái thế giới mà mô-đun ngập tràn mọi nơi như bây giờ, bạn cũng chả mấy khi cần dùng đến biểu thức hàm gọi tức thời.
// biểu thức hàm gọi tức thời(function () {console.log('Xin chào đến với thế giới. Hãy đi theo tôi.');}());
7.3 Không bao giờ khai báo một hàm bên trong một khối không phải hàm (if
, while
, v.v.). Thay vào đó, hãy gán hàm cho một biến. Các trình duyệt đều sẽ cho phép bạn làm điều đó, nhưng tiếc là, cách mà chúng diễn dịch là khác nhau. eslint: no-loop-func
7.4 Ghi chú: ECMA-262 định nghĩa một khối
là tập hợp một hoặc một vài câu lệnh. Một khai báo hàm không phải là một câu lệnh.
// không tốtif (currentUser) {function test() {console.log('Đừng!');}}// tốtlet test;if (currentUser) {test = () => {console.log('Tốt đó.');};}
7.5 Không bao giờ đặt tên một tham số là arguments
. Tham số này sẽ được ưu tiên hơn đối tượng arguments
được cung cấp cho mỗi phạm vi hàm.
// không tốtfunction foo(name, options, arguments) {// ...}// tốtfunction foo(name, options, args) {// ...}
7.6 Không bao giờ sử dụng arguments
, thay vào đó, hãy sử dụng cú pháp còn-lại ...
. eslint: prefer-rest-params
Tại sao?
...
định rõ các đối số mà bạn muốn lấy. Thêm nữa, kết quả của còn-lại là một mảng đúng nghĩa, thay vì chỉ là giống-mảng nhưarguments
.
// không tốtfunction concatenateAll() {const args = Array.prototype.slice.call(arguments);return args.join('');}// tốtfunction concatenateAll(...args) {return args.join('');}
7.7 Sử dụng tham số mặc định thay vì làm biến đổi các đối số truyền vào hàm.
// rất tệfunction handleThings(opts) {// Không! Chúng ta không nên biến đổi các đối số.// Cái tệ thứ hai: Nếu opts là kiểu sai, nó sẽ bị đổi thành một đối tượng.// Đó có thể là điều bạn muốn, nhưng nó thi thoảng gây ra lỗi.opts = opts || {};// ...}// vẫn tệfunction handleThings(opts) {if (opts === void 0) {opts = {};}// ...}// tốtfunction handleThings(opts = {}) {// ...}
7.8 Tránh gây ra hiệu ứng phụ với tham số mặc định.
Tại sao? Chúng khá là rối để có thể hình dung.
var b = 1;// không tốtfunction count(a = b++) {console.log(a);}count(); // 1count(); // 2count(3); // 3count(); // 3
7.9 Luôn để các tham số mặc định ở sau cùng.
// không tốtfunction handleThings(opts = {}, name) {// ...}// tốtfunction handleThings(name, opts = {}) {// ...}
7.10 Không bao giờ sử dụng hàm tạo Function
để tạo hàm. eslint: no-new-func
Tại sao? Tạo một hàm theo cách này cũng thực thi chuỗi giống như
eval()
vậy, thứ mà mở ra các lỗ hổng.
// không tốtvar add = new Function('a', 'b', 'return a + b');// vẫn là không tốtvar subtract = Function('a', 'b', 'return a - b');
7.11 Sử dụng các dấu cách giữa các bộ phận hàm. eslint: space-before-function-paren
space-before-blocks
Tại sao? Sự đồng nhất vẫn cứ là tốt, và bạn không cần phải thêm hoặc bớt dấu cách khi không đặt tên hàm.
// không tốtconst f = function(){};const g = function (){};const h = function() {};// tốtconst x = function () {};const y = function a() {};
7.12 Không bao giờ làm biến đổi các tham số. eslint: no-param-reassign
Tại sao? Việc can thiệp vào các đối tượng được truyền vào dưới dạng tham số có thể gây hiệu ứng phụ không mong muốn đối với biến tại tiến trình gọi.
// không tốtfunction f1(obj) {obj.key = 1;}// tốtfunction f2(obj) {const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;}
7.13 Không bao giờ gán lại các tham số. eslint: no-param-reassign
Tại sao? Việc gán lại các tham số có thể dẫn tới hành vi không mong muốn, đặc biệt là khi truy cập đối tượng
arguments
. Nó cũng có thể gây ra một số vấn đề về tối ưu hóa, nhất là trong V8.
// không tốtfunction f1(a) {a = 1;// ...}function f2(a) {if (!a) { a = 1; }// ...}// tốtfunction f3(a) {const b = a || 1;// ...}function f4(a = 1) {// ...}
7.14 Ưu tiên sử dụng toán tử liệt kê ...
để gọi các hàm bất định. eslint: prefer-spread
Tại sao? Nó nhìn sáng sủa hơn, bạn không cần phải đặt ngữ cảnh, và bạn cũng đâu thể dễ dàng kết hợp
new
vớiapply
.
// không tốtconst x = [1, 2, 3, 4, 5];console.log.apply(console, x);// tốtconst x = [1, 2, 3, 4, 5];console.log(...x);// không tốtnew (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));// tốtnew Date(...[2016, 8, 5]);
7.15 Các hàm với các bộ phận hàm, hoặc các phép gọi, nằm trên nhiều dòng nên được căn đầu dòng như tất cả các danh sách nhiều dòng khác trong hướng dẫn này: với mỗi mục nằm trên một dòng riêng biệt, cùng với một dấu phẩy ngay sau mục cuối cùng. eslint: function-paren-newline
// không tốtfunction foo(bar,baz,quux) {// ...}// tốtfunction foo(bar,baz,quux,) {// ...}// không tốtconsole.log(foo,bar,baz);// tốtconsole.log(foo,bar,baz,);
8.1 Khi bạn phải sử dụng một hàm vô danh (như khi cần truyền một hàm gọi lại trên cùng dòng), sử dụng ký pháp hàm mũi tên. eslint: prefer-arrow-callback
, arrow-spacing
Tại sao? Nó tạo ra một hàm thực thi trên ngữ cảnh của
this
, thường là thứ bạn cần, và nó rất súc tích.Khi nào thì không? Khi bạn có một hàm tương đối rắc rối, bạn cần phải chuyển lô-gíc của hàm đó sang biểu thức hàm hữu danh.
// không tốt[1, 2, 3].map(function (x) {const y = x + 1;return x * y;});// tốt[1, 2, 3].map((x) => {const y = x + 1;return x * y;});
8.2 Nếu như phần thân hàm chỉ gồm một câu lệnh trả về một biểu thức mà không có hiệu ứng phụ, bỏ qua dấu ngoặc nhọn và sử dụng trả về ngầm định. Nếu không, giữ nguyên dấu ngoặc và sử dụng lệnh return
. eslint: arrow-parens
, arrow-body-style
Tại sao? Cú pháp tiện lợi. Nó dễ đọc khi nhiều hàm nối chuỗi nhau.
// không tốt[1, 2, 3].map(number => {const nextNumber = number + 1;`Một chuỗi có chứa số ${nextNumber}.`;});// tốt[1, 2, 3].map(number => `Một chuỗi có chứa số ${number + 1}.`);// tốt[1, 2, 3].map((number) => {const nextNumber = number + 1;return `Một chuỗi có chứa số ${nextNumber}.`;});// tốt[1, 2, 3].map((number, index) => ({[index]: number,}));// Không dùng trả về ngầm định khi có hiệu ứng phụfunction foo(callback) {const val = callback();if (val === true) {// Thực hiện gì đó nếu hàm gọi lại trả về true}}let bool = false;// không tốtfoo(() => bool = true);// tốtfoo(() => {bool = true;});
8.3 Trong trường hợp biểu thức nằm trên nhiều dòng, nhóm nó trong ngoặc để dễ đọc hơn.
Tại sao? Nó cho thấy một cách rõ ràng điểm bắt đầu và kết thúc hàm.
// không tốt['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(httpMagicObjectWithAVeryLongName,httpMethod,));// tốt['get', 'post', 'put'].map(httpMethod => (Object.prototype.hasOwnProperty.call(httpMagicObjectWithAVeryLongName,httpMethod,)));
8.4 Nếu hàm của bạn nhận một đối số và không sử dụng ngoặc nhọn, loại bỏ dấu ngoặc tròn. Nếu không, luôn luôn thêm ngoặc tròn trước và sau các đối số cho rõ ràng và nhất quán. Ghi chú: việc luôn sử dụng dấu ngoặc tròn được chấp nhận, khi đó, sử dụng tùy chọn “always” cho eslint. eslint: arrow-parens
Tại sao? Nhìn bớt rối mắt.
// không tốt[1, 2, 3].map((x) => x * x);// tốt[1, 2, 3].map(x => x * x);// tốt[1, 2, 3].map(number => (`Một chuỗi thật là dài với số ${number}. Nó quá dài để chúng ta có thể viết cùng dòng với dòng .map!`));// không tốt[1, 2, 3].map(x => {const y = x + 1;return x * y;});// tốt[1, 2, 3].map((x) => {const y = x + 1;return x * y;});
8.5 Tránh gây dễ nhầm lẫn giữa cú pháp hàm mũi tên (=>
) với các toán tử so sánh (<=
, >=
). eslint: no-confusing-arrow
// không tốtconst itemHeight = item => item.height <= 256 ? item.largeSize : item.smallSize;// không tốtconst itemHeight = (item) => item.height >= 256 ? item.largeSize : item.smallSize;// tốtconst itemHeight = item => (item.height <= 256 ? item.largeSize : item.smallSize);// tốtconst itemHeight = (item) => {const { height, largeSize, smallSize } = item;return height <= 256 ? largeSize : smallSize;};
8.6 Cách đặt vị trí của phần thân hàm mũi tên với trả về ngầm định. eslint: implicit-arrow-linebreak
// không tốtfoo =>bar;foo =>(bar);// tốtfoo => bar;foo => (bar);foo => (bar)
9.1 Luôn sử dụng class
. Tránh việc can thiệp trực tiếp vào prototype
.
Tại sao? Cú pháp
class
súc tích, dễ hiểu và dễ hình dung.
// không tốtfunction Queue(contents = []) {this.queue = [...contents];}Queue.prototype.pop = function () {const value = this.queue[0];this.queue.splice(0, 1);return value;};// tốtclass Queue {constructor(contents = []) {this.queue = [...contents];}pop() {const value = this.queue[0];this.queue.splice(0, 1);return value;}}
9.2 Sử dụng extends
cho thừa kế.
Tại sao? Nó là cách sẵn có để thừa kế nguyên mẫu mà không làm ảnh hưởng đến
instanceof
.
// không tốtconst inherits = require('inherits');function PeekableQueue(contents) {Queue.apply(this, contents);}inherits(PeekableQueue, Queue);PeekableQueue.prototype.peek = function () {return this.queue[0];};// tốtclass PeekableQueue extends Queue {peek() {return this.queue[0];}}
9.3 Các phương thức, mỗi khi có thể, hãy trả về this
để tiện cho việc nối chuỗi phương thức.
// không tốtJedi.prototype.jump = function () {this.jumping = true;return true;};Jedi.prototype.setHeight = function (height) {this.height = height;};const luke = new Jedi();luke.jump(); // => trueluke.setHeight(20); // => undefined// tốtclass Jedi {jump() {this.jumping = true;return this;}setHeight(height) {this.height = height;return this;}}const luke = new Jedi();luke.jump().setHeight(20);
9.4 Có thể viết phương thức toString()
tùy ý, chỉ cần đảm bản nó hoạt động hoàn hảo và không gây ra các hiệu ứng phụ.
class Jedi {constructor(options = {}) {this.name = options.name || 'vô danh';}getName() {return this.name;}toString() {return `Jedi - ${this.getName()}`;}}
9.5 Các lớp có một hàm tạo mặc định nếu không được chỉ định. Một hàm tạo rỗng, hoặc chỉ trỏ đến lớp cha, là không cần thiết. eslint: no-useless-constructor
// không tốtclass Jedi {constructor() {}getName() {return this.name;}}// không tốtclass Rey extends Jedi {constructor(...args) {super(...args);}}// tốtclass Rey extends Jedi {constructor(...args) {super(...args);this.name = 'Rey';}}
9.6 Tránh trùng lặp các thành viên của một lớp. eslint: no-dupe-class-members
Tại sao? Với các khai báo thành viên bị lặp, khai báo cuối được tự động ưu tiên - việc có sự trùng lặp gần như chắc chắn là một lỗi.
// không tốtclass Foo {bar() { return 1; }bar() { return 2; }}// tốtclass Foo {bar() { return 1; }}// tốtclass Foo {bar() { return 2; }}
10.1 Luôn sử dụng mô-đun (import
/export
) thay vì một hệ thống mô-đun phi chuẩn. Bạn luôn có thể dịch mã sang hệ thống mô-đun mà bạn thích.
Tại sao? Mô-đun là tương lai, hãy cùng sử dụng tương lai ngay lúc này.
// không tốtconst AirbnbStyleGuide = require('./AirbnbStyleGuide');module.exports = AirbnbStyleGuide.es6;// okimport AirbnbStyleGuide from './AirbnbStyleGuide';export default AirbnbStyleGuide.es6;// bestimport { es6 } from './AirbnbStyleGuide';export default es6;
10.2 Không sử dụng ký tự đại diện để nhập.
Tại sao? Điều này đảm bảo bạn chỉ xuất mặc định một giá trị.
// không tốtimport * as AirbnbStyleGuide from './AirbnbStyleGuide';// tốtimport AirbnbStyleGuide from './AirbnbStyleGuide';
10.3 Và không xuất trực tiếp từ một lệnh nhập.
Tại sao? Mặc dù cấu trúc một dòng là súc tích, việc nhập một cách rõ ràng và xuất một cách rõ ràng làm cho mọi thứ nhất quán.
// không tốt// tên tệp es6.jsexport { es6 as default } from './AirbnbStyleGuide';// tốt// tên tệp es6.jsimport { es6 } from './AirbnbStyleGuide';export default es6;
10.4 Chỉ nhập từ một đường dẫn ở chung một chỗ. eslint: no-duplicate-imports
Tại sao? Có nhiều dòng nhập từ cùng một đường dẫn khiến mã nguồn trở nên khó bảo trì hơn.
// không tốtimport foo from 'foo';// … và nhập một vài thứ nữa … //import { named1, named2 } from 'foo';// tốtimport foo, { named1, named2 } from 'foo';// tốtimport foo, {named1,named2,} from 'foo';
10.5 Không xuất các ràng buộc có thể bị biến đổi. eslint: import/no-mutable-exports
Tại sao? Sự biến đổi, nói chung, nên được tránh, nhưng đặc biệt là đối với trường hợp xuất các giá trị có thể bị biến đổi. Trong khi kỹ thuật này có thể là cần thiết trong một số trường hợp đặc biệt, nhìn chung, chỉ nên xuất các giá trị là hằng.
// không tốtlet foo = 3;export { foo };// tốtconst foo = 3;export { foo };
10.6 Trong các mô-đun chỉ có một địa chỉ xuất, ưu tiên xuất mặc định thay vì xuất hữu danh. eslint: import/prefer-default-export
Tại sao? Nhằm khuyến khích các tệp chỉ xuất một giá trị, giúp mã nguồn dễ đọc và dễ bảo trì.
// không tốtexport function foo() {}// tốtexport default function foo() {}
10.7 Đặt tất cả các lệnh import
trên cùng. eslint: import/first
Tại sao? Vì các lệnh
import
được kéo lên, việc đặt tất cả chúng ở trên cùng nhằm ngăn chặn các hành vi không đáng có.
// không tốtimport foo from 'foo';foo.init();import bar from 'bar';// tốtimport foo from 'foo';import bar from 'bar';foo.init();
10.8 Các lệnh nhập nhiều dòng nên được căn đầu dòng giống như các mảng hay đối tượng nguyên văn nhiều dòng.
Tại sao? Các đấu ngoặc nhọn đều có cùng các quy tắc căn đầu dòng như tất cả mọi khối ngoặc nhọn trong bản định hướng này, cùng với như dấu phẩy ở cuối.
// không tốtimport {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';// tốtimport {longNameA,longNameB,longNameC,longNameD,longNameE,} from 'path';
10.9 Không cho phép cú pháp bộ tải Webpack trong các lệnh nhập mô-đun. eslint: import/no-webpack-loader-syntax
Tại sao? Vì sử dụng cú pháp Webpack trong các lệnh nhập gom mã thành một bộ tổng hợp mô-đun. Ưu tiên sử dụng cú pháp bộ tải trong
webpack.config.js
.
// không tốtimport fooSass from 'css!sass!foo.scss';import barCss from 'style!css!bar.css';// tốtimport fooSass from 'foo.scss';import barCss from 'bar.css';
11.1 Không sử dụng các đối tượng duyệt. Ưu tiên sử dụng các hàm bậc cao hơn của JavaScript thay vì các vòng lặp như for-in
hay for-of
. eslint: no-iterator
no-restricted-syntax
Tại sao? Điều này đảm bảo việc thực hiện quy tắc bất biến. Làm việc với các hàm thuần mà trả về các giá trị sẽ dễ tưởng tượng hơn so với các hiệu ứng phụ.
Sử dụng
map()
/every()
/filter()
/find()
/findIndex()
/reduce()
/some()
/ ... để duyệt qua một mảng, vàObject.keys()
/Object.values()
/Object.entries()
để tạo một mảng để bạn có thể duyệt qua một đối tượng.
const numbers = [1, 2, 3, 4, 5];// không tốtlet sum = 0;for (let num of numbers) {sum += num;}sum === 15;// tốtlet sum = 0;numbers.forEach((num) => {sum += num;});sum === 15;// tốt nhất, sử dụng hàmconst sum = numbers.reduce((total, num) => total + num, 0);sum === 15;// không tốtconst increasedByOne = [];for (let i = 0; i < numbers.length; i++) {increasedByOne.push(numbers[i] + 1);}// tốtconst increasedByOne = [];numbers.forEach((num) => {increasedByOne.push(num + 1);});// tốt nhất, vẫn là sử dụng hàmconst increasedByOne = numbers.map(num => num + 1);
11.2 Không sử dụng các hàm sinh trị function*
vào thời điểm này.
Tại sao? Nó không thể được dịch mã sang ES5 một cách hoàn hảo.
11.3 Nếu bạn bắt buộc phải dùng các hàm sinh trị, hoặc bạn bỏ qua khuyến nghị của chúng tôi, hãy đảm bảo rằng bạn sử dụng dấu cách giữa các bộ phận hàm một cách hợp lý. eslint: generator-star-spacing
Tại sao?
function
và*
là tạo thành một từ khóa riêng -*
không phải là từ khóa điều chỉnh chofunction
,function*
là một cấu tạo riêng biệt, khác vớifunction
.
// không tốtfunction * foo() {// ...}// không tốtconst bar = function * () {// ...};// không tốtconst baz = function *() {// ...};// không tốtconst quux = function*() {// ...};// không tốtfunction*foo() {// ...}// không tốtfunction *foo() {// ...}// rất tệfunction*foo() {// ...}// rất rất tệconst wat = function*() {// ...};// tốtfunction* foo() {// ...}// tốtconst foo = function* () {// ...};
12.1 Sử dụng ký pháp chấm .
để truy cập các thuộc tính. eslint: dot-notation
const luke = {jedi: true,age: 28,};// không tốtconst isJedi = luke['jedi'];// tốtconst isJedi = luke.jedi;
12.2 Sử dụng ký pháp ngoặc []
để truy cập thuộc tính với một biến.
const luke = {jedi: true,age: 28,};function getProp(prop) {return luke[prop];}const isJedi = getProp('jedi');
12.3 Sử dụng toán tử lũy thừa **
để tính các lũy thừa. eslint: no-restricted-properties
.
// không tốtconst binary = Math.pow(2, 10);// tốtconst binary = 2 ** 10;
13.1 Luôn sử dụng const
hoặc let
để khai báo biến. Không làm như vậy sẽ dẫn đến các biến toàn cục. Chúng ta muốn tránh việc làm ô nhiễm không gian tên toàn cục. Đội trưởng Hành tinh đã cảnh báo chúng ta. eslint: no-undef
prefer-const
// không tốtsuperPower = new SuperPower();// tốtconst superPower = new SuperPower();
13.2 Sử dụng một const
hoặc let
khai báo cho mỗi biến hoặc phép gán. eslint: one-var
Tại sao? Khai báo theo cách này giúp dễ thêm các khai báo mới, và bạn chẳng phải nghĩ về việc phải dùng
;
hay,
. Bạn còn có thể bước qua mỗi khai báo trong trình gỡ lỗi, thay vì nhảy qua toàn bộ chúng trong một bước.
// không tốtconst items = getItems(),goSportsTeam = true,dragonball = 'z';// không tốt// (so sánh với trên kia và thử tìm ra lỗi ở đây)const items = getItems(),goSportsTeam = true;dragonball = 'z';// tốtconst items = getItems();const goSportsTeam = true;const dragonball = 'z';
13.3 Nhóm tất cả các const
và rồi nhóm tất cả các let
.
Tại sao? Điều này hữu ích khi, sau đó, bạn sẽ cần gán lại một biến dựa trên các biến đã gán trước đó.
// không tốtlet i, len, dragonball,items = getItems(),goSportsTeam = true;// không tốtlet i;const items = getItems();let dragonball;const goSportsTeam = true;let len;// tốtconst goSportsTeam = true;const items = getItems();let dragonball;let i;let length;
13.4 Chỉ gán biến khi cần, nhưng nhớ đặt chúng ở một nơi hợp lý.
Tại sao?
let
vàconst
thuộc phạm vi khối, không phải phạm vi hàm.
// không tốt - phép gọi hàm không cần thiếtfunction checkName(hasName) {const name = getName();if (hasName === 'thí nghiệm') {return false;}if (name === 'thí nghiệm') {this.setName('');return false;}return name;}// tốtfunction