Serials Javascript Style Guide – Functions

  Javascript    —  2019, May 27    

Functions

7.1 Declarations

Sử dụng các biểu thức hàm được đặt tên thay vì khai báo hàm. eslint: func-style

Tại sao ư? Sự khai báo hàm được gia tăng lên, nghĩa là nó dễ, quá dễ để tham chiếu hàm trước khi được định nghĩa trong file. Điều này làm giảm khả năng đọc và khả năng bảo trì. Nếu bạn thấy 1 hàm lớn và phức tạp thì nên tách hẳn ra làm 1 module. Và đừng quên đặt tên rõ ràng và dễ hiểu cho module đó. Điều này sẽ giúp loại bỏ các giả định tạo ra về Error’s call stack. (Discussion)

// bad
function foo() {
  // ...
}

// bad
const foo = function () {
  // ...
};

// good
// lexical name distinguished from the variable-referenced invocation(s)
const short = function longUniqueMoreDescriptiveLexicalFoo() {
  // ...
};

7.2 Iife

Hãy bọc biểu thức hàm gọi tức thì vào trong dấu ngoặc đơn (). eslint: wrap-iife

Tại sao ư? Một biểu thức hàm gọi tức thì là 1 đơn vị duy nhất - bọc bản thân hàm và lời gọi hàm vào bên trong dấu ngoặc đơn để thể hiện rõ điều này. Tuy nhiên trong 1 thế giới với đầy rẫy modules thì bạn hầu như không cần IIFE.

// immediately-invoked function expression (IIFE)
(function () {
  console.log('Welcome to the Internet. Please follow me.');
}());

7.3 In blocks

Không bao giờ khai báo hàm trong 1 non-function block như (if, while, …). Nên khai báo biến trước và sau đó gán biến đó vào 1 function. eslint: no-loop-func

7.4 Note on blocks

Note: ECMA-262 định nghĩa block như là danh sách các lệnh. Khai báo hàm không phải là lệnh.

// bad
if (currentUser) {
  function test() {
    console.log('Nope.');
  }
}

// good
let test;
if (currentUser) {
  test = () => {
    console.log('Yup.');
  };
}

7.5 Arguments shadow

Đừng bao giờ đặt tên tham số của hàm là arguments. Vì điều này sẽ ghi đè lên đối tượng arguments của hàm.

// bad
function foo(name, options, arguments) {
  // ...
}

// good
function foo(name, options, args) {
  // ...
}

7.6 Rest

Đừng sử dụng arguments, mà nên dùng cú pháp .... eslint: prefer-rest-params

Tại sao ư? ... là rõ ràng hơn với những tham số mà bạn muốn. Thêm nữa các tham số còn lại là 1 array chứ không phải là array-like như arguments.

// bad
function concatenateAll() {
  const args = Array.prototype.slice.call(arguments);
  return args.join('');
}

// good
function concatenateAll(...args) {
  return args.join('');
}

7.7 Default parameters

Khuyến khích sử dụng cú pháp với tham số mặc định hơn là các tham số không đổi.

// really bad
function handleThings(opts) {
  // No! We shouldn’t mutate function arguments.
  // Double bad: if opts is falsy it'll be set to an object which may
  // be what you want but it can introduce subtle bugs.
  opts = opts || {};
  // ...
}

// still bad
function handleThings(opts) {
  if (opts === void 0) {
    opts = {};
  }
  // ...
}

// good
function handleThings(opts = {}) {
  // ...
}

7.8 Default side effects

Tránh side effects với các tham số mặc định.

var b = 1;
// bad
function count(a = b++) {
  console.log(a);
}
count();  // 1
count();  // 2
count(3); // 3
count();  // 3

7.9 Defaults last

Luôn để tham số default là tham số cuối cùng của hàm.

// bad
function handleThings(opts = {}, name) {
  // ...
}

// good
function handleThings(name, opts = {}) {
  // ...
}

7.10 Constructor

Không sử dụng hàm khởi tạo Function để tạo mới hàm. eslint: no-new-func

Tại sao ư? Vì tạo hàm bằng cách này giống như với hàm eval(), gây ra nhiều lỗ hổng.

// bad
var add = new Function('a', 'b', 'return a + b');

// still bad
var subtract = Function('a', 'b', 'return a - b');

7.11 Signature spacing

Hãy tạo các khoảng trắng đối với từ khóa function và block {}. eslint: space-before-function-paren space-before-blocks

Tại sao ư? Tính nhất quán là tốt, và bạn không cần phải thêm hoặc bớt khoảng trắng khi thêm hoặc xóa tên.

// bad
const f = function(){};
const g = function (){};
const h = function() {};

// good
const x = function () {};
const y = function a() {};

7.12 Mutate params

Đừng thay đổi các tham số. eslint: no-param-reassign

Tại sao ư? Vì thao tác với các đối tượng được truyền vào như là tham số có thể gây ra biến không mong muốn.

// bad
function f1(obj) {
  obj.key = 1;
}

// good
function f2(obj) {
  const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}

7.13 Reassign params

Đừng gán lại các tham số. eslint: no-param-reassign

Tại sao ư? Vì việc gán lại các tham số dẫn tới hành vi không mong muốn, đặc biệt khi truy cập vào đối tượng arguments. Nó cũng có thể gây ra vấn đề tối ưu, đặc biệt trong V8.

// bad
function f1(a) {
  a = 1;
  // ...
}

function f2(a) {
  if (!a) { a = 1; }
  // ...
}

// good
function f3(a) {
  const b = a || 1;
  // ...
}

function f4(a = 1) {
  // ...
}

7.14 Spread vs apply

Sử dụng toán tử ... để gọi chức năng. eslint: prefer-spread

Tại sao ư? Vì nó sạch hơn và không cần một ngữ cảnh hơn nữa việc xử lý new với apply là không dễ dàng.

// bad
const x = [1, 2, 3, 4, 5];
console.log.apply(console, x);

// good
const x = [1, 2, 3, 4, 5];
console.log(...x);

// bad
new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));

// good
new Date(...[2016, 8, 5]);

7.15 Signature invocation indentation

Nếu hàm có nhiều tham số và được viết trên nhiều dòng thì nên có cùng khoảng cách thụt lề và kết thúc ở tham số cuối cùng là dấu phẩy. eslint: function-paren-newline

// bad
function foo(bar,
             baz,
             quux) {
  // ...
}

// good
function foo(
  bar,
  baz,
  quux,
) {
  // ...
}

// bad
console.log(foo,
  bar,
  baz);

// good
console.log(
  foo,
  bar,
  baz,
);

Tổng hợp Serial Javascript Style Guide