Tính kế thừa nguyên mẫu
Ngoại trừ null
và undefined
, mỗi kiểu dữ liệu gốc đều có một
prototype (nguyên mẫu) là trình bao bọc đối tượng tương ứng cung cấp các phương thức để hoạt động
với các giá trị. Khi một phương thức hoặc hoạt động tra cứu thuộc tính được gọi trên một dữ liệu gốc,
JavaScript gói phần nguyên gốc ở hậu trường và gọi phương thức hoặc
sẽ thực hiện tra cứu thuộc tính trên đối tượng trình bao bọc.
Ví dụ: giá trị cố định kiểu chuỗi không có phương thức riêng, nhưng bạn có thể gọi phương thức
Phương thức .toUpperCase()
trên đó nhờ có đối tượng String
tương ứng
trình bao bọc:
"this is a string literal".toUpperCase();
> THIS IS A STRING LITERAL
Đây gọi là tính kế thừa nguyên mẫu, tức là kế thừa các thuộc tính và phương thức từ hàm khởi tạo tương ứng của một giá trị.
Number.prototype
> Number { 0 }
> constructor: function Number()
> toExponential: function toExponential()
> toFixed: function toFixed()
> toLocaleString: function toLocaleString()
> toPrecision: function toPrecision()
> toString: function toString()
> valueOf: function valueOf()
> <prototype>: Object { … }
Bạn có thể tạo dữ liệu gốc bằng các hàm khởi tạo này, thay vì chỉ xác định
theo giá trị của chúng. Ví dụ: việc sử dụng hàm khởi tạo String
sẽ tạo một
đối tượng chuỗi, không phải giá trị cố định kiểu chuỗi: một đối tượng không chỉ chứa chuỗi của chúng ta
mà là tất cả các thuộc tính và phương thức kế thừa của hàm dựng.
const myString = new String( "I'm a string." );
myString;
> String { "I'm a string." }
typeof myString;
> "object"
myString.valueOf();
> "I'm a string."
Đối với hầu hết các phần, các đối tượng thu được hoạt động như các giá trị mà chúng ta đã từng
bạn có thể xác định chúng. Ví dụ: mặc dù việc xác định giá trị số bằng phương pháp
Hàm khởi tạo new Number
tạo ra một đối tượng chứa tất cả các phương thức và
các tính chất của nguyên mẫu Number
, bạn có thể sử dụng các toán tử trên
các đối tượng đó giống như cách bạn làm với các giá trị cố định dạng số:
const numberOne = new Number(1);
const numberTwo = new Number(2);
numberOne;
> Number { 1 }
typeof numberOne;
> "object"
numberTwo;
> Number { 2 }
typeof numberTwo;
> "object"
numberOne + numberTwo;
> 3
Bạn sẽ rất hiếm khi cần sử dụng các hàm khởi tạo này vì các hàm khởi tạo được tích hợp sẵn tính kế thừa nguyên mẫu có nghĩa là chúng không mang lại lợi ích thiết thực nào. Đang tạo dữ liệu gốc sử dụng hàm khởi tạo cũng có thể dẫn đến kết quả không mong muốn, vì kết quả là một đối tượng, chứ không phải là một giá trị cố định đơn giản:
let stringLiteral = "String literal."
typeof stringLiteral;
> "string"
let stringObject = new String( "String object." );
stringObject
> "object"
Điều này có thể khiến việc sử dụng các toán tử so sánh nghiêm ngặt trở nên phức tạp:
const myStringLiteral = "My string";
const myStringObject = new String( "My string" );
myStringLiteral === "My string";
> true
myStringObject === "My string";
> false
Tự động chèn dấu chấm phẩy (ASI)
Trong khi phân tích cú pháp một tập lệnh, phiên dịch JavaScript có thể sử dụng một tính năng được gọi là tự động chèn dấu chấm phẩy (ASI) để cố gắng sửa các trường hợp bị bỏ qua dấu chấm phẩy. Nếu trình phân tích cú pháp JavaScript gặp một mã thông báo không được phép, thì trình phân tích cú pháp JavaScript hãy cố gắng thêm dấu chấm phẩy trước mã thông báo đó để sửa lỗi cú pháp có thể xảy ra, như miễn là một hoặc nhiều điều kiện sau đây là đúng:
- Mã thông báo đó được phân tách với mã thông báo trước đó bằng dấu ngắt dòng.
- Mã thông báo đó là
}
. - Mã thông báo trước đó là
)
và dấu chấm phẩy được chèn sẽ là kết thúc dấu chấm phẩy của câu lệnhdo
...while
.
Để biết thêm thông tin, hãy tham khảo các quy tắc của ASI.
Ví dụ: bỏ dấu chấm phẩy sau các câu lệnh sau sẽ không gây ra lỗi lỗi cú pháp do ASI:
const myVariable = 2
myVariable + 3
> 5
Tuy nhiên, ASI không thể giải thích cho nhiều bảng sao kê trên cùng một dòng. Nếu bạn viết nhiều câu lệnh trên cùng một dòng, hãy nhớ phân tách các câu lệnh bằng dấu chấm phẩy:
const myVariable = 2 myVariable + 3
> Uncaught SyntaxError: unexpected token: identifier
const myVariable = 2; myVariable + 3;
> 5
ASI là nỗ lực sửa lỗi, không phải là một loại cú pháp linh hoạt được tạo ra vào JavaScript. Nhớ sử dụng dấu chấm phẩy nếu thích hợp để tránh dựa vào dấu chấm phẩy để tạo mã chính xác.
Chế độ nghiêm ngặt
Các tiêu chuẩn chi phối cách viết JavaScript đã phát triển vượt xa bất kỳ điều gì được xem xét trong giai đoạn đầu của thiết kế ngôn ngữ. Mọi thay đổi mới đối với Hành vi dự kiến của JavaScript phải tránh gây ra lỗi trong các trang web cũ hơn.
ES5 giải quyết một số vấn đề dai dẳng với ngữ nghĩa JavaScript mà không
phá vỡ các triển khai hiện có bằng cách giới thiệu "chế độ nghiêm ngặt" một cách để chọn
thành một bộ quy tắc ngôn ngữ hạn chế hơn cho toàn bộ tập lệnh hoặc một
chức năng riêng lẻ. Để bật chế độ nghiêm ngặt, hãy sử dụng giá trị cố định kiểu chuỗi
"use strict"
, theo sau là dấu chấm phẩy, trên dòng đầu tiên của tập lệnh hoặc
hàm:
"use strict";
function myFunction() {
"use strict";
}
Chế độ nghiêm ngặt ngăn chặn một số trạng thái "không an toàn" hoặc các tính năng không được dùng nữa, ném
lỗi rõ ràng thay cho trạng thái "tắt tiếng" thông thường và cấm sử dụng
có thể xung đột với các tính năng ngôn ngữ trong tương lai. Ví dụ: sớm
quyết định thiết kế xoay quanh phạm vi biến
làm tăng khả năng nhà phát triển "thăm dò" do nhầm lẫn phạm vi toàn cầu khi
khai báo biến, bất kể ngữ cảnh chứa, bằng cách bỏ
Từ khoá var
:
(function() {
mySloppyGlobal = true;
}());
mySloppyGlobal;
> true
Môi trường thời gian chạy JavaScript hiện đại không thể khắc phục hành vi này mà không gây ra rủi ro phá vỡ bất kỳ trang web nào dựa vào công cụ này, dù do nhầm lẫn hay cố ý. Thay vào đó, JavaScript hiện đại ngăn chặn điều này bằng cách cho phép các nhà phát triển chọn cho công việc mới và bật chế độ nghiêm ngặt theo mặc định chỉ trong ngữ cảnh các tính năng ngôn ngữ mới mà chúng sẽ không làm hỏng cách triển khai cũ:
(function() {
"use strict";
mySloppyGlobal = true;
}());
> Uncaught ReferenceError: assignment to undeclared variable mySloppyGlobal
Bạn phải viết "use strict"
dưới dạng
giá trị cố định kiểu chuỗi.
Giá trị cố định mẫu
(use strict
) sẽ không hoạt động. Bạn cũng phải bao gồm "use strict"
trước bất kỳ
mã thực thi trong ngữ cảnh dự định của nó. Nếu không, trình phiên dịch sẽ bỏ qua hàm này.
(function() {
"use strict";
let myVariable = "String.";
console.log( myVariable );
sloppyGlobal = true;
}());
> "String."
> Uncaught ReferenceError: assignment to undeclared variable sloppyGlobal
(function() {
let myVariable = "String.";
"use strict";
console.log( myVariable );
sloppyGlobal = true;
}());
> "String." // Because there was code prior to "use strict", this variable still pollutes the global scope
Tham chiếu theo giá trị
Bất kỳ biến nào, bao gồm cả thuộc tính của một đối tượng, tham số hàm và các phần tử trong mảng, set, hoặc bản đồ, có thể chứa dữ liệu nguyên thuỷ hoặc một giá trị tham chiếu.
Khi giá trị gốc được gán từ một biến này sang biến khác, JavaScript công cụ sẽ tạo bản sao của giá trị đó và gán giá trị đó cho biến.
Khi bạn gán một đối tượng (thực thể lớp, mảng và hàm) cho một thay vì tạo một bản sao mới của đối tượng đó, biến sẽ chứa một tham chiếu đến vị trí được lưu trữ của đối tượng trong bộ nhớ. Do đó, việc thay đổi một đối tượng được một biến tham chiếu sẽ làm thay đổi đối tượng đang được tham chiếu, chứ không chỉ giá trị có trong biến đó. Ví dụ: nếu bạn khởi tạo một có chứa biến chứa tham chiếu đối tượng, thì hãy sử dụng biến mới biến để thêm một thuộc tính vào đối tượng đó, thì thuộc tính và giá trị của thuộc tính đó sẽ được thêm vào thành đối tượng gốc:
const myObject = {};
const myObjectReference = myObject;
myObjectReference.myProperty = true;
myObject;
> Object { myProperty: true }
Điều này không chỉ quan trọng khi thay đổi các đối tượng, mà còn khi thực hiện
bởi vì sự đẳng thức nghiêm ngặt giữa các đối tượng yêu cầu cả hai biến
tham chiếu cùng một đối tượng để đánh giá cho true
. Họ không thể tham chiếu
đối tượng khác nhau, ngay cả khi những đối tượng đó có cấu trúc giống hệt nhau:
const myObject = {};
const myReferencedObject = myObject;
const myNewObject = {};
myObject === myNewObject;
> false
myObject === myReferencedObject;
> true
Phân bổ bộ nhớ
JavaScript sử dụng tính năng quản lý bộ nhớ tự động, có nghĩa là bộ nhớ không cần được phân bổ hoặc giải quyết rõ ràng trong quá trình phát triển. Trong khi thông tin chi tiết về công cụ JavaScript không chỉ có các phương pháp quản lý bộ nhớ phạm vi của mô-đun này, việc hiểu cách phân bổ bộ nhớ sẽ giúp ích ngữ cảnh để làm việc với các giá trị tham chiếu.
Có hai "khu vực" trong bộ nhớ: "stack" và "vùng nhớ khối xếp". Ngăn xếp lưu trữ dữ liệu tĩnh—giá trị gốc và tham chiếu đến đối tượng—vì lượng không gian cố định cần thiết để lưu trữ dữ liệu này có thể được phân bổ trước khi thực thi tập lệnh. Vùng nhớ khối xếp lưu trữ các đối tượng cần được phân bổ động vì kích thước của chúng có thể thay đổi trong quá trình thực thi. Bộ nhớ được giải phóng nhờ một quá trình có tên là "thu gom rác" Thao tác này sẽ xoá các đối tượng không có tham chiếu khỏi bộ nhớ.
Luồng chính
JavaScript về cơ bản là ngôn ngữ đơn luồng có chế độ "đồng bộ" mô hình thực thi, nghĩa là mô hình này chỉ có thể thực thi một tác vụ cùng một lúc. Ngữ cảnh thực thi tuần tự này được gọi là luồng chính.
Luồng chính được chia sẻ bởi các tác vụ khác của trình duyệt, chẳng hạn như phân tích cú pháp HTML, kết xuất và hiển thị lại các phần của trang, chạy ảnh động CSS và xử lý các tương tác của người dùng khác nhau, từ đơn giản (như đánh dấu văn bản) đến phức tạp (như tương tác với các thành phần hình dạng). Đã tìm thấy nhà cung cấp trình duyệt cách tối ưu hoá các tác vụ do luồng chính thực hiện, nhưng phức tạp hơn các tập lệnh vẫn có thể sử dụng quá nhiều tài nguyên của luồng chính và tác động đến tổng thể hiệu suất trang.
Một số tác vụ có thể được thực thi trong các luồng trong nền có tên là Web Workers, với một số hạn chế:
- Các luồng Worker chỉ có thể hoạt động trên các tệp JavaScript độc lập.
- Chúng đã bị giảm nghiêm trọng hoặc không có quyền truy cập vào cửa sổ trình duyệt và giao diện người dùng.
- Các yếu tố này bị hạn chế về cách giao tiếp với luồng chính.
Những hạn chế này khiến ứng dụng phù hợp với những công việc tập trung và tốn nhiều tài nguyên có thể chiếm luồng chính.
Ngăn xếp lệnh gọi
Cấu trúc dữ liệu dùng để quản lý "ngữ cảnh thực thi" – mã đang được được chủ động thực thi – danh sách này được gọi là ngăn xếp lệnh gọi (thường chỉ "ngăn xếp"). Khi tập lệnh được thực thi lần đầu tiên, trình thông dịch JavaScript tạo một "ngữ cảnh thực thi chung" rồi đẩy lệnh đó vào ngăn xếp lệnh gọi bằng các câu lệnh bên trong ngữ cảnh chung được thực thi lần lượt từng câu lệnh, từ trên xuống dưới cùng. Khi trình phiên dịch gặp một lệnh gọi hàm trong khi thực thi ngữ cảnh toàn cục, nó sẽ đẩy một "ngữ cảnh thực thi hàm" cho cuộc gọi đó vào ở đầu ngăn xếp, tạm dừng ngữ cảnh thực thi chung rồi thực thi hàm ngữ cảnh thực thi.
Mỗi lần một hàm được gọi, ngữ cảnh thực thi hàm cho lệnh gọi đó sẽ là được đẩy lên đầu ngăn xếp, ngay phía trên ngữ cảnh thực thi hiện tại. Ngăn xếp lệnh gọi hoạt động theo cơ chế "vào trước, ra trước" nghĩa là lệnh gọi hàm gần đây (ở vị trí cao nhất trong ngăn xếp) được thực thi và tiếp tục cho đến khi giải quyết xong. Khi chức năng đó hoàn tất, trình phiên dịch sẽ gỡ bỏ chức năng này từ ngăn xếp lệnh gọi và ngữ cảnh thực thi chứa lệnh gọi hàm đó trở thành mục cao nhất trong ngăn xếp và tiếp tục thực thi.
Các ngữ cảnh thực thi này thu thập mọi giá trị cần thiết để thực thi. Chúng
Ngoài ra, hãy thiết lập các biến và hàm có sẵn trong phạm vi của
dựa trên ngữ cảnh gốc, đồng thời xác định và đặt giá trị của
Từ khoá this
trong ngữ cảnh của hàm.
Vòng lặp sự kiện và hàng đợi gọi lại
Quá trình thực thi tuần tự này có nghĩa là các tác vụ không đồng bộ bao gồm lệnh gọi lại
chẳng hạn như tìm nạp dữ liệu từ máy chủ, phản hồi tương tác của người dùng,
hoặc chờ đồng hồ hẹn giờ được đặt bằng setTimeout
hoặc setInterval
thì sẽ chặn
luồng chính cho đến khi hoàn tất tác vụ hoặc làm gián đoạn đột ngột
ngữ cảnh thực thi hiện tại ngay tại thời điểm ngữ cảnh thực thi của hàm callback
sẽ được thêm vào ngăn xếp. Để giải quyết vấn đề này, JavaScript sẽ quản lý các tác vụ không đồng bộ
bằng "mô hình đồng thời" dựa trên sự kiện được tạo thành từ "vòng lặp sự kiện" và
"hàng đợi gọi lại" (đôi khi được gọi là "hàng đợi thông báo").
Khi một tác vụ không đồng bộ được thực thi trên luồng chính, lệnh gọi lại ngữ cảnh thực thi của hàm được đặt trong hàng đợi gọi lại, không phải ở đầu ngăn xếp lệnh gọi. Vòng lặp sự kiện là một mẫu đôi khi được gọi là reactor (đơn vị phản ứng) liên tục thăm dò trạng thái của ngăn xếp lệnh gọi và hàng đợi gọi lại. Nếu có việc cần làm trong hàng đợi gọi lại và vòng lặp sự kiện xác định rằng ngăn xếp lệnh gọi trống, tác vụ từ hàng đợi gọi lại được đẩy lần lượt vào ngăn xếp để thực thi.