Giới thiệu cuốn Maintainable Javascript 3

1. Khai báo biến

Javascript cho phép sử dụng var nhiều lần và gần như là ở khắp nơi trong script. Hãy xem xét ví dụ sau:

function doSomething() {

  var result = 10 + value;
  var value = 10;
  return result;
}

value có thể được sử dụng trước khi nó được định nghĩa. (value nhận giá trị undefined và nó làm cho biến result nhận giá trị NaN). Javascript engine hiểu đoạn code đó như sau:

function doSomething() {

  var result;
  var value;

  result = 10 + value;
  value = 10;

  return result;
}

Nhiều lập trình viên cũng không khai báo biến trong lệnh for

function doSomethingWithItems(items) {

  for (var i=0, len=items.length; i < len; i++) {
    doSomething(items[i]);
  }
}

Đoạn code này tương đương với

function doSomethingWithItems(items) {

  var i, len;

  for (i=0, len=items.length; i < len; i++) {
    doSomething(items[i]);
  }
}

Tác giả khuyến khích khai báo biến địa phương ở đầu hàm, mỗi dòng 1 biến và sử dụng 1 từ khai báo var duy nhất như sau:

function doSomethingWithItems(items) {

  var value  = 10,
      result = value + 10,
      i,
      len;

  for (i=0, len=items.length; i < len; i++) {
    doSomething(items[i]);
  }
}

2. Khai báo hàm

Cũng giống như khai báo biến, ta có thể sử dụng một hàm trước khi nó được khai báo nhưng không nên dùng như thế. Nên khai báo hàm trước khi dùng nó.

// Không tốt
doSomething();

function doSomething() {
  alert("Hello world!");
}

JavaScript engine biên dịch đoạn code đó thành như sau:

function doSomething() {
  alert("Hello world!");
}

doSomething();

Nên viết theo thứ tự như sau:

//Tốt
function doSomethingWithItems(items) {

  var value  = 10,
      result = value + 10,
      i,
      len;

  function doSomething(item) {
    // do something
  }

  for (i=0, len=items.length; i < len; i++) {
    doSomething(items[i]);
  }
}

Ngoài ra, không nên khai báo hàm trong 1 khối lênh. Ví dụ, đoạn code sau sẽ hoạt động khác nhau trên các browser khác nhau. Firefox có đánh giá condition và sử dụng khai báo hàm thích hợp nhưng đa số các browsers tự động lấy khai báo thứ hai mà không xem xét đến condition .

// Không tốt
if (condition) {
  function doSomething() {
    alert("Hi!");
  }
} else {
  function doSomething() {
    alert("Yo!");
  }
}

3. Sử dụng khoảng trắng khi gọi hàm

Tác giả khuyến khích không chèn khoảng trắng giữa tên hàm và dấu ngoặc mở để phân biệt với khối lệnh. Ví dụ

// Tốt
doSomething(item);

// Không tốt: Trông giống như khối lệnh
doSomething (item);

// Khối lệnh (để so sánh)
while (item) {
  // do something
}

4. Gọi hàm ngay

4.1. Hàm vô danh

JavaScript cho phép khai báo hàm vô danh - hàm không có tên và gán hàm này cho biến hoặc thuộc tính. Ví dụ

var doSomething = function() {
  // function body
};

4.2. Trả giá trị của hàm về cho biến

Những hàm này cũng có thể được gọi ra ngay để trả giá trị về cho biến bằng cách thêm cặp dấu ngoặc vào cuối như sau:

// Không tốt
var value = function() {

  // function body

  return {
    message: "Hi"
  }
}();

Ở ví dụ trước, value được gán cho 1 object bởi vì hàm được gọi ra ngay. Vấn đề với trường hợp này là nó rất giống với việc gán một hàm vô danh cho một biến. Bạn không phân biệt được 2 trường hợp cho đến tận khi đọc đến dòng cuối cùng và nhìn thấy cặp dấu ngoặc. Để dể phân biệt hơn, bạn nên thêm dấu ngoặc bao quanh hàm như sau:

// Tốt
var value = (function() {

  // function body

  return {
    message: "Hi"
  }
}());

4.3. Strict mode

Để chuyển sang chế độ strict mode, sử dụng lệnh sau: "use strict"; Tuy nhiên, nên tránh sử dụng "use strict" trong global scope. Lý do là bởi vì strict mode được áp dụng cho tất cả code trong một file đơn, điều đó có nghĩa là nếu bạn ghép 11 file và 1 trong chúng bật global strict mode thì tất cả các files đều được đặt trong chế độ strict mode. Bởi vì strict mode hoạt động theo các nguyên tắc hơi khác so với nonstrict mode nên có thể sẽ có lỗi trong các file khác.

// Không tốt - global strict mode
"use strict";

function doSomething() {
  // code
}
// Tốt
function doSomething() {
  "use strict";

  // code
}

Nếu bạn muốn áp dụng strict mode cho nhiều hàm mà muốn tránh viết "use strict" nhiều lần thì hãy sử dụng gọi hàm ngay.

// Tốt
(function() {
  "use strict";

  function doSomething() {
}

  function doSomethingElse() {
    // code
  }
})();

5. So sánh bằng

So sánh bằng trong JavaScript thực hiện ép kiểu. Khi thực hiện ép kiểu dẫn đến 1 biến được tự động chuyển sang kiểu mới để một phép toán nào đó thành công nhưng có thể dẫn đến những kết quả ngoài mong đợi. Một trong những phép toán chính sử dụng ép kiểu là ==!= . Ví dụ, nếu bạn so sánh 1 số với 1 string thì trước tiên string được chuyển sang số dùng hàm

Number() rồi mới so sánh. Một số ví dụ


// Số 5 và string 5
console.log(5 == "5");      // true

// Số 25 và string 25(hệ cơ số 16)
console.log(25 == "0x19");  // true

Nếu bạn so sánh giá trị boolean với một số thì boolean được chuyển sang số trước phép so sánh. false trở thành 0 và true trở thành 1. Ví dụ


// Số 1 và true
console.log(1 == true);      // true

// Số 0 và false
console.log(0 == false);     // true

// Số 2 và true
console.log(2 == true);      // false

Nếu 1 trong 2 giá trị được so sánh có 1 gía trị là object và 1 giá trị không phải thì hàm valueOf() sẽ dược gọi. Nếu hàm valueOf() không được định nghĩa thì hàm toString() sẽ được gọi. Ví dụ,


var object = {
  toString: function() {
    return "0x19";
  }
};

console.log(object == 25);      // true

null và undefined bằng nhau (==)


console.log(null == undefined);    // true

Có thể sử dụng ===!== để so sánh mà không thực hiện ép kiểu. So sánh sự khác nhau giữa =====


// Số 5 và string 5
console.log(5 == "5");      // true
console.log(5 === "5");    // false

// Số 25 và 25 (hệ cơ số 16)
console.log(25 == "0x19");      // true
console.log(25 === "0x19");     // false

// Số 1 và true
console.log(1 == true);      // true
console.log(1 === true);     // false

// Số 0 và false
console.log(0 == false);      // true
console.log(0 === false);     // false

// Số 2 and true
console.log(2 == true);      // false
console.log(2 === true);     // false

var object = {
  toString: function() {
    return "0x19";
  }
};

// Một object và 25
console.log(object == 25);      // true
console.log(object === 25);     // false

// Null và undefined
console.log(null == undefined);      // true
console.log(null === undefined);     // false

6. eval()

Hàm eval() lấy một string của code JavaScript và chạy nó. Ví dụ,


eval("alert('Hi!')");

var count = 10;
var number = eval("5 + count");
console.log(number);      // 15

Ta cũng có thể thực chạy một JavaScript string trong JavaScript sử dụng Function , setTimeout()setInterval(). Ví dụ,


var myfunc = new Function("alert('Hi!')");

setTimeout("document.body.style.background='red'", 50);

setInterval("document.title = 'It is now '" + (new Date()), 1000);

setTimeout()setInterval()

nên được dùng với hàm hơn là strings

setTimeout(function() {
  document.body.style.background='red';
}, 50);

setInterval(function() {
  document.title = 'It is now ' + (new Date());
}, 1000);

7. Các loại Wrapper nguyên thủy

Có 3 loại wrapper nguyên thủy là String, Boolean và Number. Mục đích sử dụng chính của các loại wrapper nguyên thủy là để tạo các giá trị nguyên thủy đóng vai trò như các objects. Ví dụ,

var name = "Nicholas";
console.log(name.toUpperCase());

Dù name là một string, một loại nguyên thủy, không phải là object, bạn vẫn có thể sử dụng các hàm như toUpperCase() như thể là string là object. Điều này có thể xảy ra bởi vì JavaScript engine tạo ra 1 instance mới của loại String chỉ dùng cho lệnh này. Sau đó, nó bị hủy đi, và một cái khác được tạo ra khi cần. Bạn có thể test vấn đề này bằng cách thêm property vào string:

var name = "Nicholas";
name.author = true;
console.log(name.author);      // undefined

Thuộc tính author biến mất sau dòng 2. Bởi vì object String tạm thời biểu diễn string đã bị hủy sau khi thực hiện dòng 2, và một object String mới được tạo ra ở dòng 3. Cũng có thể tạo chính những đối tượng này.

// Không tốt
var name = new String("Nicholas");
var author = new Boolean(true);
var count = new Number(10);

Mặc dù có thể dùng các loại wrapper nguyên thủy này, tác giả khuyên nên tránh dùng nó. Các lập trình viên có thể sẽ nhầm lẫn khi nào chúng được xử lý như object, khi nào như nguyên thủy. Và cũng không có lý do gì để tạo các objects cho chính chúng.