Trong phần này chúng tôi giới thiệu với các bạn Javascript phần nâng cao.
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
|
21
|
|
22
|
|
23
|
|
24
|
|
25
|
|
26
|
|
27
|
|
28
|
|
29
|
|
30
|
|
31
|
|
32
|
|
33
|
|
34
|
|
35
|
|
36
|
|
37
|
|
38
|
|
39
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
Javascript là một ngôn ngữ có hướng đối tượng rất linh hoạt về cú pháp, tuy nhiên đối tượng trong Javascript không thể hiện đầy đủ 4 tính chất quan trọng trong lập trình hướng đối tượng đó là tính kế thừa, tính đa hình, tính đóng gói và tính trừu tượng.
Lưu ý rằng trong Javascript không tồn tại class mà thay vào đó ta có thể sử dụng đối tượng hoặc function để thể hiện, và trong bài này mình sẽ hướng dẫn các bạn ba cách tạo class trong javascript khác nhau.
Mỗi function trong Javascript sẽ tồn tại một biến cục bộ tên là this
, biến này sẽ đại diện cho chính function đó. Lúc này bạn chỉ việc sử dụng biến this
để khai báo các thuộc tính và phương thức của đối tượng, và cuối cùng là đừng quên return this
để có thể sử dụng từ khóa new
để khởi tạo mới một đối tượng nhé.
Ví dụ: Tạo hàm User như là một class.
Cách sử dụng:
Như ở ví dụ trên là mình thêm phương thức trực tiếp ngay trong hàm luôn, ngoài cách này ra thì bạn có thể sử dụng prototype để bổ sung phương thức cho đối tượng.
Ví dụ: Viết lại ví dụ trên bằng cách sử dụng prototype.
Cách sử dụng cũng không có gì khác:
Với Object thì bạn dễ dàng khởi tạo các thuộc tính và phương thức, bạn có thể thêm ngay tại lúc khai báo đối tượng hoặc sau khi khởi tạo đều được.
Để khởi tạo đối tượng thì chúng ta có hai cách như sau:
Ok bây giờ ta sẽ khởi tạo một đối tượng User và bổ sung thuộc tính + phương thức cho User.
Cách sử dụng:
Cách thứ ba này bạn có thể sử dụng một function để tạo một Singleton Object, nghĩa là bạn sử dụng từ khóa new để tạo mới một function.
Cách sử dụng:
Trong bài này chúng ta sẽ tìm hiểu cách xử lý lỗi với cấu trúc try catch trong javascript, qua đó sẽ giúp bạn nắm bắt được bí quyết điều hướng và bắt lỗi trong js cực kì đơn giản. Việc bắt lỗi trong lập trình rất quan trọng, nếu làm không đúng cách thì sẽ mất rất nhiều thời gian để tìm ra lỗi. Có nhiều người nói vui rằng, bugs không phải là lỗi mà nó là một tính năng :) Ngụ ý của câu này muốn nói rằng trong lập trình không phải lúc nào cũng hoàn hảo, một chương trình phải trải qua rất nhiều công đoạn, trong đó có công đoạn kiểm thử để tìm lỗi.
Try catch là một khối lệnh dùng để bắt lỗi chương trình trong javascript. Ta sử dụng try catch khi muốn chương trình không bị dừng khi một lệnh nào đó bị lỗi. Thường thì đó là những lỗi do người dùng nhập sai dữ liệu, hoặc người dùng thao tác bị sai.
Nếu bạn nghĩ các lỗi đó có thể sử dụng lệnh if else để bắt thì thực tế không hoàn toàn như vậy. If else rất linh động nhưng nó chỉ được dùng trong trường hợp rẻ nhánh mà thôi. Nó sẽ không phát hiện ra những lỗi nghiệm trọng về cú pháp, điều mà chỉ có try catch mới làm được.
Hầu hết các lỗi trong javascript đều nằm trong một object xác định. Ví dụ lỗi cú pháp thì sẽ nằm trong đối tượng syntaxError, lỗi sử dụng biến chưa khai báo thì nằm trong đối tượng ReferenceError, các lỗi cơ bản thì nằm trong đối tượng Error...
Với try catch bạn cũng có thể tự tạo ra một object error cho riêng mình. Nghe quá hấp dẫn phải không các bạn? Ta hãy cùng bắt đầu tìm hiểu nó ngay nhé.
Cấu trúc try - catch không còn xa lạ gì trong các ngôn ngữ lập trình khác, và trong Javascript cũng tương tự.
Cú pháp như sau:
Như vậy luồng chạy của lệnh try catch sẽ như sau:
try
.try
xuất hiện lỗi thì nhảy sang catch
finally
dù là lỗi hay không.Như vậy vị trí finally
sẽ luôn luôn được thực thi và sẽ thực thi cuối cùng. finally
là một tùy chọn, bạn có thể sử dụng hoặc không đều được. Riêng trong catch
sẽ có một tham số truyền vào, tham số này chính là một trong những đối tượng lỗi mà mình đã giới thiệu ở phần 1.
Ví dụ 1: Sử dụng biến nhưng chưa định nghĩa.
Nếu bình thường thì chương trình bị dừng, nhưng vì ta sử dụng try - catch
nên chương trình vẫn hoạt động bình thường.
Nếu chạy đoạn code này lên thì sẽ nhận được thông báo là biến message chưa được định nghĩa (message is not defined).
Ví dụ: Sử dụng sai cú pháp nhưng chương trình vẫn chạy.
Chương trình này nếu chạy lên thì xuất hiện dòng chữ 'fadsfas is not defined', và đoạn code trong finally vẫn hoạt động bình thường, điều này chứng tỏ chương trình không bị dừng đột ngột.
Tham số e
trong catch
chính là một error object mặc định của javascript. Nhưng thực tế thì có một số trường hợp không phải là một lỗi, mà nó là một tính năng. Ví dụ, mình muốn bắt lỗi nếu người dùng nhập vào dữ liệu rỗng thì sẽ xuất ra thông báo lỗi bên trong phần catch thì phải làm sao?
Trường hợp này ta sẽ sử dụng lệnh throw để quăng ra một lỗi Error như sau:
Đối tượng Error là một constructor, nên bạn có thể sử dụng từ khóa new để tạo một instance của Error.
Ví dụ: Sử dụng throw new Error('message')
để xuất thông báo lỗi.
Trong ví dụ này mình muốn kiểm tra biến domain, nếu giá trị của nó không phải là thuanhoaonline.com thì quăng ra lỗi.
Chạy lên sẽ xuất hiện dòng chữ 'domain này không phải là trang chủ'.
Mình thử console.log tham số e để xem có gì trong đó nhé.
Kết quả như sau:
Phần 3 mình đã hướng dẫn cách sử dụng từ khóa throw để quăng một instance Error. Tuy nhiên, đó là cách viết rất cơ bản, ta hoàn toàn có thể đưa nó vào trong một class hoặc một constructor function để tiện cho việc sử dụng.
Sử dụng function:
Hoặc sử dụng class:
Để sử dụng thì chúng ta làm như sau:
Đọc tới đây thì có lẽ các bạn đã muốn biết trong javascript có những object error nào rồi phải không nào? Không làm các ban thất vọng, dưới đây là danh sách những error thường gặp nhất trong javascript.
Bạn không cần phải chờ javascript quăng ra những lỗi này mà hoàn toàn có thể khởi tạo chúng.
Sau đó kết hợp với lệnh throw để quăng lỗi vào phần catch.
Trong quá tình làm việc với Javascript thì mình thấy từ khóa this đã gây không biết bao nhiêu phiền toái, không chỉ phiền cho các bạn còn non kinh nghiệm mà các bạn già kinh nghiệm đôi lúc không để ý cũng bị nó hành một cách tội nghiệp. Trước đây mình cũng từng lâm vào tình cảnh không biết lỗi do đâu, chỉ khi debug từng step mới nhận ra là do đặt this sai chỗ. Ok, vậy bây giờ mình sẽ bàn một chút về từ khóa này nhé.
Trong bài ba cách tạo class mình có sử dụng từ khóa this
để gán các thuộc tính và phương thức cho đối tượng. Mình gọi là thuộc tính và phương thức vì đang suy diễn theo ý tưởng Javascript có thể lập trình hướng đối tượng, vì vậy bạn đừng ý kiến gì ở sự suy diễn này nhé.
Xét ví dụ sau:
Trong đoạn code trên thì từ khóa this chính là function hiện tại (hàm student), và trong trường hợp này ta có thể ví hàm student là một đối tượng student => từ khóa this
chính là đối tượng student. Nếu bạn để ý kĩ hơn thì trong hàm showInfo
mình có sử dụng từ khóa this
để gọi đến các phương thức và thuộc tính của đối tượng Student.
Bạn hãy xem cách sử dụng đối tượng Student như sau.
Như vậy hàm showInfo()
đã hiển thị đúng thông tin mà ta đã gán. Bây giờ bạn sửa lại đoạn code trên như sau:
Trong đoạn code này mình đã đánh dấu hai vị trí Log1 và Log2. Khi chạy lên kết quả sẽ in ra Log1 trước với nội dung rỗng và tiếp theo là Log2 với nội dung là dữ liệu mà ta đã gán. Như vậy bản chất cả hai từ khóa this
ở hai vị trí đó đều chính là đối tượng Student .
Mọi thứ vẫn ổn phải không các bạn? Nếu vậy thì bạn hãy tiếp tục bạn hãy thử chạy đoạn code sau và đoán điều gì sẽ xảy ra nhé.
Kết quả cả hai vị trí Log1 và Log2 đều trỏ tới đối tượng Window nên nó sẽ in ra giá trị giống nhau
Có bao giờ gặp một đoạn mã HTML có chứa mã Javascript trong các thuộc tính sự kiện không? Nếu chưa thì bạn hãy xem ví dụ sau thật kỹ nhé.
Bạn để ý trong button mình có một đoạn mã gọi tới hàm showMessage
trong sự kiện click
.
Câu hỏi đặt ra là tham số this
mà ta đã truyền vào hàm đó là cái gì? Để trả lời thì bạn hãy xem nội dung của hàm showMessage
nhé.
Hàm này đã nhận một tham số obj
, như vậy đối tượng this mà ta truyền vào lúc này chính là tham số obj
. Cuối cùng đoạn code console.log(obj);
sẽ in ra giá trị của tham số obj
.
Như vậy tham số this
chính là thẻ HTML hiện tại. Đây là cách mà chúng ta hay dùng để lấy thông tin của thẻ HTML khi xảy ra sự kiện. Bây giờ bạn sửa lại nội dung của hàm showMessage
như sau:
Đoạn code này sẽ in ra giá trị (value) của obj
mà obj
lúc này chính là thẻ button nên giá trị nó sẽ in ra là "Click Me". Bạn hãy chạy lại và click vào button để xem kết quả nhé.
Trong phần này mình sẽ lấy một ví dụ được viết bằng jQuery.
Trong ví dụ này đã gán cho thẻ #button
một sự kiện click, khi click vào #button
đó thì sẽ chạy một đoạn code gán tiếp một sự kiện click cho thẻ #element
, và hai đoạn code console.log(this)
ở hai vị trí đã in ra hai giá trị khác nhau hoàn toàn, đoạn thứ nhất this chính là #button
và đoạn thứ hai this chính là #element
.
Trong bài này chúng ta sẽ tìm hiểu hàm closure trong javascript, đây là một cách tạo hàm khá hay mà chắc hẳn các bạn đã gặp rất nhiều trong quá trình làm việc với javascript.
Closure function là một khái niệm không phải ai cũng biết và thực sự hiểu về nó. Đây được xem là một cách định nghĩa hàm giúp code nhìn trong sáng và sử dụng linh hoạt hơn. Vì vậy, nếu bạn muốn học javascript nâng cao hoặc học các framework khác thì phải hiểu về closure.
Vậy closure là gì? Cách tạo closure như thế nào? Khi nào thì sử dụng hàm closure? Chúng ta cùng tìm hiểu ngay nhé.
Closure là một hàm được tạo ra từ bên trong một hàm khác, nó có thể sử dụng các biến toàn cục, biến cục bộ của hàm cha và biến cục bộ của chính nó. Việc viết hàm theo kiểu closure trong một số trường hợp sẽ giúp code nhìn sáng và dễ quản lý hơn, linh hoạt hơn trong một số trường hợp.
Để hiểu được hàm closure trong javascript thì bạn phải hiểu được khái niệm về phạm vi của một biến scope. Scope là phạm vi của biến, nơi mà biến tồn tại, hay nói chính xác hơn đó là nơi mà biến sinh ra và chết đi, và chỉ những chương trình nằm trong phạm vi đó mới sử dụng được biến.
Khi bạn tạo một biến trong một hàm, thì phạm vi hoạt động của biến đó chỉ ở bên trong hàm đó mà thôi. Sau khi gọi xong thì biến sẽ bị hủy, và như vậy là kết thúc một vòng đời.
Khi khai báo biến với từ khóa var thì phạm vi của nó rộng hơn rất nhiều so với từ khóa let. Biên let chỉ tồn tại trong phạm vi bắt đầu bằng
{
và kết thúc bằng}
(ta hay gọi là block scoped), tức mức hoạt động cao nhất của nó là cục bộ. Còn biến var thì khác, nó có thể là biến toàn cục hoặc cục bộ.Bạn có thể tham khảo bài viết từ khóa let trong javascript để hiểu rõ hơn về nó.
Quay trở lại vấn đề chính của bài viết này, đó là closure trong js. Theo khái niệm của nó thì chúng ta chỉ việc tạo một hàm nằm bên trong một hàm khác thì đó chính là closure.
Ví dụ dưới đây mình tạo một hàm sayHi()
:
Trong ví dụ này thì hàm say()
chính là một closure function. Bên trong nó có thể sử dụng được biến của hàm cha name
.
Bạn cũng có thể return về luôn thay vì đặt tên cho hàm closure đó.
Bây giờ mình sẽ gọi đến hàm sayHi
, và gán nó vào biến tên là cuong
. Sau đó mình log biến cuong
này xem nó là gì nhé.
Kết quả trả vè nó là một function như sau:
Lý do khá đơn giản, bởi vì trong hàm sayHi
mình return về một function, vì vậy biến cuong
chính là function mà mình đã return đó. Để in ra thông báo thì chúng ta phải gọi kích hoạt function này.
Như vậy là bạn đã hiểu hàm closure trong javascript rồi phải không nào? Bây giờ mình sẽ đi tiếp phần 2 để nói về cách sử dụng biến cha trong closure function.
Trước khi đi vào vấn đề chính thì mình xin nhắc lại hai lưu ý sau:
Bây giờ hãy tạo cho mình một function như sau:
Hàm counter trả về một closure function, function đó có nhiệm vụ là tăng biến counter
lên 1 đơn vị và trả kết quả về.
Mình sẽ gọi đến hàm counter như sau:
Nếu theo quy tắc trên thì hàm counter()
đã được chạy xong, và biến count
bên trong hàm này đương nhiên là sẽ bị hủy. Tuy nhiên, nó vẫn còn tồn tại bên trong closure function nhé các bạn, bằng cách thực thi hàm c()
thì ta sẽ nhận được giá trị của biến count.
Mình sẽ gọi hàm c()
thêm vài lần nữa.
Vậy, hàm closure có thể sử dụng các biến của hàm cha mặc dù hàm cha đã chạy xong.
Cũng như những function khác, nếu bạn đang chạy chế độ strict mode thì con trỏ this sẽ là undefined, còn không thì nó là đối tượng window.
Chế độ bình thường
Chế độ strict mode
Bạn có thể tạo một closure trong các phương thức của class. Tuy nhiên, vì this
trong closure là undefined nên bạn không thể truy cập đến các thuộc tính của class.
Có một mẹo khá đơn giản, đó là bạn tạo một biến và gán nó chính bằng con trỏ this ở trong các phương thức của classs.
Tới đây thì chắc hẳn các bạn đã hiểu được khái niệm closure javascript là gì rồi phải không nào? Bây giờ ta sẽ thực hành thông qua một vài ví dụ, mỗi ví dụ là một trường hợp mà chắc chắn sau này bạn sẽ gặp khi làm việc với javascript.
Trong ví dụ các phần trên trên mình tạo một closure không có tham số. Vậy nếu trường hợp có tham số thì cách viết như thế nào? Bạn xem ví dụ dưới đây sẽ hiểu.
Trong ví dụ này thì hàm showMessage
đã trả về một hàm closure, hàm này có một tham số time
.
Nếu bạn muốn return nhiều hàm closure thì bạn phải sử dụng một object, trong đó mỗi phần tử sẽ là một closure function.
Closure có thể sử dụng biến ở 3 phạm vi: Thứ nhất là biến toàn cục, thứ hai là biến của hàm cha và thứ ba là biến của chính nó.
Không chỉ sử dụng được mà nó còn có khả năng thay đổi giá trị của các biến đó.
Như ta biết thì closure có thể sử dụng biến tại ba vị trí, đó là biến toàn cục, biến hàm cha và biến của chính nó.Giả sử tên các biến ở ba vị trí đó bị trùng nhau độ ưu tiên sẽ được sắp xếp như thế nào?
Trường hợp này nó sẽ ưu tiên từ trong ra ngoài như sau:
Xem ví dụ sau:
Trong ví dụ này thì biến message
trong hàm closure chính là biến của hàm cha.
Hàm trong javascript được coi là first-class objects, điều này có nghĩa hàm là một object nên ta có thể sử dụng nó giống như các object bình thường khác. Ta có thể lưu trữ hàm trong một biến, truyền tham số là một hàm, return một hàm, tạo function trong một hàm - hay còn gọi là closure function..
Hiểu đơn giản thì hàm callback là một hàm sẽ được gọi bởi một hàm khác. Hiểu phức tạp hơn thì callback một hàm A được truyền vào hàm B thông qua các tham số của hàm B. Bên trong hàm B sẽ gọi đến hàm A để thực hiện một chức năng nào đó.
Hàm trong javascript là một object nên ta có thể truyền hàm này vào hàm khác dưới dạng một tham số.
Javascript hỗ trợ lập trình hướng sự kiện và bất đồng bộ nên callback function đóng vai trò rất quan trọng. Bạn sẽ truyền callback function vào các hàm xử lý sự kiện và hàm xử lý bất đồng bộ đó.
Sau đây là một ví dụ đơn giản về callback function trong jQuery, Trong ví dụ này thì phương thức click
có một tham số truyền vào, và đó là một function.
Một ví dụ khác về hàm setTimeout, đây cũng là một hàm cho phép bạn truyền một callback function.
Việc sử dụng callback function phải hết sức cẩn thận, bạn phải tuân thủ đúng nguyên tắc mà hàm đó đưa ra, có hàm sẽ truyền thêm tham số cho hàm callback và có hàm thì không. Sau đây là một ví dụ về hàm forEach, hàm này sẽ có tác dụng lặp một mảng và có hai tham số callback function. Mỗi vòng lặp sẽ truyền hai tham số vào hàm cakback function, tham số thứ nhất đó là giá trị của phần tử đang lặp, tham số thứ hai đó là vị trí (index) của phần tử đó.
Ok bây giờ chắc hẳn bạn đã biết callback function là gì rồi phải không nào, nếu vậy thì ta qua phần 2 tìm hiểu cách hoạt động của nó nhé.
Một hàm hỗ trợ callback function thì chắc chắn trong code xử lý của nó sẽ có gọi đến để thực thi hàm callback đó, nhưng vấn đề nó gọi tại vị trí nào trong hàm là điều chúng ta không hề biết, trừ khi chúng ta tự viết nó. Như ở phần callback là gì mình có đưa ra một số ví dụ về truyền tham số cho callback function, các tham số này sẽ phụ thuộc vào hàm cha (hàm xử lý chính), nếu hàm cha cho phép bạn truyền 3 tham số thì bạn chỉ được truyền 3 tham số, nếu bạn truyền nhiều hơn thì cũng không có tác dụng gì.
Để các bạn dễ hiểu thì mình sẽ tự viết một hàm hỗ trợ callback function, bạn hãy xem kỹ ví dụ này nhé.
Nếu bạn để ý kỹ hơn thì callback function là một closure function bởi hàm closure sẽ được định nghĩa bên trong một hàm, mà callback function lại là một hàm và nó được xử lý bên trong một hàm khác (đúng với định nghĩa closure), chỉ có một điều khác đó là hàm closure được truyền vào thông qua tham số.
Khi bạn tự viết một hàm có sử dụng tham số là một callback function thì cần chú ý một số vấn đề như sau.
Điều này rất quan trọng bởi nếu bạn không kiểm tra giá trị mà người dùng truyền vào là một #function thì bạn không thể thực thi được, đây là sự khác biệt giữa một lập trình viên non kinh nghiệm và nhiều kinh nghiệm. Xem ví dụ dưới đây để hiểu về cách kiểm tra.
Thông qua ví dụ này ta thấy để kiểm tra một biến có phải là function hay không thì ta sử dụng hàm typeof
, nếu typeof
có giá trị là "function" thì đó là một function.
Hàm được xây dựng trong Object là hàm được định nghĩa thông qua key của object và giá trị của key là một hàm. Trong ví dụ này hàm setName
được xây dựng bên trong object domainInfo
.
Theo đúng nguyên tắc thì hàm callback là một hàm đơn phương nên khi bạn sử dụng từ khóa this
trong hàm thì nó sẽ hiểu this
lúc này chính là đối tượng Window Object, vì vậy cho dù bạn định nghĩa hàm callback nằm trong một object thì không thể truy cập đến dữ liệu của object thông qua từ khóa this
.
Bạn hãy xem đoạn code sử dụng hàm setName
là một callback function dưới đây để hiểu rõ hơn.
Phần demo này mình đã giải thích trong code rồi nên mình không giải thích gì thêm.
Ở phần trên mình đã đưa ra lưu ý khi sử dụng this
trong hàm callback thì this sẽ trỏ tới đối tượng window
chứ không phải đối tượng chứa hàm callback, vậy có cách nào khắc phục tình trạng này không? Có đấy, chúng ta sẽ sử dụng phương thức apply
của hàm callback. Cú pháp như sau:
Nếu ta sử dụng cú pháp này thì từ khóa this sẽ trỏ tới đối tượng callbackObject
chứ không phải là window object. Sau đây là đoạn code khắc phục ở ví dụ phía trên.
Ngoài phương thức apply
thì bạn cũng có thể sử dụng phương thức call
để thay thế.
Bạn có thể tạo ra một hàm có nhiều calback function bằng cách tạo ra nhiều tham số và mỗi tham số là một callback function. Xem ví dụ khi xử lý ajax bằng jQuery dưới đây.
Đối tượng this
gây ra rất nhiều khó khăn cho các bạn mới học Javascript, đặc biệt là khi bạn có sử dụng hàm closure. Tuy nhiên khi bạn thành thạo rồi thì bạn sẽ thấy đối tượng this
rất hữu ích và thú vị.
Xét bài toán sau đây:
Trong ví dụ này bị lỗi vì biến this
không phải là đối tượng blog
mà nó là đối tượng window
nên hai thuộc tính domain
và author
sẽ không tồn tại. Nhìn vào chương trình thì bạn hiểu ý đồ của mình là gọi tới hai thuộc tính domain
và author
, tuy nhiên theo nguyên tắc thì trong thần hàm closure đối tượng this là một phàm vi khác hoàn toàn.
Để giải quyết vấn đề này thì ta sẽ khai báo một biến đại diện cho đối tượng this
, lúc này ta sẽ sử dụng bình thường trong hàm closure.
Như vậy ta khai báo biến _self là giải quyết được vấn đề, cách này chỉ dành cho những bạn không biết đến hàm bind.
Ngoài cách xử lý thông thường trên thì trong ES5 cung cấp hàm bind()
dùng để gán dữ liệu vào đối tượng this của hàm đang sử dụng. Quay lại ví dụ trên thì ta sẽ code nhu sau:
Bạn hãy chạy thử để xem kết quả nhé, và như vậy hàm bind()
sẽ đưa dữ liệu từ bên ngoài vào trong hàm.
Bây giờ mình thử bind một kiểu dữ liệu khác xem thế nào nhé.
Chạy lên bạn sẽ thấy kết quả như sau:
Như vậy khi bạn truyền bất kì một loại dữ liệu nào trong tham số của hàm bind
thì đối tượng this
sẽ nhận chính dữ liệu đó.
Trước đây khi viết Javascript mình đã rất thắc mắc sự khác biệt và khi nào nên sử dụng hàm call() và hàm apply(), đương nhiên Javacript không dư thừa đến mức tạo ra hai hàm có công dụng y chang nhau. Nếu bạn cũng đang có cùng thắc mắc này thì hãy cùng mình làm sáng tỏ trong bài viết này nhé.
Hàm call()
dùng để thực thi một hàm nào đó với các tham số truyền vào (nếu có), hàm này được tích hợp sẵn trong các đối tượng là function.
Ví dụ: Ví dụ với hàm call()
, bạn hãy thử debug từng tham số để hiểu rõ hơn.
Trong đó:
Đây là cách gọi với hàm call, với chương trình này thì ta viết bằng cách thông thường như sau:
Câu hỏi đặt ra là tại sao không gọi hàm với cách bình thường mà phải sử dụng hàm call cho dài code ra? Ok ta cùng tìm hiểu công dụng của hàm call rồi từ đó rút ra kết luận nhé. Dưới đây là một số cách sử dụng hàm call rất thông dụng.
Nếu bạn muốn tạo nhiều đối tượng có hàm khởi tạo giống nhau thì hãy sử dụng hàm call để thực hiện, lúc này chương trình sẽ gọn hơn (nhưng khó hiểu hơn với newbie :)).
Giả sử bạn tạo một hàm anonymous không có tên, lúc này bạn có thể sử dụng hàm call để thực thi hàm đó.
Trong một hàm thì this
chính là hàm đó, tuy nhiên bạn hoàn toàn có thể thay đổi đối tượng this trong hàm bằng cách sử dụng hàm call.
Nếu bạn đối tượng this
trong hàm showMessage chính là nó thì bạn hãy sử dụng cách sau:
Hàm apply có công dụng giống như hàm call, tuy nhiên về cú pháp thì có một chút khác biệt như sau:
call()
là đối tượng this
, tiếp theo chính là các tham số của hàm cần gọi.apply()
là đối tượng this
, tham số tiếp theo là một mảng chứa các tham số của hàm cần gọi.Như vậy sự khác biệt ở đây chính là tham số truyền vào.
Chạy lên kết quả sẽ không khác gì nhau.
Về cách sử dụng thì bạn tham khảo các cách mà mình đã trình bày ở hàm call()
nhé.
Anonymous functions hay còn gọi là hàm ẩn danh, là một hàm được sinh ra đúng vào thời điểm chạy của chương trình. Thông thường khi bạn khai báo một hàm thì trình biên dịch sẽ lưu lại trong bộ nhớ nên bạn có thể gọi ở trên hay dưới vị trí khai báo hàm đều được, nhưng với anonymous functions thì nó sẽ được sinh ra khi trình biên dịch xử lý tới vị trí của nó. Anonymous functions được khai báo bằng cách sử dụng toán tử thay vì sử dụng cú pháp định nghĩa hàm thông thường. Xét ví dụ cách khai báo hàm thông thường dưới đây:
Nhưng nếu bạn khai báo bằng cách sử dụng hàm ẩn danh (anonymous function) thì cú pháp sẽ như sau:
Nghĩa là bạn sẽ sử dụng toán tử =
để khai báo hàm ẩn danh và gán nó vào đối tượng showDomain
.
Như ở trên mình có trình bày, anonymous được khởi tạo tại thời điểm chương trình chạy, điều này khác hoàn toàn với hàm được định nghĩa bài bản.
Xét ví dụ với cách tạo hàm bình thường dưới đây:
Trong ví dụ này cho dù bạn gọi hàm ở phía trên hay dưới đều hoạt động tốt là vì chương trình đã lưu hàm đó vào bộ nhớ. Nhưng nếu ta sử dụng anonymous function như ví dụ dưới đây sẽ bị lỗi ngay.
Điều này nghe hơi lạ vì làm thế nào để gọi đến một hàm mà nó lại không có tên? Với các tạo thông thường thì ta phải có tên hàm, còn với anonymous thì bạn có thể sử dụng tên biến để thay thế cho tên hàm, hoặc bạn có thể sử dụng hàm call() để invoke (thực thi).
Với hàm không tên bạn phải bao quanh bằng cặp ()
thì mới sử dụng hàm call
được.
Nếu bạn cố tình gán tên hàm vào trong anonymous function thì tên đó sẽ không tồn tại, ví dụ:
Tùy vào từng trường hợp mà anonymous function rất hữu ích, nếu bạn cần một function ngay tại một thời điểm thì nó rất hữu ích, hoặc bạn muốn thực hiện một callback function thì nó cũng rất hữu ích.
Ví dụ: sử dụng callback function
Javascript rất hay nhưng việc lạm dụng nó hoặc code không tối ưu thì rất nguy hiểm bởi vì việc tràn bộ nhớ RAM là có thể xảy ra. nên nếu bạn đang lập trình cho một website và khi chạy thì thấy bị rò rỉ bộ nhớ một cách nghiêm trọng thì rất có thể là do ứng dụng của bạn gây ra.
Bài viết dưới đây sẽ tham khảo bài của ông Volkan, trước đây ông điều hành trang sarmal.com (giờ site đã chết), ông đã viết ra bài này trong quá trình điều hành và sử dụng website của mình.
Vấn đề rò rỉ bộ nhớ không chỉ xuất hiện ở Internet Explorer mà mọi browser như Chrome, Firefox, Netscape, Opera đều xảy ra hiện tượng này, tuy nhiên theo bản thân ông thì IE là ông vua của hiện tượng này.
Không phải chỉ mỗi ông ghét IE mà hầu như mọi lập trình viên trên thế giới đều ghét IE, không phải vì ganh tị mà về tính năng, giao diện của IE rất hạn chế, kém cỏi hơn rất nhiều so với các trình duyệt khác. ở Việt Nam thì sử dụng thông dụng nhất vẫn là Chrome, Firefox và Cốc Cốc (một sản phẩm của VN).
Mỗi trình duyệt đều có một ưu điểm và nhược điểm riêng. Chẳng hạn như Firefox thì tốn rất nhiều RAM cho việc khởi động, nó không tốt cho việc xử lý chuỗi và mảng, còn Opera thì có thể sẽ bị chết nếu bạn viết một tập lệnh DHTML phức tạp đến mức gây nhầm lẫn cho công cụ convert của nó.
Và trong bài viết này ông Volkan sẽ tập trung vào việc phân tích sự rò rỉ bộ nhớ trên trình duyệt IE, và có thể áp dụng vào các trình duyệt khác.
Hãy bắt đầu bằng một ví dụ đơn giản dưới đây, lý do ta insert một đoạn code JS inline.
Đầu tiên đoạn code parentDiv=document.createElement(...);
sẽ tạo một thẻ div
và nó sẽ hoạt động tạm thời trong phạm vi của script đó, tiếp theo parentDiv.bigString=...
gắn một đối tượng có giá trị rất lớn vào thẻ div
. Khi phương thức LeakMemory()
được gọi thì một phần tử DOM sẽ khởi tạo, và gán giá trị rất lớn đó vào thẻ div, sau đó nó sẽ kết thúc mọi thứ vì đây là một hàm nên phạm vi hoạt động của các biến là cục bộ.
Khi bạn chạy thử và soi bộ nhớ thì sẽ thấy lúc chạy chương trình này thì RAM và CPU đã thực sự bị rò rỉ.
Nếu bạn cảm thấy việc đó là quá bình thường thì hãy thử chạy chương trình đó 10 lần, 100 lần và trải nghiệm xem thé nào? có thể máy tính của bạn sẽ đơ và buộc phải khởi động nguội đấy. Bây giờ ông Volkan đã tăng việc xử lý của hàm LeakMemory lên 5000 lần vào cùng một thời điểm để xem kết quả thế nào.
Chạy chương trình này thì bạn sẽ thấy sơ đồ của RAM và CPU như sau:
Nếu bạn thử chạy ứng dụng demo này ở nhiều tab khác nhau thì rất có thể bại sẽ phải khởi động lại trình duyệt, nặng hơn là khởi động lại máy tính, điều này gây phiền toái cho khách hàng khi sử dụng website rất nhiều.
Bây giờ ta thử sửa lại đoạn code đó với việc lặp 5000 lần nhưng chỉ khởi tạo thẻ div thôi, không gán dữ liệu quá lớn vào thì kết quả sẽ như thế nào?
Và dưới đây là biểu đồ bộ nhớ.
Như hình này thì bạn thấy việc rò rỉ bộ nhớ là khoong có, bởi vì sơ đồ hoạt động theo một đường thẳng, còn việc nó cao là do ông đã chạy một số ứng dụng khác => không có sự rò rỉ bộ nhớ trong trường hợp này.
Vậy, chúng ta hãy thay đổi code một cách đơn giản và hạn chết việc lạm dụng quá nhiều JS trên trang để tránh tình trạng tốn tài nguyên của khách hàng khi sử dụng website.
Khi làm việc với Javascript Object thì bạn sẽ gặp rất nhiều đoạn code có sử dụng từ khóa this
. Nếu bạn đã từng học lập trình hướng đối tượng thì không còn xa lạ gì với từ khóa này. This
được hiểu là đối tượng hiện tại, nơi mà phạm vi của dòng code đó đang đứng.
Trong lập trình hướng đối tượng thì this
là một từ khóa dùng để trỏ đến đối tượng hiện tại, qua đó ta có thể truy cập đến những phương thức và thuộc tính trong đối tượng đó. Điều kiện là bạn sẽ phải đặt this
trong phạm vi chương trình của đối tượng.
Nhưng this trong javascript thì khác, bạn có thể đặt this
ngay cấp ngoài cùng của chương trình, vì cấp bật của BOM thì đối tượng cao nhất chính là đối tượng Windows.
Ví dụ: Các bạn hãy mở trình duyệt lên và chạy đoạn code sau:
Kết quả trả về chính là đối tượng windows trong Javascript.
Nói cách khác, khi bạn sử dụng this ở cấp ngoài cùng của chương trình thì nó chính là đối tượng window, vì vậy bạn có thể sử dụng nó thay thế cho windows object. Ví dụ như hai đoạn code dưới đây là tương đương.
https://thuanhoaonline.com
Đây chỉ là một trường hợp đơn giản. Bây giờ chúng ta sẽ tìm hiểu một số trường hợp khác nhé.
Theo quy tắc thì từ khóa this khi đặt trong một function thì nó phải trỏ đến function đó. Tuy nhiên, thực tế thì lại có hai trường hợp xảy ra như sau:
Trường hợp 1: Nếu không bật chế độ strict mode thì javascript sẽ hiểu this
là một biến toàn cục, và cấp cao nhất như mình đã nói ở phần 1 chính là đối tượng window.
Chính vì vậy bạn dễ dàng bổ sung các thuộc tính cho đối tượng window:
Trường hợp 2: Nếu bật chế độ strict mode thì từ khóa this trong hàm là một biến chưa được định nghĩa, nên giá trị của nó là undefined.
Vì vậy nếu bạn bạn cố gắng thêm thuộc tính cho this
thì sẽ bị lỗi ngay, vì this
không phải là một object.
Lưu ý: Khi bạn sử dụng hàm như một constructor thì đối tượng this chính là các thể hiện (instance) của hàm.
Ví dụ
Chi tiết thì bạn có thể xem ở bài constructor trong javascript.
Khi gán hành động cho các sự kiện javascript thì this
chính là đối tượng html đang được gán sự kiện đó.
Ví dụ 1: Khi bạn gán một hành động cho sự kiện click
, nếu trong hành động đó có sử dụng từ khóa this
thì lúc này this
chính là đối tượng html dom mà người dùng đã click
Như vậy, trong ví dụ này this
chính là thẻ HTML mà chúng ta đang xử lý, và đó chính là thẻ HTML có id="btn"
.
Ví dụ 2: Truyền đối tượng this khi gọi sự kiện trong thuộc tính của thẻ html
Trong ví dụ này chúng ta truyền this
ở dạng một tham số nên ở hàm xử lý show_type()
tuy ta đặt tên tham số là obj
nhưng bản chất nó chính là đối tượng this
.
Javascript object là một tính năng rất quan trọng trong lập trình javascript. Nếu bạn đã từng sử dụng qua các thư viện như Angular, ReactJS thì sẽ thấy hầu như khi làm việc với js chúng ta đều phải thông qua các object.
Ví dụ dưới đây mình đã tạo một object Persion, hai thuộc tính là name và age, và hai phương thức tên là showInfor và setInfor.
Trong các phương thức setInfor
và showInfor
mình sẽ sử dụng từ khóa this
để lấy các thuộc tính.
Giống như object, từ khóa this trong javascript cũng có thể được sử dụng trong các class (ES6). Lúc này thì this chính là instance mà bạn đã tạo ra từ class đó.
Ví dụ
Không giống như function bình thường. Arrow function không tồn tại đối tượng this, bởi vậy khi bạn sử dụng thì nó sẽ lấy đối tượng global window.
Kết quả:
Trong Javascript, prototype được đề cập đến một hệ thống dữ liệu của đối tượng, qua đó cho phép bạn xác định các thuộc tính và phương thức trên các đối tượng có thể truy cập được qua các phiên bản của đối tượng (instance). Prototype có liên quan chặt chẽ với lập trình hướng đối tượng, nên nếu bạn đã từng học qua các ngôn ngữ như C++ hay Java thì sẽ là một lợi thế rất lớn. Còn nếu bạn chưa hiểu thì mình xin giải thích một chút như sau.
Lập trình hướng đối tượng là kỹ thuật đưa các đối tượng trong thực tế vào trong chương trình máy tính thông qua những dòng code. Tùy vào các chương trình mà có các đối tượng khác nhau. Ví dụ bạn đang xây dựng chương trình quản lý sinh viên thì chúng ta có các đối tượng như: Sinh viên, Khoa, Lớp, Trường, Giáo Viên ...
Quay trở lại vấn đề chính là javascript prototype nhé.
Giả sử mình có các mảng như sau:
Bây giờ mình sẽ sử dụng lệnh console.log để xem biến array có những gì nhé.
Như bạn thấy, ngoài các giá trị mà mình đã gán vào thì không có một phương thức hay thuộc tính nào khác, nhưng ta vẫn có thể sử dụng các phương thức như concat
, slice
, filter
, và map
trên đối tượng array này.
Tại sao lại như vậy?
Bởi vì các phương thức trên nằm trong phần prototype của array. Bạn có thể xem nó bằng cách mở rộng mục __proto__ mà mình đã chụp hình ở trên.
Trên là danh sách các thuộc tính và phương thức có sẵn trong prototype của Array. Vì vậy, mỗi khi bạn tạo một array mới thì nó đều được tích hợp sẵn, và array mới này ta gọi là một thể hiện (instance) của đối tượng Array.
Khi bạn gọi array.map thì javascript sẽ tìm phương thức map trong danh sách thuộc tính của nó. Nếu không tồn tại thì nó sẽ dò tìm trong phần prototype.
Vì vậy, định nghĩa chính xác của prototype đó là: Prototype là một đối tượng chứa các thuộc tính và phương thức, qua đó các instance sẽ tìm kiếm trong trường hợp thuộc tính cần tìm không tồn tại trong instance.
Trong javascript, khi bạn truy cập vào một thuộc tính / phương thức thì nó sẽ hoạt động theo các bước như sau:
Bước 1: JavaScript sẽ kiểm tra xem thuộc tính có sẵn bên trong đối tượng hay không. Nếu có, JavaScript sử dụng thuộc tính này và kết thúc, nếu không có thì nhảy qua bước 2.
Bước 2: Javascript sẽ kiểm tra trong prototype của đối tượng đó có hay không? Nếu có thì sử dụng và kết thúc, không có thì nhảy qua bước 3.
Bước 3: Nếu đến bước 2 mà vẫn không tìm thấy thì sẽ có hai trường hợp xảy ra:
Bây giờ ta sẽ làm một ví dụ để mô phỏng cho sơ đồ hoạt động của prototype trong javasript nhé.
Mình sẽ sử dụng Number Constructor để tạo ra một con số, sau đó in ra xem nó có những gì nhé.
Như bạn thấy trong hình, đối tượng number không tồn tại một thuộc tính nào cả, nhưng prototype của nó lại có rất nhiều phải không nào?
Bây giờ mình sẽ sử dụng phương thức toString để chuyển đổi number này thành một chuỗi.
Vì phương thức toString
không tồn tại trong number object
nên nó sẽ tìm trong prototype
, và trong prototype
có phương thức toString
nên nó sử dụng luôn, kết quả là ta đã chuyển được number sang chuỗi string.
Bây giờ mình sẽ định nghĩa một phương thức toString vào đối tượng bằng cách sau:
Đoạn code này sẽ in ra kết quả như sau:
Như bạn thấy, ta có cả hai phương thức toString, một là của đối tượng, một là nằm trong prototype của đối tượng. Nếu như xét về độ ưu tiên khi gọi đến phương thức toString thì javascript sẽ sử dụng cái đầu tiên. Ta hãy thử xem ngay bây giờ nhé.
Kết quả đúng như ta mong đợi.
Lưu ý: Đây là quy tắc quan trọng và mọi đối tượng đều áp dụng quy tắc này nhé các bạn.
Prototype chain là một thuật ngữ về quá trình truy xuất đến các thuộc tính giữa các lớp kế thừa với nhau.
Nếu bạn chưa biết khái niệm kế thừa thì xem trong bài viết extends trong javascript nhé.
Giả sử mình có lớp Developer như sau:
Bây giờ mình tạo mới một instance của đối tượng Developer, và sau đó console.log xem nó có gì.
Kết quả:
Giải thích:
A
thì object này sẽ được thay thế bằng lớp A
đó.Giả sử mình sẽ định nghĩa một lớp Human
, sau đó tạo thêm một lớp Developer
kế thừa từ lớp Human
.
Human
có phương thức sayHello
.Developer
có phương thức code
.Đây là code cho lớp Human.
Và đây là code cho lớp Developer.
Vì lớp Developer
được kế thừa từ lớp Human
nên nó tồn tại hai phương thức sayHello
và code
.
Thử console.log xem có gì trong đối tượng cuong
này nhé.
Giải thích kết quả như sau: Instance cuong
sẽ có ..
Developer
, có hai thuộc tính là firstName
và lastName
.code
.sayHello
Khi bạn gọi một thuộc tính thì nó sẽ tìm lần lượt theo thứ tự như trong hình sau:
Nó sẽ áp dụng quy tắc mà chúng ta đã học ở phần 2:
Mỗi đối tượng trong javascript được chia làm hai phần, thứ nhất là thuộc tính của đối tượng, thứ hai là prototype của đối tượng.
Khi bổ sung một thuộc tính cho đối tượng thì bạn có thể chọn một trong hai cách trên, cụ thể như sau:
Tuy nhiên, mỗi cách lại có công dụng khác nhau, và tùy thuộc vào từng trường hợp mà bạn chọn cách cho phù hợp.
Trường hợp 1: Thêm trực tiếp vào đối tượng
Cách này thì thuộc tính thêm sử dụng được trong instance mà bạn đã thêm.
Trường hợp 2: Thêm vào prototype
Cách này thì thuộc tính thêm có thể sử dụng trong mọi instance.
Nếu bạn đã từng học qua Java hay một ngôn ngữ lập trình hướng đối tượng khác thì chắc hẳn đã gặp khái niệm constructor rồi phải không nào? Đây là kỹ thuật tạo ra các hàm khởi tạo cho các đối tượng.
Với javascript thì có một chút khác biệt về định nghĩa vì nó không tồn tai khái niệm về lớp. Trong javascript, constructor là kỹ thuật giúp tạo ra các thuộc tính và phương thức, và ta có thể sử dụng từ khóa new để khởi tạo một thể hiện của đối tượng.
Constructor trong js là một kỹ thuật giúp ta có thể thêm các phương thức và thuộc tính cho một function, lúc này cách hoạt động của function này giống như một đối tượng, cụ thể là:
Trước tiên, các bạn hãy xem nguyên nhân tại sao constructor ra đời bằng cách so sánh một vài chương trình nhé.
Giả sử mình có một hàm Person như dưới đây.
Bây giờ mình muốn tạo ra một người khác thì sẽ định nghĩa thêm một hàm nữa như sau:
Nếu mình muốn tạo 1000 người thì ... phải tạo 1000 hàm phải không các bạn? Điều này thật là kinh khủng và chương trình sẽ không chuyên nghiệp.
Để giải quyết vấn đề trên thì mình sẽ sử dụng từ khóa new để khởi tạo ra nhiều thể hiện khác nhau của hàm Person.
Trước tiên hãy tạo một hàm như sau:
Bây giờ muốn tạo ra 2 người khác nhau thì chỉ việc sử dụng từ khóa new:
Mỗi người sẽ có những đặc điểm và hành động khác nhau, vì vậy ta cũng có thể thêm thuộc tính cho chúng ngay trong function. Ví dụ dưới đây mình thêm thuộc tính name và một hành động showName.
Từ khóa this sẽ trỏ đến đối tượng hiện tại, và đó chính là các thể hiện của hàm Person.
Ngoài ra, nếu bạn gọi trực tiếp hàm như sau:
Thì đối this chính là đối tượng window chứ không phải là đối tượng hiện tại.
Khác hoàn toàn với object, bạn không thể thêm thuộc tính ở bên ngoài hàm như sau:
Javascript class được giới thiệu từ ES6, điều này giúp javascript trở thành một ngôn ngữ cực kì khó bởi nó có quá nhiều điều bí ẩn. Nếu trước đây JS được đánh giá là ngôn ngữ chỉ làm được ở client thì nay ta có thể sử dụng nó ở backend.
Nói đến class thì phải có hàm khởi tạo, cú pháp như sau:
Như vậy là chúng ta đã tìm hiểu xong hàm constructor trong Javascript qua hai phần, thứ nhất là constructor function và thứ hai là hàm constructor của class trong javascript. Qua đó, ta thấy rằng nếu muốn làm việc với đối tượng thì các bạn nên sử dụng ES6 class, còn nếu sử dụng function thì cần xem kỹ vấn đề về prototype và chế độ strict mode.
Class là một kỹ thuật trong lập trình dùng để cài đặt một đối tượng trong thực tế, qua đó ta có thể thêm các tính chất và hành động của đối tượng vào trong class, và chúng được thể hiện qua các thuộc tính và phương thức.
Class trong javascript là một kỹ thuật giúp ta tạo ra các lớp mẫu chương trình để thể hiện cho các đối tượng. Qua đó, ta có thể thêm các hành động và thuộc tính của đối tượng vào trong lớp thông qua hai khái niệm thuộc tính và phương thức.
Ví dụ, đối tượng Sinh Viên sẽ có các đặc điểm như: Mã sinh viên, tên, tuổi, quê quán ... Và các hành động như: Thêm / xóa / sửa và thi cử.. Lúc này ta sẽ cài đặt đối tượng sinh viên này trong javascript thông qua class.
Thực tế thì bạn có thể sử dụng constructor function, nhưng thời điểm hiện tại thì người ta vẫn ưu tiên sử dụng class hơn, bởi nó thể hiện rõ kỹ thuật lập trình hướng đối tượng OOP trong ngôn ngữ này.
Cú pháp tạo class như sau:
Các thuộc tính sẽ được định nghĩa trong hàm khởi tạo constructor ngay bên trong class.
Để khai báo thuộc tính cho class thì chúng ta có hai cách. Thứ nhất là khai báo trong hàm khởi tạo constructor, thứ hai là khai báo trực tiếp.
Cách 1: Khai báo trong hàm constructor.
Cách 2: Khai báo trực tiếp ngay bên trong class.
Trong class, khi tạo một phương thức thì bạn không cần sử dụng từ khóa function nhé. Tiếp theo là cú pháp tạo function như chúng ta đã học.
Sau đây là một ví dụ tổng hợp.
Ví dụ: Tạo một đối tượng User gồm thuộc tính name và một phương thức sayHi.
Hàm constructor
sẽ được khởi chạy mỗi khi bạn sử dụng từ khóa new để khởi tạo class. Vì vậy, thuộc tính name sẽ được bổ sung vào đối tượng this (this chính là đối tượng hiện tại).
Lưu ý: Bạn đừng nhầm lẫn class với object nhé.
Để tạo một class thì bạn nên thực hiện theo các bước dưới đây:
Ví dụ: Xây dựng class Person với mục đích khai báo y tế trong mùa dịch Covid - 19.
Bước 1: Đối tượng này Person sẽ cần có tên, địa chỉ, số điện thoại, số căn cước. Ta sẽ có một hành động là khai báo y tế.
Bước 2: Cài đặt chương trình.
Trong javascript, class là một function. Ta có thể kiểm tra bằng cách sử dụng từ khóa typeof.
Kết quả trả về sẽ là function. Vì vậy, sẽ có một đối tượng tên là User, trong đối tượng này sẽ có các prototype.
Như vậy, ta có thể sử dụng function để khai báo class User thay thế cho đoạn code trên như sau:
Để hiểu rõ hơn về vấn đề này thì bạn hãy xem thêm trong bài constructor function nhé.
Ngoài cách sử dụng cơ bản như trên thì ta có thêm một vài biến thể khác nữa của class.
Giống như một function, bạn có thể khai báo class và gắn trực tiếp nó vào một biến như sau:
Trường hợp này bạn cũng có thể đặt một cái tên ảo cho class, và tên này chỉ được phép sử dụng bên trong class mà thôi.
Với hai cách dùng trên thì ta có thể tạo ra một cách dùng khá hay khác, đó là khởi tạo class trong một hàm và cho hàm đó return về chính class đó.
Như vậy hàm makeClass sẽ có nhiệm vụ là khởi tạo một class theo ý mình.
Getters và setters là hai khái niệm khá hay trong lập trình oop, cụ thể như sau:
Javascript cung cấp hai từ khóa get và set giúp ta định nghĩa hai loại phương thức này.
Ví dụ
Trong hàm constructor mình đã thiết lập giá trị cho thuộc tính name như sau:
Lúc này javascript sẽ tự gọi đến phương thức set name, nên nếu bạn nhập ngắn hơn 4 ký tự thì sẽ bị thông báo lỗi.
Còn khi mình gọi đến thuộc tính của lớp alert(user.name);
thì javascript sẽ tự động thêm vào dòng chữ "Tên của bạn là ...".
Trong bài này chúng ta sẽ tìm hiểu cách sử dụng từ khóa extends trong Javascript, qua đó sẽ giúp bạn hiểu được tính kế thừa và cách ghi đè phương thức trong js.
Kể từ ES6 thì javascript đã trở thành một ngôn ngữ siêu khó, ta có thể tạo đối tượng bằng nhiều cách khác nhau như function và class. Và với sự ra đời của class thì chúng ta có thêm các khái niệm về tính kế thừa (inheritance) và ghi đè phương thức (overriding).
Trong javascript, extends là một từ khóa dùng để khai báo một lớp được kế thừa từ một lớp khác. Qua đó lớp kế thừa có thể sử dụng những phương thức và thuộc tính của lớp được kế thừa.
Nếu lớp A kế thừa từ lớp B thì lớp A ta gọi là lớp con, còn lớp B ta gọi là lớp cha.
Câu hỏi được đặt ra là tại sao cần tính kế thừa? Câu trả lời như sau:
Thực tế thì nhiều đối tượng có thể có những thuộc tính và hành động giống nhau, và bắt buộc mỗi đối tượng chúng ta phải cài đặt một class riêng lẻ, thì việc kế thừa sẽ giúp cho việc cài đặt các đối tượng trở nên đơn giản hơn.
Ví dụ: Ta có đối tượng động vật, trong động vật lại có nhiều loại động vật khác nhau như sư tử, rùa.
Những đặc tính và hành động trên cũng có trong hai đối tượng sư tử và rùa, vì vậy hai đối tượng này ta sẽ khai báo kế thừa đối tượng động vật.
Bây giờ ta sẽ xây dựng ba class như ví dụ mà mình đã nói ở phần 1, bằng cách sử dụng từ khóa extends trong js.
Khai báo lớp Animal.
Khai báo lớp Lion kế thừa từ lớp Animal: Lion có một đặc tính riêng biệt mà động vật khác không có, đó là Gầm. Vì vậy ngoài các hành động run
và stop
thì mình sẽ khai báo thêm hành động roar
.
Khai báo lớp con thỏ kế thừa từ lớp Animal: Con thỏ là một động vật nhỏ bé nên nó hay bị các động vật khác tấn công, vì vậy nó có thêm hành động ẩn nấp.
Như vậy lớp con sẽ có đầy đủ những phương thức và thuộc tính của lớp cha, đây là một quy tắc hiển nhiên trong việc phân cấp cha con.
Có một vấn đề rất quan trọng trong việc kế thừa, đó là mức độ truy cập đến các thuộc tính và phương thức trong lớp. Vấn đề này mình sẽ trình bày trong bài viết private và protected trong js.
Overriding là kỹ thuật ghi đè hàm (viết lại phương thức) của lớp cha.
Đôi khi những hành động bên trong lớp cha sẽ không thích ứng với các lớp con, lúc này lớp con có thể ghi đè lại các phương thức của lớp cha thông qua kỹ thuật Overriding.
Ví dụ với lớp con thỏ dưới đây, hành động chạy (run) của nó thường sẽ đi kèm với hành động ẩn nấp (hide). Vì vậy ta có thể viết lại hàm run trong lớp Rabbit như sau:
Tòm lại:
hide
đã bị ghi đè lại vì nó được khai báo ngay trong lớp con.Nếu bạn muốn overriding hàm khởi tạo constructor thì cần chú ý một số vấn đề như sau.
Trường hợp này là ok.
Trường hợp dưới đây là sai, bởi vì bạn đã tạo một constructor ở lớp con, nên bắt buộc phải sử dụng từ khóa super.
Sửa lại như sau:
Mặc dù hàm cha không có hàm constructor nhưng bạn cũng phải sử dụng từ khóa supper.
Static xuất hiện trong các ngôn ngữ lập trình hướng đối tượng là quá bình thường. Nhưng với javascript thì khác, static chỉ xuất hiện kể từ phiên bản ES6 - 2015. Vì vậy, với những trình duyệt cũ thì sẽ không hoạt động.
Static là một từ khóa giúp ta khai báo những phương thức tĩnh hoặc thuộc tính tĩnh trong các class của javascript. Khi được khai báo static thì phương thức / thuộc tính đó có thể được gọi đến mà không cần phải sử dụng từ khóa new để khởi tạo đối tượng.
Cú pháp như sau: Ta chỉ cần đặt từ khóa static đằng trước phương thức / thuộc tính là được.
Static method là những phương thức có từ khóa static phía trước. Các phương thức như vậy được gọi là static method.
Ví dụ
Điều này giống như bạn tạo một phương thức từ bên ngoài lớp như sau:
Qua 2 ví dụ này thì ta thấy từ khóa this
bên trong static method chính là class của nó.
Tuy nhiên, ta không thể sử dụng từ khóa this để gọi đến một phương thức không phải là static.
Static properties hay còn gọi là thuộc tính tĩnh, là những thuộc tính có đặt từ khóa static phía trước.
Những thuộc tính như vậy ta có thể truy cập đến mà không cần phải khởi tạo đối tượng.
Nếu bạn sử dụng this
để truy cập đến một thuộc tính static thì nó sẽ trả về undefined. Bởi vì một thuộc tính static sẽ không nằm trong danh sách thuộc tính của class, mà nó được lưu trữ trong constructor của class.
Thử sử dụng lệnh console.log để xem trong biến a có gì nhé.
|
Như bạn thấy ở trong hình, thuộc tính static publisher đã được lưu trữ trong constructor. Vì vậy, nếu bạn muốn sử dụng this
để truy cập đến nó thì phải sử dụng cú pháp this.constructor.publisher
:
Thuộc tính / phương thức static trong javascript là duy nhất nhé các bạn. Vì nó được lưu trữ trong constructor của class, mà dữ liệu trong constructor là duy nhất, nghĩa là những thay đổi bên trong constructor là ảnh hưởng đến đối tượng chứ không phải instance.
Đọc tới đây chắc nhiều bạn không hiểu instance là gì, thì mình xin giải thích như sau:
Ví dụ mình có lớp A, và mình tạo 2 biến như sau:
Hai biến instance1 và instance2 ta gọi là các thể hiện (instance) của đối tượng A.
Quay lại vấn đề chính. Giả sử mình có class Article như sau:
Trong đó, hàm change()
mình tạo ra với mục đích thay đổi dữ liệu cho thuộc tính static publisher
.
Bây giờ mình chạy đoạn code dưới đây:
Như bạn thấy,
b.change()
thì giá trị của publisher đã thay đổib.show()
và a.show()
thì đều cho ra kết quả giống nhau, và đó là giá trị mới thay đổi.Điều này chứng tỏ thuộc tính publisher có giá trị duy nhất, bởi nó là dữ liệu của class chứ không phải trên instance.
Một điều khá thú vị nữa, đó là nếu một thuộc tính là static thì trong kế thừa sẽ như thế nào? Để hiểu vấn đề này thì hơi hại não một chút, nên các bạn cần tập trung để xem những giải thích của mình nhé.
Đầu tiên bạn phải hiểu dữ liệu __proto__.constructor
của một instance.
Khi bạn tạo một instance thì instance đó sẽ có một thuộc tính tên là __proto__
. Trong __proto__
sẽ có một thuộc tính tên là constructor
. Đây chính là thông tin của class dùng để tạo ra biến instance đó.
Kêt quả trên cửa sổ console như sau:
Mình thử dùng phép toán so sánh thì kết quả là true:
Bây giờ mình sẽ cho class Post kế thừa một class khác, sau đó dùng console.log để xem:
Kết quả:
Như vậy class Article sẽ nằm trong hai vị trí:
__proto__.constructor.__proto__
của Post__proto__.__proto__.constructor
của PostTa thử dùng phép so sánh xem có chuẩn không nhé.
Cấu trúc dữ liệu của các đối tượng trong javascript quả là phức tạp phải không các bạn? Mục đích mình giải thích ở trên là giúp các bạn hiểu được một class kế thừa thì nó có cấu trúc như thế nào.
Bây giờ ta sẽ đi vào vấn đề chính nhé. Vẫn tiếp tục lấy hai class dưới đây làm ví dụ:
Bây giờ thử in thuộc tính publisher
xem giá trị thế nào:
Cả hai đều cho một kết quả. Bây giờ ta thử thay đổi giá trị của publisher
.
Trường hợp này đã xuất hiện sự sai lệch. Nếu lớp con thay đổi giá trị static của lớp cha thì nó chỉ thay đổi cho lớp con mà thôi.
Bây giờ ta thử tạo mới một instance, sau đó log ra xem có gì nhé.
Các bạn thấy có sự sai lệch rồi phải không? Có vẻ như do mình sử dụng phép gán nên javascript sẽ tạo ra một thuộc tính static mới trên lớp Post. Vì vậy khi truy cập thì javascript vẫn ưu tiên lấy ở lớp Post.
Bây giờ ta thử hoán đổi hai class xem thế nào.
Kết quả là dữ liệu ở cả hai class đều thay đổi.
Ta thử chạy lệnh dưới đây để xem điêu gì đã xảy ra nhé.
Mọi thứ suôn sẻ, trong lớp Post không có thuộc tính publisher, nên khi gọi đến thuộc tính này thì cả hai class đều lấy chung một thuộc tính ở lớp Article.
Private / protected và public là ba mức độ truy cập đến các thuộc tính và phương thức trong class. Nó giúp ta bảo vệ được dữ liệu bên trong các class, chỉ chia sẻ những dữ liệu cần thiết, hoặc cho phép chia sẻ đến các lớp con kế thừa từ nó.
Private là cấp độ bảo mật nhất, nó chỉ cho phép sử dụng bên trong class, còn bên ngoài không truy cập được.
Protected là cấp độ bảo mật thứ hai, nó chỉ cho phép sử dụng bên trong class và cả class kế thừa. Bên ngoài không truy cập được.
Mưc này thì rất khó cài đặt, bởi bản thân Javascript không hỗ trợ. Vì vậy, ta sẽ sử dụng getter và setter để tạo ra một thuộc tính protected.
Public thì khác, bạn có thể truy cập ở bất kì đâu. Đây là mức dễ dàng nhất.
Public là cấp độ lỏng lẻo nhất. Các thuộc tính và phương thức đều có thể truy cập được dù ở bên trong lớp hay bên ngoài lớp.
Những ví dụ mà từ trước đến giờ các bạn đã học đều thuộc dạng public.
Cấp độ protected thì khác, bạn chỉ được phép truy cập ở bên trong lớp đó và cả bên trong lớp kế thừa.
Vì javascript không hỗ tợ cấp độ này, vì vậy ta phải kết hợp một mẹo khá đơn giản.
_
. Theo quy ước thì khi sử dụng thuộc tính thì bạn phải bỏ đi dấu gạch dưới.Quy ước: Để khai báo protected thì ta đặt dấu gạch dưới đằng trước thuộc tính và phương thức.
Ví dụ: Tạo hai class Human và Student để demo cho protected.
Như bạn thấy, mặc dù mình đã thiết lập giá trị mới cho h.name
nhưng vẫn không có tác dụng.
Nhưng nếu bạn cố tình thiết lập như thế này h._name = "Nguyễn";
thì sẽ có tác dụng đấy nhé. Vì vậy, đây chỉ là một quy ước chung mà thôi.
Private là cấp độ bảo mật cao nhất, nó chỉ cho phép sử dụng bên trong lớp mà thôi. Ngay cả lớp kế thừa cũng không đươc phép sử dụng.
Để khai báo một thuộc tính là private thì ta thêm dấu thăng đằng trước.
Cấp độ này chỉ xuất hiện từ ES9.
Ví dụ: thuộc tính #name
là private nên không được phép sử dụng ở bên ngoài, ngay cả lớp kế thừa Student cũng không được phép.
Khi bạn xây dựng một ứng dụng nhỏ thì việc đặt mã javascript ở đâu, phân chia cấu trúc file như thế nào cho phù hợp, đó là điều mà ít người quan tâm. Nhưng khi dự án của bạn bự lên thì khác, nếu không phân chia cấu trúc thì rất khó cho việc nâng cấp và bảo trì sau này.
Nếu bạn đã từng học qua bộ môn kỹ thuật lập trình cơ bản thì sẽ có khái niệm lập trình hướng module. Đây là kỹ thuật phân chia các file trong dự án một cách khóa học nhất, giúp dự án trở nên sạch sẽ, dễ dàng mở rộng chức năng sau này.
Vậy module là gì mà lại quan trọng đến thế? Và module trong javascript được sử dụng như thế nào? Chúng ta cùng tìm hiểu ngay nhé.
Trong javascript, một module là một file javascript bình thường, chỉ đơn giản là file đó sẽ đặt một cái tên có ý nghĩa, và các dòng code bên trong cũng phục vụ cho ý nghĩa đó.
Ví dụ, bạn đang xây dựng ứng dụng quản lý sinh viên thì sẽ tạo ra các module như:
sinhvien.js
, là tập hợp những lệnh dùng để xử lý sinh viên.khoa.js
, là tập hợp những dòng lệnh dùng để xử lý các khoa của trường.Việc phân chia thành từng module (tức là từng file) và mỗi module có một công dụng giúp cho quá trình code nhanh hơn, mạch lạc hơn, nhìn chuyên nghiệp hơn.
Chẳng bạn đang quản lý thư viện, bạn sẽ phân chia sách thành nhiều kệ khác nhau, mỗi kệ là tập hợp những cuốn sách cùng chủ đề. Ví dụ như sách lớp 6, 7, 8, 9 sẽ chia làm 4 kệ khác nhau. Mỗi kệ như vậy ta gọi là một module.
Khái niệm module đã xuất hiện từ lâu, nhưng trong javascript thì nó chỉ được công nhận là có tính năng module này kể từ phiên bản ES5 2015. Trước đây, chúng ta vẫn thường tìm những giải pháp khác để hiện thực hóa module trên javascript, điển hình là các thư viện như Module Pattern, CommonJS, Asynchronous Module Definition (AMD).
Để khai báo và sử dụng module thì ta phải quan tâm đến hai từ khóa, thứ nhất là lệnh import trong js và thứ hai là lệnh export trong js.
Giả sử mình có một file tên là greet.js
chứa đoạn code dưới đây.
greet.js
Trong đó từ khóa export
dùng để khai báo dữ liệu được chia sẻ, và module khác muốn dùng hàm này thì chỉ việc gọi đến tên hàm là được. Nếu bạn không đặt từ khóa export thì dữ liệu sẽ ở phạm vi cục bộ trong module này.
Ngoài ra, bạn cũng có thể khai báo như sau:
Bây giờ, để sử dụng hàm greetPerson
bên trong module greet.js
thì có hai trường hợp.
TH1: Nếu dùng trong một file javascript thì sẽ làm như sau:
file_bat_ki.js
TH2: Nếu dùng trong file html thì bạn phải khai báo thẻ script
với thuộc tính type="module"
.
Như vậy chúng ta:
import
để gọi đến dữ liệu từ một module khác.Ví dụ như đây là một trường hợp lỗi, vì ta đã gọi đến tên dữ liệu không tồn tại.
Nếu bạn muốn import nhiều dữ liệu thì hãy đặt chúng cách nhau bởi dấu phẩy.
Ví dụ ta có file module.js
với nội dung như sau:
Để sử dụng nó trong một file khác thì ta sẽ làm như sau:
Có một số trường hợp bạn phải thay đổi tên cho thuộc tính và phương thức từ module, bởi vì trong chương trình chính đã tồn tại tên như vây, hoặc tên cũ quá dài nên bạn muốn rút gọn lại. Lúc này, tại file chính bạn có thể thay đổi tên để tránh vấn đề trùng tên (conflict name).
Có hai cách để xử lý vấn đề này. Thứ nhất là rename ngay tại module, và thứ hai là rename trong file chương trình chính.
Để đổi tên dữ liệu nào đó thì trong module bạn hãy khai báo như sau:
file module.js
Trong đó function1 và function là tên gốc, còn newName1 và newName 2 là tên mới.
Lúc này, trong file chương trình bạn sẽ sử dụng cú pháp như sau:
Ví dụ: Mình sẽ đổi tên cho hàm greetPerson trong module greet.js như sau.
File greet.js
Lúc này, trong file chương trình ta sẽ gọi như sau:
Cách này thường hay sử dụng nhất. Thường thì mỗi người sẽ có một quy tắc khai báo tên biến và tên hàm khác nhau, nên để họ tự đặt lại tên mới là giải pháp tốt nhất.
module.js
Đoạn code trên mình đã khai báo export hai hàm, đó là function1
và function2
.
Bây giờ để sử dụng trong chương trình thì bạn sẽ làm như sau:
main.js
Tóm lại: Để đổi tên dữ liệu trong module thì ta sẽ sử dụng từ khóa as.
Default export là cách khai báo một dữ liệu export mặc định, trong trường gọi đến mà không có thì module sẽ trả về dữ liệu default này.
Ví dụ trong file greet.js
có nội dung như sau:
greet.js
Trong đó hàm greet mình đã thiết lập nó là default export. Lúc này trong file chính sẽ gọi đến một dữ liệu trong module greet.js như sau:
Như bạn thấy, random_name
không hề tồn tại trong module greet.js
. Vì vậy, javascript sẽ lấy default export trả về cho random_name
.
ES6 ra đời đánh dấu một mốc lịch sử rất lớn của ngôn ngữ javascript. Có nhiều tính năng được bổ sung vào mà trước đây ta mơ ước cũng không có, điển hình là các khái niệm về lập trình hướng đối tượng class, extends, static, và module. Sự nâng cấp luôn tạo ra một giá trị mới hữu ích hơn cái cũ.
Module cũng vậy, nó ra đời nhằm giải quyết sự rườm ra của javascript, nên sẽ có những lợi ích sau đây:
Strict mode là chế độ biên dịch nghiêm ngặt, bắt buộc mọi thứ phải viết theo đúng chuẩn của javascript.
Bình thường thì bạn có thể chạy một chương trình mà trong đó các câu lệnh không có kết thúc bằng dấu hai chấm, hoặc sử dụng một biến mà không cần phải khai báo, .. đó là vì chế độ strict mode chưa được bật.
Khi sử dụng module thì nó rất nghiêm túc trong việc tuân thủ theo quy tắc này. Vì vậy, bạn hãy tuân thủ theo cú pháp mà javascript đưa ra nhé.
Nếu bạn chưa biết strict mode là gì thì xem tại bài viết Strict Mode trong javascript.
Nếu bạn sử dụng lênh script để import một file mà không thiết lập thuộc tính module thì dữ liệu ở các module có thể sử dụng lẫn nhau. Nhưng khi bạn sử dụng dạng module thì lại khác, dữ liệu ở mỗi module sẽ tách biệt hoàn toàn.
Ví dụ: Sử dụng thẻ script để import module.
Lúc này hai file user.js
và hello.js
là riêng biệt, không thể sử dụng dữ liệu của nhau.
Thậm chí khi bạn viết trên cùng một file html thì vẫn không sử dụng được của nhau.
Nếu một module đã import rồi, thì nếu những lần sau bạn vẫn tiếp tục import thì nó sẽ không làm gì cả, bởi dữ liệu của module đó đã được load.
Điều này làm tăng tốc độ xử lý, bởi trình biên dịch sẽ không mất thời gian xử lý cái mà nó đã load.
Ví dụ: Giả sử mình có module alert.js
như sau.
alert.js
Bây giờ mình sẽ import ở một file khác.
Lệnh import thứ hai sẽ không làm gì cả, bởi module alert.js đã được load ở lệnh đầu tiên.
Trong mỗi module, con trỏ this sẽ có giá trị là undefined. Lý do module luôn ở chế độ strict mode, mà ở chế độ này thì this là một biến chưa được khai báo.
Ví dụ dưới đây cho thấy nếu bạn sử dụng this trong module thì giá trị của nó là undefined, còn ở trong thẻ script bình thường thì nó là đối tượng windows.
Khi load một module thì nó luôn ở dạng defer, tức giống như bạn đặt thêm thuộc tính defer khi load một file javascript. Load theo dạng defer tức là dữ liệu javascript sẽ được load ngầm trong lúc trình duyệt vẫn đang load mã HTML và CSS.
Tóm lại, quy trình load của module sẽ tuân theo quy tắc sau:
Có một số trường hợp bạn phải sử dụng load bất đồng bộ async cho các module, ví dụ load những mã quảng cáo hoặc những chương trình không có sử dụng hệ tài nguyên trên website.
Nói qua một chút về load bất đồng bộ async và defer. Nếu defer là load ngầm và sẽ chờ khi toàn bộ mã HTML của trình duyệt đã load xong thì mới biên dịch module. Còn asycn thì khác, trình biên dịch sẽ load script và load đến đâu sẽ biên dịch đến đó.
Ví dụ dưới đây là mình load một module về analytics theo dạng async.
Bây giờ mình sẽ nói thêm một chút về cách dùng lệnh export trong javascript nhé.
Đầu tiên, nếu muốn export một hàm hoặc biến thì bạn chỉ cần đặt lệnh export phía trước hàm và biến đó là được.
Lưu ý là bạn không cần đặt dấu chấm phẩy đằng sau function hoặc class.
Thứ hai, bạn có thể sử dụng từ khóa export riêng biệt để khai báo dữ liệu.
Như bạn thấy, mình đã viết hai function bình thường, sau đó đặt nó trong lệnh export.
Thứ ba, bạn có thể đặt lại tên cho dữ liệu ngay lệnh export bằng cách sử dụng từ khóa as.
Tương tự, chúng ta cũng sẽ có một số cách dùng lệnh import trong js như sau.
Thứ nhất, để import một vài dữ liệu thôi thì ta sử dụng cú pháp sau.
Các dữ liệu sẽ được ngăn cách bởi dấy phẩy.
Thứ hai, nếu bạn muốn import toàn bộ dữ liệu thì hãy sử dụng dấu sao *.
Thứ ba, nếu bạn muốn đổi tên của dữ liệu trong lệnh import thì làm như sau.
Hôm nay chúng ta bắt đầu tìm hiểu một thứ mới hơn về Javascript vừa được ra mắt vào năm 2015 đó là ECMAScript, đây có thể coi là được một phiên bản nâng cấp mới nhất dành cho Javascript nên phải học nó để khỏi tụt hậu phải không các bạn :)
Trước tiên chúng ta sẽ tìm hiểu định nghĩa của ES6 trước.
ES6 là chữ viết tắt của ECMAScript 6, đây được coi là một tập hợp các kỹ thuật nâng cao của Javascript và là phiên bản mới nhất của chuẩn ECMAScript. ECMAScript do hiệp hội các nhà sản xuất máy tính Châu Âu đề xuất làm tiêu chuẩn của ngôn ngữ Javascript. Bạn cứ nghĩ xem hiện nay có khá nhiều trình duyệt Browser ra đời và nếu mỗi Browser lại có cách chạy Javascript khác nhau thì các trang web không thể hoạt động trên tất cả các trình duyệt đó được, vì vậy cần có một chuẩn chung để bắt buộc các browser phải phát triển dựa theo chuẩn đó. ES6 ra đời vào năm 2015 nên cái tên ES2015 được lấy làm tên chính thức với nhiều tính năng mới, học hỏi các ngôn ngữ cấp cao khác. Hy vọng dần theo thời gian Javascript trở thành một ngôn ngữ lập trình hướng đối tượng.
Phiên bản sắp ra trong năm 2017 đó là phiên bản ES7 cũng đang được nghiên cứu và phát triển, họ cũng nhắm đến các kiến thức mới lạ như async function, observer, .. Hy vọng sẽ có nhiều biến động mới.
Sau đây là một số chức năng mới thêm vào trong ES6.
let
, cách định nghĩa này thì biến chỉ tồn tại trong phạm vi khối của nó (Block Scope)=>
.Còn khá nhiều tính năng nhưng mình không liệt kê thêm, bạn có thể theo dõi toàn bộ series này để hiểu rõ hơn. Các tính năng mới của ES6 được đánh giá tương đối khó học, vì vậy nếu bạn không vững các kiến thức Javascript căn bản thì bạn không thể học ES6 được.
Hiện nay các JS Framework như NodeJS, Angular2, React Native ... đều sử dụng ES6 nên để học được các framework đó thì ban phải thành thạo Javascript nói chung và ES6 nói riêng.
ES6 là phiên bản mới nhất tính đến thời điểm hiện tại là tháng 1 năm 2017, các phiên bản khác của ES6 bao gồm:
ES6 được ra đời vào năm 2015 nên có thể coi nó là một phiên bản mới nhất của Javascript, vì vậy có thể các trình duyệt ra đời trước năm 2015 sẽ không hoạt động được. Nhưng bạn cũng đừng lo lắng quá bởi vì các Version mới nhất của Chrome và Firefox hiện nay hầu hết đã được tích hợp ES6 nên bạn hoàn toàn yên tâm về việc cài đặt môi trường ES6.
Nếu bạn chưa cài đặt browser nào thì hãy lên trang chủ của nó, download version mới nhất về và cài đặt là xong. Còn bạn đang sử dụng browser quá lỗi thời thì hãy thực hiện nâng cấp nó theo các hướng dẫn dưới đây.
Vậy công việc bây giờ của bạn là hãy cập nhật phiên bản mới nhất của Firefox để học bài nhé.
Trên thanh công cụ bạn chọn Help -> About Firefox. Nếu không thấy thanh công cụ thì bạn nhấn phím alt
nhé.
Một ô cửa sổ xuất hiện và bạn vui lòng chờ nó tự động cập nhật, sau khi cập nhật xong nó sẽ bắt bạn reload lại là coi như đã thành công.
Nếu bạn sử dụng Chrome thì hãy nâng cấp version mới nhất bằng cách thực hiện các bước sau.
Tại menu công cụ bạn chọn Help -> About Google Chrome.
Một tab mới mở ra và bạn cũng chờ nó tự động cập nhật nhé.
Trong ES6 có cung cấp một từ khóa nữa dùng để khởi tạo biến đó là từ khóa let
, từ khóa này khác với từ khóa var
ở chỗ phạm vi hoạt động. Với từ khóa var
nếu ban khai báo biến bên trong hàm thì đó là biến cục bộ, còn nếu bạn khai báo bên ngoài hàm thì nó sẽ là một biến toàn cục. Còn với từ khóa let
thì phạm vi hoạt động của nó nhỏ hơn, nó chỉ tồn tại bên trong khối đang khai báo và ta gọi đây là phạm vi block scoped.
Trước tiên mình sẽ giới thiệu từ khóa Block Scoped đã nhé.
Block Scoped là phạm vi trong một khối, nghĩa là chỉ hoạt động trong phạm vi được khai báo bời cặp {}
.
Xem hình dưới đây để hiểu rõ hơn.
Như trong hình thì vị trí 1 là một block, vị trí 2 là một block và vị trí 3 cũng là một block. Nếu để ý kỹ hơn nữa thì bạn thấy vị trí 1 sẽ bao trùm cả vị trí 2 + 3 và vị trí 2 bao trùm cả vị trí 3. Vậy Block Scoped là phạm vi chứa tất cả những đoạn code nằm bên trong cặp thẻ {}
.
Lưu ý: Khi sử dụng ES6 thì bạn nên bật chế độ strict mode, chế độ này tạm gọi là chế độ nghiêm ngặt trong việc sử dụng cú pháp của Javascript.
Như ở phần giới thiệu, từ khóa let
dùng để khởi tạo một biến nhưng biến đó chỉ có tác dụng bên trong khối đang khai báo (block-scoped).
Cú pháp: let var_name = var_value;
Ở hình dưới đây mình khai báo biến domain và biến này chỉ hoạt động trong phạm vi của lệnh if
.
Với phạm vi hoạt động hẹp như vậy thì let
thường dùng để khai báo các biến mang tính chất tạm thời, nghĩa là nó chỉ sống trong một phạm vi hoạt động của khối đó thôi, không sử dụng qua vị trí khác.
Ví dụ: Viết chương trình hoán đổi giá trị của hai biến a
và b
nếu giá trị của a
nhỏ hơn giá trị của b
.
Với bài toán này thì thông thường ta phải khai báo thêm một biến tạm như sau:
|
Sử dụng firebug bạn sẽ thấy kết quả như hình sau:
Như vậy biến tmp
sau khi kết thúc lệnh if
nó vẫn tồn tại => dư thừa không cần thiết.
Quay lại bài toán ta sử dụng từ khóa let
như sau:
|
Đoạn code này chạy bình thường, tuy nhiến nếu bạn cố ý console.log(tmp)
như ví dụ trên thì sẽ xuất hiện lỗi biến tmp
không tồn tại vì biến tmp
chỉ tồn tại bên trong lệnh if
(xem hình).
Trong Javascript để tạo một function thì thông thường chúng ta sử dụng hai cách sau:
|
Với ES6 thì bạn có thêm một cách đó là sử dụng dấu mũi tên =>
rất phức tạp, chi tiết thế nào thì chúng ta cùng tìm hiểu nhé.
Nói là ES6 nhưng thực chất hầu hết các trình duyệt hiện nay đều đã hỗ trợ ES6 nên bạn có thẻ coi như đây là một tính năng mới của Javascript.
Cú pháp căn bản nhất của arrow function như sau:
|
Ví dụ: Viết arrow function in ra câu chào và so sánh với cách tạo function thông thường.
Arrow function:
|
Normal function:
|
So sánh hai các trên thì rõ ràng cách thông thường sẽ đơn giản hơn rất nhiều, và cả hai đoạn code đều cho kết quả như sau:
Trường hợp trong thân của hàm chỉ có một lệnh duy nhất thì bạn có thể sử dụng theo ví dụ dưới đây.
|
Nghĩa là bạn có thể bỏ đi cặp dấu {}
, điều này tuân thủ theo nguyên tắc "nếu bên thân cặp {}
chỉ là một câu lệnh thì bạn có thể bỏ cặp {}
".
Trường hợp truyền vào chỉ một tham số thì bạn có thể bỏ cặp ()
.
|
Trường hợp không có tham số truyền vào thì bạn sử dụng cặp ()
rỗng, xem ví dụ sau:
|
Bây giờ ta sẽ thực hành thông qua một số ví dụ để sau này các bạn không bị bỡ ngỡ khi gặp các đoạn code người ta viết trong thực tế.
Ví dụ đầu tiên ta sử dụng kết hợp hàm map
của array trong Javascript, hàm này giống như vòng lặp vậy, nó có một tham số truyền vào và đó là một callback function, hàm callback function này sẽ có hai tham số truyền vào đại diện cho value và key của mỗi phần tử trong mảng.
Ví dụ: Sử dụng hàm map để chuyển đổi các giá trị của các phần tử trong mảng sang chữ in hoa.
Sử dụng code thông thường:
|
Chạy lên kết quả sẽ như sau:
Nêu bạn thắc mắc tại sao danh sách domain sau khi xử lý (tức là lệnh console.log(domain);
) lại không chuyển sang chữ in hoa thì ban phải xem lại đoạn code ở trên mình đã không lưu lại giá trị sau khi chuyển sang uppercase, vì vậy để giải quyết thì bạn chỉ việc lưu lại là được.
|
Chạy lên kết quả như hình sau:
Ok bây giờ ta sử dụng Arrow Function để viết.
|
Quá đơn giản phải không các bạn :
Hàm setTimeout cũng có một callback function nên ta sẽ truyền vào callback đó một Arrow Function.
|
Do arrow function không có tham số truyền vào nên mình chỉ để là ()
.
Có một số lôi cú pháp khi sử dụng arrow function mà ta thường ít chú ý tới, nhin rất đơn giản nhưng đôi khi lại gây khó khăn cho những bạn mới học.
Trường hợp bạn sử dụng arrow function bên trong một hàm hoặc sử dụng dạng một biến thì ban phải dùng cặp đóng mở để bao quanh lại.
|
Trong ví dụ trên thì ví dụ đầu tiên sai vì arrow function được sử dụng này như một tham số, vì vậy bạn phải đặt nó bên trong cặp đóng mở như ở ví dụ 2. Trường hợp bạn không muốn đặt nó bên trong cặp đóng mở thì ban phải khai báo arrow function thành một biến như ví dụ dưới đây, tuy nhiên nhìn rất rườm rà.
|
Đúng với cái tên của nó là hàm mũi tên và mũi tên này rất khó chịu về cú pháp sử dụng, bạn phải đặt mũi tên cùng hàng với tên hàm.
|
Nếu bạn muốn xuống hàng mà không bị lỗi thì phải sử dụng cú pháp sau:
|
Nếu bạn đã từng đọc qua bài viết hàm bind trong javascript thì từ version ES5 trở về trước sẽ có nhược điểm với đối tượng this đó là phạm vi hoạt động, và trong ES5 có sử dụng hàm bind để khắc phục. Vấn đề này được khắc phục hoàn toàn trong ES6 bằng cách sử dụng hàm arrow function.
Xét ví dụ sử dụng trong ES5 trở về trước.
|
Với ES6 thì viết như sau:
|
Trong bài này chúng ta học một chức năng mới nữa trong ES6 đó là Destructured Assignment, chức năng này giống hàm list
trong PHP vậy, nghia là nó sẽ phân các giá trị trong mảng vào các biến theo thứ tự được truyền vào.
Một ví dụ hàm list
trong PHP:
|
Như vậy rõ ràng Javascript ES6 đang dần bắt kịp các ngôn ngữ khác. Tuy nhiên cá nhân mình lại thấy cách sử dụng này đôi khi lại khó kiểm soát code, vì vậy bài viết này chỉ mang tính chất giới thiệu chứ không khuyên bạn nên sử dụng. Và rõ ràng là chúng ta cũng nên bắt kịp công nghệ phải không nào :) Biết đâu CS7 ra đời sẽ có một cuộc cách mạng Javascript vĩ đại thì sao.
Destructuring Assignments đơn giản chỉ là cách tách các phần tử của Array hoặc Object thành nhiều biến chỉ bằng một đoạn code duy nhất.
Bạn có thể sử dụng mảng để tách các phần tử thành các biến.
|
Chạy lên kết quả như sau:
Nếu bạn muốn lấy một phần tử thôi thì hãy bỏ trống các phần tử không muốn lấy.
|
Chạy lên kết quả như sau:
Ngoài mảng ra thì bạn có thể tách biến từ object.
|
Kết quả:
Với trường hợp này thì bạn có thể lấy một phần tử bất kì chứ không bắt buộc theo thứ tự như bên mảng.
|
Kết quả:
Bây giờ ta sẽ thực hành một số ví dụ nâng cao khác.
Bạn có thể lấy giá trị dựa vào tên key của Object.
|
Kết quả:
Nhưng nếu bạn cố ý truy cập vào một key không tồn tại thì sẽ bị lỗi undefined.
|
Nếu phần tử không tồn tại thì sẽ bị undefined
.
|
Kết quả do phần tử thứ hai không tồn tại nên biến sub
sẽ có giá trị là undefined
.
Để tránh lỗi undefined
thì bạn có thể gán giá trị mặc định cho nó.
|
Kết quả sẽ không bị lỗi undefined
.
Bạn có thể sử dụng function return về array hoặc object để gán vào thay vì gán một giá trị trực tiếp như vậy.
|
Kết quả giống như hình trên.
Default Parameter là giá trị mặc định của tham số khi truyền vào các function. Đối với Javascript thì có nhiều bạn chưa biết chức năng này mặc dù trong ES5 đã cung cấp săn cho chúng ta, tuy nhiên người ta cảm thấy cách tạo giá trị mặc định trong ES5 vẫn không hay nên họ đã bổ sung một cách khác mới hơn và đơn giản hơn rất nhiều trong ES6.
Trong ES5 để tạo giá trị mặc định thì bạn sử dụng cặp dấu ||
để thiết lập ngay bên trong thân của hàm.
Ví dụ: Tạo giá trị mặc định cho biến domain trong ES5
|
Chạy lên bạn sẽ thấy kết quả như hình sau:
Trong ES6 có cách khai báo giá trị mặc định đơn giản hơn rất nhiều, cách này cũng tương tự như khai báo trong PHP đó là sử dụng phép gán ngay tại vị trí khai báo tham số cho function.
Ví dụ: Tạo giá trị mặc định cho biến domain trong ES6
|
Chạy lên kết quả vẫn không khác gì ở ví dụ ES5.
Rest Parameters dịch theo tiếng Anh chuyên ngành công nghệ thông tin có nghĩa là tham số còn lại, điều này có nghĩa là bạn có thể khai báo một hàm với số lượng tham số không xác định, đây là một tính năng mới khiến Javascript ngày càng trở nên mạnh mẽ hơn.
Để khai báo các tham số còn lại của một function thì bạn đặt 3 dấu chấm .
trước biến đại diện.
|
Trong đó tham số other
là một Rest Parameter vì nó có 3 dấu chấm đặt ở trước.
Ví dụ: In ra tất giá trị của tham số truyền vào trong hàm.
|
Hàm này có 3 tham số truyền vào là main
, sub
và Rest Parameter other
. Chạy lên bạn sẽ thấy giao diện như sau:
Dựa vào trong hình bạn thấy biến other
sẽ lưu một mảng giá trị các tham số cuối cùng, vì vậy để lấy từng giá trị thì bạn chỉ việc sử dụng vòng lặp là được.
Trường hợp tham số truyền vào vừa đủ thì Rest Parameter sẽ có giá trị là một mảng rỗng. Xem ví dụ:
|
Kết quả:
Khái niệm const
(hằng số) đã tồn tại trong các ngôn ngữ lập trình cấp cao như C#, Java. Nhưng với Javascript thì chỉ vào năm 2015 kể từ lúc ES6 ra đời mới tồn tại khái niệm này.
Về định nghĩa thì const
có nghĩa là hằng số, điều này có nghĩa nếu một biến được khai báo là hằng số thì bạn phải gán giá trị lúc khai báo luôn, và kể từ đó về sau bạn sẽ không thể thay đổi giá trị cho biến đó được nữa. Tuy nhiên có một lưu ý là biến const
là một block-scoped (giống với let
), vì vậy nó chỉ tồn tại trong phạm vi nó được khai báo mà thôi.
Với ngôn ngữ C#, PHP thì hằng số phải có giá trị là kiểu chuỗi, kiểu số hoặc kiểu boolean, ta không thể thiết lập giá trị cho hằng số là một mảng hoặc một đối tượng được, nhưng với Javascript thì biến const
có thể chứa bất kì gí trị gì.
Ví dụ: Giá trị của const
là một Object
|
Kết quả:
Nếu bạn cố tình thay đổi giá trị cho biến const
thì sẽ bị lỗi ngay.
Ví dụ: Cố tình đổi giá trị const nên bị lỗi
|
Chạy lên ngay lập tức bạn sẽ nhận một thông báo lỗi như sau:
Nếu bạn khai báo biến const
trong phạm vi của cặp {}
thì nó chỉ tồn tại trong phạm vi đó mà thôi.
Ví dụ: Sử dụng biến const
ngoài phạm vi nên bị lỗi.
|
Kết quả:
Theo định nghĩa thì biến const không được thay đổi giá trị, vậy liệu ta có đặt biến const
trong vòng lặp được không? Câu trả lời là được nhé các bạn, bởi vì trong mỗi vòng lặp là một block-scoped nên khi qua vòng lặp kế tiếp biến const
sẽ không tồn tại nữa, vì vậy đoạn code khai báo coi như là bắt đầu lại từ đầu nên sẽ không bị lỗi.
|
Kết quả:
Trong ES5 không tồn tại dữ liệu dạng cấu trúc tập hợp, vì vậy chúng ta sử dụng mảng để lưu trữ dữ tập hợp các phần tử. Tuy nhiên với ES6 thì mọi chuyện đơn giản hơn bởi vì nó có hỗ trợ kiểu dữ liệu tập hợp Set với các giá trị truyền vào tùy ý kèm theo tốc độ xử lý nhanh chóng.
Chúng ta có bốn thao tác chính khi làm việc với set
như sau:
let set = new Set();
set.add(value);
set.delete(value);
set.has(value);
set.size;
set.clear();
Đối với Set thì các giá trị không được trùng, vì vậy nếu bạn cố tình thêm vào hai giá trị giống nhau thì nó chỉ lưu một lần mà thôi.
Lúc khởi tạo sẽ có một tham số truyền vào, tham số này bắt buộc phải là một mảng.
|
|
|
|
|
|
Set là một tập hợp nên bạn hoàn toàn có thể lặp qua các phần tử như ví dụ sau:
|
Để chuyển đổi Set sang Array thì bạn sử dụng ba dấu chấm đặt trước biến Set.
|
Ngược lại để chuyển đổi một Array sang Set thì bạn sử dụng cách sau:
|
Lưu ý: Khi chuyển từ Array sang Set nếu 2 phần tử có giá trị trùng nhau thì nó chỉ lấy đúng một phần tử.
Bây giờ ta sẽ tìm hiểu đến hai hàm đó là map
và filter
. được dùng để chuyển đổi qua Array vả xử lý.
Mapping là một hàm được tích hợp sẵn trong Array với chức năng là thiết lập giá trị cho phần tử trong môi lần lặp (tham khảo hàm map).
|
Như vậy trong hàm mapping trả về giá trị bao nhiêu thì phần tử tại lần lặp đó sẽ có giá trị bấy nhiêu.
Filtering cũng được tích hợp sẵn trong Array, chức năng của hàm này là trả về true thì phần tử được chọn, trả về false thì phần tử không được chọn (tham khảo hàm filter).
|
Map là một kiểu dữ liệu tương tự như Set, tuy nhiên với Map thì có cấu trúc dạng key => value
còn với Set thì chỉ có value
.
Chúng ta có các thao tác chính với map như sau:
let map = new Map()
map.set('Name', 'Nguyen Van Cuong')
;map.delete("Name");
map.has('Name')
map.size
map.clear();
Đối với Map thì các key không được trùng, vì vậy nếu bạn truyền vào 2 key giống nhau thì nó chỉ lưu đè vào một key duy nhất.
Lúc khởi tạo sẽ có một tham số truyền vào và giá trị của tham số này là một mảng chứa nhiều mảng con, mỗi mảng con sẽ có hai phần tử đại diện cho key và value.
|
|
|
|
|
|
Như mình đã giới thiệu mỗi phần tử trong Map luôn ở dạng key => value
, vậy có câu hỏi đặt ra là định dạng của key
như thế nào?
Key bạn có thể là một string
, number
, const
hay thậm chí là một NaN
.
Ví dụ: Key là const
|
Ví dụ: Key là NaN
|
Sau đây là các hàm bổ trợ giúp bạn xử lý đối tượng Map
.
Trước tiên mình tạo một đối tượng Map
như sau:
|
Tất cả các ví dụ dưới đây sẽ sử dụng biến map
này.
|
|
|
Để lặp qua các phần tử trong Map thì bạn có thể sử dụng vòng lặp for hoặc hàm forEach
được tích hợp sẵn trong map
.
Cách 1: Dùng vòng lặp for.
|
Cách 2: Sử dụng hàm forEach.
|
Tương tự như Set thì chúng ta có thể chuyển đổi sang Array vaf kết hợp với hai hàm map và filter.
Ví dụ: Nối giá trị của key vào value
|
Ví dụ: Lọc những phần tử có key chia hết cho 2.
|
WeakMap là một loại kiểu dữ liệu giống như Map
vậy, nghĩa là sẽ tồn tại hai tham số key => value
cho mỗi phần tử. Tuy nhiên với WeakMap thì key truyền vào phải là một biến và biến này phải là một Object (class, function, object), con với Map thì bạn có thể thiết lập key là chuỗi, number, object đều được.
WeakMap cũng có một số phương thức tương tự Map
như:
Ví dụ: Một ví dụ tổng hợp các thao tác trên.
|
WeakSet có thể được coi là một phiên bản tương tự như Set
, tuy nhiên với WeakSet
thì dữ liệu truyền vào luôn phải là một đối tượng (object, class, function) và bạn phải tạo một giá trị (key) trước khi lưu vào, điều này khác hoàn toàn với Set
là Set
có thể lưu trữ mọi dữ liệu kể cả number và string.
WeakSet có một số thao tác chính như sau:
|
Trong bài này chúng ta tìm hiểu về đối tượng Symbol và ứng dụng của nó trong việc tạo các giá trị không trùng lặp.
Trong ES6 xuất hiện thêm một kiểu dữ liệu đó là Symbol, đây là kiểu dữ liệu dạng primitive type, nó sẽ tạo ra các ký tự duy nhất (unique) và không trả về một chuỗi mà nó chỉ là một dạng object, vì vậy bạn sẽ không thể thấy được giá trị thực của nó.
Symbol được sinh ra dùng để xử lý cho bài toán mang tính duy nhất (unique), mỗi khi bạn tạo một đối tượng Symboy thì đối tượng đó sẽ không bao giờ trùng lặp với một đối tượng khác. Trước tiên ta cùng tìm hiểu cú pháp khởi tạo đã nhé.
Để khởi tạo một Symbol thì ta dùng cú pháp sau:
|
Hàm khởi tạo Symbol có một tham số truyền vào và đây có thể coi là một secret key.
|
Nếu bạn tạo hai symbols và so sánh chúng với nhau thì sẽ thấy chúng KHÔNG BAO GIỜ bằng nhau cả.
|
Để kiểm tra một biến là một symbol thì bạn sử dụng hàm typeof
.
|
Nếu bạn tạo ra nhiều Symbol thì các symbols đó sẽ không bao giờ giống nhau, vì vậy chúng ta có thể tận dụng đối tượng này để tạo các key trong Object hoặc Map.
Ví dụ: Với Object
|
Ví dụ: Với Map
|
Bây giờ ta thử chuyển đổi giá trị của Symbol sang một số kiểu dữ liệu.
Để chuyển kiếu symbol sang kiểu boolean thì ta sử dụng cú pháp sau:
|
Khi chuyển symbol sang boolean thì nó sẽ luôn luôn có giá trị là true, cho dù bạn có gán secret key lúc khởi tạo hoặc không.
|
Symbol là một kiểu giá trị đặc biệt nên bạn không thể chuyển nó sang kiểu number được. Thông thường để chuyển sang kiểu number thì ta dùng hàm Number(value)
, vậy thì thử xem sao nhé.
|
Bạn sẽ nhận được một lỗi như thế này: Uncaught TypeError: Cannot convert a Symbol value to a number.
Để chuyển symbol sang string thì bạn sử dụng hàm String(value)
, tuy nhiên chuỗi đó sẽ được bao quanh bởi chuỗi Symbol()
.
|
Bây giờ ta thử ép kiểu hai đối tượng symbol sang string với secret key giống nhau và so sánh xem chúng có bằng nhau không nhé.
|
Trong ví dụ này cho thấy nếu bạn chuyển sang kiểu chuỗi của hai symbol có secret key giống nhau thì hai chuỗi đó sẽ giống nhau, ngược lại thì nó sẽ khác nhau.
Chính điểm mạnh của Symbol lại chính là điểm yếu của nó. Giả sử khi bạn sử dụng Symbol trong Object thì bắt buộc ban phải lưu lại symbol đó bởi theo nguyên tắc mỗi khi tạo mới symbol thì nó sẽ không bao giờ trùng với giá trị khác, cho dù bạn truyền secret key giống nhau.
Ví dụ: Secret của key giống nhau nhưng in ra giá trị lại khác nhau
|
Trong bài này chúng ta sẽ tìm hiểu về cú pháp cũng như làm một số ví dụ về Template String trong ES6.
Trong Laravel có Blade template, trong Phalcon có Volt template, vậy trong Javascript có template nào hay không? Tính từ ES5 trở về trước thì chưa xuất hiện khái niệm template string mà nó chỉ vừa mới thêm vào trong phiên bản ES6, nên đa số các lập trình viên không sử dụng.
Template String sẽ thay thế cách nối chuỗi thông thường.
Template String được bao bọc bởi cặp dấu ``
.
|
Để khai báo tham số trong chuỗi Template String thì bạn sử dụng cú pháp ${ten_bien} và đặt vào vị trí muốn hiển thị.
|
Trong chuỗi Template String nếu có tham số thì bắt buộc trước khi sử dụng ta phải khai báo biến, nếu không sẽ bị lỗi ngay.
|
Kết quả:
Trong những chức năng hiển thị danh sách thì chúng ta sẽ lặp dữ liệu và gán các giá trị vào ở mỗi vòng lặp.
Ví dụ dưới đây lặp danh sách domain và gán vào template.
|
Kết quả:
Trên là ví dụ lặp một mảng đơn giản, bây giờ ta sẽ lặp môt mảng chứa các đối tượng vì trong thực tế khi bạn lấy dữ liệu từ database thì nó sẽ trả về một mảng các record.
|
Kết quả:
Trước khi vào tìm hiểu các vấn đề nâng cao như Promise, Class thì chúng ta sẽ tìm hiểu thế nào la Asynchronous và Synchronous. Đây là hai khái niệm mà đa số các lập trình viên mới vào nghề chưa hiểu được bản chất của nó nên dẫn tới xử lý tình huống bị sai.
Trong bài có sử dụng từ viết tắt:
Synchronous có nghĩa là xử lý đồng bộ, chương trình sẽ chạy theo từng bước và chỉ khi nào bước 1 thực hiện xong thì mới nhảy sang bước 2, khi nào chương trình này chạy xong mới nhảy qua chương trình khác. Đây là nguyên tắc cơ bản trong lập trình mà bạn đã được học đó là khi biên dịch các đoạn mã thì trình biên dịch sẽ biên dịch theo thứ tự từ trên xuống dưới, từ trái qua phải và chỉ khi nào biên dịch xong dòng thứ nhât mới nhảy sang dòng thứ hai, điều này sẽ sinh ra một trạng thái ta hay gọi là trạng thái chờ. Ví dụ trong quy trình sản xuất dây chuyền công nghiệp được coi là một hệ thống xử lý đồng bộ.
Synchronous hai mặt là mặt xấu và mặt tốt.
Chương trình sẽ chạy theo đúng thứ tự và có nguyên tắc nên sẽ không mắc phải các lỗi về tiến trình không cần thiết. Không chỉ trong lập trình mà trong thực tế cũng vậy, một công ty đưa ra quy trình đồng bộ sẽ đảm bảo được chất lượng của sản phẩm, nếu bị lỗi thì sẽ biết ngay là lỗi tại quy trình nào và từ đó sẽ dễ dàng khắc phục.
Chương trình chạy theo thứ tự đồng bộ nên sẽ sinh ra trạng thái chờ và là không cần thiết trong một số trường hợp, lúc này bộ nhớ sẽ dễ bị tràn vì phải lưu trữ các trạng thái chờ vô duyên đó.
Khi bạn viết một chương trình quản lý và trong đó có thao tác lưu, mỗi khi lưu bạn yêu cầu người dùng có muốn lưu hay không? Nếu muốn lưu thì click Yes, ngược lại click No. Trường hợp nay gây tai họa nếu người dùng vô tình chỉ click Lưu mà không chú ý đến câu hỏi mà hệ thống đưa ra nên ngồi nhâm nhi cafe, đột nhiên cúp điện thế là cứ tưởng đã lưu rồi :) Vậy quy trình xử lý nên đưa ra chức năng lưu tự động, nghĩa là thao tác lưu sẽ bỏ qua bước hỏi đáp kia đi, không nhất thiết phải chờ nó OK mới lưu.
Ngược lại với Synchronous thì Asynchronous là xử lý bất động bộ, nghĩa là chương trình có thể nhảy đi bỏ qua một bước nào đó, vì vậy Asynchronous được ví như một chương trình hoạt động không chặt chẽ và không có quy trình nên việc quản lý rất khó khăn. Nếu một hàm A phải bắt buộc chạy trước hàm B thì với Asynchronous sẽ không thể đảm bảo nguyên tắc này luôn đúng.
Có thể xử lý nhiều công việc một lúc mà không cần phải chờ đợi nên tạo cảm giác thoải mái :) Ví dụ bạn đi ký một văn bản ở Xã, Phường thì nếu bạn có tiền bạn sẽ bỏ qua được một vài công đoạn phải không nào, lúc đó măt sẽ tươi rói và đương nhiên là anh nhân viên cũng tươi không kém :)
Nếu một chuong trình đòi hỏi phải có quy trình thì bạn không thể sử dụng Asynchronous được, điển hình là trong quy trình sản xuât một sản phẩm của các nhà máy công nghiệp không thể áp dụng kỹ thuật làm nhiều công việc một lúc thế này được. Còn về chương trình trong lập trình thì sao? Một thao tác thêm dữ liệu phải thông qua hai công đoạn là validate dữ liệu và thêm dữ liệu, nếu thao tác validate xảy ra sau thao tác thêm thì còn gì tệ hại hơn nữa :).
Từ trước giờ mình có nhận một số câu hỏi như: Tại sao em gán thay đổi giá trị của biến vào action success mà nó không thấy thay đổi vậy anh? Teamviewer kiểm tra thì thấy bạn đã mắc phải lỗi "chưa hiểu về xử lý bất đồng bộ" :).
Theo khái niệm của Ajax là gì thì Ajax được viết tắt của các từ Asynchronous JavaScript and XML, rõ ràng từ Asynchronous đã nói lên Ajax là một kỹ thuật xử lý bất đồng bộ. Nhiều bạn lập trình viên khi viết ứng dụng Ajax mà quên mất rằng đây là một chương trình bất đồng bộ, tức là thao tác gửi Ajax và các đoạn code bên dưới sẽ được chạy song song.
|
Như vậy trong ĐOẠN 3 đã không nhận được giá trị của ĐOẠN 2, lý do tại sao?
Theo quy trình xử lý thì chương trình hoạt động từ trên xuông dưới và từ trái qua phải (điều đương nhiên), nhưng do Ajax phải mất một khoảng thời gian rất lớn (so với tốc độ của trình biên dịch) để request đến URL nên nếu đưa nó vào xử lý đồng bộ thì quả thật trình duyệt phải mất một khoảng thời gian chờ, vì vậy nó sẽ tiếp tục chạy xuống phía dưới mặc kệ đoạn ajax khi nào xong thì xong => dẫn đến giá trị message không thay đổi.
Nhưng nếu bạn tạm ngưng trong vòng 10 giây chẳng hạn (ta coi như 10 giây đủ để request thực hiện xong) thì biến message sẽ nhận được giá trị mới.
|
Tại sao lại như vậy? Bản chất setTimeout cũng là một Async đấy các bạn, nghĩa là các đoạn code bên dưới sẽ hoạt động trước nội dung bên trong setTimeout()
.
Ví dụ:
|
Kết quả sẽ xuất hiện 2 -> 1 chứ không phải là 1 - 2 như bạn đang nghĩ đâu :
Promise được đưa vào Javascript từ ES6, đây có thể coi là một kỹ thuật nâng cao giúp xử lý vấn đề bất đồng bộ hiệu quả hơn. Trước đây kết quả của một tác vụ đồng bộ và bất đồng bộ sẽ trả về một kiểu dữ liệu nào đó hoặc thực hiện một Callback Function, điều này quá là bình thường bởi ta đã thấy kể từ ngày bắt đầu học về hàm trong lập trình phải không nào :). Với trường hợp thực hiện Callback Function thì sẽ dễ xảy ra lỗi Callback Hell, nghĩa là gọi callback quá nhiều và lồng nhau nên dẫn đến không kiểm soát được chương trình hoặc bộ nhớ không đủ để hoạt động. Và Trong bài này chúng ta sẽ tìm hiểu về Promise, một package được đưa vào từ ES6 giúp giải quyết vấn đề Callback Hell này.
Javascript là ngôn ngữ chạy một luồng duy nhất, nghĩa là một đoạn mã xử lý một nhiệm vụ sẽ chỉ chạy một lần duy nhất và nếu có lần thứ hai thì nó phải chờ lần thứ nhất kết thúc, điều này tuân thủ theo nguyên tắt hoạt động đồng bộ và hoạt động này đã gây ra phiền toái cho một số trường hợp. Hai người đàn ông cần cắt tóc để đi gặp cùng một người yêu và họ đã vào cắt tại một tiệm Salon duy nhất nằm trong làng, khốn nạn ở chỗ cả hai đến cùng thời điểm nên thợ cắt tóc chỉ có thể cắt một trong hai người mà thôi, người còn lại vui lòng chờ đến lượt. Vậy hành đồng cắt tóc của người thứ nhất đã làm ảnh hưởng đến người thứ hai, và người thứ hai sẽ mất một khoảng thời gian chờ để đến lượt của mình (ta gọi là đồng bộ - Synchronous).
Sự kiện trong Javascript đã giải quyết vấn đề đa luồng, một hành động sẽ xảy ra khi một sự kiện được kích hoạt. Bạn viết một chương trình gửi mail và bạn gán nó vào sự kiện click vào button, tôi click hai lần liên tiếp thì chương trình sẽ xử lý gửi mail 2 lần cùng một thời điểm, tuy nhiên sẽ rất khó để biết được bên nào sẽ xử lý xong trước.
|
Liệu event trong Javascript có phải là cách tốt nhất để xử lý vấn đề này?
Sự kiện xử lý rất tuyệt vời cho các trường hợp xử lý nhiều lần trên một đối tượng như bàn phím, chuột, form. Tuy nhiên với sự kiện thì thường ta sẽ không quan tâm đến chuyện gì sẽ xảy ra cho tới khi ta bổ sung một Listener (gửi mail).
|
Và việc xử lý các events này là một hành động bất đồng bộ (Async).
Một ví dụ khác là khi load một hình ảnh thì có sự kiện ready()
, ta sẽ áp dụng sự kiện này để đổi hình mặc định nếu như hình không tồn tại.
Note: Các ví dụ dưới đây có sử dụng jQuery.
|
Và nếu áp dụng Promise trong ES6 thì nó rất đơn giản.
|
Vậy câu hỏi của chủ đề này là làm thế nào để quản lý tốt kết quả của một hành động bất đồng bộ (Async)? Để trả lời câu hỏi này thì ta sẽ nghiên cứu về Promise.
Vậy promise sinh ra để xử lý kết quả của một hành động cụ thể, kết quả của mỗi hành động sẽ là thành công hoặc thất bại và Promise sẽ giúp chúng ta giải quyết câu hỏi "Nếu thành công thì làm gì? Nếu thất bại thì làm gì?". Cả hai câu hỏi này ta gọi là một hành động gọi lại (callback action).
Như ở ví dụ trên mình demo việc xử lý hành động load hình ảnh của trình duyệt, nếu hình ảnh load không được thì sẽ làm thao tác bổ sung hình mặc định, đây là một hành động callback. Nói trong lập trình thì đây là một callback function.
Khi một Promise được khởi tạo thì nó có một trong ba trạng thái sau:
Trong đó hai trạng thái Reject và Fulfilled ta gọi là Settled, tức là đã xử lý xong.
Bây giờ ta sẽ tìm hiểu về cách khởi tạo Promise.
Để tạo một Promise bạn sử dụng cú pháp sau:
|
Trong đó callback là một function có hai tham số truyền vào như sau:
|
Trong đó:
Thenable không có gì to tác mà nó là một phương thức ghi nhận kết quả của trạng thái (thành công hoặc thất bại) mà ta khai báo ở Reject và Resolve. Nó có hai tham số truyền vào là 2 callback function. Tham số thứ nhất xử lý cho Resolve và tham số thứ 2 xử lý cho Reject.
|
Ví dụ: Demo thao tác Resolve và Reject
|
Với đoạn code này chạy lên bạn sẽ nhận giá trị là Success!.
Bây giờ ta thử với một đoạn Reject.
|
Chạy đoạn code này lên sẽ nhận giá trị là Error!.
Vậy hai hàm callback trong then
chỉ xảy ra một trong hai mà thôi, điều này tương ứng ở Promise sẽ khai báo một là Resolve và hai là Reject, nếu khai báo cả hai thì nó chỉ có tác dụng với khai báo đầu tiên.
|
Kết quả:
Chạy lên nó cũng chỉ nhận đúng một giá trị là Error! => callback error đã hoạt động.
then
có hai tham số callbacks đó là success và error. Tuy nhiên bạn cũng có thể sử dụng phương thức catch
để bắt lỗi.
|
Ví dụ:
|
Chạy lên kết quả sẽ là Error!.
Câu hỏi bây giờ đặt ra là nếu ta vừa truyền callback error và vừa sử dụng catch thì thế nào? Câu trả lời nó sẽ chạy hàm callback error và catche sẽ không chạy.
|
Kết quả mình chụp hình luôn cho các bạn thấy rõ hơn.
Nếu bạn đang xem bài này lần đầu tiên thì nên quay lại đọc bài Promise trong Javascript sẽ giúp bạn dễ dàng follow hơn.
Ở bài trước mình có giới thiệu 3 trạng thái của Promise đó là pending, fulfilled và rejected, đây là ba trạng thái mà bất kì một Promise nào cũng phải có.
Pending là trạng thái khi bạn khởi tạo một Promise nhưng chưa thiết lập kết quả cho nó, tức là chưa sử dụng resolve và reject.
|
Kết quả:
Fulfilled còn được gọi là resolved, đây là trạng thái của Promise đã thành công, trạng thái này xảy ra khi bạn sử dụng resolve.
|
Kết quả:
Rejected là trạng thái thao tác thất bại, trạng thái này xảy ra khi bạn sử dụng reject. Khi bạn sử dụng reject thì bắt buộc phải khai báo hành động xử lý cho nó (tức sử dụng then
hoặc catch
).
|
Kết quả:
Phương thức then()
có nhiệm vụ tiếp nhận kết quả trả về của promise và nó cũng return về một promise nên bạn có thể dùng nhiều lần liên tiếp với nhau.
|
Kết quả:
Việc đặt thứ tự các phương thức rất quan trọng nhé các bạn. Nếu hành động của promise trả về là một Reject thì sẽ có hai trường hợp xảy ra như sau:
Trường hợp 1: Nếu trong phương thức then()
nào đó có sử dụng callback function Reject thì các phương thức then()
ở phía sau sẽ là một Fulfilled, nghĩa là nó sẽ chạy ở callback function Resolve.
|
Kết quả:
Nếu chiếu theo quy luật thì ở biến promise
mình đã sử dụng Reject
nên suy ra ở then()
tham số callback thứ hai sẽ hoạt động. Điều này hoàn toàn đúng với phương thức then()
thứ hai, vì vậy nó in ra chư Error!. Tuy nhiên nhảy qua phương thức then()
thứ tư thì nó lại chạy phần callback Resolve nên in ra số 3.
Trường hợp 2: Bạn sử dụng catch
để bắt lỗi, lúc này chỉ có phương thức catch()
là hoạt động.
|
Kết quả:
Khi sử dụng thenable liên tiếp thì kết quả return của phương thức
then()
hiện tại sẽ quyết định trạng thái của phương thứcthen()
tiếp theo. Ví dụthen()
phía trên return về một Promise Rejected thìthen()
phía dưới sẽ nhận một trang thái Rejected.
ví dụ:
|
Kết quả của đoạn code đó phụ thuộc vào các Async mà nó return về. Và sau đây là bảng mô tả các trường hợp xảy ra.
Trong hình này đường màu xanh mô tả cho Async Promise return về một Fulfilled và màu tím mô tả cho Asycn Promise return về một Rejected.
Ví dụ chạy thực
|
Kết quả:
Vì then()
thứ nhất return về một Reject Promise nên then()
thứ hai không chạy, và trạng thái bây giờ là Rejected nên catche sẽ được chạy => in ra chữ Error!.
Vậy là hết rồi đó !
Từ trước đến nay để lặp qua các phần tử của một mảng hoặc một danh sách thì chúng ta sử dụng vòng lặp, tuy nhiên việc sử dụng vòng lặp đôi lúc gây khó khăn trong một số trường hợp. Ví dụ bạn muốn lặp qua từng phần tử và có thể dừng ở một phần tử bất kì, điều này hoàn toàn làm được bằng vòng lặp nhưng không được hay và tốn chi phí tính toán. Vậy trong ES6 cung cấp cho chúng ta khả năng duyệt nâng cao hơn nữa bằng cách sử dụng iterators.
Một ví dụ điển hình trong PHP về iterators như sau:
|
Đây là một ví dụ được viết bằng PHP, hàm mysqli_fetch_assoc()
sẽ lần lượt lấy từng phần tử trong danh sách kết quả trả về.
Iterators là một bộ duyệt dùng để duyệt qua một mảng, một danh sách hoặc một collection mà qua mỗi lần duyệt sẽ ghi lại vị trí đã duyệt để từ đó có thể biết và lấy vị trí tiếp theo.
Trong Javascript thì iterators có chung cấp phương thức next()
và phương thức này sẽ return về phần tử kết tiếp, đồng thời ghi nhận luôn phần tử đã lặp là phần tử next()
. Phương thức next()
sẽ return về một Object gồm hai thuộc tính là value
và done
.
Ví dụ: Sử dụng Iterators với mảng
|
Kết quả:
Giá trị lần next()
cuối cùng là một giá trị undefined
và key done
của nó sẽ là true.
Có lẽ đoạn code trên bạn sẽ thắc mắc tại sao lại có Symbol.iterator
phải không nào :) Nếu vậy thì ta sẽ tìm hiểu qua một chút về Iterator Protocol và Iterable.
Trước tiên chúng ta tìm hiểu vè Iterable đã nhé.
Iterable là khả năng cho phép các đối tượng trong Javascript sử dụng các kỹ thuật xử lý dữ liệu như for of loop
, toán tử ba chấm ...
.
Ví dụ:
|
Với ES6 thì các đối tượng như Array
, Object
, Map
, WeakMap
, Set
, WeakSet
đều là đối tượng Iterable.
Iterator Protocol chẳng qua chỉ là các giao thức (method) xử lý một đối tượng có đánh dấu vị trí đã duyệt, vì vậy với các đối tượng thông thường sẽ không sử dụng được nên ta phải sử dụng Symbol.iterator
để chuyển đôi.
Quay lại ví dụ trên mình sẽ giải thích như sau:
|
Bây giờ chúng ta sẽ mổ xẻ tấm hình dưới đây.
Trong tấm hình mô phỏng ba tầng cấp như sau:
Symbol.iterator
để chuyển đổi.Tóm lại:
next()
thì bạn phải thực hiện thao tác chuyển đổi thông qua Symbol.iterator
.Sau đây là một số đối tượng có sẵn Iterable và cách sử dụng Iterator trên đối tượng đó.
|
Kết quả:
|
Kết quả:
|
Kết quả:
Arguments là tổng các tham số truyền vào mảng.
|
Kết quả:
Còn khá nhiều trong thực tế nhưng mình không thể biểu diễn hết được. Nếu bạn muốn tự tìm hiểu thì hãy lên trang này nhé.
Toán tử ba chấm trong Iterator dùng để liệt kê các phần tử của các đối tượng.
|
Kết quả:
|
|
Kết quả:
Và còn rất rất nhiều cách nữa, bạn tự tìm hiểu thêm nhé :
ES7 được giới thiệu vào năm 2017 với nhiều tính năng mới được thêm vào. Đây không phải là điều khó hiểu bởi Javascript thực sự là ngôn ngữ rất mạnh, nói chính xác hơn là ngôn ngữ tương lai.
ES7 có những nâng cấp ít hơn so với phiên bản tiền nhiệm của nó là ES6. Vì vậy mình sẽ viết một bài đơn giản và ngắn gọn để nói về những tính năng mới của ES7.
ES7 giới thiệu một toán tử toán học mới được gọi là toán tử lũy thừa. Toán tử này tương tự như sử dụng phương thức Math.pow (). Toán tử lũy thừa được biểu diễn bằng dấu hoa thị kép **
. Toán tử chỉ có thể được sử dụng với các giá trị số. Cú pháp để sử dụng toán tử lũy thừa như sau:
|
Ví dụ với 2 lũy thừa ba thì ta sẽ viết như sau:
|
Nếu khai báo bằng biến và biểu diễn ở hai cách thì bạn hãy xem ví dụ sau:
|
Phương thức Array.includes() được giới thiệu trong ES7 nhằm giúp kiểm tra một phần tử có sẵn trong mảng hay không, tương tự như hàm in_array trong PHP.
Trước đây khi kiểm tra một phần tử có xuất hiện trong mảng hay không thì ta phải sử dụng vòng lặp for, lần lượt lặp qua từng phần tử mảng cho đến khi tìm thấy. Nếu duyệt đến cuối mảng vẫn xuất hiện thì phần tử không nằm trong mảng.
Đương nhiên bạn có thể tạo ra một hàm đẻ sử dụng được nhiều lần. Nhưng với ES7 thì bạn không cần phải lo lắng nữa vì đã có phương thức Array.includes.
Lưu ý là phương thức này chỉ dùng cho Object Array thôi nhé, vì vậy nó có một tham số truyền vào, đó cũng chính là giá trị mà ta muốn tìm.
|
Trường hợp bạn muốn kiểm tra giá trị cần tìm có nằm trong một dãy bắt đầu từ phần tử nào đó thì sử dụng cú pháp 2 tham số như sau:
|
Trong đó start_index chính là vị trí bắt đầu, tức là nó sẽ duyệt từ vị trí này đến cuối mảng.
Ví dụ: Kiểm tra xem 50 có nằm trong mảng không
|
Trong ES6 đã giới thiệu về array rest và spread giúp việc kết hợp và tách mảng trở nên rất dễ dàng. Nhận thấy đây là một tính năng thú vị nên ES7 cũng đã bổ sung nó vào trong Object.
Bạn có thể tách hoặc kết hợp một Object rất dễ dàng. Xem ví dụ dưới đây để hiểu rõ hơn.
|
Trường hợp bạn muốn tách đối tượng ra với số lượng nhỏ hơn, điều này đồng nghĩa với biến cuối cùng sẽ lưu một object chứa các phần tử còn lại.
|
Tách ra thì quá dễ dàng rồi, bây giờ bạn muốn gom lại thì sao? Ta chỉ cần khai báo một biến mới và gán danh sách các object một cách bình thường.
|
Đây được xem là một trong những bản nâng cấp đáng giá nhất của ES7. Nếu trước đây để xử lý bất đồng bộ trong ES5 thì ta sử dụng callback function, và trong ES6 thì ta sử dụng promise. Nhưng bây giờ ES7 đã có async và await.
Hãy xem một ví dụ dưới đây mà mình lấy từ trang viblo.asia, tác giả là bạn Đào Tùng.
Callback function trong ES5
|
Trường hợp bạn muốn xử lý 100 cấp thì đoạn code sẽ quá dài phải không các bạn? Điều này sẽ đẫn đến lỗi callback hell. Và ES7 đã giới thiệu Promise.
|
Nhìn có vẻ đơn giản và dễ hiểu hơn rất nhiều, loại bỏ được vấn đề phân cấp quá dài và gây ra lỗi callback hell. Vây thì trong ES7 cải tiến thế nào?
|
Thứ tự chạy từ trên xuống dưới nên rất rõ ràng và tường minh. Đây chính là phần nâng cấp rất hay mà mình thấy khắc phục được những nhược điểm của ES5 và ES6 khi xử lý bất đồng bộ.
Khi bạn gắn một sự kiện click vào button và bạn thử click 2 hay nhiều lần thì sự kiện đó kích hoạt tương đương với số lần mà bạn click. Điều này đã khiến các nhà phát triển phải đối mặt với vấn đề quản lý sự kiện trong một đối tượng DOM.
Trước đây để giải quyết vấn đề này thì ta sử dụng một biến flag dùng để lưu trữ trạng thái của đối tượng. Ban đầu sẽ gắn trạng thái cho nó là false (chưa click), sau khi click thì flag có giá trị true (đã click), dựa vào trạng thái này ta sẽ kiểm soát được số lần click.
Vấn đề này đã được giải quyết trong ES7 bằng cách sử dụng đối tượng Observable.
Giả sử mình tạo sự kiện reszie để lắng nghe người dùng khi họ thay đổi kích thước của trình duyệt.
|
Hoặc sự kiện change:
|
ES8 vừa ra mắt vào tháng 6 năm 2017 với nhiều nânp cấp đáng kể, và trong bài này mình sẽ chia sẻ những tính năng mới của ES8 nhé.
Mọi nâng cấp đều mong muốn loại bỏ những cái thừa thải, bổ sung những cái hay và tối ưu hơn vào ngôn ngữ. Và Javascript cũng vậy, tiếp nối ES7 thì ES8 ra đời với nhiều nâng cấp về các phương thức, thuộc tính của các đối tượng.
Phương thức Object.values()
sẽ trả về một mảng các giá trị nằm trong Object đó. Điều này giống như bạn sử dụng vòng lặp for để lặp qua từng phần tử của Object và lấy giá trị của chúng.
Ví dụ
|
Thực tế mảng cung là một object, vì vậy phương thức này ta cũng có thể áp dụng vào mảng. Tuy nhiên tại sao chúng ta phải làm điều này vì trong khi kết quả nó trả về cũng chính là mảng hiện tai. Nhưng code không hề sai nhé.
Phương thức này trả về một mảng các cặp key => value của Object đó, và thứ tự của các phần tử cũng giống như thứ tự trả về của phương thức Object.values()
.
Ví dụ
|
Bạn có thể thấy, kết quả trả về của Object.entries(object1) sẽ trả về một mảng gồm các cặp key và value.
Phương thức này dùng để kéo dài chuỗi ra với tổng số ký tự sẽ là tham số truyền vào. Tham số này chính là tổng số chiều dài của chuỗi sau khi kéo dài ra, nhưng nó sẽ giữ nguyên nếu bạn nhập vào số nhỏ hơn tổng số ký tự hiện tai. Nếu bạn không nhập ký tự cần thêm thì nó sẽ lấy khoảng trắng lam giá trị mặc định.
Ví dụ
|
Phương thức này dùng để đệm ký tự vào đầu chuỗi, công dụng và ách dùng giống như phương thức String.prototype.padEnd().
Ví dụ
|
Phương thức này trả về tất cả những mô tả của đối tượng. Trước đây chúng ta hay sử dụng console.log để xem dữ liệu của một biến bất kì, nhưng với Object và mảng thì bây giờ có thêm phương thức này.
Ví dụ
|
Như vậy Javascript sẽ bỏ qua dấu phẩy cuối cùng nếu bạn cố tình gõ dư trong mảng.
Ví dụ
|
Nhưng nếu bạn truyền nhiều dấu phẩy thì mặc nhiên nó sẽ tính thêm phần tử, chỉ có dấu cuối cùng là không tính.
Ví dụ
|
Hoặc bạn cũng có thể sử dụng trong Object.
Ví dụ
|
Thậm chí trong hàm hoặc class đều được.
Ví dụ
|
Shared memory is being exposed in the form of a new SharedArrayBuffer type; The new global Atomics object provides atomic operations on shared memory locations, including operations that can be used to create blocking synchronization primitives.
Có nghĩa là, Chia sẻ bộ nhớ cho phép ta đọc và ghi dữ liệu trong cùng một dữ liệu. Vấn đề này là kỹ thuật chuyên môn nên lập trình viên cũng không cần phải quan tâm lắm.
Trên là một số tính năng mới trong ES8. Tuy nâng cấp rất ít nhưng điều đó cho thấy JS luôn phát triển không ngừng.
ES11 hay còn gọi là ES2020, là phiên bản kế tiép của ES10 và phát hành theo từng năm nên nó được public vào năm 2020. Trong bài viết này chúng ta sẽ tìm hiểu những tính năng mới nhất cảu ES11 nhé.
Lưu ý: Những ví dụ dưới đây được test trên Google Chrome từ phiên bản 79 trở lên nhé.
Trong lập trình hướng đối tượng thì mỗi thuộc tính của lớp sẽ có ba trạng thái thông dụng, đó là private, public và protected. Nhưng vì JS là ngôn ngữ cấp thấp nên nó không hỗ trợ vấn đề này.
Tuy nhiên với ES11 thì đã khác, bạn có thể tạo ra thuộc tính private cho một lớp bất kì bằng cách đặt ký tự thăng (#) đằng trước tên biến.
Ví dụ
|
Promises được giới thiệu từ ES6, và nó hỗ trợ hai loại promise combinators, đó là hai phương thức tĩnh Promise.all
và Promise.race
. Nhưng với ES11 thì bạn có thêm phương thức Promise.allSettled
.
Nếu Promise.all
và Promise.race
sẽ dừng lại nếu có bất kì một promise nào bị rejected thì Promise.allSettled
thì khác, nó sẽ chạy tất cả các Promise mặc dù có các promise trước bị reject.
Với Promise.allSettled
chúng ta có thể tạo một promise mới và nó chỉ quay trở lại khi tất cả các promise truyền vào hoàn thành. Vì vậy nó sẽ cho phép chúng ta truy cập vào dữ liệu của tất cả các promise.
Ví dụ
|
Bạn có thể thấy, biến data lưu thông tin dữ liệu của cả hai promise.
Một bổ sung mới cho việc xử lý biểu thức chính quy (Regular Expression). Phương thức match chỉ trả về một kết quả và khong có áp dụng Capture Group, còn matchAll thì có. Bạn có thể xem hai ví dụ dưới đây để hiểu rõ hơn.
Sử dụng match:
|
Sử dụng matchAll:
|
Trước đây việc truy cập vào các key của object sẽ bị lỗi nếu key đó không tồn tại, hoặc cách tốt hơn là trước khi truy cập phải kiểm tra key đó có tồn tại hay khong. Nhưng với ES11 thì bạn có thể xử lý đơn giản bằng toán tử ?
.
Hãy cùng xem một ví dụ đơn giản dưới đây.
|
Để truy cập đến phần tử consumption thì ta sẽ dùng dấu chấm để trỏ đến hai lần như sau:
|
Nhưng chuyện gì xảy ra nếu thuộc tính engine không tồn tại? Nếu lấy thì sẽ bị lỗi ngay như ví dụ dưới đây.
|
Hoặc sử dụng toán tử ba ngôi là ok:
|
Hoặc có thể kiểm tra bằng lệnh if else:
|
Với ES2020 thì bạn có thể xử lý đơn giản bằng một đoạn code như sau:
|
Thậm chí có thể dùng nhiều lần:
|
Và sử dụng với array:
|
Đây là cách import thư viện một cách linh hoạt, ta có thể import một thư viện ở bất kì đâu trong đoạn code, nó giống như hàm include trong PHP.
Giả sử ta có module như sau:
greetingsModule.js
|
Và đây là đoạn code chúng ta import thư viện này tĩnh (static import).
main.js
|
Cú pháp static import này chúng ta chỉ có thể sử dụng ở vị trí top trên cùng của file.
Nhưng với dynamic import thì khác, bạn có thể load ở đâu cũng được.
Ví dụ
|
Hoặc sử dụng trong async/await.
Ví dụ
|
BigInt là một đối tượng built-in object mới được ra mắt trong ES11, dùng để biểu diễn các số nguyên lớn hơn 2 ^ (53) –1. Trong Javascript có đối tượng Number rồi, nhưng nó bị giới hạn trong phạm vi quá hẹp, vì vậy BigInt ra đời nhằm giải quyết vấn đề này.
Hãy xem giới hạn giá trị của Number:
|
Ở ví dụ thứ 3 dữ liệu quá lớn, vì vậy ta có thể giải quyết bằng cách sử dụng BigInt để thay thế.
|
Trên là một vài tính năng mới trong ES11 mà bạn nên biết. Kể từ ES7 trở đi thì những cập nhật chỉ mang tính bổ sung nhỏ, khác hoàn toàn với ES6 nên rất dễ dàng trong việc nâng cập kiến thức.
ES9 được release vào năm 2019, phía sau nó là ES8, ES7, ES6, ... Và cũng giống như các phiên bản trước, ngoài những tính năng cũ thì nó còn bổ sung một số tính năn mới như sau.
Async iterators cũng giống như các iterators, nhưng trong phiên bản này thì nó sẽ trả về một promise, bởi vì tại thời điểm đó iterator sẽ trả về một giá trị không xác định.
Ví dụ:
|
Bài viết vẫn đang cập nhật những tính năng của ES9.
Có lẽ ai cũng biết google map là một dịch vụ bản đồ miễn phí hoàn toàn của google, nó cung cấp các API để người dùng có thể tìm kiếm, xử lý các thông tin trên bản đồ một cách dễ dàng và thuận tiện nhất. Tuy nhiên để sử dụng được nó không hề đơn giản, bạn phải rành Javascript, nếu viết ứng dụng thực tế thì bạn phải kết hợp code ngôn ngữ server như PHP để xử lý. Và trong bài này ta sẽ tìm hiểu bước đầu tiên làm việc với google map đó là đăng ký google API để lấy API key.
Để sử dụng được Google Map thì bước đầu tiên bạn phải đăng ký với google API để tạo một ứng dụng và lấy key google map để sư dụng. Tuy nhiên giao diện hiện tại của google map đã thay đổi nên mình sẽ trình bày theo hai cách, nếu bạn đang sử dụng giao diện nào thì làm theo giao diện đó nha.
Đăng nhập vào tài khoản google và truy cập vào địa chỉ https://console.developers.google.com.
Bạn chọn như trong hình vẽ (APIs & auth -> Credentials) sau đó click vào Create new Key (nút màu xanh)
Một popup hiện ra và bạn sẽ chọn Browser key như hình vẽ.
Một popup khác hiển thị ra và tại đây bạn sẽ nhập website có thể sử dụng được key này, nếu bạn muốn web nào cũng sử dụng được thì để trống.
Sau khi tạo nó sẽ cho bạn một ô thông tin như hình dưới đây, bạn sẽ lấy API trong đó.
Ok vậy là hoàn thành rồi đấy
Truy cập vào địa chỉ website https://code.google.com/apis/console và đăng nhập với tài khoản Gmail mà bạn muốn đăng ký.
Click vào tap API Access, sau đó bạn click Create new Browser key ...
Một popup hiện ra, tại đây bạn chỉ cần click Create button hoặc nhập vào website có thể sử dụng được API này.
Mọi thứ tới đây rất hoàn hảo rồi :D. Sau khi tạo xong nó sẽ hiển thị ra một ô thông tin như hình dưới đây, tại ô này bạn sẽ thấy toàn bộ thông tin ứng dụng mà bạn vừa tạo
Bạn sẽ lưu lại thông tin API key để đưa vào ứng dụng của bạn.
Trong bước này ta sẽ tạo một file hiển thị google map, bạn tạo file index.html
với nội dung như sau:
|
Trong đoạn code này bạn chú ý div#map-canvas
, bản đồ google map sẽ hiển thị trong thẻ div này. Đoạn code thứ hai bạn cần chú ý đó là đoạn style CSS tôi gán #map-canvas { height: 100% }
mục đích cho bản đồ hiển thị full màn hình. Như vậy muốn style cho bản đồ bạn sẽ style lên dviv#map-canvas
.
Chúng ta đang sử dụng javascript google map để xây dựng ứng dụng hiển thị bản đồ với điểm bắt đầu là TPHCM nên bước đầu tiên phải import thư viện js của google vào, với thư viện này ta sẽ sử dụng các API mà nó cung sẵn để thao tác trên bản đồ google map.
Trong thẻ head của file index.html bạn thêm đoạn mã như bên dưới:
|
Trong đoạn này ban sẽ thay API_KEY bằng với mã API_KEY mà bạn đã tạo ở bài trước.
Có lẽ đây là bước quan trọng nhất, ở đây ta sẽ sử dụng một số API google map căn bản để thực hiện hiển thị bản đồ. Và điều đầu tiên bản phải nghĩ tới là tung độ và vỹ độ của vị trí bắt đầy (TP Hồ Chí Minh), ở đây tôi đã tìm được hai thông số này đó là (10.771971, 106.697845). Giờ ta bắt đầu nhé, trong file index.html bạn sẽ thêm một số đoạn mã javascript như bên dưới đây:
|
Như vậy là ta đã tạo được một ứng dụng google map đơn giản, chạy lên thì màn hình sẽ như demo dưới đây: Xem demo
Như vậy ta có toàn file như sau:
|
Nếu bạn chưa có ứng dụng API KEY của google map thì quay lại bài đăng ký google map API để lấy API key sử dụng google map
Với google map API v3 đã được nâng cấp hỗ trợ load bản đồ trên thiết bị di động một cách nhanh chóng, nó tập trung vào các thiết bị smart phone hiện đại như Android và IOS có kích thước hoàn toàn nhỏ hơn thiết bị desktop thông thường. Tuy nhiên chúng ta hoàn toàn có thể lập trình sử dụng google map trên thiết bị di động,
Như bạn biết để hiển thị toàn màn hinh ở bất kỳ độ phân giải và kích thước nào thì ta sẽ dùng chiều rộng và chiều cáo tính theo %, browser sẽ tự động đo kích thước và tính % hiển thị. Như vậy để hiển thị full màn hình trên thiết bị di động thì ta sẽ thiết lập thẻ DIV chứa google map width = 100%
và height = 100%
.
Một điều cần chú ý nữa là với thiết bị điện thoại IOS hay Android thì ta sẽ dùng thẻ meta viewport <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />.
Để kiểm tra người dùng đang sử dụng thiết bị nào thì ta sẽ dùng một hàm đơn giản dưới đây:
|
Để tìm hiểu thêm các thông tin google map trên thiết bị di động bạn vào những link dưới đây để tham khảo thêm:
Mặc định ngôn ngữ hiển thị trên google map là tiếng anh, tuy nhiên nếu bạn có nhu cầu chuyển sang một ngôn ngữ khác thì Google map API v3 đã cung cấp cho bạn một cách rất đơn giản đó là truyền tham số ngôn ngữ muốn hiển thị khi load script google map:
|
Ví dụ: Hiển thị bản đồ thành phố Hồ Chí Minh với ngôn ngữ hiển thị là Tiếng Việt:
|
Javascript là một ngôn ngữ Client, có nghĩa là nó sẽ thao tác với người dùng thông qua các sự kiện và ngôn ngữ này sẽ lắng nghe những thao tác của người dùng thông qua sự kiện đó. Trong bài này ta sẽ tìm hiểu hai loại sự kiện trong google map:
property_changed
, trong đó property thay đổi tùy theo sự kiệnMỗi đối tượng của Google Map sẽ đưa ra những sự kiện có tên xác định, các chương trình viết thao tác với đối tượng API này thông qua hàm addListener(), hàm này nằm trong google.maps.event namespace. Bạn có thể tham khảo danh sách các sự kiện Google Map Events tại đây.
Các đối tượng Google Map có nhiệm vụ nhận diện các sự kiện của người dùng như Hover, Click hay những sự kiện trên bàn phím như keypress, uy keyup, ... Dưới đây là danh sách các sự kiện mà đối tượng google.maps.Marker có thể lắng nghe được:
Để xem danh sách đầy đủ bạn có thể tham khảo danh sách sự kiện Marker class. Những sự kiện này có thể trông giống như các sự kiện của DOM nhưng trong thực tế có những sự kiện chỉ dành riêng cho Google API Map. Vì các trình duyệt có cách lắng nghe sự kiện khác nhau nên google API Map sẽ đáp ứng mà không cần phải check trình duyệt, những sự kiện này có thể sẽ có truyền tham số như những sự kiện bình thường.
Đối tượng Google Map MVC thường lưu các trạng thái và nó sẽ thay đổi nếu lập trình viên thiết lập sự thay đổi đó khi nào (khi click, dbclick). Ví dụ khi load bản đồ google map TP HCM lên thì sự kiện zoom_changed sẽ thay đổi độ phóng to thu nhỏ theo tham số mà ta truyền vào. Như ở trên tôi có trình bày là ta có thể dùng hàm addListener() để đăng ký xử lý cho sự kiện nào đó của Google API Map.
Để đăng ký thông báo với hệ thống Google Map là ta cần xử lý sự kiện gì thì dùng hàm addListener()
, đây là phương thực của đối tượng google.maps.event
.
Ví dụ: Map và Marker Events - Xem demo
Trong ví dụ này là sự kết hợp giữa người dùng và sự thay đổi trạng thái. Tôi sẽ đính kèm một hành động là phóng to bản đồ lên khi click vào biểu tượng của Marker Google Map, và tôi có thêm một hành động nữa đó là ở sự kiện center_changed, khi sự kiện này kích hoạt (nghĩa là người dùng kéo bản đồ) thì trong vòng 3 giây tôi sẽ đưa nó trở về vị trí cũ.
|
Ở bài Google map events - UI Events - MVC State Changes chúng ta đã tìm hiểu một số khái niệm về Events trong google map, các ví dụ điển hình của events. Tuy nhiên các ví dụ chưa thể hiện được tất cả những cách dùng google map thông dụng, nên trong bài này ta tiếp tục tìm hiểu các ví dụ của google map nhé.
Getting (Lấy) và Setting (Thiết lập) là hai khái niệm rất quen thuộc trong lập trình hướng đối tượng, nó được sử dụng trong các mô hình thiết kế Design Partern của OOP. Trong javascript cũng tương tự, ta sử dụng hai khái niệm này để lấy các thuộc tính của UI Events hoặc MVC State google map, lúc này biến mà chúng ta gán thuộc tính này sẽ lưu toàn bộ các trạng thái hiện tại của thuộc tính, và khi bạn thay đổi thuộc tính trong biến đó thì tương ứng trong đối tượng Google cũng thay đổi theo.
Ví dụ dưới đây mô tả cách sử dụng GET và SET trong Google Map:
Trong ví dụ này xử lý sự kiện khi Zoom bản đồ google map ta sẽ lấy độ Zoom và hiển thị tại vị trí trung tâm của bản đồ.
|
Trong mô hình sự kiện của Google Map được tạo và quản lý dưới hệ thống Instance của Google Map, như vậy muốn truy xuất tới Events Google Map thì bắt buộc bạn phải thao tác lên trên đối tượng của Google Map. Giả sử giờ tôi muốn tạo một button, khi click vào button đó thì sự kiện nào đó trong google map sẽ nhận thấy và thực thi theo, rất đơn giản ta sử dụng hàm addDomListener() của Google Map.
Cú pháp: addDomListener(instance:Object, eventName:string, handler:Function)
Lưu ý rằng addDomListener() chỉ đơn giản là nhận diện các sự kiện của DOM trên trình duyệt và xử lý theo mô hình DOM của trình duyệt. Tuy nhiên đa số các trình duyệt hiện tại hỗ trợ DOM ở mức Level2, để tham khảo các mức Dom Level thì bạn tham khảo theo link Mozilla DOM Levels.
Ở các bài trước tôi sử dụng hàm window.onload để khởi tạo bản đồ google map, tuy nhiên ta vẫn còn cách khác nữa là gán vào sự kiện onload của thẻ <body>
. Tham khảo 2 cách dưới đây:
Cách 1:
|
Cách 2:
|
Ví dụ dưới đây sẽ thực hiện alert lên một thông báo nếu như ta click vào div canvas chứa bản đồ.
|
Để thêm một sự kiện nào đó ta dùng hàm addListener(), và để xóa một sự kiện nào đó ra khỏi hệ thống DOM của Google Map ta dùng hàm removeListener().
Ví dụ:
|
Để xóa hết các listener ta dùng hàm clearInstanceListeners().
Ví dụ:
|
Để xóa danh sách các listener theo một sự kiện nào đó, ví dụ như click thì ta sử dụng hàm clearListeners().
Ví dụ:
|
Để tham khảo danh sách các sự kiện bạn vào link google.maps.event namespace để đọc thêm.
Khi bản đồ google map hiển thị lên thì người dùng sẽ thao tác với bản đồ thông qua giao diện người dùng, giao diện này được cấu hình từ những hệ thống Google Map Controls. Vì thế để đi sâu vào tìm hiểu google map thì ta bắt buộc phải tìm hiểu danh sách các controls trong google map (google map api controls).
Dưới đây là danh sách những Controls trong google map mà bạn có thể sử dụng được:
Bạn không thể truy cập hoặc thay đổi các controls của google map một cách trực tiếp được mà phải thông qua một đối tượng mà Google Map cung cấp sẵn, đó là MapOptions, bạn sẽ can thiệp vào trong khi nó khởi tạo bản đồ. Hoặc bạn có thể sửa đổi bản đồ thông qua hàm setOptions() để đổi các Options của Google Map.
Không hẳn là tất cả các controls đều được chạy theo mặc định mà đôi lúc còn phụ thuộc vào giao diện người dùng, bạn có thể tham khảo link Default UI.
Bảng dưới đây là danh sách các Default UI của google map: (Mình không dịch sang tiếng việt)
Control | Large Screens | Small Screens | iPhone | Android |
---|---|---|---|---|
Zoom | Large Zoom for sizes larger than 400x350px | Small Zoom for sizes smaller than 400x350px | Not present. Zooming is accomplished by a two finger pinch. | "Touch" style control |
Pan | Present for sizes larger than 400x350px | Not present for sizes smaller than 400x350px | Not present. Panning is accomplished by touch. | Not present. Panning is accomplished by touch. |
MapType | Horizontal Bar for screens 300px wide and larger | Dropdown for screens smaller than 300px wide | Same as Large/Small Screens | Same as Large/Small Screens |
Scale | Not present | Not present | Not present | Not present |
Ngoài những thông số UI Default này thì các sự kiện của bàn phím luôn bật (mặc định), tham khảo hai bài (google map events và ví dụ google map events). Bạn có thể tham khảo giao diện Default UI khi hiển thị ở hình dưới đây:
Đôi lúc bạn muốn tắt đi những thông số mặc định của Default UI thì rất đơn giản, Google Map có cung cấp một API giúp ta xử lý vấn đề này, tên gọi của nó là disableDefaultUI. Thông số disableDefaultUI sẽ xóa đi những cấu hình mặc đinh của Google Map.
Ví dụ: Hiển thị bản đồ google map thành phố Hồ Chí Minh ở dạng xóa Default UI:
|
Chạy lên giao diện hoàn toàn mất đi các controls như hình dưới đây:
Ở bài tìm hiểu google map controls chúng ta đã biết danh sách các controls trong google map, và chúng ta cũng đã biết cách thiết lập cấu hình bỏ đi tất cả những thông số mặc định của google map (Default UI) bằng cách sử dụng API disableDefaultUI. Tuy nhiên trong thực tế đôi lúc chúng ta lại muốn có những cấu hình khác mang tính nâng cao hơn thì làm thế nào? Trong bài này ta sẽ tìm hiểu cách cấu hình google map controls nhé.
Đôi lúc bạn muốn chỉnh giao diện của Map bằng cách thêm, bỏ một số control trong google map và những hành động này sẽ không bị ảnh hưởng với những phiên bản nâng cấp của Google Map API. Chính vì những thông số Default UI dẫn đến tình trạng đôi lúc nó không phù hợp với project của bạn nên bắt buộc ban phải điều chỉnh. Dưới đây là các trình điểu khiển Map Controls API, bạn sẽ thiết lập true nếu muốn hiển thị và false nếu không muốn hiển thị.
|
Dưới đây là một ví dụ ẩn đi một số Map Controls (Zoom Control và Pan Control) và hiển thị Scale Control. Lưu ý với bạn rằng cấu hình này không phải là mặc định của google map mà do chính ta định nghĩa.
|
Một số control được cấu hình cho phép bạn thay đổi hành vi hoặc thay đổi sự xuất hiện của nó. Chẳng hạn như Zoom Control, nó có thể xuất hiện bản zoom lớn điều khiển dạng thanh trượt, hoặc nó cũng có thể xuất hiện là một bản nhỏ và zoom bản đồ nhỏ hơn. Những điều khiển này được bằng cách thay đổi các Options của chính đối tượng đó. Ví dụ với Zoom thì được tùy chỉnh bởi các thông số nằm trong zoomControlOptions.
Với Zoom Control bạn có thể cấu hình ở một trong các dạng sau:
Với Map Control bạn có thể cấu hình ở một trong các dạng sau:
Lứu ý: Nếu bạn sửa đổi bất kỳ một thông số nào của các Control thì bạn nên chỉ định rõ ràng bằng cách thiết lập là TRUE/FALSE chứ không nên để nó lấy giá trị mặc định, điều này sẽ không tường minh và trong tương lại các version sau đôi khi có sự thay đổi giá trị default nên sẽ dấn đến tình trang ứng dụng bạn chạy sai.
Ví dụ bạn khai báo tường minh:
... zoomControl: true, zoomControlOptions: { style: google.maps.ZoomControlStyle.SMALL } ...
Ví dụ dưới đây hiển thị bản đồ với MapType dạng Dropdown và sử dụng Zoom control dạng Mini-zoom (không có thanh scroll kéo zoom).
|
Chạy lên bạn thấy Maptype đã chuyển sang dạng dropdown và Zoom sẽ ở chế độ Mini-zoom như hình dưới đây:
Google map cung cấp các options cho phép ta cấu hình các Map Controls, như vậy các trường cấu hình sẽ nằm trong một danh sách xác định, coder không được phép thêm hay xóa. Ví dụ với mapTypeControl
thì sẽ có các options cấu hình chứa trong đối tượng mapTypeControlOptions
. Dưới đây là danh sách các options tương ứng với các controls:
Hầu hết các controls trong google map đều cấu hình được vị trí hiển thị của nó, các vị trí bao gồm:
Tương ứng với hình sau:
Dưới đây là một ví dụ thể hiện xác định vị trí của các controls trong google map.
|
Tiếp theo bài cấu hình map controls nâng cao chúng ta tiếp tục tìm hiểu các kiến thức nâng cao hơn đó là Custom Google Map Controls Advance. Như bạn biết tất cả những gì hiển thị trên Browser để là các đoạn mã HTML bình thường nên bạn muốn hiển thị như thế nào thì chỉ cần đánh vào thẻ HTML và CSS. Dưới đây là một quy trình chuẩn giúp bạn custom google map.
Bây giờ ta sẽ đi sâu vào từng phần cho bạn thấy.
Như phần giới thiệu trên, ta sẽ đặt các controls vào một thẻ DIV và style cho thẻ DIV này. Dưới đây là một ví dụ thể hiện vấn đề này:
|
Ví dụ này chỉ đọc tham khảo để hiểu cách tạo mới một thẻ div, gán một thẻ HTML khác vào thẻ div, ...
Ví dụ:
|
Các trình điều khiển tùy chỉnh (tự tạo) được đặt ở một vị trí nào đó trên bản đồ được xác định bởi chính coder (google map controls position), danh sách các control hiện tại ở một vị trí nào đó được xác định bởi mảng map.controls và các key chính là những vị trí của đối tượng google.maps.ControlPositions.
Ví dụ:
|
Trong ví dụ này ta sẽ tạo một bản đồ google map, một button với tên là HOME và đặt nó ở vị trí TOP_RIGHT. Khi click vào biểu tượng HOME này thì bản đồ sẽ di chuyển đến vị trí trung tâm thành phố Hồ Chí Minh.
|
Ở ví dụ ở mục 4 chúng ta xác định vị trí bản đồ TP Hồ Chí Minh, bây giờ chúng ta sẽ làm ví dụ nâng cao hơn xíu là khi người dùng kéo bản đồ đến vị trí khác, sau đó click vào một control với tên (Thiết lập trung tâm) thì sẽ thiết lập vị trí trung tâm chính là nơi đang ở chứ không phải là TPHCM nữa.
|
Google Map cho phép bạn tùy chỉnh thông tin hình ảnh hiển thị, đường đi, cây cối, công viên hay các công trình xây dựng theo chuẩn của google. Điều này giúp ta tạo ra bản đồ theo phong cách riêng và nhìn bắt mắt hơn bản đồ mặc định của google map.
Hiện tại có hai cách để tùy chỉnh giao diện google map (apply styles to a map)
Cả hai cách trên đều xử lý thông qua một mảng các options style. Chúng ta sẽ đi tìm hiểu hai cách styled maps này sau nhé.
Có hai cách để ta style riêng cho bản đồ google map:
Các tính năng style bản đồ được thông qua MapOptions dưới dạng một mảng có dạng:
|
Một bản đồ bao gồm nhiều tính năng, đối tượng. Ví dụ như đường đi, cây cối, công viên, ... tất cả những thuộc tính đối tượng này đều sử dụng một MapTypeStyleFeatureType.
Cú pháp Map Features như sau: featureType: 'feature'
Ví dụ:
|
Lưu ý: Một số thuộc tính có thêm thuộc tính con thì nó được coi là thuộc tính chứa nhiều thuộc tính con, và ta dùng dấu chấm (.) khai báo (ví dụ landscape.natural
). Ngoài ra, các thuộc tính bản đồ của các đối tượng không phải lúc nào cũng giống nhau, có loại thể hiện bằng hình học đồ họa, có loại biểu thị bằng chữ (text). Để chọn phần tử đối tượng nào thì ta xác định nó ở thuộc tính MapTypeStyleElementType. Dưới đây là danh sách các Feature được hỗ trợ:
Ví dụ dưới đây chọn nhãn cho tất cả local roads:
|
Các bộ chọn tính năng bản đồ được xác định bở một mảng các Options, bạn có thể kết hợp nhiều options nhưng cũng có những Options kết hợp lại sẽ bị hạn chế. Nếu mảng các options của bạn vượt khỏi danh sách số lượng Feature thì sẽ không được thực hiện. Dưới đây là một ví dụ đơn giản:
|
Để thay đổi phong cách mặc định của bản đồ (thay đổi nhãn và đường bộ cũng ảnh hưởng đến địa hình và vệ tinh) thì ta thiết lập mảng các style MapOptions bởi hàm setOptions().
Ví dụ:
|
Bạn có thể tự tạo một style map rồi đưa nó vào hệ thống style của google map.
Ví dụ:
|
Trong ví dụ này các label bị ẩn đi, địa hình (geometry) và nước (water) chuyển thành màu đà đà (#890000).
|
Trong ví dụ này ta làm phức tạp hơn chú xíu đó là chỉnh nhiều thông số hơn.
|
Map Marker chính là những biểu tượng gắn cho một địa điểm cụ thể nào đó, nó cung cấp các API với tên gọi là Google Marker Api. Dựa vào các thông số Marker API này ta dễ dàng custom được giao diện marker, cũng như là thêm, xóa marker ra khỏi bản đồ google map.
Những biểu tượng hình quả cầu mang ký hiệu A, B,C,D,E,F,G chính là những Marker của google map. Tuy nhiên biểu tượng đó chính là hình mặc định, chúng ta có thẻ thể lập một hình khác đẹp hơn tùy theo sở thích của mình. Vấn đề này ta sẽ đi sâu vào cụ thể hơn ở những phần dưới đây.
Đối tượng google.maps.Marker
được khởi tạo bởi các options sau:
setMap()
.Ví dụ dưới đây thêm một Marker vào bản đồ tại vị trí Uluru.
|
Trong ví dụ trên Marker được tạo trên map ngay lúc nó khởi tạo, tuy nhiên chúng ta vẫn có thể dùng hàm setMap() như ví dụ dưới đây:
|
Để xóa một Marker ra khỏi map thì ta dùng hàm setMap(null)
, sự khác biệt chính là giá trị null khi truyền vào:
|
Lưu ý: Khi sử dụng hàm này thì đối tượng Marker sẽ không bị xóa mà nó chỉ không gắn vào một map nào đó thôi. Để xóa hẳn nó thì ta phải thiết lập biến Marker sang giá trị null.
Nếu bạn muôn thêm nhiều Marker và quản lý chúng dễ dàng thì hay sử dụng mảng để khởi tạo, bạn sẽ lặp từng phần tử và dùng hàm setMap()
để thiết lập. Như vậy khi delete thì bạn sẽ lặp mảng và thiêt lập setMap(null)
, sao đó gán mảng chứa danh sách thành null là mọi thứ đã ok.
Ví dụ: Tạo một chương trình quản lý Marker đơn giản.
Trong ví dụ này sẽ hiển thị một Marker mặc định, sau đó khi click vào vị trí nào đó trên bản đồ thì sẽ tạo thêm một marker tại vị trí đó. Sau đó tạo 3 controls (Google Map Custom Map Controls) có chức năng Hide, Show và Delete Marker.
|
Các Marker trong google map có thể di chuyển được bằng cách cấu hình thuộc tính Animate a marker animation của google.maps.Animation. Dưới đây là những loại animation marker mà Marker có support.
Nếu một Map đã được tạo và chưa có Marker, sau đó bạn muốn thêm marker thì hãy dùng hàm setAnimation() của đối tượng Marker nhé.
Ví dụ: Animation Marker.
Trong ví dụ này lúc đầu Marker sẽ hiển thị dạng DROP, nhưng khi người dùng click vào Marker thì sẽ hiển thị dạng BOUNCE.
|
Nếu bạn có nhiều Marker trên bản đồ và bạn muốn delete từng Marker thay vì khi delete thì nó xóa 1 lúc nhìn không đẹp mắt. Trường hợp này bạn sẽ dùng hàm setTimeOut để thiết lập thời gian xóa cho từng Marker trong vòng lặp.
|
Các Markers Icons có thể được thay đổi hình hiển thị thay vì nó hiển thị hình mặc định. Trong phần này chúng ta sẽ tìm hiểu ba ví dụ căn bản đó là simple icons, complex icons, and symbols.
Người dùng thường chọn những hình ảnh icon đơn giản (Simple Icons) thay vì Icon mặc định của google map. Để làm việc này ta sẽ chú ý đến thuộc tính icon, thuộc tính này sẽ chứa đường dẫn trỏ đến link icon ta muốn hiển thị thay cho Marker Icon Default.
Ví dụ: Hiển thị bản đồ Google Map thành phố Hồ Chí Minh
|
Đôi lúc bạn muốn icon của mình mang chút phong cách riêng và phức tạp hơn, điều này google map cũng chấp nhận cho phép bạn thực hiện nhưng code sẽ khó khăn hơn nhiều. Riêng đối với Marker Shadow thì phiên bản mới nhất này đã bỏ đi nên không thể sử dụng được.
Trong ví dụ này vị trí các Marker đa bị thay đổi, bởi các thông số hình ảnh. Các bạn thay đổi các thông số Image (size,origin, anchor) để test nhé.
|
Trong đối tượng object Marker có một options cho phép người dùng được kéo di chuyển Marker hoặc không, đó là draggable. Giá trị của nó là TRUE thì di chuyển được, FALSE thì sẽ không di chuyển được.
|
InfoWindow được khởi tạo bởi các thông số InfoWindowOptions gồm:
Thông thường nếu bạn muốn nội dung content của Info Window rõ ràng thì bạn nên đặt nó bao ngoài bởi một thẻ DIV, và bên trong thẻ DIV bạn có thể đặt thêm các đoạn mã HTML khác. Như vậy giao diện cửa sổ thông báo nhìn sẽ đẹp và ít lỗi hơn.
Góp ý: Nếu bản đồ của bạn có quá nhiều cửa sổ thì lúc hiển thị ra nó sẽ chiếm không gian của bản đồ, vì thế các cửa sổ thông tin bạn nên ẩn nó đi rồi tạo các sự kiện khi click vào rồi mới hiển thị. Như vậy nhìn bản đồ sẽ rất thoáng, thay vì hiển thị một loạt các InfoWindow thì chỉ show các Control quản lý.
Ví dụ:
|
Ở phần Add an info window ta chỉ tìm hiểu cách tạo một Object Info Window chứ chưa có hiển thị lên màn hình, dù trong ví dụ có hiển thị nhưng lúc đó chỉ là tạm thời chấp nhận hàm open đó :D. Nên trong phần này ta sẽ tìm hiểu cú pháp nó chi tiết hơn.
Cú pháp: infowindow.open(map, control);
Trong đó:
Ví dụ: Trong ví dụ này khi click vào Marker sẽ hiển thị thông tin cửa sổ. Nếu bạn chưa biết sự kiện thì đọc bài Google Map Events.
|
Để tắt một cửa sổ InfoWindow thì ta dùng hàm Close().
Ví dụ: infowindow.close()
Để thay đổi vị trí của infowindow thì ta sẽ có hai cách:
Trong phần này tôi sẽ không đưa ra ví dụ vì thực sự nó cũng đơn giản, chỉ cần các bạn nắm phần 1,2 là mọi thứ ok cho phần 3,4 rồi.
Đôi lúc trong ứng dụng google map ta cần vẽ một số hình dạng để nhấn mạnh một địa điểm nào đó. Một hình dạng ta gọi là một đối tượng nằm trong bản đồ và được xác định bởi nhiều vĩ độ/kinh độ kết hợp với nhau tạo thành một hình. Các hình dạng đó có thể là đường thẳng, hình trong, hình đa giác và hình chữ nhật (lines, polygons, circles and rectangles). Ngoài ra bạn còn có thể cho phép người dùng di chuyển và kéo dãn các đối tượng này trên bản đồ.
Trong bài này chúng ta sẽ tìm hiểu cách vẽ đường thẳng trên bản đồ trước (How to draw a line on google map). Như tôi đã trình bày, để vẽ đối tượng nào thì chúng ta sẽ sử dụng chính đối tượng đó (ví dụ vẻ marker thì ta dùng đối tượng Marker google map), để vẽ đường thẳng thì ta dùng đối tượng Polyline google map, đối tượng này chứa danh sách các địa chỉ vĩ độ và kinh độ được lưu trong một mảng, dựa vào mảng này Polilines sẽ hiển thị trên bản đồ.
Đối tượng Polyline được khởi tạo bởi những PolylineOptions, những PolylineOptions bao gồm các thông số vĩ độ/kinh độ và các tùy chọn hiển thị. Bạn có thể tùy chọn hiển thị màu sắc, định dạng, hình mờ ... Dưới đây là danh sách các Options của Polyline:
Các PolylineOptions có các thông số cấu hình cho phép người dùng di chuyển, kéo thả, và để gắn đối tượng Polyline vào bản đồ thì ta sẽ dùng hàm setMap(). Để rõ ràng hơn các bạn xem ví dụ dưới đây:
Trong ví dụ này sẽ tạo ra một đường thẳng màu đen và rông 4 pixels với các vĩ độ và kinh độ xác định cho trước.
|
Để xóa đường thẳng ra khỏi bản đồ thì cách làm cũng tương tự như Marker, ta sẽ thiết lập giá trị null cho hàm setMap(null). Sau khi thiết lập null thì đường thẳng này không mất, nó chỉ bị ẩn đi thôi, nếu bạn muốn nó mất luôn thì hãy gán giá trị của đối tượng polyline hiện tại bằng null.
Ví dụ:
|
Một đường thẳng vẽ trên bản đồ được xác định bởi vĩ độ và tung độ, như vậy ta có thể lấy được tọa độ tại một điểm trong đường thẳng đã vẽ bởi hàm getPath(), hàm này sẽ trả về một mảng array chứa danh sách các tọa độ. Sau đây là danh sách các hàm thao tác lên đường thẳng:
Các tham số đều là vĩ độ và kinh độ.
Trong ví dụ này khi click (Google map events) vào bản đồ sẽ tạo các Marker, và khi click từ marker thứ 2 trở đi sẽ tạo một đường thẳng nối hai điểm đó lại với nhau
|
+ Lê Văn Thuyên-0379136392:Cảm ơn quý vị và các bạn đã vào Website của Lê Thuyên! Lê thuyên rất mong nhận được sự góp ý của quý vị và các bạn cho sự phát triển của website này. Xin chân thành cảm ơn!
* Dũng Trung-090567448:Lê Văn Thuyên0379136392--->Ok.Anh!
* Bé Nguyễn-benguyen@gmail,com:Lê Văn Thuyên0379136392--->Good job!
+ -:
+ -: