1. Phân biệt ES5 và ES6, trình bày những điểm mới của ES6
ECMAScript5 (ES5) là phiên bản thứ năm của ECMAScript, được chuẩn hóa vào năm 2009
ECMAScript6 (ES6) là phiên bản thứ sáu, hoàn thành vào năm 2015, còn được gọi là ES2015
ES6 là một cải tiến so với ES5, ngắn gọn hơn và giúp tăng hiệu suất phát triển
Một số tính năng mới của ES6:
-
let và const: let để khai báo biến, const cho hằng số, cả hai đều có phạm vi khối ES5 không có khối phạm vi, và var có nâng biến, trong khi với let, biến phải được khai báo trước khi sử dụng
-
Hàm mũi tên ES6 cho phép định nghĩa hàm không dùng từ khóa function(), mà sử dụng ()=>
-
Chuỗi mẫu Chuỗi mẫu là phiên bản cải tiến của chuỗi thông thường, sử dụng dấu ngoặc ngược (`), có thể dùng như chuỗi thông thường hoặc định nghĩa chuỗi nhiều dòng
-
Phân cấu trúc gán ES6 cho phép trích giá trị từ mảng và đối tượng theo một mẫu cụ thể để gán cho biến
-
Vòng lặp for...of Vòng lặp for...of có thể duyệt qua mảng, Set, Map, một số đối tượng giống mảng, đối tượng và chuỗi
-
Import, export ES6 hỗ trợ mô-đun (module) nguyên bản. Chia mã JS thành các phần nhỏ chức năng, viết các chức năng khác nhau vào các tệp riêng biệt, mỗi mô-đun chỉ xuất phần giao diện công cộng, sau đó có thể sử dụng ở nơi khác qua import
-
Cấu trúc dữ liệu Set Set giống mảng, tất cả giá trị là duy nhất không trùng lặp. Nó là một hàm tạo
-
Toán tử trải ... Có thể mở rộng giá trị trong mảng hoặc đối tượng; cũng có thể gom nhiều giá trị thành một biến
-
Decorator @ Decorator là một hàm dùng để sửa hành vi của lớp hoặc phương thức. Về bản chất, decorator là hàm thực thi tại thời gian biên dịch
-
Kế thừa lớp ES6 không sử dụng chuỗi nguyên mẫu như ES5 để thực hiện kế thừa, mà giới thiệu khái niệm Class
-
async, await Sử dụng async/await kết hợp với promise, có thể xử lý luồng bất đồng bộ bằng cách viết mã giống đồng bộ, tăng tính ngắn gọn và dễ đọc async dùng để khai báo một hàm là bất đồng bộ, trong khi await dùng để chờ một phương thức bất đồng bộ hoàn thành
-
Promise Promise là một giải pháp lập trình bất đồng bộ, hợp lý và mạnh mẽ hơn các giải pháp truyền thống (hàm callback và sự kiện)
-
Symbol Symbol là một kiểu nguyên bản. Symbol được tạo bằng cách gọi hàm symbol, nhận một tham số tên tùy chọn, giá trị trả về là duy nhất
-
Proxy Sử dụng proxy để giám sát thao tác trên đối tượng, sau đó có thể thực hiện các hành động tương ứng
2. Khác biệt giữa var, let, const
var có thể khai báo lại, trong khi let không được
var không bị giới hạn bởi khối, trong khi let bị giới hạn
var ánh xạ với window (thuộc tính được gắn), trong khi let không
var có thể truy cập biến ở trên khai báo, trong khi let có vùng chết tạm thời, truy cập ở trên khai báo sẽ báo lỗi
const phải được gán giá trị sau khi khai báo, nếu không sẽ báo lỗi
const định nghĩa hằng số, thay đổi sẽ báo lỗi
const và let không ánh xạ với window, hỗ trợ phạm vi khối, truy cập biến ở trên khai báo sẽ báo lỗi
3. Cần lưu ý gì khi sử dụng hàm mũi tên?
(1) Dùng hàm mũi tên, this không còn trỏ đến window mà trỏ đến cấp cha (có thể thay đổi)
(2) Không thể sử dụng đối tượng arguments
(3) Không thể dùng làm hàm tạo, tức là không thể dùng lệnh new, nếu không sẽ ném ra lỗi
(4) Không thể sử dụng lệnh yield, do đó hàm mũi tên không thể dùng làm Generator function
4. Chuỗi mẫu của ES6 có tính năng mới nào? và thực hiện một chức năng tương tự chuỗi mẫu
Chuỗi mẫu cơ bản. Nhúng biểu thức vào chuỗi để nối. Dùng ${} để phân định
Trong ES5, chúng ta dùng dấu gạch chéo ngược () để làm chuỗi nhiều dòng hoặc nối chuỗi từng dòng. ES6 dùng dấu ngoặc ngược (``) để giải quyết
let name = 'web';
let age = 10;
let str = 'Xin chào, ${name} đã ${age} tuổi'
str = str.replace(/\$\{([^}]*)\}/g,function(){
return eval(arguments[1]);
})
console.log(str);//Xin chào, web đã 10 tuổi
5. Giới thiệu sự khác biệt giữa Set và Map?
Ứng dụng: Set dùng để tái tổ chức dữ liệu, Map dùng để lưu trữ dữ liệu
Set:
(1) Thành viên không được trùng lặp (2) Chỉ có giá trị không có khóa, giống mảng (3) Có thể duyệt, có các phương thức add, delete, has
Map:
(1) Về bản chất là tập hợp cặp khóa-giá trị, giống tập hợp (2) Có thể duyệt, có thể chuyển đổi với các định dạng dữ liệu khác nhau
6. Cách viết class trong ECMAScript 6, tại sao lại xuất hiện class?
Class trong ES6 có thể coi là một cú pháp đường hóa, hầu hết các chức năng của nó đều có thể thực hiện được bằng ES5, cách viết class mới chỉ làm cho cách viết nguyên mẫu đối tượng rõ ràng hơn, giống với cú pháp lập trình hướng đối tượng hơn
//Định nghĩa lớp
class Diem {
constructor(x,y) {
//Phương thức khởi tạo
this.x = x; //this đại diện cho đối tượng thực thể
this.y = y;
} toString() {
return '(' + this.x + ',' + this.y + ')';
}
}
7. Constructor của Promise thực thi đồng bộ hay bất đồng bộ, vậy phương thức thì sao?
Constructor của Promise thực thi đồng bộ, phương thức then thực thi bất đồng bộ
8. Sự khác biệt giữa setTimeout, Promise, Async/Await
Trong vòng lặp sự kiện, có hàng đợi tác vụ macro và hàng đợi tác vụ micro
Callback của setTimeout được đặt vào hàng đợi tác vụ macro, chờ khi stack thực thi trống rồi mới thực hiện
Callback trong promise.then được đặt vào hàng đợi tác vụ micro của tác vụ macro tương ứng, sau khi code đồng bộ trong tác vụ macro hoàn thành thì thực hiện
Hàm async có thể chứa phương thức bất đồng bộ, await theo sau một biểu thức
Khi thực thi hàm async, gặp await sẽ thực thi biểu thức ngay lập tức, sau đó đặt code sau biểu thức vào hàng đợi tác vụ micro, nhường stack thực thi để code đồng bộ thực hiện trước
9. Promise có bao nhiêu trạng thái, khi nào vào catch?
Ba trạng thái: pending, fulfilled, reject
Hai quá trình: padding -> fulfilled, padding -> rejected
Khi pending là rejectd, sẽ vào catch
10. Kết quả xuất đoạn code sau là gì
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
})
promise.then(() => {
console.log(3);
})
console.log(4);
1 2 4 3
Promise được tạo ra sẽ thực thi ngay lập tức, nên xuất 1,2 trước, trong khi code trong Promise.then() sẽ thực thi ngay sau cùng của vòng lặp sự kiện hiện tại, nên tiếp tục xuất 4, cuối cùng xuất 3
11. Sử dụng phân cấu trúc gán, thực hiện hoán giá trị của hai biến
let a = 1;let b = 2;
[a,b] = [b,a];
12. Thiết kế một đối tượng, ít nhất một khóa có kiểu Symbol, và duyệt tất cả các key
let ten = Symbol('ten');
let sanPham = {
[ten]: "Máy giặt",
"gia":799
};
Reflect.ownKeys(sanPham);
13. Với cấu trúc Set sau, giá trị size in ra là bao nhiêu
let s = new Set();
s.add([1]);
s.add([1]);console.log(s.size);
Đáp án: 2
Hai mảng [1] không phải cùng một giá trị, chúng được định nghĩa riêng biệt, trong bộ nhớ tương ứng với các địa chỉ lưu trữ khác nhau, do đó không phải là giá trị giống nhau
Đều có thể lưu trong cấu trúc Set, nên size là 2
14. Sự khác biệt trong xử lý giữa reject và catch trong Promise
reject dùng để ném ra ngoại lệ, catch dùng để xử lý ngoại lệ
reject là phương thức của Promise, còn catch là phương thức của thực thể Promise
Sau reject, chắc chắn sẽ vào callback thứ hai của then, nếu then không viết callback thứ hai, sẽ vào catch
Lỗi mạng (như mất kết nối), sẽ vào catch trực tiếp mà không vào callback thứ hai của then
15. Viết một Promise bằng class
//Tạo một lớp Promise
class Promise{
constructor(thucThi){//Hàm khởi tạo constructor chứa một thực thi
this.status = 'pending';//Trạng thái mặc định pending
this.value = undefined//Giá trị thành công mặc định undefined
this.reason = undefined//Giá trị thất bại mặc định undefined
//Chỉ khi ở trạng thái pending thì mới có thể thay đổi
let resolveFn = value =>{
//Chỉ khi đang chờ mới có thể resolve thành công
if(this.status == pending){
this.status = 'resolve';
this.value = value;
}
}
//Chỉ khi đang chờ mới có thể reject thất bại
let rejectFn = reason =>{
if(this.status == pending){
this.status = 'reject';
this.reason = reason;
}
}
try{
//Truyền hai hàm resolve và reject cho thực thi executer
thucThi(resolve,reject);
}catch(e){
reject(e);//Thất bại thì vào catch
}
}
then(thanhCong,thatBai){
//Nếu trạng thái thành công gọi thanhCong
if(this.status = 'resolve'){
thanhCong(this.value);
}
//Nếu trạng thái thất bại gọi thatBai
if(this.status = 'reject'){
thatBai(this.reason);
}
}
}
16. Cách sử dụng Set để loại bỏ trùng lặp
let arr = [12,43,23,43,68,12];
let item = [...new Set(arr)];
console.log(item);//[12, 43, 23, 68]
17. Chuyển vòng lặp for thành for of
let arr = [11,22,33,44,55];
let sum = 0;
for(let i=0;i<arr.length;i++){
sum += arr[i];
}
Đáp án:
let arr = [11,22,33,44,55];
let sum = 0;
for(giaTri of arr){
sum += giaTri;
}
18. Hiểu về async/await và ưu điểm so với Generator
async await dùng để giải quyết bất đồng bộ, hàm async là cú pháp đường hóa của Generator function
Sử dụng từ khóa async để biểu thị, trong hàm sử dụng await để biểu thị bất đồng bộ
Hàm async trả về một đối tượng Promise, có thể sử dụng phương thức then để thêm hàm callback
Khi thực thi hàm, gặp await sẽ trả về ngay, đợi thao tác bất đồng bộ hoàn thành rồi mới tiếp tục thực thi các câu lệnh sau trong hàm
Ưu điểm của async so với Generator:
(1) Có trình thực thi tích hợp. Hàm Generator phải dựa vào trình thực thi, trong khi hàm Async có trình thực thi riêng, cách gọi giống như hàm thông thường
(2) Ngữ nghĩa tốt hơn. async và await so với * và yield có ngữ nghĩa rõ ràng hơn
(3) Tính ứng dụng rộng hơn. Lệnh yield sau chỉ có thể là hàm Thunk hoặc đối tượng Promise, trong khi await sau trong hàm async có thể là Promise hoặc giá trị kiểu nguyên bản
(4) Trả về Promise. Hàm async trả về đối tượng Promise, tiện hơn đối tượng Iterator trả về của hàm Generator, có thể sử dụng trực tiếp phương thức then()
19. Sự khác biệt giữa forEach, for in, for of
forEach chủ yếu dùng để duyệt mảng
for in thường dùng để duyệt đối tượng hoặc json
for of có thể duyệt cả mảng và đối tượng, duyệt đối tượng cần kết hợp với Object.keys()
for in duyệt ra key, for of duyệt ra value
20. Giải thích về nhập/xuất mô-đun trong ES6
Nhập bằng từ khóa import
// Chỉ nhập một
import {tong} from "./viDu.js"
// Nhập nhiều
import {tong,nhan,thoiGian} from "./xuatViDu.js"
// Nhập toàn bộ mô-đun
import * as viDu from "./xuatViDu.js"
Xuất bằng từ khóa export
//Có thể đặt export trước bất kỳ biến, hàm hoặc khai báo lớp nào
export var ho = 'Michael';
export var ten = 'Jackson';
export var namSinh = 1958;
//Cũng có thể dùng ngoặc nhọn để chỉ định một tập hợp biến cần xuất
var ho = 'Michael';
var ten = 'Jackson';
var namSinh = 1958;
export {ho, ten, namSinh};
//Khi sử dụng export default, câu lệnh import tương ứng không cần dùng ngoặc nhọn
let ham = function(){}
export default ham;
import hamMoi from 'ham';
//Khi không sử dụng export default, câu lệnh import tương ứng cần dùng ngoặc nhọn
let ham = function(){}
export ham;
import {ham} from 'ham';