Các kiểu giá trị

Các kiểu giá trị của JavaScript

  • Kiểu undefined chỉ gồm một giá trị undefined.
  • Kiểu null chỉ gồm một giá trị null.
  • Kiểu boolean gồm hai giá trị true false.
  • Kiểu number gồm các số nguyên và số thực, ví dụ -123, 3.141.
  • Kiểu bigint gồm các số nguyên có độ dài tùy ý, có thêm hậu tố n ở cuối: 9007199254740991n.
  • Kiểu string gồm các chuỗi văn bản đặt giữa hai kí tự ' (single quote), " (double quote) và ` (backtick) , ví dụ 'abc', "abc"`abc`.
  • Kiểu symbol gồm các symbol là các định danh duy nhất, thường dùng làm tên thuộc tính, ví dụ Symbol('My Symbol').
  • Kiểu object gồm tất cả các đối tượng.

Trong đó 7 kiểu dữ liệu đầu gọi là các kiểu cơ sở (primitive type).

Các kiểu cơ sở

Trong phần này ta giới thiệu các đặc tính của các giá trị có kiểu cơ sở.

Không thể bị thay đổi

Bạn không thể thay đổi, thêm hoặc xóa một thuộc tính của các giá trị cơ sở:

let str = 'abc';
assert.equal(str.length, 3);
assert.throw(
  () => { str.length = 1 },
  /^TypeError: Cannot assign to read only property 'length'/
);

Truyền theo nội dung

Các giá trị cơ sở được truyền theo nội dung: các biến (gồm cả các tham số) lưu trữ nội dung của giá trị cơ sở. Khi gán một giá trị cơ sở tới một biến hoặc truyền nó như đối số tới một hàm, nội dung của nó được sao chép:

let x = 123;
let y = x;
assert.equal(y, 123);

So sánh theo nội dung

Các giá trị cơ sở được so sánh theo nội dung của nó: khi so sánh hai giá trị cơ sở, chúng ta đang so sánh nội dung của chúng.

assert.equal(123 === 123, true);
assert.equal('abc' === 'abc', true);

Các đối tượng

Phần này giới thiệu các đặc tính của các đối tượng, giúp bạn có thể so sánh nó với các giá trị cơ sở.

Các literal

Hai đối tượng quen thuộc ta hay gặp là đối tượng thuần và mảng.

Sử dụng các literal là cách tạo mảng và đối tượng thuận tiện nhất:

  • Object literal (literal đối tượng):
const obj = {
  first: 'Jane',
  last: 'Doe',
};
  • Array literal (literal mảng):
const arr = ['foo', 'bar'];

Có thể thay đổi

Không như các giá trị cơ sở, các đối tượng có thể bị thay đổi. Chúng ta có thể thay đổi, thêm, xóa các thuộc tính của đối tượng:

const obj = {};

obj.foo = 'abc'; // thêm một thuộc tính
assert.equal(obj.foo, 'abc');

obj.foo = 'def'; // thay đổi một thuộc tính
assert.equal(obj.foo, 'def');

Truyền bởi tham chiếu

Các đối tượng được truyền bởi tham chiếu, tham chiếu là địa chỉ của đối tượng trong bộ nhớ: các biến (gồm các tham số) lưu tham chiếu tới đối tượng, không lưu nội dung đối tượng.

Khi gán một đối tượng tới một biến hoặc truyền đối tượng như đối số tới một hàm, tham chiếu của nó được sao chép. Mỗi object literal tạo nên một đối tượng mới trong bộ nhớ heap và trả về tham chiếu tới nó.

const a = {};  // đối tượng trống mới
// a đang chứa tham chiếu tới đối tượng
const b = a; // tham chiếu được sao chép cho b

// Giờ a và b cùng tham chiếu tới một đối tượng
// Thay đối đối tượng thông qua a
// thi qua b có thể thấy sự thay đổi này
a.foo = 123;
assert.equal(b.foo, 123);

Khi một đối tượng không được tham chiếu bởi bất kỳ biến nào, nó tự động bị xóa khởi bộ nhớ heap:

let obj = { prop: 'value' };
obj = {};
// đối tượng { prop: 'value' } bị xóa khỏi bộ nhớ

Ban đầu tham chiếu tới {prop: 'value' } được lưu trong biến obj. Sau câu lệnh thứ hai, biến này tham chiếu tới một đối tượng mới là {}. Vậy đối tượng ban đầu { prop: 'value' } không còn được tham chiếu bởi obj nữa nên nó bị xóa khỏi bộ nhớ.

So sánh theo tham chiếu

Các đối tượng được so sánh theo tham chiếu, hai biến bằng nhau nếu nó cùng tham chiếu tới một đối tượng trong bộ nhớ heap. Hai biến dù tham chiếu tới hai đối tượng có nội dung giống hệt nhau, cũng không được xem là bằng nhau:

const obj1 = { prop: 'value' };
const obj2 = obj1;
const obj3 = { prop: 'value' };
assert.equal(obj1 === obj2, true); // cùng tham chiếu tới một đối tượng
assert.equal(obj1 === obj3, false); // tham chiếu tới hai đối tượng khác nhau

Toán tử typeofinstanceof

typeof

Toán tử typeof cho bạn biết kiểu của một giá trị là gì. Cú pháp là typeof x hoặc typeof(x). Giá trị trả về là chuỗi mô tả kiểu của x.

xtypeof x
undefined'undefined'
null'object'
true hoặc false'boolean'
'abc''string'
3.141'number'
12345n'bitint'
Symbol('My Symbol')'symbol'
Các hàm: function() {}'function'
Các đối tượng: {}'object'

Chú ý rằng typeof null trả về 'object' mặc dù kiểu thực của nó là null. Đây là lỗi vẫn còn tồn tại trong JavaScript và nó không bao giờ được sửa.

Đối với hàm, giá trị trả về là 'function' cũng không đúng, nhưng nó lại tiện lợi trong lập trình. Thực ra kiểu của hàm vẫn là đối tượng ('object').

instanceof

Toán tử này trả lời câu hỏi: đối tượng x có là thực thể của lớp C không?

Cú pháp: x instanceof C

Ví dụ:

> (function() {}) instanceof Function
true
> ({}) instanceof Object
true
> [] instanceof Array
true

Như bạn thấy các hàm, các đối tượng và các mảng đều là các đối tượng, nhưng chúng có thể là thực thể của các lớp khác nhau.

Các giá trị cơ sở không phải là thực thể của bất cứ lớp nào (không phải là đối tượng):

> 123 instanceof Number
false
> '' instanceof String
false
> '' instanceof Object
false

Các lớp và hàm tạo

Ngoài cách tạo đối tượng qua object literal, JavaScript còn cho phép tạo nhiều đối tượng cùng loại nhờ các hàm tạo (constructor functions). Các hàm tạo được gọi bằng cách sử dụng toán tử new đặt trước, nó trả về thực thể của hàm tạo này.

ES6 giới thiệu thêm các lớp (class), là cú pháp cải tiến hơn hàm tạo.

Các hàm tạo tương ứng với các kiểu cơ sở

Mỗi kiểu cơ sở (ngoại trừ undefinednull) có một hàm tạo tương ứng:

  • Kiểu boolean có hàm tạo là Boolean.
  • Kiểu number có hàm tạo là Number.
  • Kiểu string có hàm tạo là String.
  • Kiểu symbol có hàm tạo là Symbol.

Các hàm tạo này có nhiều vai trò:

  • Sử dụng như hàm chuyển đổi thành kiểu tương ứng, ví dụ chuyển một giá trị thành số:
assert.equal(Number('123'), 123);
  • prototype của các hàm tạo này là nơi cung cấp các thuộc tính và phương thức cho giá trị cơ sở tương ứng. Ví dụ, ta có thể sử dụng phương thức toString() đối với một số:
assert.equal((123).toString, Number.prototype.toString);
  • Chứa nhiều phương thức hữu ích để sử dụng với các giá trị có kiểu tương ứng. Ví dụ:
assert.equal(Number.isInteger(123), true);
  • Có thể sử dụng để tạo ra các đối tượng bao lấy giá trị cơ sở:
assert.notEqual(new Number(123), 123);
assert.equal(new Number(123).valueOf(), 123);

Chuyển đổi giữa các kiểu

Có hai cách để chuyển đổi một giá trị sang kiểu khác:

  • Chuyển đổi rõ ràng nhờ hàm tạo như String().
  • Ép kiểu (chuyển đổi tự động): xảy ra khi toán tử nhận được một giá trị có kiểu không phù hợp.

Chuyển đổi rõ ràng

Ta sử dụng hàm tạo để chuyển đổi một giá trị sang kiểu tương ứng:

> Boolean(0)
false
> Number('123')
123
> String(123)
'123'

Bạn cũng có thể sử dụng Object() để chuyển đổi giá trị thành đối tượng:

> typeof Object(123)
'object'

Ép kiểu

Với nhiều toán tử/hàm, JavaScript tự động chuyển đổi toán hạng/tham số thành kiểu thích hợp, nếu kiểu gốc không phù hợp. Kiểu chuyển đổi này gọi là ép kiểu (coercion).

Ví dụ, toán tử nhân ép kiểu toán hạng của nó thành số:

> '7' * '3'
21

Cũng có nhiều hàm có sẵn giúp bạn ép kiểu. Ví dụ parseInt() phân tích tham số của nó thành số nguyên (sự phân tích dừng lại khi gặp kí tự không phải chữ số):

> parseInt(123.45)
123