Effective JavaScript - Chapter 1 - Accustoming Yourself to JavaScript (Part II)
JavaScript được thiết kế để mang lại cảm giác quen thuộc. Với cú pháp (syntax) gợi nhớ về Java và hàm dựng vốn dĩ đã phổ biến ở rất nhiều ngôn ngữ scripting (function, array, dictionary và regular expression), JavaScript dường như là một cái gì đó dễ học với bất cứ ai đã có một chút kinh nghiệm về ...
JavaScript được thiết kế để mang lại cảm giác quen thuộc. Với cú pháp (syntax) gợi nhớ về Java và hàm dựng vốn dĩ đã phổ biến ở rất nhiều ngôn ngữ scripting (function, array, dictionary và regular expression), JavaScript dường như là một cái gì đó dễ học với bất cứ ai đã có một chút kinh nghiệm về programming. Và với các programmer ít kinh nghiệm, họ có thể bắt đầu viết các chương trình mà không cần training quá nhiều tại vì lượng khái niệm core trong JavaScript là không quá nhiều.
Việc tiếp cận tuy dễ dàng, nếu muốn thuần thục (master) nó thì sẽ mất khá nhiều thời gian và đòi hỏi sự hiểu biết sâu hơn về ngữ nghĩa, các đặc tính và các idiom hữu hiệu nhất của nó. Mỗi chapter của cuốn sách này sẽ đề cập đến một phạm vi chủ đề khác nhau của effective JavaScript. Chương đầu tiên này bắt đầu với một vài topic cơ bản nhất.
Hầu hết các ngôn ngữ lập trình (programming language) có một vài kiểu dữ liệu số nhưng JavaScript lại chỉ có một. Bạn có thể thấy điều này được thể hiện trong hành vi (behaviour) của toán tử typeof - coi số nguyên và số dấu phẩy động như là các number:
typeof 17; // "number" typeof 98.6; // "number" typeof -2.1; // "number"
Trong thực tế, tất cả các number trong JavaScript là các số dấu phẩy động có độ chính xác kép (double-precision floating-point number - double). Xem chuẩn IEEE 754 để tìm hiểu thêm. Nếu sự thật này khiến bạn tự hỏi là chuyện gì đã xảy ra với số nguyên thì hãy nhớ rằng double có thể biểu diễn số nguyên một cách hoàn hảo với độ chính xác lên tới 53 bit, từ –9,007,199,254,740,992 (–2^53) đến 9,007,199,254,740,992 (2^53). Vậy nên việc tính toán số học số nguyên trong JavaScript là điều dễ dàng mặc dù thiếu kiểu dữ liệu nguyên riêng biệt.
Đa số các toán tử số học làm việc với số nguyên, số thực hoặc kết hợp cả hai:
0.1 * 1.9 // 0.19 -99 + 100; // 1 21 - 12.3; // 8.7 2.5 / 5; // 0.5 21 % 8; // 5
Tuy nhiên, các toán tử số học theo bit lại rất đặc biệt. Thay vì thao tác trên các tham biến của chúng một cách trực tiếp như là với các số dấu phẩy động, chúng ngầm định convert các tham biến thành các số nguyên 32 bit (Chính xác hơn, chúng được coi như các số nguyên 32 bit, big-endian, two's complement (bù hai)). Ví dụ:
8 | 1; // 9
Biểu thức nhìn có vẻ đơn giản này thực tế cần vài bước để tính toán. Như thường lệ, 8 và 1 là các double. Nhưng chúng có thể được biểu diễn như các số nguyên 32 bit. Ở dạng số nguyên 32 bit, 8 sẽ trông giống như sau:
00000000000000000000000000001000
Bạn có thể nhìn thấy chuỗi bit trên bằng cách sử dụng method toString của number:
(8).toString(2); // "1000"
Tham biến của toString chính là cơ số (radix). Trong trường hợp này, cơ số là 2. Kết quả sẽ loại bỏ các bit 0 dư thừa ở bên trái vì chúng không ảnh hưởng tới giá trị.
Số nguyên 1 có biểu diễn 32 bit là:
00000000000000000000000000000001
Biểu thức OR kết hợp hai chuỗi bit bằng cách giữ lại các bit 1 tìm thấy ở đầu vào. Kết quả thu được là:
00000000000000000000000000001001
Chuỗi này chính là biểu diễn của 9 ở dạng 32 bit. Bạn có thể kiểm tra điều này bằng cách sử dụng hàm thư viện chuẩn (standard library function) parseInt với cơ số 2:
parseInt("1001", 2); // 9
Chú ý là tham biến đã được lược bỏ các bit 0 vì chúng không ảnh hưởng tới kết quả.
Tất cả các toán tử theo bit hoạt động cùng một kiểu - convert đầu vào thành số nguyên và thực hiện các tính toán trên bit trước khi convert các kết quả trở lại thành số dấu phẩy động. Nhìn chung, việc convert đòi hỏi engine JavaScript làm thêm việc: Bởi vì number được lưu dưới dạng số dấu phẩy động, chúng phải được convert thành số nguyên và sau cùng lại được convert trở lại thành số phẩy động một lần nữa. Tuy nhiên, việc tối ưu trình biên dịch đôi khi cần được tính đến nếu như các biểu thức số học và các biến chỉ làm việc với số nguyên.
Cảnh báo cuối cùng về số dấu phẩy động: Nếu chúng không làm cho bạn lo lắng thì có lẽ sự thực sẽ ngược lại. Số dấu phẩy động nhìn quen thuộc đến nỗi con người ta có thể bị đánh lừa: Chúng nổi tiếng về tính thiếu chính xác. Thậm chí một vài tính toán trông đơn giản nhất cũng có thể tạo ra các kết quả không chính xác:
0.1 + 0.2; // 0.30000000000000004
Mặc dù độ chính xác 64 bit là khá lớn, double cũng chỉ có thể biểu diễn được một tập hữu hạn thay vì tập vô hạn các số thực. Số học dấu phẩy động chỉ có thể sinh ra các kết quả gần đúng - tức là đã được làm tròn tới giá trị thực gần nhất có thể biểu diễn được. Khi bạn thực hiện một chuỗi các tính toán, những sai số do làm tròn này sẽ được tích lũy, dẫn đến việc các kết quả càng thiếu chính xác. Việc làm tròn cũng có thể gây ra những sai lệch bất ngờ về các tính chất số học mà chúng ta vẫn biết. Ví dụ, số thực có tính kết hợp (associative), nghĩa là:
Với x, y, z là các số thực bất kỳ, ta luôn có:
(x + y) + z = x + (y + z)
Số dấu phẩy động chấp nhận đánh đổi giữa tính chính xác và hiệu năng. Khi tính chính xác là vấn đề quan trọng, sẽ thật cần thiết để nhận ra những mặt hạn chế của nó. Một cách khắc phục hữu hiệu chính là làm việc với các giá trị nguyên bất cứ khi nào có thể bởi vì chúng có thể được biểu diễn mà không cần làm tròn. Khi thực hiện các tính toán với tiền, các programmer thường mở rộng các con số để làm việc với đơn vị tiền tệ nhỏ nhất. Từ đó, họ có thể tính toán với các số nguyên không âm (whole number). Ví dụ, nếu tính toán bên trên được thực hiện với dollar, chúng ta có thể làm việc với các số nguyên không âm khi đổi dollar sang cent:
(10 + 20) + 30; // 60 10 + (20 + 30); // 60
Với số nguyên, bạn phải cẩn thận rằng là tất cả các tính toán phải nằm trong khoảng -2^53 và 2^53. Tuy nhiên, bạn lại không cần phải quan tâm đến sai số do làm tròn.
Things to Remember:
- Số trong JavaScript là số dấu phẩy động có độ chính xác kép
- Các số nguyên trong JavaScript chỉ là một tập con của double thay vì là một kiểu dữ liệu riêng biệt
- Các toán tử theo bit sẽ coi các tham biến như thể chúng là các số nguyên có dấu 32 bit
- Phải nhận thức được những hạn chế về độ chính xác trong số học số dấu phẩy động
Trên đây là phần dịch cho item thứ hai - Item 2 - trong 68 item được đề cập đến trong cuốn sách Effective JavaScript của tác giả David Herman.
Homepage: http://effectivejs.com/