Javascript nâng cao

Ngày đăng: 2023-05-12 14:09:02

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

III. Javascript Nâng cao

III.1.Ba cách tạo class trong Javascript

      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.

1. Sử dụng Function

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.

function User()

{

    // Thuộc tính

    this.username = '';

    this.password = '';

     

    // Phương thức

    this.setInfo = function(username, password){

        this.username = username;

        this.password = password;

    };

     

    this.checkLogin = function(){

        return (this.username === 'admin' && this.password === '@123');

    };

     

    // Phải return this thì mới tạo mới được đối tượng

    return this;

}

Cách sử dụng:

// Cách sử dụng

var user = new User();

user.setInfo('admin', '@123');

if (user.checkLogin()){

    alert('Đăng nhập thành công');

}

else{

    alert('Đăng nhập thất bại');

}

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.

function User()

{

    // Thuộc tính

    this.username = '';

    this.password = '';

     

    // Phải return this thì mới tạo mới được đối tượng

    return this;

}

 

// Bổ sung phương thức

User.prototype.setInfo = function(username, password){

    this.username = username;

    this.password = password;

};

 

User.prototype.checkLogin = function(){

    return (this.username === 'admin' && this.password === '@123');

};

Cách sử dụng cũng không có gì khác:

// Cách sử dụng

var user = new User();

user.setInfo('admin', '@123');

if (user.checkLogin()){

    alert('Đăng nhập thành công');

}

else{

    alert('Đăng nhập thất bại');

}

2. Sử dụng Object

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:

var objectName = {};

// Hoặc

var objectName = new Object();

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.

var User = {

     

    // Thuộc tính

    username : "",

    password : "",

     

    // Phương thức

    setInfo : function(username, password){

        this.username = username;

        this.password = password;

    },

    checkLogin : function(){

        return (this.username === 'admin' && this.password === '@123');

    }

     

};

Cách sử dụng:

// Cách sử dụng

User.setInfo('admin', '@123');

if (User.checkLogin()){

    alert('Đăng nhập thành công');

}

else{

    alert('Đăng nhập thất bại');

}

3. Sử dụng Singleton Object

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.

var User = new function(){

     

     // Thuộc tính

    this.username = '';

    this.password = '';

     

    // Phương thức

    this.setInfo = function(username, password){

        this.username = username;

        this.password = password;

    };

     

    this.checkLogin = function(){

        return (this.username === 'admin' && this.password === '@123');

    };

     

};

Cách sử dụng:

// Cách sử dụng

User.setInfo('admin', '@123');

if (User.checkLogin()){

    alert('Đăng nhập thành công');

}

else{

    alert('Đăng nhập thất bại');

}

III.2.Cách xử lý lỗi với try catch trong Javascript

         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.

1. Try catch javascript là gì?

   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é.

2. Cú pháp lệnh try catch trong javascript

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:

try {

    // Quăng lỗi ra

    throw("Noi dung loi"); 

} catch (e){

    // Đón nhận lỗi và in ra

    // Vị trí này chỉ chạy khi ở try có quăng lỗi hoặc ở try

    // sử dụng sai cú pháp ...

    console.log(e.message);

} finally{

    // Cuối cùng chạy cái này

    // Luôn luôn chạy sau cùng

    console.log('End of try catch');

}

Như vậy luồng chạy của lệnh try catch sẽ như sau:

  • Bước 1: Thực hiện trong try.
  • Bước 2: Nếu trong try xuất hiện lỗi thì nhảy sang catch
  • Bước 3: Cuối cùng nhảy xuống 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.

try {

    // Sử dụng biến message chưa được định nghĩa

    console.log(message); 

} catch (e){

    console.log(e.message);

}

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.

try {

    fadsfas

    fasdfas

    fsda

} catch (e){

    console.log(e.message);

} finally{

    console.log('End');

}

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.

3. Throw new Error() trong try catch javascript

    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:

throw new Error('Nội dung thông báo lỗi');

Đố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.

var domain = 'thuanhoaonline.com'

 

try {

    if (domain !== 'thuanhoaonline.com'){

        throw new Error('Domain nay khong phai la trang chu');

    }

} catch (e){

    console.log(e.message);

} finally{

    console.log('End');

}

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é.

catch (e){

    console.log(e);

}

Kết quả như sau:

Error: Domain nay khong phai la trang chu

    at editor_display.html:16

editor_display.html:21 End

4. Tạo một object error có thể sử dụng trong try catch

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.

Bước 1: Tạo một object UserError

Sử dụng function:

function UserError(){

     

    this.throwLogin = function(){

        throw new Error('Invalid username and password');

    };

     

    this.throwSession = function(){

        throw new Error('Your request is timeout');

    };

     

    return this;

}

Hoặc sử dụng class:

class UserError {

    throwLogin() {

        throw new Error('Invalid username and password');

    }

 

    throwSession = function () {

        throw new Error('Your request is timeout');

    }

}

Bước 2: Sử dụng object UserError

Để sử dụng thì chúng ta làm như sau:

var username = 'thehalfheart';

var password = 'admin@';

try {

    if (username !== 'admin' || password != 'admin@'){

        new UserError().throwLogin();

    }

}catch (e){

    console.log(e.message);

}

5. Danh sách các Object Error trong Javascript

Đọ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.

  • EvalError - Lỗi trong hàm eval.
  • RangeError - Nằm ngoài phạm vi giới hạn của một kiểu dữ liệu nào đó.
  • ReferenceError - Sử dụng một biến chưa được khai báo
  • SyntaxError - Lỗi về cú pháp
  • URI (Uniform Resource Identifier) Error - Lỗi được đưa ra nếu bạn sử dụng các ký tự không hợp lệ trong một hàm URI.

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.

let error = new Error(message);

// or

let error = new SyntaxError(message);

let error = new ReferenceError(message);

Sau đó kết hợp với lệnh throw để quăng lỗi vào phần catch.

III.3.Hiểu rõ hơn về từ khóa this trong Javascript

   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é.

1. Từ khóa this trong Function

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:

function Student()

{

    this.name = '';

    this.age = '';

 

    this.showInfo = function()

    {

        console.log(this.name);

        console.log(this.age);

    };

}

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.

function Student()

{

    this.name = '';

    this.age = '';

 

    this.showInfo = function()

    {

        console.log(this.name);

        console.log(this.age);

    };

}

 

// Khởi tạo đối tượng

var student = new Student();

 

// Gán giá trị cho các thuộc tính

student.name = 'Nguyễn Văn Cường - freetuts.net';

student.age = '27';

 

// Hiển thị thông tin

student.showInfo();

 

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:

function Student()

{

    this.name = '';

    this.age = '';

     

    // Log1

    console.log(this);

 

    this.showInfo = function()

    {

        // Log2

        console.log(this);

    };

}

 

// Khởi tạo đối tượng

var student = new Student();

 

// Gán giá trị cho các thuộc tính

student.name = 'Nguyễn Văn Cường - freetuts.net';

student.age = '27';

 

// Hiển thị thông tin

student.showInfo();

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é.

setTimeout(function(){

    // Log1

    console.log(this);

}, 2000);

 

// Log2

console.log(this);

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

2. Từ khóa this trong thẻ HTML

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é.

<!DOCTYPE html>

<html>

    <head>

        <title>Từ khóa this trong thẻ HTML</title>

        <meta charset="UTF-8">

        <meta name="viewport" content="width=device-width, initial-scale=1.0">

        <script language="javascript">

            function showMessage(obj)

            {

                console.log(obj);

            }

        </script>

    </head>

    <body>

        <input type="button" id="clickme" onclick="showMessage(this)" value="Click Me" />

    </body>

</html>

Bạn để ý trong button mình có một đoạn mã gọi tới hàm showMessage trong sự kiện click.

<input type="button" id="clickme" onclick="showMessage(this)" value="Click Me" />

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é.

function showMessage(obj)

{

    console.log(obj);

}

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:

function showMessage(obj)

{

    alert(obj.value);

}

Đ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é.

3. Từ khóa this sự kiện lồng nhau

Trong phần này mình sẽ lấy một ví dụ được viết bằng jQuery.

 

$('#button').click(function(){

 

    // This của #button

    console.log(this);

 

    $('#element').click(function(){

        // This của #element

        console.log(this);

    });

});

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.

III.4.Hàm closure trong javascript

     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é.

1. Closure javascript là gì?

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():

function sayHi(name){

    let say = function(){

        alert("Xin chào, tôi là " + name);

    };

    return say;

}

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 đó.

function sayHi(name){

    return function(){

        alert("Xin chào, tôi là " + name);

    };

}

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é.

var cuong = sayHi("Cường");

console.log(cuong);

Kết quả trả vè nó là một function như sau:

ƒ (){

        alert(message);

    }

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.

var cuong = sayHi("Cường");

cuong(); // Kết quả: Xin chào, tôi là cường

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.

2. Closure trong javascript với biến của hàm cha

Trước khi đi vào vấn đề chính thì mình xin nhắc lại hai lưu ý sau:

  • Các biến bên trong hàm sẽ kết thúc khi hàm đó được chạy xong.
  • Closure có thẻ sử dụng biến cục bộ, biến hàm cha và biến trong chính hàm đó.

Bây giờ hãy tạo cho mình một function như sau:

function counter(){

    var count = 1;

    return function(){

        return count++;

    };

}

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:

var c = counter();

   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.

var c = counter();

console.log(c()); // Kết quả: 1

Mình sẽ gọi hàm c() thêm vài lần nữa.

var c = counter();

console.log(c()); // Kết quả: 1

console.log(c()); // Kết quả: 2

console.log(c()); // Kết quả: 3

console.log(c()); // Kết quả: 4

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.

3. Con trỏ this trong closure function

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

function sayHi(){

    return function(){

        console.log(this);

    };

}

 

var msg = sayHi();

msg(); // window object

Chế độ strict mode

'use strict';

function sayHi(){

    return function(){

        console.log(this);

    };

}

 

var msg = sayHi();

msg(); // Undefined

4. Closure trong class javascript

    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.

class Student{

    constructor(name){

        this.name = name;

    }

     

    showName(){

        // Đặt một cái tên khác cho this

        let obj = this;

        return function(){

            console.log("Xin chào, tôi là " + obj.name);

        };

    }

}

 

var student1 = new Student("Cường");

var cuong = student1.showName();

cuong();

5. Một vài ví dụ về closure trong javascript

   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.

Closure có tham số

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.

// Bước 1: Tạo hàm closure

function showMessage(message)

{

    return function(time){

        for (var i = 1; i <= time; i++){

            alert(message + ' - thuanhoaonline.com');

        }

    };

}

 

// Bước 2: khởi tạo hàm closure

var messageFunc = showMessage('Xin chào các bạn');

 

// Bước 3: Chạy hàm closure

messageFunc(2);

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.

Return nhiều closure function

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.

function multiClosure()

{

    return {

        func1 : function(){

            console.log('Closure1');

        },

        func2 : function(){

            console.log('Closure2');

        }

    };

}

 

// Cách sử dụng

var object = multiClosure();

object.func1();

object.func2();

Closure thay đổi giá trị biến toàn cục lẫn cục bộ

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 đó.

// Bước 1: Tạo hàm closure

function Student()

{

    var name = '';

    var age = '';

 

    return {

        set : function(in_name, in_age){

            name = in_name;

            age = in_age;

        },

        getName : function(){

            return name;

        },

        getAge : function(){

            return age;

        }

    };

}

 

// Bước 2: khởi tạo hàm closure

var studentObj = Student();

 

// Bước 3: Chạy hàm closure

studentObj.set('Nguyễn Văn Cường', '27');

alert(studentObj.getName()); // Nguyễn Văn Cường

alert(studentObj.getAge());  // 27

6. Độ ưu tiên các biến trong closure function

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:

  • Bước 1: Xem biến có nằm trong closure function không? Nếu không thì nhảy qua bước 2, nếu có thì sử dụng.
  • Bước 2: Xem biến có nằm trong hàm cha không? Nếu không thì qua bước 3, nếu có thì sử dụng.
  • Bước 3: Xem có phải là biến cục bộ không? Nếu có thì sử dụng, nếu không thì nó sẽ khởi tạo biến mới mới.

Xem ví dụ sau:

// Bước 1: Tạo hàm closure

var message = 'Biên toàn cục';

function showMessage()

{

    var message = 'Biến cục bộ của hàm cha';

    return function(){

        alert(message);

    };

}

 

// Bước 2: khởi tạo hàm closure

var messageFunc = showMessage();

 

// Bước 3: Chạy hàm closure

messageFunc();

Trong ví dụ này thì biến message trong hàm closure chính là biến của hàm cha.

III.5.Callback trong Javascript

    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.. 

1. Callback function là gì?

   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 đó.

function A(){

   // code

}

 

// Hàm B có một tham số callback

function B(callback){

    callback();

}

 

// Gọi hàm B và truyền tham số là hàm A

B(A);

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.

$('#test').click(function(){

    // đây là callback 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.

setTimeout(function(){

    // day la callback function

}, 200);

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ử đó.

// Mảng           

var keywords = ["Cuong", "thuanhoaonline.com", "Học lập trình", "thehalfheart"];

// Lặp qua từng phần tử và xử lý trong hàm callback

keywords.forEach(function (eachName, index){

    console.log(index + 1 + ". " + eachName);

});

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é.

2. Cách Callback Function hoạt động

   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é.

// Hàm tạo chuỗi mật khẩu

function createPassword(callback) {

    return callback('thuanhoaonline.com);

}

 

// Sử dụng

var password = createPassword(function (secret_key) {

    return secret_key;

});

 

alert(password);

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ố.

3. Nguyên tắc khi thực hiện callback function

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.

Phải chắc chắn tham số truyền vào là một function

Đ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.

function createPassword(callback) {

    if (typeof callback !== 'function'){

        alert('Bạn phải truyền vào là một function');

        return false;

    }

    // do something

}

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.

Cẩn thận với this khi hàm callback nằm trong object

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.

var domainInfo = {

    name : 'thuanhoaonline.com',

    setName : function(name){

        this.name = name;

    }

};

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.

// Object chứa hàm callback

var domainInfo = {

    name : 'thuanhoaonline.com',

    setName : function(name){

        // giá trị này sẽ không có tác dụng với key name trong object này

        // nếu như ta sử dụng nó là một callback function

        this.name = name;

    }

};

 

// Hàm có tham số callback

function test(callback){

    callback('Nguyễn Văn Cường');

}

 

// Gọi đến hàm và truyền hàm callback vào

test(domainInfo.setName);

 

// Vẫn kết quả cũ freetuts.net, tức là hàm callback setName đã ko tác động

// gì tới thuộc tính name

document.write(domainInfo.name);

 

// Xuống hàng

document.write('<br/>');

 

// kết quả nguyễn văn cường, tức đối tượng window đã tự tạo ra một key name

// và giá trị của nó chính là giá trị ta đã sét trong hàm setName

// => this chính là window object

document.write(window.name);

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.

Khắc phục this khi hàm callback nằm trong object

Ở 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:

// Trước đây

callback(var1, var2, ...);

 

// Bây giờ

callback.apply(callbackObject, [var1, var2, ... ]);

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.

// Object chứa hàm callback

var domainInfo = {

    name : 'thuanhoaonline.com',

    setName : function(name){

        this.name = name;

    }

};

 

// Hàm có tham số callback

function test(callback, callbackObject){

    var name = "Nguyễn Văn Cường";

    callback.apply(callbackObject, [name]);

}

 

// Gọi đến hàm và truyền hàm callback vào

test(domainInfo.setName, domainInfo);

 

// Kết quả: Nguyễn Văn Cường

document.write(domainInfo.name);

Ngoài phương thức apply thì bạn cũng có thể sử dụng phương thức call để thay thế.

Multiple Callback Functions

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.

 

function successCallback() {

    // Do something

}

function successCallback() {

    // Do something

}

function completeCallback() {

    // Do something

}

function errorCallback() {

    // Do something

}

$.ajax({

    url     :"https://thuanhoaonline.com",

    success :successCallback,

    complete:completeCallback,

    error   :errorCallback

});

III.6.Hiểu hơn về hàm bind() trong Javascript

1. Đặt vấn đề với this

Đố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:

var blog = {

    domain : "thuanhoaonline.com",

    author : "Nguyễn Văn Cường",

    showWebsite : function (callbackFunction){

        callbackFunction();

    },

    render : function(){

        this.showWebsite(function(){

            console.log(this); // là đối tượng window

            console.log(this.domain); // nên thuộc tính domain không tồn tại

            console.log(this.author); /// nên thuộc tính author không tồn tại

        });

    }

};

 

blog.render();

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.

var blog = {

    domain : "thuanhoaonline.com",

    author : "Nguyễn Văn Cường",

    showWebsite : function (callbackFunction){

        callbackFunction();

    },

    render : function(){

        var _self = this;

        this.showWebsite(function(){

            console.log(_self); // là đối tượng this

            console.log(_self.domain); // ok

            console.log(_self.author); /// ok

        });

    }

};

 

blog.render();

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.

2. Khắc phục với hàm bind trong Javascript

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:

var blog = {

    domain : "thuanhoaonline.com",

    author : "Nguyễn Văn Cường",

    showWebsite : function (callbackFunction){

        callbackFunction();

    },

    render : function(){

        this.showWebsite(function(){

            console.log(this); // là đối tượng this

            console.log(this.domain); // ok

            console.log(this.author); /// ok

        }.bind(this));

    }

};

 

blog.render();

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é.

var blog = {

    showWebsite : function (callbackFunction){

        callbackFunction();

    },

    render : function(){

        this.showWebsite(function(args){

           console.log(this); // This chính là mảng truyền vào

        }.bind([" thuanhoa", "thehalfheart@gmail.com"]));

    }

};

 

blog.render();

 

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 đó.

III.7.Hiểu hơn về hàm call() và apply() trong Javascript

    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é.

1. Hàm call() trong Javascript

    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.

 

function myProfile(name, age){

    console.log(name);

    console.log(age);

    return this;

}

 

var Cuong = myProfile.call(myProfile, "Nguyễn Văn Cường", 27);

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:

function myProfile(name, age){

    console.log(name);

    console.log(age);

    return this;

}

 

myProfile("Nguyễn Văn Cường", 27);

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.

Dùng hàm call gán giá trị cho hàm khởi tạo

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 :)).

// Hàm này dùng để xử lý khởi tạo

function initProduct(name, price) {

    this.name = name;

    this.price = price;

}

 

function Food(name, price) {

    // Khởi tạo

    // biến this chính là Food, vì vậy sau khi chạy xong đối tượng Food sẽ có hai

    // thuộc tính là name và price

    initProduct.call(this, name, price);

}

 

function Hat(name, price) {

    // Khởi tạo

    // biến this chính là Food, vì vậy sau khi chạy xong đối tượng Food sẽ có hai

    // thuộc tính là name và price

    initProduct.call(this, name, price);

}

 

 

var food = new Food('Trái xoài', 5);

var hat = new Food('Cái mũ', 6);

 

console.log(food);

console.log(hat);

Dùng hàm call để gọi hàm anonymous

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 đó.

(function(name) {

    console.log(name);

}).call(this, "Nguyễn Văn Cường");

Dùng hàm call để đổi giá trị của this

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.

function showMessage(message)

{

    this.message = message;

    return this;

}

 

var blog = showMessage.call({"blog" : "thuanhoa"}, "Nguyễn Văn Cường");

console.log(blog); // blog chính là đối tượng {"blog" : "thuanhoa", "message" : "Nguyễn Văn Cường"}

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:

function showMessage(message)

{

    this.message = message;

    return this;

}

 

var blog = showMessage.call(showMessage, "Nguyễn Văn Cường");

console.log(blog); // blog chính là hàm showMessage

2. Hàm apply() trong Javascript

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:

  • Tham số đầu tiên của hàm call() là đối tượng this, tiếp theo chính là các tham số của hàm cần gọi.
  • Tham số đầu tiên của hàm 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.

var sayHello = function(name, message){

    console.log(message + name);

};

 

 

sayHello.call(sayHello, 'Cường', ' Xin chào ');

sayHello.apply(sayHello, ['Cường', ' Xin chà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é.

III.8.Anonymous function trong javascript

1. Anonymous functions là gì?

      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:

function showDomain()

{

    alert('Học Javascript tại thuanhoaonline.com');

}

 

// Gọi hàm

showDomain();

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:

var showDomain = function()

{

    alert('Học Javascript tại thuanhoaonline.com');

};

 

// Gọi hàm

showDomain();

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.

2. Anonymous functions được khởi tạo tại thời điểm chạy

  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: 

// gọi trước hàm

showDomain(); // hoạt động

 

function showDomain()

{

    alert('Học Javascript tại thuanhoaonline.com');

}

 

// gọi sau hàm

showDomain(); // hoạt động

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.

// gọi trước hàm

showDomain(); // Lỗi vì hàm này chưa tồn tại

 

var showDomain = function()

{

    alert('Học Javascript tại thuanhoaonline.com');

};

 

// gọi sau hàm

showDomain(); // hoạt động vì hàm đã tồn tại

3. Anonymous function là hàm không tên

   Đ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).

(function(){

    alert('thuanhoa');

}).call(this);

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ụ:

var demo = function test()

{

    alert('thuanhoa');

};

demo(); // dúng

test(); // sai vì test không tồn tại

4. Anonymous function có hữu ích hay không?

  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

function caller(func)

{

    func();

}

 

caller(function(){

    alert('Hàm callback');

});

III.9.Rò rỉ bộ nhớ RAM và CPU khi làm việc với Javascript

    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.

1. Tại sao lại rò rỉ bộ 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.

2. Ví dụ rò rỉ bộ nhớ đơn giản

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.

<html>

<head>

<script type="text/javascript">

    function LeakMemory(){

        var parentDiv =

             document.createElement("<div onclick='foo()'>");

  

        parentDiv.bigString = new Array(1000).join(

                              new Array(2000).join("XXXXX"));

    }

</script>

</head>

<body>

<input type="button"

       value="Memory Leaking Insert" onclick="LeakMemory()" />

</body>

</html>

Đầ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ỉ.

ro ri ram js 1 gif

3. Ví dụ rò rỉ phức tạp hơn

     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.

<html>

<head>

<script type="text/javascript">

    function LeakMemory(){

        for(i = 0; i < 5000; i++){

            var parentDiv =

               document.createElement("<div onClick='foo()'>");

        }

    }

</script>

</head>

<body>

<input type="button"

       value="Memory Leaking Insert" onclick="LeakMemory()" />

</body>

Chạy chương trình này thì bạn sẽ thấy sơ đồ của RAM và CPU như sau:

ro ri ram js 2 gif

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?

<html>

<head>

<script type="text/javascript">

    function LeakMemory(){

        for(i = 0; i < 50000; i++){

            var parentDiv =

            document.createElement("div");

        }

    }

</script>

</head>

<body>

<input type="button"

       value="Memory Leaking Insert" onclick="LeakMemory()" />

</body>

</html>

Và dưới đây là biểu đồ bộ nhớ.

ro ri ram js 3 gif

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

III.10.Hiểu rõ về this trong Javascript qua các ví dụ thực hành

     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.

1. This trong Javascript là gì?

    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:

console.log(this);

Kết quả trả về chính là đối tượng windows trong Javascript.

windows javascript JPG

     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

this.location.href;

window.location.href

Đâ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é.

2. Sử dụng this trong javascript function

     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.

function test(){

    console.log(this);

}

 

test(); // In ra windows object

Chính vì vậy bạn dễ dàng bổ sung các thuộc tính cho đối tượng window:

function test(){

    this.author = "Cường thuanhoaonline.com";

}

 

test();

 

console.log(window.author); // Cường thuanhoaonline.com

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.

function test(){

    console.log(this);

}

 

test(); // 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.

'use strict';

function test(){

    this.author = "Cường thuanhoa";

}

 

test(); // Lỗi

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ụ

function test(){

    console.log(this);

}

 

let test1 = new test(); // test object

let test2 = new test(); // test object

Chi tiết thì bạn có thể xem ở bài constructor trong javascript.

3. This trong các sự kiện 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

// Lấy đối tượng

var button = document.getElementById('btn');

 

// Gán sự kiện

button.addEventListener("click", function(){

// Lấy thuộc tính type của đối tượng đang xử lý

// chính là button có id="btn"

    alert(this.type);

});

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

<script language="javascript">

    function show_type(obj)

    {

        alert(obj.type);

    }

</script>

<input type="button" onclick="show_type(this)" value="Check" />

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.

4. Từ khóa this trong javascript object

  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.

var Person = {

    name : "",

    age : "",

    setInfor : function(){

 

    },

    showInfor : function(){

 

    }

};

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.

var Person = {

    name : "",

    age : "",

    setInfor : function(name, age){

        this.name = name;

        this.age = age;

    },

    showInfor : function(){

        console.log(this.name);

        console.log(this.age);

    }

};

5. Từ khóa this trong class javascript

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ụ

class Person {

    constructor(name) {

        this.name = name;

    }

  

    // Adding a method to the constructor

    showName() {

        console.log(this.name);

    }

}

  

let person1 = new Person("Kinh");

person1.showName(); // Kinh

6. Từ khóa this trong arrow function

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.

var showUser = () => {

    console.log(this);

};

 

showUser();

Kết quả:

this arrow function JPG

III.11.Đọc và hiểu về prototype trong Javascript trong 10 phút

1. Hiểu về prototype trong javascript

    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:

const array = ['one', 'two', 'three'];

// Hoặc sử dụng lệnh new Array()

const array = new Array('one', 'two', 'three');

Bây giờ mình sẽ sử dụng lệnh console.log để xem biến array có những gì nhé.

console.log(array);

javascript prototype JPG

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ư concatslicefilter, 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.

array prototype javascript JPG

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.

2. Sơ đồ hoạt động của prototype trong javascript

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:

  • Trả về undefined nếu bạn đang truy cập thuộc tính.
  • Thông báo lỗi nếu bạn truy cập vào phương thức.

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é.

let number = new Number(12);

console.log(number);

javascript prototype 2 JPG

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.

let number = new Number(12);

let string = number.toString();

 

console.log(typeof string); // String

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:

let number = new Number(12);

number.toString = function(){

    console.log("Bạn đang gọi đến phương thức number của object");

};

 

console.log(number);

Đoạn code này sẽ in ra kết quả như sau:

javascript prototype 3 JPG

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é.

let number = new Number(12);

number.toString = function(){

    console.log("Bạn đang gọi đến phương thức number của object");

};

 

// Kết quả: Bạn đang gọi đến phương thức number của object

var string = number.toString();

 

console.log(typeof string); // Undefined

Kết quả đúng như ta mong đợi.

javascript prototype 4 JPG

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.

3. Prototype chain trong Javascript

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é.

Prototype của lớp không có kế thừa.

Giả sử mình có lớp Developer như sau:

class Developer  {

  constructor(name){

      this.name = name;

  }

  code (thing) {

    console.log(`${this.firstName} coded ${thing}`)

  }

}

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ì.

const cuong = new Developer('Cuong')

console.log(cuong);

Kết quả:

javascript chain prototype 2 JPG

Giải thích:

  • Instance cuong thuộc đối tượng Developer,
  • Nó không có thuộc tính nào cả,
  • Nó prototype và trong prototype này có một function code.
  • Trong prototype lại có thêm một prototype khác, và đó là một object mặc định của javascript. Nếu lớp Developer có kế thừa từ lớp A thì object này sẽ được thay thế bằng lớp A đó.

Prototype của lớp có kế thừ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.

  • Lớp Human có phương thức sayHello.
  • Lớp Developer có phương thức code.

Đây là code cho lớp Human.

class Human {

  constructor(firstName, lastName) {

    this.firstName = firstName

    this.lastname = lastName

  }

 

  sayHello () {

    console.log(`Hi, I'm ${this.firstName}`)

  }

}

Và đây là code cho lớp Developer.

class Developer extends Human {

  code (thing) {

    console.log(`${this.firstName} coded ${thing}`)

  }

}

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.

const cuong = new Developer('Cuong', 'Nguyen')

cuong.sayHello() // Hi, I'm Cuong

cuong.code('website') // Cuong coded website

Thử console.log xem có gì trong đối tượng cuong này nhé.

console.log(cuong);

javascript chain prototype 1 JPG

Giải thích kết quả như sau: Instance cuong sẽ có ..

  • Đây là một đối tượng Developer, có hai thuộc tính là firstName và lastName.
  • Có prototype, trong prototype này có:
    • Một hàm tên là code.
    • Một prototype khác trỏ tới lớp Human.
      • Prototype này có hàm 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:

javascript chain prototype 3 JPG

Nó sẽ áp dụng quy tắc mà chúng ta đã học ở phần 2:

  • Đầu tiên là tìm trong thuộc tính của đối tượng
  • Tiếp theo tìm trong prototype của đối tượng
  • Nếu trong prototype p1 có thêm một prototype (p2) khác nữa thì nó sẽ tiếp tục áp dụng quy tắc để tìm kiếm trên p2 này.

4. Có nên sử dụng prototype trong Javascript?

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:

let domain = new String("thuanhoaonline.com");

domain.show = function(){

    // Code

};

 

// Hoặc

String.prototype.show = function(){

    // code

};

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.

let domain_1 = new String("thuanhoaonline.com");

domain_1.show = function(){

    console.log(this.valueOf());

};

 

// Kết quả: freetuts.net

domain_1.show();

 

 

let domain_2 = new String("thuanhoaonline.com");

 

// Kết quả: Lỗi do domain_2 không tồn tại method show

domain_2.show();

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.

String.prototype.show = function(){

    console.log(this.valueOf());

};

 

let domain_1 = new String("thuanhoaonline.com");

domain_1.show(); // Kết quả: thuanhoaonline.com

 

let domain_2 = new String("tingting.com");

domain_2.show(); // Kết quả: tingting.com

III.12.Constructor trong Javascript (function và class constructor)

     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. 

1. Constructor trong Javascript là gì?

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à:

  • Ta có thể xem function là một object.
  • Ta có thể thêm các thuộc tính và phương thức.
  • Ta có thể sử dụng từ khóa new để khởi tạo nhiều thể hiện cho đối tượng.

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.

function Person() {

    console.log("Tôi là Cường");

}

Person(); // Tôi là Cường

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:

function Person_2(){

    console.log("Tôi là Kính");

}

Person_2(); // Tôi là Kính

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.

2. Constructor với function trong javascript

Để 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:

function Person(name) {

    console.log("Tôi là " + name);

}

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:

function Person(name) {

    console.log("Tôi là " + name);

}

 

let person1 = new Person("Cường"); // Tôi là Cường

let person2 = new Person("Kính"); // Tôi là Kính

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.

function Person(name) {

    this.name = name;

    this.showName = function(){

        console.log(this.name);

    }

}

 

let person1 = new Person("Cường");

person1.showName(); // Cường

 

let person2 = new Person("Kính");

person2.showName(); // Kính

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:

Person("Cường")

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:

function Person(name) {

    this.name = name;

    this.showName = function(){

        console.log(this.test);

    }

}

 

// Thêm thế này sẽ không có tác dụng, thuộc tính test trong hàm

// person là undefined

Person.test = 123;

  

let person1 = new Person("Cường");

person1.showName(); // undefined

3. Constructor với class trong javascript

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:

class Person {

    // Khai báo constructor, đc phép thêm nhiều tham số

    constructor(name) {

        this.name = name;

    }

 

    // Thêm phương thức vào class

    showName() {

        console.log(this.name);

    }

}

 

let person1 = new Person("Cuong");

person1.showName();

   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.

III.13.Cách tạo class trong Javascript (Hướng đối tượng OOP ES6)

   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.

1. Class trong Javascript là gì?

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:

class MyClass {

  // Hàm khởi tạo

  constructor() { ... }

  // Các phương thức

  method1() { ... }

  method2() { ... }

  method3() { ... }

  ...

}

Các thuộc tính sẽ được định nghĩa trong hàm khởi tạo constructor ngay bên trong class.

2. Khai báo thuộc tính cho class trong js

Để 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.

class User {

    constructor(){

        // Khai báo các thuộc tính

        this.name = '';

        this.age = '';

        this.address = '';

    }

}

Cách 2: Khai báo trực tiếp ngay bên trong class.

class User {

    name = ''

    age = ''

    address = ''

}

3. Khai báo phương thức cho class trong js

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.

class User {

    // Định nghĩa phương thức showMessage

    showMessage(){

        console.log("Đây là nội dung tin nhắn");

    }

}

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.

class User {

 

  constructor(name) {

    this.name = name;

  }

 

  sayHi() {

    alert(this.name);

  }

 

}

 

// Cách dùng:

let user = new User("Cường");

user.sayHi(); // Cường

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é.

  • Trong object, ta sẽ phải sử dụng dấu phẩy để ngăn cách giữa các thuộc tính hoặc phương thức.
  • Trong class, ta không cần sử dụng dấu phẩy

4. Các bước tạo class trong javascript

Để tạo một class thì bạn nên thực hiện theo các bước dưới đây:

  • Bước 1: Phân tích kỹ đối tượng sẽ có những phương thức nào, thuộc tính nào.
  • Bước 2: Cài đặt class trong js

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.

class Person{

    constructor(name, address, phone, cmnd){

        this.name = name;

        this.address = address;

        this.phone = phone;

        this.cmnd = cmnd;

    }

     

    khaibao(){

        return `Tôi tên là ${this.name}, đang sống tại ${this.address},

                số điện thoại ${this.phone}, số cmnd là ${this.cmnd}`;

    }

}

5. Dùng function để thay thế class trong javascript

Trong javascript, class là một function. Ta có thể kiểm tra bằng cách sử dụng từ khóa typeof.

class User {

  constructor(name) { this.name = name; }

  sayHi() { alert(this.name); }

}

 

// Xem User thuộc thể loại gì

console.log(typeof User); // function

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.

class User {

  constructor(name) { this.name = name; }

  sayHi() { alert(this.name); }

}

 

// class là một function

alert(typeof User); // function

 

// User chính là một constructor

alert(User === User.prototype.constructor); // true

 

// Phương thức sayHi cũng nằm trong prototype, e.g:

alert(User.prototype.sayHi); // the code of the sayHi method

 

// Xem danh sách các thuộc tính của User.prototype

alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi

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:

function User(name) {

    this.name = name;

    this.sayHi = function () {

        alert(this.name);

    }

}

Để hiểu rõ hơn về vấn đề này thì bạn hãy xem thêm trong bài constructor function nhé.

6. Một vài biểu thức khác của class trong javascript

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:

let User = class {

  sayHi() {

    alert("Hello");

  }

};

 

let user1 = new User();

user1.sayHi();

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.

let User = class TenClass{

  sayHi() {

    alert(TenClass);

  }

};

 

let user1 = new User();

user1.sayHi(); // Hiển thị nội dung của class

 

alert(TenClass); // Không tồn tạ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 đó.

function makeClass(phrase) {

  // Định nghĩa class và return nó về

  return class {

    sayHi() {

      alert(phrase);

    }

  };

}

 

// Tạo một class

let User = makeClass("Hello");

 

new User().sayHi(); // Hello

Như vậy hàm makeClass sẽ có nhiệm vụ là khởi tạo một class theo ý mình.

7. Getters và setters của class trong javascript

Getters và setters là hai khái niệm khá hay trong lập trình oop, cụ thể như sau:

  • Getters là những hàm có nhiệm vụ lấy giá trị của thuộc tính
  • Setters là những hàm có nhiệm vụ thiết lập giá trị cho các thuộc tính

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ụ

class User {

    constructor(name) {

        // Tự động gọi đến hàm set name

        this.name = name;

    }

 

    get name() {

        return "Tên bạn là " + this._name;

    }

 

    set name(value) {

        if (value.length < 4) {

            alert("Vui lòng nhập tên dài hơn 4 kí tự");

            return;

        }

        this._name = value;

    }

}

 

let user = new User("Cường");

alert(user.name); // Tên bạn là Cường

 

user = new User(""); // Lỗi "Vui lòng nhập tên dài hơn 4 kí tự"

Trong hàm constructor mình đã thiết lập giá trị cho thuộc tính name như sau:

this.name = name

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à ...".

III.14.Extends trong Javascript - kế thừa và ghi đè phương thức

       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).

1. Extends trong Javascript là gì?

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.

  • Động vật sẽ có các đặc tính như: Số cân, màu lông, chủng loại...
  • Động vật sẽ có các hành động như: Ăn, chạy, săn mồi ...

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.

2. Xây dựng class extends trong javascript

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.

class Animal {

    constructor(name) {

        this.name = name;

        this.speed = 0; // Tốc độ chạy 0km / h

    }

    run(speed) {

        this.speed = speed;

        alert(`${this.name} chạy tốc độ là ${this.speed}.`);

    }

    stop() {

        this.speed = 0;

        alert(`${this.name} đứng yên.`);

    }

}

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.

class Lion extends Animal{

    roar(){

        alert(`${this.name} đang gầm`);

    }

}

 

let lion = new Lion("Sử tử");

lion.run(80);

lion.stop();

lion.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.

class Rabbit extends Animal {

  hide() {

    alert(`${this.name} đang ẩn nấp`);

  }

}

 

let rabbit = new Rabbit("Con thỏ");

rabbit.run(0.05);

rabbit.stop();

rabbit.hide();

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.

3. Overriding trong Javascript

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:

class Animal {

    constructor(name) {

        this.name = name;

    }

    run() {

        console.log(`${this.name} đang chạy`);

    }

}

 

class Rabbit extends Animal {

    // Override hàm run

    run(){

        super.run();

        this.hide();

    }

    hide() {

        console.log(`${this.name} đang ẩn nấp`);

    }

}

 

let rabbit = new Rabbit("Con thỏ");

rabbit.run();

Tòm lại:

  • Trong ví dụ này thì phương thức hide đã bị ghi đè lại vì nó được khai báo ngay trong lớp con.
  • Để gọi đến hàm run ở lớp cha thì ta phải sử dụng từ khóa super, nếu không nó sẽ hiểu là bạn đang gọi đếm hàm run ở lớp con.

4. Overriding constructor javascript

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.

  • Nếu bạn không muốn override hàm constructor thì không phải làm gì cả.
  • Nếu bạn tạo một hàm constructor ở hàm con thì bắt buộc phải sử dụng từ khóa super để gọi đến hàm constructor của lớp cha, và phải đặt vị trí đầu tiên.

Trường hợp này là ok.

class Animal {

    constructor(name) {

        console.log('Hàm khởi tạo lớp cha');

    }

}

 

class Rabbit extends Animal {

 

}

 

let rabbit = new Rabbit();

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.

class Animal {

    constructor(name) {

        console.log('Hàm khởi tạo lớp cha');

    }

}

 

class Rabbit extends Animal {

    constructor(name, age) {

        console.log('Hàm khởi tạo lớp con');

    }

}

Sửa lại như sau:

class Animal {

    constructor(name) {

        console.log('Hàm khởi tạo lớp cha');

    }

}

 

class Rabbit extends Animal {

    constructor(name, age) {

        super(name);

        console.log('Hàm khởi tạo lớp con');

    }

}

 

let rabbit = new Rabbit();

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.

class Animal {

}

 

class Rabbit extends Animal {

    constructor() {

        super();

        console.log('Hàm khởi tạo lớp con');

    }

}

 

let rabbit = new Rabbit();

III.15.Cách dùng static trong Javascript (thuộc tính và phương thức tĩnh)

  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.

1. Static trong javascript là gì?

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.

class className {   

    // Static property

    static name = "";

     

    // Static method

    static functionName(){

         

    }

}

Static method trong javascript

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ụ

class User {

  static staticMethod() {

    alert(this === User);

  }

}

// Không cần phải khởi tạo

User.staticMethod(); // true

Đ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:

// Khai báo lớp

class User { }

 

// Thêm một static method

User.staticMethod = function() {

  alert(this === User);

};

 

User.staticMethod(); // true

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.

class User {

    sayHi() {

        console.log("Xin chào");

    }

    static staticMethod() {

        // Sai, vì sayHi không phải là static

        this.sayHi();

    }

}

// Không cần phải khởi tạo

User.staticMethod(); // true

Static properties trong javascript

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.

class Article {

  static publisher = "Cường Nguyễn";

}

 

alert( Article.publisher ); // Cường Nguyễn

2. Sử dụng this để truy cập thuộc tính static

    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.

class Article {

    static publisher = "Cường Nguyễn"

    show() {

        // Xuất ra undefined

        alert(this.publisher);

    }

}

 

let a = new Article();

a.show(); // kết quả là undefined

Thử sử dụng lệnh console.log để xem trong biến a có gì nhé.

console.log(a);

static javascript 1 JPG

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:

class Article {

    static publisher = "Cường Nguyễn"

    show() {

        alert(this.constructor.publisher);

    }

}

 

let a = new Article();

a.show(); // Cường Nguyễn

3. Thuộc tính static có giá trị duy nhất

      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:

class A{

    // code

}

 

let instance1 = new A();

let instance2 = new A();

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:

class Article {

    static publisher = "Cường Nguyễn"

    change(new_value){

        this.constructor.publisher = new_value;

    }

    show() {

        console.log(this.constructor.publisher);

    }

}

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:

let a = new Article();

a.show(); // Cường Nguyễn

 

let b = new Article();

b.change('Nguyễn Văn Cường');

b.show(); // Nguyễn Văn Cường

 

a.show(); // Nguyễn Văn Cường

console.log(Article.publisher); // Nguyễn Văn Cường    

Như bạn thấy,

  • Ban đầu a.show() sẽ in ra giá trị là Cường Nguyễn.
  • Sau khi chạy hàm b.change() thì giá trị của publisher đã thay đổi
  • Tiếp tục chạy b.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.

4. Static trong kế thừa thuộc tính và phương thức

    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 đó.

class Post {

    show(){

        // code

    }

}

 

let p = new Post();

 

console.log(p);

Kêt quả trên cửa sổ console như sau:

static javascript 2 JPG

Mình thử dùng phép toán so sánh thì kết quả là true:

console.log(p.__proto__.constructor == Post); // True

Bây giờ mình sẽ cho class Post kế thừa một class khác, sau đó dùng console.log để xem:

class Article {

    static publisher = "Cường Nguyễn"

}

 

class Post extends Article{

    show(){

        // code

    }

}

 

let p = new Post();

 

console.log(p); // True

Kết quả:

static javascript 3 JPG

Như vậy class Article sẽ nằm trong hai vị trí:

  • Thứ nhất là __proto__.constructor.__proto__ của Post
  • Thứ hai là trong __proto__.__proto__.constructor của Post

Ta thử dùng phép so sánh xem có chuẩn không nhé.

console.log(p.__proto__.constructor.__proto__ == Article); // True

console.log(p.__proto__.__proto__.constructor == Article); // True

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ụ:

class Article {

    static publisher = "Cường Nguyễn"

}

 

class Post extends Article{

    show(){

        // code

    }

}

Bây giờ thử in thuộc tính publisher xem giá trị thế nào:

console.log(Post.publisher); // Cường Nguyễn

console.log(Article.publisher); // Cường Nguyễn

Cả hai đều cho một kết quả. Bây giờ ta thử thay đổi giá trị của publisher.

console.log(Post.publisher); // Cường Nguyễn

console.log(Article.publisher); // Cường Nguyễn

 

Post.publisher = "Nguyễn Văn Cường";

 

console.log(Post.publisher); // Nguyễn Văn Cường

console.log(Article.publisher); // Cường Nguyễn

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é.

Post.publisher = "Nguyễn Văn Cường";

let t = new Post();

console.log(t);

static javascript 4 JPG

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.

console.log(Post.publisher); // Cường Nguyễn

console.log(Article.publisher); // Cường Nguyễn

 

Article.publisher = "Nguyễn Văn Cường";

 

console.log(Post.publisher); // Nguyễn Văn Cường

console.log(Article.publisher); // Nguyễn Văn Cường

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é.

Article.publisher = "Nguyễn Văn Cường";

let t = new Post();

console.log(t);

static javascript 5 JPG

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.

III.16.Cấp độ private / protected của class trong Javascript

1. Private / protected và public trong javascript là gì?

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.

2. Cấp độ public trong Javascript

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.

class Human{

    name = ""

    sayHi(){

        // this.name là truy cập trong lớp

        console.log(`Xin chào, tôi là ${this.name}`);

    }

}

 

let cuong = new Human();

 

// Truy cập ngoài lớp

cuong.name = "Cường";

cuong.sayHi();

Những ví dụ mà từ trước đến giờ các bạn đã học đều thuộc dạng public.

3. Cấp độ protected trong Javascript

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.

  • Đặt quy ước cho protected là thuộc tính sẽ bắt đầu bằng dấu gạch dưới _. 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.
  • Ta sẽ tạo một getter, nhưng không tạo setter, để ở bên ngoài class có sử dụng setter thì sẽ không có tác dụng.

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.

class className{

     

    // _name là protected

    _name = "Cường"

     

    // sayHi là protected

    _sayHi(){

        // code

    }

}

Ví dụ: Tạo hai class Human và Student để demo cho protected.

class Human {

    _name;

 

    constructor(name) {

        this._name = name;

    }

 

    get name() {

        return this._name;

    }

     

    sayHi(){

        console.log(`Xin chào, tôi là ${this._name}`);

    }

}

 

let h = new Human("Cường");

h.sayHi(); // Tôi là Cường

h.name = "Nguyễn";

h.sayHi(); // Tôi là Cường

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.

4. Cập độ private trong Javascript

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.

class className{

    #protected_property = ""

     

    #protected_method(){

         

    }

}

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.

class Human{

    // Protected

    #name = ""

     

    constructor(name){

        // Lệnh này đúng, vì nó là lớp con

        // nên được phép truy cập đến thuộc tính private

        this.#name = name;

    }

     

    // Public

    sayHi(){

        // Lệnh này đúng, vì đang truy cập trong lớp

        console.log(`Xin chào, tôi là ${this.#name}`);

    }

}

 

class Student extends Human{

    sayGoodbye(){

        // Lệnh này sai, vì #name là private

        console.log(`${this.#name} tạm biệt mọi người nhé`);

    }

}

 

let cuong = new Student("Cường");

 

 

cuong.sayHi();  // Lệnh này đúng

cuong.#name;    // Lệnh này sai

III.17.Cách dùng Import / Export Module trong javascript  

  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é.

1. Module trong javascript là gì?

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ư:

  • Module sinhvien.js, là tập hợp những lệnh dùng để xử lý sinh viên.
  • Module 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 PatternCommonJSAsynchronous Module Definition (AMD).

2. Khai báo module trong Javascript

Để 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

// exporting a function

export function greetPerson(name) {

    return `Hello ${name}`;

}

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:

export {

    greetPerson

};

 

// exporting a function

function greetPerson(name) {

    return `Hello ${name}`;

}

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

// import hàm greetPerson của file greet.js

import { greetPerson } from './greet.js';

 

// Sau đó có thể sử dụng hàm greetPerson()

let displayName = greetPerson('Cuong');

 

console.log(displayName); // Hello Cuong

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".

<script type="module">

    // import hàm greetPerson của file greet.js

    import { greetPerson } from './greet.js';

 

    // Sau đó có thể sử dụng hàm greetPerson()

    let displayName = greetPerson('Cuong');

 

    console.log(displayName); // Hello Cuong

</script>

Như vậy chúng ta:

  • Sử dụng lệnh import để gọi đến dữ liệu từ một module khác.
  • Sử dụng lệnh export để đặt đằng trước hàm hoặc biến cho phép file khác sử dụng.

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.

<script type="module">

    // import hàm greetPersonABC của file greet.js

    import { greetPersonABC } from './greet.js';

</script>

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:

// exporting variable

export const name = 'JavaScript Program';

 

// exporting function

export function sum(x, y) {

    return x + y;

}

Để sử dụng nó trong một file khác thì ta sẽ làm như sau:

import { name, sum } from './module.js';

 

console.log(name);

let add = sum(4, 9);

console.log(add); // 13

3. Đổi tên dữ liệu của module trong javascript

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 trong module

Để đổi tên dữ liệu nào đó thì trong module bạn hãy khai báo như sau:

file module.js

// Đổi tên ngay trong module module.js

export {

    function1 as newName1,

    function2 as newName2

};

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:

import { newName1, newName2 } from './module.js';

Ví dụ: Mình sẽ đổi tên cho hàm greetPerson trong module greet.js như sau.

File greet.js

export {

    greetPerson as person

};

 

// exporting a function

function greetPerson(name) {

    return `Hello ${name}`;

}

Lúc này, trong file chương trình ta sẽ gọi như sau:

<script type="module">

    // import hàm greetPerson của file greet.js

    import { Person } from './greet.js';

 

    // Sau đó có thể sử dụng hàm greetPerson()

    let displayName = Person('Cuong');

 

    console.log(displayName); // Hello Cuong

</script>

Đổi tên trong file import

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

// Khai báo export hai hàm

export {

    function1,

    function2

};

Đ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

// import và đổi tên luôn

import { function1 as newName1, function2 as newName2 } from './module.js';

Tóm lại: Để đổi tên dữ liệu trong module thì ta sẽ sử dụng từ khóa as.

4. Default export module trong javascript

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

// default export

export default function greet(name) {

    return `Hello ${name}`;

}

 

export const age = 23;

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:

import random_name from './greet.js';

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.

<script type="module">

    // import hàm random_name của file greet.js

    import random_name from './greet.js';

 

    // Sử dụng

    let displayName = random_name('Cuong');

 

    console.log(displayName); // Hello Cuong

</script>

5. Một vài đặc điểm của module trong Javascript

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:

Chế độ strict mode

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.

Dữ liệu trong các module là riêng biệt

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.

<script type="module" src="user.js"></script>

<script type="module" src="hello.js"></script>

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.

<script type="module">

    var a = 12;

</script>

 

<script type="module">

    alert(a); // biến a chưa được định nghĩa

</script>

Chỉ import một lần

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

alert("Module được tải!");

Bây giờ mình sẽ import ở một file khác.

// 1.js

import `./alert.js`; // Module được tải!

 

// 2.js

import `./alert.js`; // (không hiển thị gì 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.

Con trỏ this trong module là undefined

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.

script>

  alert(this); // window

</script>

 

<script type="module">

  alert(this); // undefined

</script>

Module tải theo dạng defer

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:

  • Module sẽ được load ngầm, không ảnh hưởng đến tốc độ load của HTML.
  • Mã script của js sẽ được biên dịch khi trình duyệt đã load xong HTML nên không ảnh hưởng đến hệ thống DOM. Vì vậy, các đoạn mã trong module luôn luôn truy cập đến các đối tượng HTML cho dù bạn đặt ở đầu file hay cuối file.
  • Thứ tự load vẫn luôn được bảo toàn, file nào load trước thì sẽ được thực thi trường.

Thiết lập async cho module

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.

<script async type="module">

    import {counter} from './analytics.js';

    counter.count();

</script>

6. Hai cách dùng export trong javascript

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.

// export an array

export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

 

// export a constant

export const MODULES_BECAME_STANDARD_YEAR = 2015;

 

// export a class

export class User {

  constructor(name) {

    this.name = name;

  }

}

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.

// say.js

function sayHi(user) {

  alert(`Hello, ${user}!`);

}

 

function sayBye(user) {

  alert(`Bye, ${user}!`);

}

 

export {sayHi, sayBye}; // a list of exported variables

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.

export {sayHi as hi, sayBye as bye};

7. Hai cách dùng lệnh import trong javascript

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.

import {sayHi, sayBye} from './say.js';

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 *.

import * as say from './say.js';

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.

import {sayHi as hi, sayBye as bye} from './say.js';

III.18.ECMAScript 2015 - ES6 là gì?

   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.

1. ECMAScript ES6 là gì? 

     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.

2. Các chức năng mới của ES6

Sau đây là một số chức năng mới thêm vào trong ES6.

  • Block Scoped: Định nghĩa biến với từ khóa 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)
  • Destructuring Assignments: Bạn có thể khởi tạo các biến từ một mảng  bằng một dòng code đơn giản.
  • Default Parameters: Bạn có thể gán giá trị mặc định cho các tham số.
  • Rest Parameter: Tham số không giới hạn
  • Arrow function: Bạn có thể tạo hàm bằng cách sử dụng dấu mũi tên =>.
  • Template String: Tạo templaet HTML cực kì đơn giản
  • Weak, Set: các kiểu dữ liệu phức tạp mới
  • Iterables và iterators 
  • Class, import
  • ...

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.

3. Một số phiên bản khác của ES6

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:

  • ECMAScript 5 (December 2009): Phiên bản này hầu hết các trình duyệt đều hỗ trợ, nó cải tiến và bổ sung thêm một số thư viện chuẩn, sử dụng chế độ strict mode nghiêm ngặt.
  • ECMAScript 5.1 (June 2011): ES5 đã được xem như là một chuẩn ISO, phiên bản này đã sửa một số lỗi nhỏ.
  • ECMAScript 6 (June 2015): Là phiên bản hiện tại, bổ sung nhiều cú pháp và thư viện.
  • ECMAScript 2016: Hay còn gọi là ES7, đây là phiên bản tương lai chưa được public, được hứa hẹn có nhiều bổ sung giúp Javascript trở thành ngôn ngữ sử dụng được hầu hết ở mọi môi trường.

III.19.Điều kiện cần để tự học ES6

  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.

1. Update Firefox để học ES6

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.

2 Update Chrome để học ES6

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é.

III.20.ES6 Block Scoped - Khởi tạo biến với từ khóa let

      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é.

1. Block Scoped là gì?

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ẻ {}.

2. Dùng từ khóa let để khai báo biến trong ES6

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áplet 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.

3. Khi nào nên sử dụng let để khai báo biến

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:

var a = 12;

var b = 20;

 

if (a < b)

{

    var tmp = a;

    a = b;

    b = tmp;

}

 

console.log("a: " + a);

console.log("b: " + b);

console.log("tmp: " + tmp);

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:

 

var a = 12;

var b = 20;

 

if (a < b)

{

    let tmp = a;

    a = b;

    b = tmp;

}

 

console.log("a: " + a);

console.log("b: " + b);

console.log("tmp: " + tmp); // đoạn này lỗi

Đ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).

III.21.Arrow Function trong ES6

Trong Javascript để tạo một function thì thông thường chúng ta sử dụng hai cách sau:

// Cách 1

function Name(var1, var2){

     

}

 

// Cách 2

var Name = function(var1, var2,){

     

}

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é.

1. Sử dụng Arrow function trong ES6

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:

Cú pháp căn bản nhất của arrow function như sau:

var functionName = (var1, var2) => {

    // Nội dung function

};

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:

var hello = (name, message) => {

    console.log("Chào " + name + ", bạn là " + message);

};

 

hello('Cường', 'admin freetuts.net');

 

Normal function:

 

function hello(name, message)

{

    console.log("Chào " + name + ", bạn là " + message);

}

 

hello('Cường', 'admin freetuts.net');

 

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:

Nội dung là một câu lệnh đơn:

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.

var hello = (name, message) => console.log("Chào " + name + ", bạn là " + message);

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 một tham số:

Trường hợp truyền vào chỉ một tham số thì bạn có thể bỏ cặp ().

var hello = message => {

    console.log(message);

};

     

hello('Chào mừng bạn đến với thuanhoaonline.com');

Trường hợp không có tham số:

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:

var hello = () => {

    console.log('Chào mừng bạn đến với thuanhoaonline.com');

};

     

hello();

2. Một số ví dụ arrow function trong ES6

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ụ với hàm map:

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:

var domain = ["thuanhoaonline.com", 'qa.thuanhoaonline.com', 'demo.thuanhoaonline.com'];

 

domain.map(function(val, key){

    console.log(val.toUpperCase());  

});

 

console.log(domain);

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.

var domain = ["thuanhoaonline.com", 'qa.thuanhoaonline.com', 'demo.thuanhoaonline.com'];

 

domain.map(function(val, key){

    console.log(val.toUpperCase());

     

    // Lưu lại

    domain[key] = val.toUpperCase();

});

 

console.log(domain);

Chạy lên kết quả như hình sau:

Ok bây giờ ta sử dụng Arrow Function để viết.

var domain = ["thuanhoaonline.com", 'qa.thuanhoaonline.com', 'demo.thuanhoaonline.com'];

 

domain.map((val, key) => {

    console.log(val.toUpperCase());

});

Quá đơn giản phải không các bạn :

Ví dụ với hàm setTimeout:

Hàm setTimeout cũng có một callback function nên ta sẽ truyền vào callback đó một Arrow Function.

setTimeout(() => {

    console.log('3 giây đã trôi qua');

}, 3000);

Do arrow function không có tham số truyền vào nên mình chỉ để là ().

3. Lỗi cú pháp với Arrow function

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.

Đóng arrow function

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.

console.log(typeof () => {}); // Cú pháp sai

console.log(typeof (() => {})); // Cú pháp đúng

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à.

var x = () => {}

console.log(typeof x);

Ràng buộc mũi tên

Đú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.

const func1 = (x, y) // Sai

=> {

    return x + y;

};

const func2 = (x, y) => // Đúng

{

    return x + y;

};

const func3 = (x, y) => { // OK

    return x + y;

};

 

const func4 = (x, y) // Sai

=> x + y;

const func5 = (x, y) => // Đúng

x + y;

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:

const func6 = ( // Đúng

    x,

    y

) => {

    return x + y;

};

4. Khắc phục nhược điểm với this trong closure function

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.

var blog = {

    domain : "thuanhoaonline.com",

    showWebsite : function (callbackFunction){

        callbackFunction();

    },

    render : function(){

        this.showWebsite(function (){

           console.log(this.domain); // this chính là blog

        }.bind(this)); // phải sử dụng hàm bind thì đối tượng this mới chính là blog

    }

};

 

blog.render();

Với ES6 thì viết như sau:

var blog = {

    domain : "thuanhoaonline.com",

    showWebsite : function (callbackFunction){

        callbackFunction();

    },

    render : function(){

        this.showWebsite((() => {

           console.log(this.domain); // this chính là blog

        }));

    }

};

 

blog.render();

III.22.Destructuring Assignments trong ES6

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:

$domain = array(

    'thuanhoaonline.com',

    'qa.thuanhoaonline.com'

);

 

// Gán hai phần tử vào hai biến $main và $sub

list($main, $sub) = $domain;

 

echo $main; // kết quả thuanhoaonline.com

echo $sub; // kết quả qa.thuanhoaonline.com

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.

1. Destructuring Assignments trong ES6

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.

Sử dụng mảng:

Bạn có thể sử dụng mảng để tách các phần tử thành các biến.

// Array

let date = [10, 03, 2016]

 

// Chuyển ba giá trị vào ba biến d, m, y

let [d, m, y] = date;

 

// In kết quả

console.log("Day: " + d);   // Day: 10

console.log("Month: " + m); // Month: 03

console.log("Year: " + y);  // Year : 2016

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.

// Array

let date = [10, 03, 2016]

 

// Chuyển ba giá trị vào ba biến y

let [, , y] = date;

 

// In kết quả

console.log("Year: " + y);

Chạy lên kết quả như sau:

Sử dụng object:

Ngoài mảng ra thì bạn có thể tách biến từ object.

// Object

let date = {

    day : 10,

    month : 06,

    year : 2016

}

 

// Chuyển ba giá trị vào ba biến

let {day : d, month : m, year : y} = date;

 

// In kết quả

console.log("Day: " + d);

console.log("Month: " + m);

console.log("Year: " + y);

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.

// Object

let date = {

    day : 10,

    month : 06,

    year : 2016

}

 

// Chuyển ba giá trị vào ba biến y

let {

    year : y

} = date;

 

// In kết quả

console.log("Year: " + y);

Kết quả:

2. Một sô ví dụ nâng cao khác

Bây giờ ta sẽ thực hành một số ví dụ nâng cao khác.

Lấy giá trị theo key Object

Bạn có thể lấy giá trị dựa vào tên key của Object.

var person = {name : "Cường", email : "thehalfheart@gmail.com"};

 

var {name : cuong_name, email : cuong_email} = person;

 

console.log(cuong_name);    // Cường

console.log(cuong_email);   // thehalfheart@gmail.com

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.

var {foo:bar} = {baz: 'ouch'};

 

console.log(bar); // Undefined

Lỗi undefined:

Nếu phần tử không tồn tại thì sẽ bị undefined.

let domain = ['thuanhoaonline.com'];

 

let [main, sub] = domain;

 

// Xem kết quả

console.log(main);

console.log(sub);

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.

Giá trị mặc đinh:

Để tránh lỗi undefined thì bạn có thể gán giá trị mặc định cho nó.

let domain = ['thuanhoaonline.com'];

 

let [main, sub = 'qa.thuanhoaonline.com'] = domain;

 

// Xem kết quả

console.log(main);

console.log(sub);

Kết quả sẽ không bị lỗi undefined.

Sử dụng function:

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.

 

// Function

var domains = () => {

    return [

        'thuanhoaonline.com',

        'qa.thuanhoaonline.com'

    ];

};

 

// Chuyển đổi

let [main, sub] = domains();

 

// In kết quả

console.log(main);

console.log(sub);

 

Kết quả giống như hình trên.

III.23.Default Parameters trong ES6

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.

1. Giá trị mặc định tham số trong ES5

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

function sayHello(domain)

{

    // Tạo giá trị mặc định là thuanhoaonline.com

    domain = domain || 'thuanhoaonline.com';

     

    return domain;

}

 

// Sử dụng

console.log("KHÔNG truyền tham số: " + sayHello());

console.log("CÓ truyền tham số: " + sayHello('facebook.com'));

Chạy lên bạn sẽ thấy kết quả như hình sau:

2. Giá trị mặc định tham số trong ES6

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

function sayHello(domain = 'thuanhoaonline.com')

{

    return domain;

}

 

// Sử dụng

console.log("KHÔNG truyền tham số: " + sayHello());

console.log("CÓ truyền tham số: " + sayHello('facebook.com'));

Chạy lên kết quả vẫn không khác gì ở ví dụ ES5.

III.24.Rest Parameters trong ES6

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.

1. Rest Parameters trong ES6

Để 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.

let functionName = (param1, param2, ...other) => {

    //

}

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.

// Khai báo hàm

let domainList = (main, sub, ...other) =>

{

    console.log("Main: " + main);

    console.log("Sub: " + sub);

    console.log("Other");

    console.log(other);

}

 

// Gọi hàm

domainList('thuanhoaonline.com', 'facebook.com', 'google.com', 'zalo.com', 'iphone.com');

Hàm này có 3 tham số truyền vào là mainsub 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ụ:

// Khai báo hàm

let domainList = (main, sub, ...other) =>

{

    console.log("Main: " + main);

    console.log("Sub: " + sub);

    console.log("Other");

    console.log(other);

}

 

// Gọi hàm

domainList('thuanhoaonline.com', 'facebook.com');

Kết quả:

III.25.Const - biến không thay đổi giá trị trong ES6

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.

1. Const trong ES6

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

const info = {

    name : "Nguyen Van Cuong",

    domain : "thuanhoaonline.com"

};

 

console.log(info);

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

// Khai báo const

const domain = 'thuanhoaonline.com';

 

// Thay đổi giá trị

domain = 'qa.thuanhoaonline.com';

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.

{

    // Khai báo const

    const domain = 'thuanhoaonline.com';

    console.log(domain);

}

{

    console.log(domain);

}

Kết quả:

2. Const trong vòng lặp

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.

var domains = [

    'thuanhoaonline.com',

    'google.com',

    'facebook.com'

];

 

for (domain of domains){

    const message = "Domain " + domain;

    console.log(message);

}

Kết quả:

III.26.Collection Sets trong ES6

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.

1. Set trong ES6

Chúng ta có bốn thao tác chính khi làm việc với set như sau:

  • Khởi tạolet set = new Set();
  • Thêm phần tửset.add(value);
  • Xóa phần tửset.delete(value);
  • Kiểm tra tồn tại giá trịset.has(value);
  • Đếm tổng số phần tửset.size;
  • Xóa toàn bộ phần tử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.

Khởi tạo:

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.

var numbers = new Set([1, 2, 3, 4]);

Thêm phần tử:

var numbers = new Set([1, 2, 3, 4]);

 

numbers.add(5); // numbers = Set {1, 2, 3, 4 ,5}

Xóa phần tử:

var numbers = new Set([1, 2, 3, 4]);

 

numbers.delete(2); // numbers = Set {1, 3, 4}

Kiểm tra phần tử tồn tại:

var numbers = new Set([1, 2, 3, 4]);

 

console.log(numbers.has(1)); // True

console.log(numbers.has(5)); // False

Đếm tổng số phần tử:

var numbers = new Set([1, 2, 3, 4]);

 

console.log(numbers.size); // 4

Xóa toàn bộ phần tử:

var numbers = new Set([1, 2, 3, 4]);

 

numbers.clear(); // numbers = Set()

2. Sử dụng vòng lặp với Set

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:

let numbers = new Set([1, 2, 3, 4]);

 

for (let number of numbers){

    console.log(number);

}

 

 

// Output:

// 1

// 2

// 3

// 4

3. Chuyển đổi Set sang Array

Để chuyển đổi Set sang Array thì bạn sử dụng ba dấu chấm đặt trước biến Set.

let numbers = new Set([1, 2, 3, 4]);

 

let arr_numbers = [...numbers];

 

// Lúc này  biến arr_numbers sẽ là một mảng gồm 4 phần tử

// [1, 2, 3, 4]

Ngược lại để chuyển đổi một Array sang Set thì bạn sử dụng cách sau:

let arr_numbers = [1, 2, 3, 4];

 

let set_numbers = new Set(arr_numbers);

 

// Lúc này  biến set_numbers sẽ là Set {1, 2, 3, 4}

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ử.

4. Mapping và filtering

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:

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).

let set = new Set([1, 2, 3]);

let arr = [...set].map(function(x){

    return x * 2;

});

 

console.log(set); // Set(1, 2, 3)

console.log(arr); // [2, 4, 6]

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:

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).

let set = new Set([1, 2, 3]);

 

// Lấy các số chẵn

let arr = [...set].filter(function(x){

    return (x % 2) == 0;

});

 

console.log(set); // Set(1, 2, 3)

console.log(arr); // [2]

III.27.Collection Maps trong ES6

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.

1. Map trong ES6

Chúng ta có các thao tác chính với map như sau:

  • Khởi tạo: let map = new Map()
  • Thêm phần tử: map.set('Name', 'Nguyen Van Cuong');
  • Xóa phần tử: map.delete("Name");
  • Kiểm tra phần tử tồn tại: map.has('Name')
  • Đếm tổng số phần tử: map.size
  • Xóa toàn bộ phần tử: 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.

Khởi tạo:

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.

// Tạo một map gồm 3 thông tin

 let map = new Map([

     ["Name", "Nguyen Van Cuong"],

     ["Email", "thehalfheart@gmail.com"],

     ["Website", "thuanhoaonline.com"]

]);

 

console.log(map);

// Map {"Name" => "Nguyen Van Cuong", "Email" => "thehalfheart@gmail.com", "Website" => "thuanhoaonline.com"}

Thêm phần tử:

let map = new Map();

  

map.set('Name', 'Nguyen Van Cuong');

 

console.log(map);

// Map {"Name" => "Nguyen Van Cuong"}

Xóa phần tử:

let map = new Map();

  

map.set('Name', 'Nguyen Van Cuong');

 

console.log(map);

// Map {"Name" => "Nguyen Van Cuong"}

 

map.delete("Name");

 

console.log(map);

// Map {}

Kiểm tra phần tử tồn tại:

let map = new Map();

  

map.set('Name', 'Nguyen Van Cuong');

 

console.log(map.has('domain')); // False

//console.log(map.has('Name')); // True

Đếm tổng số phần tử:

let map = new Map();

  

map.set('Name', 'Nguyen Van Cuong');

 

console.log(map.size); // 1

Xóa toàn bộ phần tử:

let map = new Map();

  

map.set('Name', 'Nguyen Van Cuong');

map.set('Domain', 'freetuts.net');

 

map.clear();

 

console.log(map.size); // 0

2. Hiểu rõ hơn về Key trong Map

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 stringnumberconst hay thậm chí là một NaN.

Ví dụ: Key là const

let map = new Map();

 

// KEY1

const KEY1 = {};

map.set(KEY1, 'Nguyễn Văn Cường');

console.log(map.get(KEY1)); // Nguyễn Văn Cường

 

const KEY2 = {};

 

map.set(KEY2, 'Freetuts.net');

console.log(map.get(KEY2)); // Freetuts.net

Ví dụ: Key là NaN

let map = new Map();

     

map.set(NaN, 'freetuts.net');

console.log(map.get(NaN)); // freetuts.net

3. Các hàm bỗ trợ trong Map

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:

let map = new Map([

    ['name' , 'Nguyen Van Cuong'],

    ['domain', 'thuanhoaonline.com']

]);

Tất cả các ví dụ dưới đây sẽ sử dụng biến map này.

Hàm keys() - lấy danh sách keys

// Xử lý

for (var key of map.keys())

{

    console.log(key);

}

 

// Kết quả

// name

// domain

Hàm values() - lấy danh sách values

// Xử lý

for (var value of map.values())

{

    console.log(value);

}

 

// Kết quả

// Nguyen Van Cuong

// freetuts.net

Hàm entries() - lấy danh sách keys + values

// Xử lý

for (var entry of map.entries())

{

    console.log(entry[0] + ' - ' + entry[1]);

}

 

// Kết quả

// name - Nguyen Van Cuong

// domain - freetuts.net

4. Lặp qua các phần tử trong Map

Để 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.

let map = new Map([

    ['name' , 'Nguyen Van Cuong'],

    ['domain', 'thuanhoaonline.com']

]);

 

// Xử lý

for (var [key, value] of map)

{

    console.log(key + ' - ' + value);

}

 

// Kết quả

// name - Nguyen Van Cuong

// domain - freetuts.net

Cách 2: Sử dụng hàm forEach.

let map = new Map([

    ['name' , 'Nguyen Van Cuong'],

    ['domain', 'thuanhoaonline.com']

]);

 

// Xử lý

map.forEach((value, key) => {

    console.log(key + ' - ' + value);

});

 

// Kết quả

// name - Nguyen Van Cuong

// domain - freetuts.net

5. Mapping và Filtering

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.

Hàm map()

Ví dụ: Nối giá trị của key vào value

 

// Giá trị ban đầu

let map = new Map()

    .set(1, 'a')

    .set(2, 'b')

    .set(3, 'c')

    .set(4, 'd')

    .set(5, 'e');

     

// Chuyển đổi

let map1 = new Map(

        [...map].map(

            ([key, value]) => [key, key + '-' + value]

        )

);

 

console.log(map1);

// Kết quả: Map {1 => "1-a", 2 => "2-b", 3 => "3-c", 4 => "4-d", 5 => "5-e"}

Hàm filter()

Ví dụ: Lọc những phần tử có key chia hết cho 2.

// Giá trị ban đầu

let map = new Map()

    .set(1, 'a')

    .set(2, 'b')

    .set(3, 'c')

    .set(4, 'd')

    .set(5, 'e');

     

// Chuyển đổi

let map1 = new Map(

        [...map].filter(

            ([key, value]) => key % 2 == 0

        )

);

 

console.log(map1);

// Kết quả: Map {2 => "b", 4 => "d"}

III.28.Collection WeakMap trong ES6

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.

1. WeakMap trong ES6

WeakMap cũng có một số phương thức tương tự Map như:

  • Khởi tạo 
  • Thêm phần tử
  • Lấy giá trị phần tử
  • Xóa phần tử
  • Kiểm tra phần tử tồn tại

Ví dụ: Một ví dụ tổng hợp các thao tác trên.

// Khởi tạo

var weak = new WeakMap();

 

// Danh sách key

var key1 = {};

var key2 = {};

 

// Thêm phần tử

weak.set(key1, "Giá trị 01");

weak.set(key2, "Giá trị 02");

 

// Lấy giá trị

console.log(weak.get(key1)); // Giá trị 01

console.log(weak.get(key2)); // Giá trị 02

 

// Kiểm tra tồn tại

var other_key = {};

console.log(weak.has(key1)); // true

console.log(weak.has(other_key)); // false

 

// Xóa phần tử

weak.delete(key1);

console.log(weak.get(key1)); // Undefined

III.29.Collection WeakSet trong ES6

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.

1. WeakSet trong ES6

WeakSet có một số thao tác chính như sau:

  • Khởi tạo
  • Thêm phần tử
  • Kiểm tra phần tử tồn tại
  • Xóa phần tử

// Khởi tạo

var weak = new WeakSet();

 

// Danh sách key

var key1 = {

    name : "thehalfheart"

};

var key2 = {

    website: "thuanhoaonline.com"

};

 

// Thêm phần tử

weak.add(key1);

weak.add(key2);

 

// Kiểm tra tồn tại

var other_key = {};

console.log(weak.has(key1)); // true

console.log(weak.has(other_key)); // false

 

// Xóa phần tử

weak.delete(key1);

III.30.Symbol trong ES6

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ó.

1. Cú pháp Symbol trong ES6

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:

let symbol1 = Symbol();

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.

let symbol2 = Symbol('symbol2');

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ả.

console.log(symbol1 === symbol2); // false

Để kiểm tra một biến là một symbol thì bạn sử dụng hàm typeof.

console.log(typeof symbol1); // symbol

2. Sử dụng Symbol như một Key trong Object

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

const MY_KEY = Symbol();

let obj = {

    [MY_KEY]: 123

};

console.log(obj[MY_KEY]);

Ví dụ: Với Map

const MY_KEY = Symbol();

 

let map = new Map();

 

map.set(MY_KEY, 'freetuts.net');

 

console.log([...map]);

3. Chuyển đổi giá trị của Symbol

Bây giờ ta thử chuyển đổi giá trị của Symbol sang một số kiểu dữ liệu.

Chuyển sang kiểu boolean

Để chuyển kiếu symbol sang kiểu boolean thì ta sử dụng cú pháp sau:

sym = Boolean(sym);

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.

// Khỏi tạo

var sym = Symbol();

 

// Ép kiểu

sym = Boolean(sym);

 

// In ra giá trị

document.write(sym); // true

Chuyển sang kiểu number

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é.

// Khỏi tạo

var sym = Symbol();

 

// Ép kiểu

sym = Number(sym);

 

// In ra giá trị

document.write(sym); // error: Uncaught TypeError: Cannot convert a Symbol value to a number

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 sang kiểu string

Để 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().

// Khỏi tạo

var sym = Symbol('thuanhoaonline.com');

 

// Ép kiểu

sym = String(sym);

 

// In ra giá trị

document.write(sym); // Kết quả: Symbol('thuanhoaonline.com')

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é.

// Khỏi tạo

var sym1 = Symbol('thuanhoaonline.com');

var sym2 = Symbol('thuanhoaonline.com');

// Ép kiểu

var sym1_new = String(sym1);

var sym2_new = String(sym2);

 

// In ra giá trị

document.write(sym1 === sym2); // Kết quả: False

document.write(sym1_new === sym2_new); // Kết quả: True

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.

4. Điểm yếu của Symbol

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

// Tạo key

var key = Symbol('thuanhoaonline.com');

 

// Khai báo đối tượng

var object = {};

object[key] = 'thuanhoaonline.com';

 

// In ra đúng giá trị

console.log(object[key]);

 

// Tạo một key mới có giá trị giống key

var key = Symbol('thuanhoaonline.com');

// In ra giá trị sai (undefined), điều này có nghĩa key trên khác key dưới

// cho dù bạn khai báo secret key giống nhau

console.log(object[key]);

III.31.HTML Template String trong ES6

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.

1. HTML Template String trong ES6

Template String sẽ thay thế cách nối chuỗi thông thường.

Cấu trúc

Template String được bao bọc bởi cặp dấu ``.

let temp = `

    Chào mừng bạn đến với thuanhoaonline.com

`;

Khai báo tham số

Để 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ị.

let temp = `

    Chào mừng bạn đến với ${website}

`;

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.

var website = 'thuanhoaonline.com';

 

let temp = `

    Chào mừng bạn đến với ${website}

`;

 

console.log(temp);

Kết quả:

2. Lặp dữ liệu và gán vào Template String

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.

Lặp đơn giản

Ví dụ dưới đây lặp danh sách domain và gán vào template.

var domains = [

    'thuanhoaonline.com',

    'facebook.com',

    'google.com'

];

 

// Loop

domains.map(function(domain, key){

    console.log(`<h1>${domain}</h1>`);

});

Kết quả:

Lặp phức tạp

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.

var domains = [

    {

        domain : "thuanhoaonline.com",

        author: "Nguyen Van Cuong"

    },

    {

        domain : "google.com",

        author: "Sergey Brin"

    }

];

 

// Loop

domains.map(function(domain, key){

    var tmpl = `

        <div>

            <h1>${domain.domain}</h1>

            <h2>${domain.author}</h2>

        </div>

    `;

    console.log(tmpl);

});

 

Kết quả:

III.32.Synchronous là gì? Asynchronous là gì?

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:

  • Sync => Synchronous
  • Async => Asynchronous

1. Synchronous là gì?

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.

Mặt tốt của Synchronous

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.

Mặt xấu của Synchronous

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.

2. Asynchronous là gì?

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.

Mặt tốt của Asynchronous

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 :)

Mặt xấu của Asynchronous

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 :).

3. Ajax Asynchronous

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ộ" :).

Ajax Async

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.

// ĐOẠN 1

var message = 'Website freetuts.net thật tuyệt';

 

// ĐOẠN 2

$.ajax({

    url : "some-url",

    data : {}

    success : function(result){

        message = 'Giá trị đã được thay đổi';

    }

});

 

// ĐOẠN 3

alert(message); // kết quả là Website freetuts.net thật tuyệt ? What the Fuck?

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.

setTimeout Async

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.

// ĐOẠN 1

var message = 'Website thuanhoaonline.com thật tuyệt';

 

// ĐOẠN 2

$.ajax({

    url : "some-url",

    data : {}

    success : function(result){

        message = 'Giá trị đã được thay đổi';

    }

});

 

// ĐOẠN 3

setTimeout(function(){

    alert(message);

}, 10000);

 

//// Giá trị đã được thay đổ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ụ:

setTimeout(function(){

    alert('1');

}, 0);

 

alert('2');

Kết quả sẽ xuất hiện 2 -> 1 chứ không phải là 1 - 2 như bạn đang nghĩ đâu :

III.33.Tìm hiểu Promise trong Javascript - ES6

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.

1. Xử lý đồng bộ trong Javascript?

Javascript là đơn luồng

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 là đa luồng

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.

// Xử lý gửi email

function sendEmail()

{

    $.ajax({

        url : "some-url",

        data : {},

        success : function(result){

            alert('Send Success!');

        }

    });

}

 

// Gán  hàm send Email vào sự kiện click

$('#button').click(function(){

    sendEmail();

});

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).

 

// Xử lý gửi email

function sendEmail()

{

    $.ajax({

        url : "some-url",

        data : {},

        success : function(result){

            alert('Send Success!');

        },

        error : function(eror){

            alert('Send Error!');

        }

    });

}

 

// Khi chưa bổ sung listener

$('#button').click(function(){

    // Tôi chả quan tâm nó làm cái gì cả

});

 

// Khi bổ sung listener

// Gán  hàm send Email vào sự kiện click

$('#button').click(function(){

    // Tôi quan tâm đến thao tác gửi email có thành công hay không?

    // Lúc này phụ thuộc vào hàm callback sendEmail

    sendEmail();

});

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.

$('img').ready().then(function() {

    // loaded

}, function() {

    // failed

    $(this).attr('src', 'default.png');

});

Và nếu áp dụng Promise trong ES6 thì nó rất đơn giản.

Promise.all([$('img')]).then(function() {

    // all loaded

}, function(img) {

    $(img).attr('src', 'default.png');

});

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.

2. Promise trong Javascript - ES6

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:

  • Fulfilled Hành động xử lý xông và thành công
  • Rejected Hành động xử lý xong và thất bại
  • Pending Hành động đang chờ xử lý hoặc bị từ chối

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

Để tạo một Promise bạn sử dụng cú pháp sau:

var promise = new Promise(callback);

Trong đó callback là một function có hai tham số truyền vào như sau:

var promise = new Promise(function(resolve, reject){

     

});

Trong đó

  • resolve là một hàm callback xử lý cho hành động thành công.
  • reject là một hàm callback xử lý cho hành động thất bại.

Thenable trong Promise

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.

var promise = new Promise(function(resolve, reject){

    resolve('Success');

    // OR

    reject('Error');

});

 

 

promise.then(

        function(success){

            // Success

        },

        function(error){

            // Error

        }

);

Ví dụ: Demo thao tác Resolve và Reject

var promise = new Promise(function(resolve, reject){

    resolve('Success!');

});

 

 

promise.then(

    function(success){

        console.log(success);

    }

);

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.

var promise = new Promise(function(resolve, reject){

    reject('Error!');

});

 

 

promise.then(

    function(success){

        console.log(success);

    },

    function(error){

        console.log(error);

    }

);

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.

var promise = new Promise(function(resolve, reject){

    reject('Error!');

    resolve('Success!');

});

 

 

promise.then(

    function(success){

        console.log(success);

    },

    function(error){

        console.log(error);

    }

);

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.

Catch trong Promise

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.

promise.then().catch();

Ví dụ:

var promise = new Promise(function(resolve, reject){

    reject('Error!');

});

 

 

promise

        .then(function(message){

            console.log(message);

        })

        .catch(function(message){

            console.log(message);

        });

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.

var promise = new Promise(function(resolve, reject){

    reject('Error!');

});

 

 

promise

        .then(function(message){

            console.log(message);

        }, function(message){

            console.log('Callback Error!');

            console.log(message);

        })

        .catch(function(message){

            console.log('Catch!');

            console.log(message);

        });

Kết quả mình chụp hình luôn cho các bạn thấy rõ hơn.

III.34.Hiểu rõ hơn về Promise trong Javascript - ES6

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.

1. Ba trạng thái Pending - Fulfilled - Rejected

Ở bài trước mình có giới thiệu 3 trạng thái của Promise đó là pendingfulfilled và rejected, đây là ba trạng thái mà bất kì một Promise nào cũng phải có.

Pending

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.

var promise = new Promise(function(resolve, reject){

 

});

 

console.log(promise);

Kết quả:

Fulfilled

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.

var promise = new Promise(function(resolve, reject){

    resolve();

});

 

console.log(promise);

Kết quả:

Rejected

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).

var promise = new Promise(function(resolve, reject){

    reject();

});

 

promise.catch(function(){

    // Something

});

 

console.log(promise);

Kết quả:

2. Thenable liên tiếp

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.

var promise = new Promise(function(resolve, reject){

    resolve();

});

 

promise.then(function(){

            console.log(1);

        })

        .then(function(){

            console.log(2);

        })

        .then(function(){

            console.log(3);

        });

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.

var promise = new Promise(function(resolve, reject){

    reject();

});

 

promise.then(function(){

            console.log(1);

        })

        .then(function(){

            console.log(2);

        }, function(){

            console.log('Error!')

        })

        .then(function(){

            console.log(3);

        });

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.

var promise = new Promise(function(resolve, reject){

    reject();

});

 

promise.then(function(){

            console.log(1);

        })

        .then(function(){

            console.log(2);

        })

        .then(function(){

            console.log(3);

        })

        .catch(function(){

            console.log('Error!')

        });

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ức  then() 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ụ:

asyncPromise1()

        .then(function () {

            return asyncPromise2();

        })

        .then(function () {

            return asyncPromise3();

        })

        .catch(function (err) {

            return asyncRecovery1();

        })

        .then(function () {

                return asyncPromise4();

            }, function (err) {

                return asyncRecovery2();

            }

        )

        .catch(function (err) {

            console.log("Đừng lo lắng gì cả :)");

        })

        .then(function () {

            console.log("Mọi thứ đã xong!");

        });

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 

var promise = new Promise(function(resolve, reject){

    resolve();

});

 

promise

    .then(function(){

        return new Promise(function(resolve, reject){

            reject();

        });

    })

    .then(function(){

        console.log('Success!');

    })

    .catch(function(){

        console.log('Error!');

    });

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 đó !

III.35.Iterables và iterators trong ES6.

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:

// câu truy vấn

$sql = 'Select * from Users';

 

// Thực hiện câu truy vấn

$result = mysqli_query($link, $sql);

 

// Lấy kết quả đầu tiên

$first = mysqli_fetch_assoc($result);

 

// Lấy phần tử kế tiếp

$second = mysqli_fetch_assoc($result);

 

Đâ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ề.

1. Iterators là gì?

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

let arr = ['a', 'b', 'c'];

 

var iterator = arr[Symbol.iterator]();

 

console.log(iterator.next());   // a

console.log(iterator.next());   // b

console.log(iterator.next());   // c

console.log(iterator.next());   // undefined

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.

2. Iterable và iterator protocol

Trước tiên chúng ta tìm hiểu vè Iterable đã nhé.

Iterable

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ụ:

var array = [1, 2, 3, 4];

 

for (let x of array) {

    console.log(x);

}

 

console.log(...array);

Với ES6 thì các đối tượng như ArrayObjectMapWeakMapSetWeakSet đều là đối tượng Iterable.

Iterator protocol

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:

// Mảng nguyên thủy

let arr = ['a', 'b', 'c'];

 

// Chuyển sang Iterable

var iterable = arr[Symbol.iterator]();

 

// Sử dụng các Iterator Protocol để chuyển qua các phần tử

console.log(iterable.next());   // a

console.log(iterable.next());   // b

console.log(iterable.next());   // c

console.log(iterable.next());   // undefined

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:

  • Data consumers: Các phương thức - hành động của Iterator, đây cũng chính là Iterator Protocol.
  • Interface: Interface là một lớp trung gian kế thừa tất cả các Data consummers, đây chính là Iterable. Và vì trong Javascript không tồn tại Interface nên ta sử dụng Symbol.iterator để chuyển đổi.
  • Data Resources: Các đối tượng dữ liệu trong Javascript muốn chuyenr sang Iterable.

Tóm lại

  • Các đối tượng muốn sử dụng được Iterator Protocol thì phải thông qua Interface. Thông thường thì các đối tượng như array, collection đề đã có sẵn iterable.
  • Với phương thức next() thì bạn phải thực hiện thao tác chuyển đổi thông qua Symbol.iterator.

3. Ví dụ Iterable và 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 đó.

Array

var array = ['a', 'b', 'c'];

 

console.log('***** Lặp qua các phần tử *****');

for (let x array) {

    console.log(x);

}

 

console.log('***** Sử dụng next *****');

var iterable = array[Symbol.iterator]();

console.log(iterable.next());

console.log(iterable.next());

console.log(iterable.next());

console.log(iterable.next());

Kết quả:

Map

let map = new Map().set('a', 1).set('b', 2);

 

console.log('***** Lặp qua các phần tử *****');

for (let pair of map) {

    console.log(pair);

}

 

console.log('***** Sử dụng next *****');

let iterable = map[Symbol.iterator]();

console.log(iterable.next());

console.log(iterable.next());

console.log(iterable.next());

Kết quả:

Set

let set = new Set().add('a').add('b');

 

console.log('***** Lặp qua các phần tử *****');

for (let x of set) {

    console.log(x);

}

 

console.log('***** Sử dụng next *****');

var iterable = set[Symbol.iterator]();

console.log(iterable.next());

console.log(iterable.next());

console.log(iterable.next());

Kết quả:

Arguments

Arguments là tổng các tham số truyền vào mảng.

function printArgs()

{

    console.log('***** Lặp qua các phần tử *****');

    for (let x of arguments) {

        console.log(x);

    }

     

    console.log('***** Sử dụng next *****');

    var iterable = arguments[Symbol.iterator]();

    console.log(iterable.next());

    console.log(iterable.next());

    console.log(iterable.next());

}

 

// Sử dụng

printArgs('a', 'b');

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é.

4. Toán tử ...

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.

Sử dụng với Array

var array = ['a', 'b', 'c'];

 

console.log('Liệt kê');

console.log(...array); // a b c

 

console.log('Bổ sung');

var new_arr = [...array, 'd', 'e'];

console.log(...new_arr); // a b c d e

Kết quả:

Sử dụng với Map

console.log('Liệt kê');

let map = new Map().set('a', 1).set('b', 2);

console.log(...map);

 

console.log('Bổ sung');

let new_map = new Map([...map]).set('d', 3).set('e', 4);

console.log(...new_map);

Sử dụng với Set

console.log('Liệt kê');

let set = new Set().add('a').add('b');

console.log(...set);

 

console.log('Bổ sung');

let new_set = new Set([...set]).add('c').add('d');

console.log(...new_set);

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é :

III.36.Tính năng mới trong ES7 (ECMAScript 2016)

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.

1. Toán tử lũy thừa **

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:

base_value ** exponent_value

Ví dụ với 2 lũy thừa ba thì ta sẽ viết như sau:

2**3

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:

let base = 2

let exponent = 3

console.log('Sử dụng Math.pow()',Math.pow(base,exponent))

console.log('Sử dụng toán tử **',base**exponent)

2. Array.includes

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.

Array.includes(value)

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:

Array.includes(value,start_index)

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

let marks = [50,60,70,80];

//Kiểm tra 50 có nằm trong mảng hay không?

if(marks.includes(50)){

    console.log('Tìm thấy trong mảng');

}else{

    console.log('Không tìm thấy trong mảng');

}

3. Object Rest / Spread

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.

var {a, b, c} = {a: 1, b: 2, c: 3};

 

console.log(a); // 1

console.log(b); // 2

console.log(c); // 3

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.

var {a, b, c, ...x} = {a: 1, b: 2, c: 3, x: 4, y: 5, z: 6};

 

console.log(a); // 1

console.log(b); // 2

console.log(c); // 3

 

console.log(x); // { x: 4, y: 5, z: 6 }

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.

var a = 1, b = 2, c = 3;

var x = {x: 4, y: 5, z: 6};

 

var obj = {a, b, c, ...x};

 

console.log(obj); //{a: 1, b: 2, c: 3, x: 4, y: 5, z: 6};

4. Async Functions

Đâ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

myFirstOperation(function(error, firstResult) {

    mySecondOperation(firstResult, function(error, secondResult) {

        myThirdOperation(secondResult, function(error, thirdResult) {

            \* viết code xử lý với kết quả nhận được ở  hoạt động thứ 3 *\

        });

    });

});

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.

myFirstPromise()

      .then(firstResult => mySecondPromise(firstResult)

      .then(secondResult => myThirdPromise(secondResult)

      .then(thirdResult => {

          \* viết code xử lý với kết quả nhận được ở  hoạt động thứ 3 *\

      },

      error => {

          \* xử lý lỗi *\

      }

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?

async function myOperations() {

    const firstResult = await myFirstOperation();

    const secondResult = await mySecondOperation(firstResult);

    const thirdResult = await myThirdResult(secondResult);

   \* viết code xử lý với kết quả nhận được ở  hoạt động thứ 3 *\

}

 

try {

    myOperations();

} catch(e) {

     \* xử lý lỗi *\

}

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ộ.

5. Observables

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.

var resize = new Observable((o) => {

 

  // listen for window resize and pass height and width

  window.addEventListener("resize", () => {

    var height = window.innerHeight;

    var width = window.innerWidth;

    o.next({height, width});

  });

 

});

Hoặc sự kiện change:

var change = new Observable((o) => {

 

  // listen for a data model's change event

  // and pass along the key and value that changed

  myModel.on("change", (key, value) => {

    o.next({ key, value });

  });

 

});

III.37.Tính năng mới trong ES8 (2017)

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.

1. Object.values()

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ụ

const object1 = {

  a: 'somestring',

  b: 42,

  c: false

};

 

console.log(Object.values(object1));

// expected output: Array ["somestring", 42, false]

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é.

2. Object.entries()

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ụ

const object1 = {

  a: 'somestring',

  b: 42

};

 

for (const [key, value] of Object.entries(object1)) {

  console.log(`${key}: ${value}`);

}

 

// expected output:

// "a: somestring"

// "b: 42"

// order is not guaranteed

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.

3. String.prototype.padEnd()

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ụ

"Javascript".padEnd(10) // "Javascript"

"Javascript".padEnd(12) // "Javascript  "

"Javascript".padEnd(15, 'x') // "Javascriptxxxxx"

"Javascript".padEnd(12, 'th') // "Javascriptth"

"Javascript".padEnd(1) // "Javascript"

4, String.prototype.padStart()

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ụ

"Javascript".padStart(10) // "Javascript"

"Javascript".padStart(12) // "  Javascript"

"Javascript".padStart(15, 'x') // "     Javascript"

"Javascript".padStart(12, 'th') // "thJavascript"

"Javascript".padStart(1) // "Javascript"

5. Object.getOwnPropertyDescriptors

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ụ

const object1 = {

  property1: 42

};

 

const descriptors1 = Object.getOwnPropertyDescriptors(object1);

 

console.log(descriptors1.property1.writable);

// expected output: true

 

console.log(descriptors1.property1.value);

// expected output: 42

6. Dấu phẩy theo sau danh sách tham số các hàm

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ụ

var arr = [

  1,

  2,

  3,

];

 

arr; // [1, 2, 3]

arr.length; // 3

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ụ

var arr = [1, 2, 3,,,];

arr.length; // 5

Hoặc bạn cũng có thể sử dụng trong Object.

Ví dụ

var object = {

  foo: "bar",

  baz: "qwerty",

  age: 42,

};

Thậm chí trong hàm hoặc class đều được.

Ví dụ

function f(p) {}

function f(p,) {}

 

(p) => {};

(p,) => {};

 

class C {

  one(a,) {}

  two(a, b,) {}

}

 

var obj = {

  one(a,) {},

  two(a, b,) {},

};

7. Bộ nhớ dùng chung

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.

III.38.Những tính năng mới trong ES11

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é.

1. Private Class Variables

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à privatepublic 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ụ

class Person {

  #born = 1980

  age() { console.log(2020 - this.#born) }

}

const person1 = new Person()

person1.age() // 40

console.log(person1.#born)

//Uncaught SyntaxError: Private field '#born' must be declared in an    

//enclosing class

2. Promise.allSettled

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ụ

const promiseOne = new Promise((resolve, reject) => 

                       setTimeout(resolve, 3000));

const promiseTwo = new Promise((resolve, reject) =>

                       setTimeout(reject, 3000));

Promise.allSettled([promiseOne, promiseTwo]).then(data => console.log(data));

 

//(2) [{…}, {…}]

    //0: {status: "fulfilled", value: undefined}

    //1: {status: "rejected", reason: undefined}

Bạn có thể thấy, biến data lưu thông tin dữ liệu của cả hai promise.

3. String.prototype.matchAll

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:

const regexp = /g(ro)(up(\d?))/g;

const groups = 'group1group2group3';

groups.match(regexp);

//(3) ["group1", "group2", "group3"]

//0: "group1"

//1: "group2"

//2: "group3"

Sử dụng matchAll:

const regexp = /g(ro)(up(\d?))/g;

const someString = 'group1group2group3';

const array = [...someString.matchAll(regexp)];

array

//(3) [Array(4), Array(4), Array(4)]

//0: (4) ["group1", "ro", "up1", "1", index: 0, input: //"group1group2group3", groups: undefined]

//1: (4) ["group2", "ro", "up2", "2", index: 6, input: //"group1group2group3", groups: undefined]

//2: (4) ["group3", "ro", "up3", "3", index: 12, input: //"group1group2group3", groups: undefined]

//length: 3

4. Toán tử kiểm tra tồn tại trong Object và Array

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.

let car = {

  engine : {

    consumption: 10

  }

}

Để truy cập đến phần tử consumption thì ta sẽ dùng dấu chấm để trỏ đến hai lần như sau:

let consumption = car.engine.consumption

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.

let car = {

}

car.engine.consumption; // => Lỗi

Hoặc sử dụng toán tử ba ngôi là ok:

let consumption = car.engine ? car.engine.consumption : undefined

Hoặc có thể kiểm tra bằng lệnh if else:

let car = {

}

//Check if exists

let consumption;

if(car.engine && car.engine.consumption){

  let consumption = cat.engine.consumption

}else{

  let consumption = undefined

}

Với ES2020 thì bạn có thể xử lý đơn giản bằng một đoạn code như sau:

let car = {

}

let consumption = car.engine?.consumption

console.log(consumption);

//undefined

Thậm chí có thể dùng nhiều lần:

let car = null;

let consumption = car?.engine?.consumption

console.log(consumption);

Và sử dụng với array:

//first element.

let car1 = array?.[1];

5. Dynamic Import

Đâ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

export hello () => console.log("Hello World!");

Và đây là đoạn code chúng ta import thư viện này tĩnh (static import).

main.js

import * as greet from './ greetingsModule.js’;

greet.hello();

//Hello World!

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ụ

...

if( 1 === 1){

import(’./greetingsModule.js’).then( (greet) => {

             greet.hello();

            // Hello World!

         });

}

...

Hoặc sử dụng trong async/await.

Ví dụ

...

async function load() {

    let greet = await import(’./greetingsModule.js’);

    greet.hello();

    // Hello!

  }

...

6. BigInt

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:

console.log(Number.MAX_SAFE_INTEGER);

//9007199254740991

const max = Number.MAX_SAFE_INTEGER;

console.log(max +1);

//9007199254740992  -> Correct value!

console.log(max +10);

//9007199254741000  -> Incorrect value! (1001)

Ở 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ế.

const myBigNumber = 9007199254740991n;

console.log(myBigNumber +1n);

//9007199254740992n  -> Correct value!

console.log(myBigNumber +10n);

//9007199254741001n  -> Correct value!

//Note:

console.log(myBigNumber +10);

//Error: you cannot mix BigInt and other types, use explicit //conversions.

//Correct way: You have to add the letter 'n' on the end of the //number

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.

III.39.Những tính năng mới của ES9 (2018)

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.

Asynchronous Generators và Iteration

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ụ:

function asyncIterator() {

  const array = [1, 2];

  return {

    next: function() {

      if (array.length) {

        return Promise.resolve({

          value: array.shift(),

          done: false

        });

      } else {

        return Promise.resolve({

          done: true

        });

      }

    }

  };

}

 

var iterator = asyncIterator();

 

(async function() {

    await iterator.next().then(console.log); // { value: 1, done: false }

    await iterator.next().then(console.log); // { value: 2, done: false }

    await iterator.next().then(console.log); // { done: true }

})();

Bài viết vẫn đang cập nhật những tính năng của ES9.

IV. GOOGLE MAPS

IV.1. Đăng ký google map API để lấy API key sử dụng google map

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.

1. Đăng ký google API theo giao diện mới

Bước 1:

Đă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)

Bước 2:

Một popup hiện ra và bạn sẽ chọn Browser key như hình vẽ.

Bước 3:

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.

Bước 4:

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

2. Đăng ký google API theo giao diện cũ

Bước 1:

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ý.

Đăng ký google map API để lấy key sử dụng google map

Click vào tap API Access, sau đó bạn click Create new Browser key ...

Bước 2:

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.

Bài 01: Đăng ký google map API để lấy key sử dụng google map

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

 Đăng ký google map API để lấy key sử dụng google map

Bạn sẽ lưu lại thông tin API key để đưa vào ứng dụng của bạn.

IV.2.Viết ứng dụng google map hiển thị bản đồ thành phố Hồ Chí Minh

1. Tạo file html hiển thị bản đồ google map

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:

<!DOCTYPE html>

<html>

  <head>

    <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />

    <style type="text/css">

      html { height: 100% }

      body { height: 100%; margin: 0; padding: 0 }

      #map-canvas { height: 100% }

    </style>

  </head>

  <body>

    <div id="map-canvas"/>

  </body>

</html>

Trong đoạn code này bạn chú ý div#map-canvasbả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.

2. Thêm thư viện javascript google map

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:

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=API_KEY"></script>

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.

3. Sử dụng javascript API để hiển thị bản đồ TP Hồ Chí Minh

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:

// Function khởi tạo google map

function initialize()

{

    // Config google map

    var mapOptions = {

        // Tọa độ muốn hiển thị ban đầu (tung độ,vỹ độ)

        center: new google.maps.LatLng(10.771971, 106.697845),

        // Mức độ zoom

        zoom: 8

    };

 

    // Hiển thị map lên bản đồ (div#map-canvas)

    var map = new google.maps.Map(document.getElementById("map-canvas"),mapOptions);

}

 

// Gán hàm initialize vào trong sự kiện load dom google map

google.maps.event.addDomListener(window, 'load', initialize);

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

Viết ứng dụng google map hiển thị bản đồ thành phố hồ chí minh

Như vậy ta có toàn file như sau:

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />

        <style type="text/css">

            html { height: 100% }

            body { height: 100%; margin: 0; padding: 0 }

            #map-canvas { height: 100% }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw"></script>

        <script type="text/javascript">

             

        // Function khởi tạo google map

        function initialize()

        {

            // Config google map

            var mapOptions = {

                // Tọa độ muốn hiển thị ban đầu (tung độ,vỹ độ)

                center: new google.maps.LatLng(10.771971, 106.697845),

                // Mức độ zoom

                zoom: 8

            };

 

            // Hiển thị map lên bản đồ (div#map-canvas)

            var map = new google.maps.Map(document.getElementById("map-canvas"),mapOptions);

        }

 

        // Gán hàm initialize vào trong sự kiện load dom google map

        google.maps.event.addDomListener(window, 'load', initialize);

        </script>

    </head>

    <body>

        <div id="map-canvas"/>

    </body>

</html>

IV.3.Chọn ngôn ngữ google map và chạy google map trên thiết bị di động

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

1. Phát triển google map trên thiết bị di động

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:

function detectBrowser()

{

    // User Agent hiện tại

    var useragent = navigator.userAgent;

 

    // Div hiển thị google map

    var mapdiv = document.getElementById("map-canvas");

 

    // Nếu là iPhone hoặc Android thì thiết lập màn hình full 100%

    if (useragent.indexOf('iPhone') != -1 || useragent.indexOf('Android') != -1) {

        mapdiv.style.width = '100%';

        mapdiv.style.height = '100%';

    } else {

        // Ngược lại người dùng đang dùng desktop, ta thiết lập theo percent

        mapdiv.style.width = '600px';

        mapdiv.style.height = '800px';

    }

}

Để 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:

2. Lựa chọn ngôn ngữ hiển thị trên google map

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:

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=API_KEY&language=LANG_CODE"></script>

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:

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />

        <style type="text/css">

            html { height: 100% }

            body { height: 100%; margin: 0; padding: 0 }

            #map-canvas { height: 100% }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script type="text/javascript">

            // Function khởi tạo google map

            function initialize()

            {

                // Config google map

                var mapOptions = {

                    // Tọa độ muốn hiển thị ban đầu (tung độ,vỹ độ)

                    center: new google.maps.LatLng(10.771971, 106.697845),

                    // Mức độ zoom

                    zoom: 8

                };

 

                // Hiển thị map lên bản đồ (div#map-canvas)

                var map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);

            }

 

            // Gán hàm initialize vào trong sự kiện load dom google map

            google.maps.event.addDomListener(window, 'load', initialize);

        </script>

    </head>

    <body>

        <div id="map-canvas"/>

    </body>

</html>

IV.4.Google map events - UI Events - MVC State Changes

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:

  • User evenets (như click, dbclick) được truyền từ DOM tới Google Map API, những sự kiện này nó riêng và không giống với DOM bình thường vì nó theo chuẩn của google map
  • MVC state change: đây là thông báo thay đổi trạng thái của bản đồ tới các đối tượng Google Map API với việc đặt tên theo một quy luật là property_changed, trong đó property thay đổi tùy theo sự kiện

Mỗ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.

1. Google Map - UI Events

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:

  • 'click'
  • 'dblclick'
  • 'mouseup'
  • 'mousedown'
  • 'mouseover'
  • 'mouseout'

Để 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.

2. Google Map - MVC State Changes

Đố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.

3. Google Map - Xử lý sự kiện

Để đă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ũ.

<!DOCTYPE html>

<html>

    <head>

        <title>Simple click event</title>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            function initialize()

            {

                // Thuộc tính map tại thành phố Hồ Chí Minh

                var mapOptions = {

                    zoom: 4,

                    center: new google.maps.LatLng(10.771971, 106.697845)

                };

 

                // Khởi tạo map

                var map = new google.maps.Map(document.getElementById('map-canvas'),mapOptions);

                 

                // Thuộc tính khởi tạo Marker

                var marker = new google.maps.Marker({

                    position: map.getCenter(),

                    map: map,

                    title: 'Click to zoom'

                });

                 

                // Khi thay đổi vị trí của bản đồ (center_changed events)

                // thì sẽ xử lý sau 3 giây đưa về vị trí cũ.

                google.maps.event.addListener(map, 'center_changed', function() {

                    // marker.getPosition() là lấy vị trí trung tâm của bản đồ

                    window.setTimeout(function() {

                        map.panTo(marker.getPosition());

                    }, 3000);

                });

 

                // Đưa sự kiện click vào marker vừa tạp trên

                // sẽ zoom lên 8 và đưa bản đồ về trạng thái nằm giữa

                google.maps.event.addListener(marker, 'click', function() {

                    map.setZoom(8);

                    map.setCenter(marker.getPosition());

                });

            }

             

            // Lúc laod các thẻ DOM thì chạy hàm initialize

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

IV.5.Google map events - Getting - Setting -Quản lý Listeners

Ở 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é.

1. Google Map - Getting and Setting Properties within Event Handlers

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 đồ.

<!DOCTYPE html>

<html>

    <head>

        <title>Getting properties with event handlers</title>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            function initialize()

            {

                // Lấy tọa độ ban đầu

                var myLatLng = new google.maps.LatLng(10.771971, 106.697845);

                 

                var mapOptions = {

                    zoom: 4,

                    center: myLatLng

                };

 

                // Tạo bản đồ

                var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

                 

                // Tạo một cửa sổ mới với vị trí nằm ngay giữa bản đồ

                var infowindow = new google.maps.InfoWindow({

                    content: 'Change the zoom level',

                    position: myLatLng

                });

                 

                // Mở cửa sổ

                infowindow.open(map);

                 

                // Gán vào sự kiện Zoom_change một hàm, hàm này se show độ zoom

                google.maps.event.addListener(map, 'zoom_changed', function()

                {

                    // Lấy thông số Zoom hiện tại

                    var zoomLevel = map.getZoom();

                     

                    // Thiết lập trung tâm bản đồ tại ví trí cũ

                    map.setCenter(myLatLng);

                     

                    // Đổi thông tin của infowindow

                    infowindow.setContent('Zoom: ' + zoomLevel);

                });

            }

             

            // Hàm gọi tạo bản đồ

            google.maps.event.addDomListener(window, 'load', initialize);

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

2. Google Map - Listening to DOM Events

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:

<script>

    function initialize() {

      // Map initialization

    }

</script>

<body onload="initialize()">

    <div id="map-canvas"></div>

</body><br><br><br><br><br><br><br>

Cách 2:

<script>

  function initialize() {

    // Map initialization

  }

  google.maps.event.addDomListener(window, 'load', initialize);

</script>

<body>

  <div id="map-canvas"></div>

</body>

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 đồ.

<!DOCTYPE html>

<html>

    <head>

        <title>Listening to DOM events</title>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            function initialize()

            {

                var mapOptions = {

                    zoom: 8,

                    center: new google.maps.LatLng(10.771971, 106.697845)

                };

                 

                var mapDiv = document.getElementById('map-canvas');

                 

                var map = new google.maps.Map(mapDiv, mapOptions);

                 

                // Gán sự kiện khi click vào thẻ div mapDiv vào google map,

                // sẽ thông báo message

                google.maps.event.addDomListener(mapDiv, 'click', showAlert);

            }

 

            function showAlert() {

                window.alert('DIV clicked');

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

3. Google Map - Removing Event Listeners

Để 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ụ:

var listener1 = google.maps.event.addListener(marker, 'click', aFunction);

google.maps.event.removeListener(listener1);

Để xóa hết các listener ta dùng hàm clearInstanceListeners().

Ví dụ:

var listener1 = google.maps.event.addListener(marker, 'click', aFunction);

var listener2 = google.maps.event.addListener(marker, 'mouseover', bFunction);

 

// Remove listener1 and listener2 from marker instance.

google.maps.event.clearInstanceListeners(marker);

Để 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ụ:

marker.addListener('click', aFunction);

marker.addListener('click', bFunction);

marker.addListener('click', cFunction);

 

// Remove all click listeners from marker instance.

google.maps.event.clearListeners(marker, 'click');

Để tham khảo danh sách các sự kiện bạn vào link google.maps.event namespace để đọc thêm.

IV.6.Google Map Controls - Danh sách các controls của google map

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:

  • Zoom control hiển thị một thanh trượt trên bản đồ lớn hoặc dấu (+/-) ở bản đồ nhỏ và mục đích là Zoom kích thước của bản đồ. Biểu tượng Zoom control sẽ hiển thị góc trên bên trái của bản đồ ở các thiết bị không cảm ứng và ở góc dưới bên trái ở các thiết bị cảm ứng.
  • Pan control hiển thị các nút di chuyển bản đồ. Pan control kiểm soát này xuất hiện mặc định ở góc trên bên trái của bản đồ trên các thiết bị không cảm ứng. Pan control cũng cho phép bạn xoay 45 ° hình ảnh nếu có.
  • MapType control cho phép người dùng chuyển đổi giữa các kiểu bản đồ như (ROADMAP and SATELLITE). MapType control hiển thị mặc định ở góc bên phải của bản đồ
  • Street View control chứa biểu tượng Pegman, dùng để xem hình mô tả tại địa điểm hiện tại, nó nằm ở góc trên bên trái của bản đồ
  • Rotate control là một biểu tượng nhỏ dùng để xoay bản đồ, mặc định nó nằm góc trên bên trái của bản đồ. Xem thêm 45° Imagery.
  • Overview Map control hiển thị một bản đồ tổng thể giống như là sự thu nhỏ lại của bản đồ chính. Overview Map control nằm ở góc dưới bên phải của bản đồ.

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:

Bài 06: Google Map Controls - Danh sách các controls của google map

Disable Default Google Map PI Controls (Default UI)

Đô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: 

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Disabling the default UI</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            function initialize() {

                var mapOptions = {

                    zoom: 8,

                    center: new google.maps.LatLng(10.771971, 106.697845),

                    disableDefaultUI: true

                }

                var map = new google.maps.Map(document.getElementById('map-canvas'),

                        mapOptions);

            }

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

Chạy lên giao diện hoàn toàn mất đi các controls như hình dưới đây:

Hiển thị bản đồ google map thành phố Hồ Chí Minh ở dạng xóa Default UI

IV.7.Google Map Controls - cấu hình map controls nâng cao

Ở 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é.

1. Thêm Controls vào Map

Đô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ị.

{

  panControl: boolean,

  zoomControl: boolean,

  mapTypeControl: boolean,

  scaleControl: boolean,

  streetViewControl: boolean,

  overviewMapControl: boolean

}

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.

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Adding controls to the map</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script language="javascript">

             

            function initialize()

            {

                var mapOptions = {

                    zoom: 4,

                    center: new google.maps.LatLng(10.771971, 106.697845),

                    panControl: false,

                    zoomControl: false,

                    scaleControl: true

                };

                var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

2. Các Control Options Google Map

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:

  • google.maps.ZoomControlStyle.SMALL: hiển thị dạng Mini-zoom với chỉ hai nút (+) và (-). Phong cách này phù hợp với những bản đồ hiển thị nhỏ, trên các thiết bị di động bản đồ hiển thị dạng này cho phép người dùng click chọn dấu (+) và (-) để phóng to và thu nhỏ bản đồ.
  • google.maps.ZoomControlStyle.LARGE: Hiển thị thanh trượt Zoom dạng chuẩn, trên các thiết bị di động nó có phép người dùng chọn dấu (+) và (-) để phóng to và thu nhỏ.
  • google.maps.ZoomControlStyle.DEFAULT: chọn một điều khiển zoom thích hợp dựa trên kích thước của bản đồ và các thiết bị mà bản đồ đang chạy

Với Map Control bạn có thể cấu hình ở một trong  các dạng sau:

  • google.maps.MapTypeControlStyle.HORIZONTAL_BAR: hiển thị các mảng điều khiển dạng thanh Bar nằm ngang như được hiển thị trên Google Maps.
  • google.maps.MapTypeControlStyle.DROPDOWN_MENU: hiển thị một nút điều khiển duy nhất cho phép bạn chọn các loại bản đồ thông qua một Dropdown Menu.
  • google.maps.MapTypeControlStyle.DEFAULT: hiển thị mặc định,  nó phụ thuộc vào kích thước màn hình và có thể thay đổi trong các phiên bản tương lai của API

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).

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Control options</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            function initialize()

            {

                var mapOptions = {

                    zoom: 8,

                    center: new google.maps.LatLng(10.771971, 106.697845),

                    mapTypeControl: true, // Hiển thị mapType

                    mapTypeControlOptions: {

                        style: google.maps.MapTypeControlStyle.DROPDOWN_MENU

                    },

                    zoomControl: true,

                    zoomControlOptions: {

                        style: google.maps.ZoomControlStyle.SMALL

                    }

                };

                var map = new google.maps.Map(document.getElementById('map-canvas'),

                        mapOptions);

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

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:

Zoom Control Maptype Control

3. Modifying Controls Google Map

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:

  • mapTypeControl: MapTypeControlOptions
  • panControl: PanControlOptions .
  • zoomControl: zoomControlOptions .
  • scaleControl: ScaleControlOptions .
  • rotateControl: rotateControlOptions 
  • overviewMapControl: overviewMapControlOptions 

4. Xác định vị trí Google Map 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:

  • TOP_CENTER
  • TOP_LEFT 
  • TOP_RIGHT
  • LEFT_TOP
  • RIGHT_TOP
  • LEFT_CENTER
  • RIGHT_CENTER
  • LEFT_BOTTOM
  • RIGHT_BOTTOM
  • BOTTOM_CENTER
  • BOTTOM_LEFT 
  • BOTTOM_RIGHT

Tương ứng với hình sau:

google map controls position

Dưới đây là một ví dụ thể hiện xác định vị trí của các controls trong google map.

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Control positioning</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            function initialize()

            {

                var mapOptions = {

                    zoom: 12,

                    center: new google.maps.LatLng(10.771971, 106.697845),

                    mapTypeControl: true,

                    mapTypeControlOptions: {

                        style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,

                        position: google.maps.ControlPosition.BOTTOM_CENTER

                    },

                    panControl: true,

                    panControlOptions: {

                        position: google.maps.ControlPosition.TOP_RIGHT

                    },

                    zoomControl: true,

                    zoomControlOptions: {

                        style: google.maps.ZoomControlStyle.LARGE,

                        position: google.maps.ControlPosition.LEFT_CENTER

                    },

                    scaleControl: true,

                    streetViewControl: true,

                    streetViewControlOptions: {

                        position: google.maps.ControlPosition.LEFT_TOP

                    }

                };

                var map = new google.maps.Map(document.getElementById('map-canvas'),

                        mapOptions);

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

IV.8.Google Map Controls - Custom Map Control

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.

  • Định nghĩa các style cho element bạn muốn custom
  • Xử lý tương tác người dùng hoặc bản đồ thông qua các sự kiện (events)
  • Tạo một thẻ DIV bao quanh Controls và thêm nó vào hệ thống Properties của Map Control.

Bây giờ ta sẽ đi sâu vào từng phần cho bạn thấy.

1. Vẽ lại giao diện controls google map

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:

// Tạo một thẻ div

var controlDiv = document.createElement('div');

 

// Thiết lập style cho thẻ div

controlDiv.style.padding = '5px';

 

// Tạo control UI bằng thẻ div

var controlUI = document.createElement('div');

 

// Thiết lập style cho controlUI

controlUI.style.backgroundColor = 'white';

controlUI.style.borderStyle = 'solid';

controlUI.style.borderWidth = '2px';

controlUI.style.cursor = 'pointer';

controlUI.style.textAlign = 'center';

controlUI.title = 'Click to set the map to Home';

 

// Gắn controlUI vào controlDiv

controlDiv.appendChild(controlUI);

 

// Thiết lập CSS tiếp, tạo một thẻ div tiếp theo

var controlText = document.createElement('div');

 

// Và thiets lập style

controlText.style.fontFamily = 'Arial,sans-serif';

controlText.style.fontSize = '12px';

controlText.style.paddingLeft = '4px';

controlText.style.paddingRight = '4px';

controlText.innerHTML = '<strong>Home</strong>';

 

// ControlUI tiếp tục append controlText vào

controlUI.appendChild(controlText);

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, ...

2. Xử lý sự kiện các Custom Controls Google Map

Khi bạn tạo một trình điều khiển thì chắc chắn bạn muốn trình điều khiển đó thực hiện một nhiệm vụ gì đó trong project của bạn. Control đó có thể được thay đổi trạng thái bởi chính người dùng hoặc do bản đồ Map can thiệp vào. Với người dùng thì đơn giản nhưng đối với MAP thì lại là vấn đề khó khăn. Nhưng bạn đừng lo, google map đã cung cấp các API cho phép ta giải quyết vấn đề này đó chính là hàm addDomListener().

Ví dụ:

 

// Tạo vị trí bản đồ tại Chicago

var chicago = new google.maps.LatLng(41.850033, -87.6500523);

 

// Them sự kiện click vào outer thì sẽ di chuyển bản đồ tới chicago

google.maps.event.addDomListener(outer, 'click', function() {

  map.setCenter(chicago);

});

3. Positioning Custom Controls

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ụ:

// Tạo div control mới

var controlDiv = document.createElement('div');

 

// gắn nó vào vịt rí TOP_RIGHT

map.controls[google.maps.ControlPosition.TOP_RIGHT].push(controlDiv);

4. Ví dụ Custom Control Google Map

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.

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Custom controls</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

             

            // Biến lưu trữ map

            var map;

             

            // Thông số tung độ vĩ độ HCM

            var hochiminh = new google.maps.LatLng(10.771971, 106.697845);

 

            /**

             * The HomeControl thêm control vào google map

             * kết quả trả về là vị trí trung tâm thành phố HCM

             * tham số truyenf vào là control Div và map hiện tại

             * @constructor

             */

            function HomeControl(controlDiv)

            {

 

                // Style cho div

                controlDiv.style.padding = '5px';

 

                // Tạo UI và Style cho UI

                var controlUI = document.createElement('div');

                controlUI.style.backgroundColor = 'white';

                controlUI.style.borderStyle = 'solid';

                controlUI.style.borderWidth = '2px';

                controlUI.style.cursor = 'pointer';

                controlUI.style.textAlign = 'center';

                controlUI.title = 'Click to set the map to Home';

                // Gán ControlUI vào controlDiv

                controlDiv.appendChild(controlUI);

                 

                // Style cho đoạn text bên trong  và gán vào controlUI

                var controlText = document.createElement('div');

                controlText.style.fontFamily = 'Arial,sans-serif';

                controlText.style.fontSize = '12px';

                controlText.style.paddingLeft = '4px';

                controlText.style.paddingRight = '4px';

                controlText.innerHTML = '<b>Home</b>';

                controlUI.appendChild(controlText);

                 

                // Khi click vào controlUI thì sẽ thiết lập vị trí center TPHCM

                google.maps.event.addDomListener(controlUI, 'click', function() {

                    map.setCenter(hochiminh);

                });

 

            }

 

            function initialize()

            {

                 

                var mapDiv = document.getElementById('map-canvas');

                 

                var mapOptions = {

                    zoom: 12,

                    center: hochiminh

                };

                 

                map = new google.maps.Map(mapDiv, mapOptions);

                 

                var homeControlDiv = document.createElement('div');

                 

                // Khởi tạo bản đồ

                var homeControl = new HomeControl(homeControlDiv);

                 

                // thiết lập index

                homeControlDiv.index = 1;

                 

                // Gắn vào vị trí TOP_RIGHT

                map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv);

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

             

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

5. Thêm trạng thái vào Custom Control Google Map

Ở 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.

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Custom controls</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

 

            var map;

             

            var hochiminh = new google.maps.LatLng(10.771971, 106.697845);

             

            // Định nghĩa một property tên home_ vào đối tượng HomeControl

            HomeControl.prototype.home_ = null;

             

            // Định nghĩa method getHome

            HomeControl.prototype.getHome = function() {

                return this.home_;

            };

             

            // Định nghĩa method setHome

            HomeControl.prototype.setHome = function(home) {

                this.home_ = home;

            };

             

            function HomeControl(controlDiv, map, home)

            {

 

                var control = this;

 

                // thuộc tính home_ có giá trị bằng tọa độ home truyền vào

                control.home_ = home;

                 

                controlDiv.style.padding = '5px';

 

                // Set CSS for the control border

                var goHomeUI = document.createElement('div');

                goHomeUI.style.backgroundColor = 'white';

                goHomeUI.style.borderStyle = 'solid';

                goHomeUI.style.borderWidth = '2px';

                goHomeUI.style.cursor = 'pointer';

                goHomeUI.style.textAlign = 'center';

                goHomeUI.title = 'Click to set the map to Home';

                controlDiv.appendChild(goHomeUI);

 

                // Set CSS for the control interior

                var goHomeText = document.createElement('div');

                goHomeText.style.fontFamily = 'Arial,sans-serif';

                goHomeText.style.fontSize = '12px';

                goHomeText.style.paddingLeft = '4px';

                goHomeText.style.paddingRight = '4px';

                goHomeText.innerHTML = '<b>Home</b>';

                goHomeUI.appendChild(goHomeText);

 

                // Set CSS for the setHome control border

                var setHomeUI = document.createElement('div');

                setHomeUI.style.backgroundColor = 'white';

                setHomeUI.style.borderStyle = 'solid';

                setHomeUI.style.borderWidth = '2px';

                setHomeUI.style.cursor = 'pointer';

                setHomeUI.style.textAlign = 'center';

                setHomeUI.title = 'Click to set Home to the current center';

                controlDiv.appendChild(setHomeUI);

 

                // Set CSS for the control interior

                var setHomeText = document.createElement('div');

                setHomeText.style.fontFamily = 'Arial,sans-serif';

                setHomeText.style.fontSize = '12px';

                setHomeText.style.paddingLeft = '4px';

                setHomeText.style.paddingRight = '4px';

                setHomeText.innerHTML = '<b>Set Home</b>';

                setHomeUI.appendChild(setHomeText);

 

                // Click vào HOME

                google.maps.event.addDomListener(goHomeUI, 'click', function() {

                    var currentHome = control.getHome();

                    map.setCenter(currentHome);

                });

 

                // Click vào setHome, thay đội vị trí center hiện tại

                google.maps.event.addDomListener(setHomeUI, 'click', function() {

                    var newHome = map.getCenter();

                    control.setHome(newHome);

                    alert('Bạn thiết lập home cho vị trí ' + map.getCenter());

                });

            }

 

            function initialize() {

                var mapDiv = document.getElementById('map-canvas');

                var mapOptions = {

                    zoom: 12,

                    center: hochiminh

                };

                map = new google.maps.Map(mapDiv, mapOptions);

                 

                // tạo 2 control

                var homeControlDiv = document.createElement('div');

                var homeControl = new HomeControl(homeControlDiv, map, hochiminh);

 

                homeControlDiv.index = 1;

                map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv);

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

IV.9.Google Map Styles - Styled Maps

1. Styled maps là gì?

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)

  • Bằng cách thiết lập các style của google map, cách thiết lập này bị giới hạn bởi các style mặc định của nó.
  • Bằng cách tạo ra các StyledMapType và đưa nó vào google map, tạo ra một bản đồ mới hoàn toàn.

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é.

2. Cú pháp styled maps

Có hai cách để ta style riêng cho bản đồ google map:

  • Map features (tính năng bản đồ) là những yếu tố về địa lý, địa hình mà ta có thể nhắm tới mục tiêu chính xác trên bản đồ. Ví dụ như đường giao thông, các công viên, công trình lớn.
  • Stylers là khả năng hiển thị màu sắc và đặc tính của bản đồ.

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:

var stylesArray = [

  {

    featureType: '',

    elementType: '',

    stylers: [

      {hue: ''},

      {saturation: ''},

      {lightness: ''},

      // etc...

    ]

  },

  {

    featureType: '',

    // etc...

  }

]

Map Features

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ụ:

{

  featureType: "road"

}

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ợ:

  • all (default): Lấy tất cả các thuộc tính của feature.
  • geometry: lấy tất cả các thuộc tính hình học của feature.
    • geometry.fill: chọn fill geometry.
    • geometry.stroke: chọn strock geometry.
  • labels: chọn nhãn và các tính chất liên quan đến nhãn.
    • labels.icon: lựa chọn mỗi icon.
    • labels.text: lựa chọn mỗi text của icon.
    • labels.text.fill: chọn fill label.
    • labels.text.stroke: lựa chọn strock text.

Ví dụ dưới đây chọn nhãn cho tất cả local roads:

{

  featureType: "road.local",

  elementType: "labels"

}

Stylers

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:

var styleArray = [

  {

    featureType: "all",

    stylers: [

      { saturation: -80 }

    ]

  },{

    featureType: "road.arterial",

    elementType: "geometry",

    stylers: [

      { hue: "#00ffee" },

      { saturation: 50 }

    ]

  },{

    featureType: "poi.business",

    elementType: "labels",

    stylers: [

      { visibility: "off" }

    ]

  }

];

3. Thay đổi phong cách mặc định của bả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ụ:

var styles = [

  {

    stylers: [

      { hue: "#00ffe6" },

      { saturation: -20 }

    ]

  },{

    featureType: "road",

    elementType: "geometry",

    stylers: [

      { lightness: 100 },

      { visibility: "simplified" }

    ]

  },{

    featureType: "road",

    elementType: "labels",

    stylers: [

      { visibility: "off" }

    ]

  }

];

 

map.setOptions({styles: styles});

4. Tạo StyledMap Type

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ụ:

function initialize() {

 

  // Create an array of styles.

  var styles = [

    {

      stylers: [

        { hue: "#00ffe6" },

        { saturation: -20 }

      ]

    },{

      featureType: "road",

      elementType: "geometry",

      stylers: [

        { lightness: 100 },

        { visibility: "simplified" }

      ]

    },{

      featureType: "road",

      elementType: "labels",

      stylers: [

        { visibility: "off" }

      ]

    }

  ];

 

  // Create a new StyledMapType object, passing it the array of styles,

  // as well as the name to be displayed on the map type control.

  var styledMap = new google.maps.StyledMapType(styles,

    {name: "Styled Map"});

 

  // Create a map object, and include the MapTypeId to add

  // to the map type control.

  var mapOptions = {

    zoom: 11,

    center: new google.maps.LatLng(55.6468, 37.581),

    mapTypeControlOptions: {

      mapTypeIds: [google.maps.MapTypeId.ROADMAP, 'map_style']

    }

  };

  var map = new google.maps.Map(document.getElementById('map-canvas'),

    mapOptions);

 

  //Associate the styled map with the MapTypeId and set it to display.

  map.mapTypes.set('map_style', styledMap);

  map.setMapTypeId('map_style');

}

5. Các ví dụ Custom Styled Map

Maptype styled simple

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).

<!DOCTYPE html>

<html>

    <head>

        <title>Simple styled maps</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

             

            // biến map

            var map;

             

            // Tọa độ TP HCM

            var brooklyn = new google.maps.LatLng(10.771971, 106.697845);

             

            // Tạo một token tên

            var MY_MAPTYPE_ID = 'demo_custom_style';

 

            function initialize() {

                 

                // Danh sách Style

                var featureOpts = [

                    {

                        elementType: 'geometry',

                        stylers: [

                            {hue: '#890000'},

                            {visibility: 'simplified'},

                            {gamma: 0.5},

                            {weight: 0.5}

                        ]

                    },

                    {

                        elementType: 'labels',

                        stylers: [

                            {visibility: 'off'}

                        ]

                    },

                    {

                        featureType: 'water',

                        stylers: [

                            {color: '#890000'}

                        ]

                    }

                ];

                 

                // Tạo options hiển thị bản đồ

                var mapOptions = {

                    zoom: 12,

                    center: brooklyn,

                    mapTypeControlOptions: {

                        mapTypeIds: [google.maps.MapTypeId.ROADMAP, MY_MAPTYPE_ID]

                    },

                    mapTypeId: MY_MAPTYPE_ID

                };

                 

                // Hiển thị map

                map = new google.maps.Map(document.getElementById('map-canvas'),

                        mapOptions);

 

                var styledMapOptions = {

                    name: 'Custom Style'

                };

                 

                // Đổi style cho map

                var customMapType = new google.maps.StyledMapType(featureOpts, styledMapOptions);

 

                map.mapTypes.set(MY_MAPTYPE_ID, customMapType);

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

Complex styled map

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.

<!DOCTYPE html>

<html>

    <head>

        <title>Complex styled maps</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

 

            var map;

            var hochiminh = new google.maps.LatLng(10.771971, 106.697845);

 

            function initialize() {

 

                var roadAtlasStyles = [

                    {

                        featureType: 'road.highway',

                        elementType: 'geometry',

                        stylers: [

                            {hue: '#ff0022'},

                            {saturation: 60},

                            {lightness: -20}

                        ]

                    }, {

                        featureType: 'road.arterial',

                        elementType: 'all',

                        stylers: [

                            {hue: '#2200ff'},

                            {lightness: -40},

                            {visibility: 'simplified'},

                            {saturation: 30}

                        ]

                    }, {

                        featureType: 'road.local',

                        elementType: 'all',

                        stylers: [

                            {hue: '#f6ff00'},

                            {saturation: 50},

                            {gamma: 0.7},

                            {visibility: 'simplified'}

                        ]

                    }, {

                        featureType: 'water',

                        elementType: 'geometry',

                        stylers: [

                            {saturation: 40},

                            {lightness: 40}

                        ]

                    }, {

                        featureType: 'road.highway',

                        elementType: 'labels',

                        stylers: [

                            {visibility: 'on'},

                            {saturation: 98}

                        ]

                    }, {

                        featureType: 'administrative.locality',

                        elementType: 'labels',

                        stylers: [

                            {hue: '#0022ff'},

                            {saturation: 50},

                            {lightness: -10},

                            {gamma: 0.90}

                        ]

                    }, {

                        featureType: 'transit.line',

                        elementType: 'geometry',

                        stylers: [

                            {hue: '#ff0000'},

                            {visibility: 'on'},

                            {lightness: -70}

                        ]

                    }

                ];

 

                var mapOptions = {

                    zoom: 12,

                    center: hochiminh,

                    mapTypeControlOptions: {

                        mapTypeIds: [google.maps.MapTypeId.ROADMAP, 'hochiminh_road']

                    }

                };

 

                map = new google.maps.Map(document.getElementById('map-canvas'),

                        mapOptions);

 

                var styledMapOptions = {

                    name: 'Hồ Chí Minh Road'

                };

 

                var usRoadMapType = new google.maps.StyledMapType(

                        roadAtlasStyles, styledMapOptions);

 

                map.mapTypes.set('hochiminh_road', usRoadMapType);

                map.setMapTypeId('hochiminh_road');

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

IV.10.Google Map Marker - Marker Icon - Marker API

1. Google Map Marker là gì?

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.

 

Google Map Marker là gì?

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.

2. Thêm Marker vào Map (Add a marker)

Đối tượng google.maps.Marker được khởi tạo bởi các options sau:

  • Position: vị trí Marker hiển thị trên bản đồ (giống google.maps.LatLng)
  • Map: bản đồ gắn Marker. Nếu bạn không chọn map để hiển thị thì Marker vẫn được tạo, tuy nhiên nó sẽ không hiển thị trên bản đồ. Sau đó nếu thêm marker cho map thì bạn sẽ dùng hàm setMap().

Ví dụ dưới đây thêm một Marker vào bản đồ tại vị trí Uluru.

var myLatlng = new google.maps.LatLng(-25.363882,131.044922);

var mapOptions = {

  zoom: 4,

  center: myLatlng

}

var map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);

 

// To add the marker to the map, use the 'map' property

var marker = new google.maps.Marker({

    position: myLatlng,

    map: map,

    title:"Hello World!"

});

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:

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Simple markers</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            function initialize() {

                var myLatlng = new google.maps.LatLng(10.771971, 106.697845);

                var mapOptions = {

                    zoom: 4,

                    center: myLatlng

                };

                var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

                var marker = new google.maps.Marker({

                    position: myLatlng,

                    title: 'Hello World!'

                });

                 

                marker.setMap(map);

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

3. Xóa Marker khỏi Map (Remove  a Marker)

Để 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:

setMap(null)

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.

<!DOCTYPE html>

<html>

    <head>

        <title>Remove Markers</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

            #panel {

                position: absolute;

                top: 5px;

                left: 50%;

                margin-left: -180px;

                z-index: 5;

                background-color: #fff;

                padding: 5px;

                border: 1px solid #999;

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            // Map

            var map;

             

            // Mảng Marker

            var markers = [];

 

            function initialize()

            {

                // Khợi tạo địa điểm

                var haightAshbury = new google.maps.LatLng(37.7699298, -122.4469157);

                 

                // Khởi tạo options, gắn địa điểm vào

                var mapOptions = {

                    zoom: 12,

                    center: haightAshbury,

                    mapTypeId: google.maps.MapTypeId.TERRAIN

                };

                 

                // Tạo map

                map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

 

                // Gắn sự kiện khi click vào bản đồ thì sẽ thêm Marker

                // tham số của event có chứa thông số latLng nên ta

                // dùng nó để lấy vị trí luôn

                google.maps.event.addListener(map, 'click', function(event) {

                    addMarker(event.latLng);

                });

                 

                // Thêm Marker ban đầu

                addMarker(haightAshbury);

            }

 

            // Hàm thêm Marker và insert vào mảng markers

            function addMarker(location) {

                var marker = new google.maps.Marker({

                    position: location,

                    map: map

                });

                markers.push(marker);

            }

             

            // Thiết lập bản đồ cho tất cả markers

            function setAllMap(map) {

                for (var i = 0; i < markers.length; i++) {

                    markers[i].setMap(map);

                }

            }

             

            // Clear all marker

            // dùng hàm setAllMap(null)

            function clearMarkers() {

                setAllMap(null);

            }

 

            // Hiển thị tất cả markers

            function showMarkers() {

                setAllMap(map);

            }

             

            // Xóa tất cả marker ra khỏi bộ nhớ

            function deleteMarkers() {

                clearMarkers();

                markers = [];

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="panel">

            <input onclick="clearMarkers();" type=button value="Hide Markers">

            <input onclick="showMarkers();" type=button value="Show All Markers">

            <input onclick="deleteMarkers();" type=button value="Delete Markers">

        </div>

        <div id="map-canvas"></div>

        <p>Click on the map to add markers.</p>

    </body>

</html>

4. Chuyển động Marker (Animate a 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.

  • DROP: Marker sẽ đứng im và kéo được Marker ra khỏi vị trí khác.
  • BOUNCE Marker sẽ nhảy liên tục và kéo được Marker ra khỏi vị trí khác. Nếu bạn muốn dừng nhảy thì thiết lập thuộc tính animation bằng null.

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.

<!DOCTYPE html>

<html>

    <head>

        <meta charset="utf-8">

        <title>Marker Animations</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

             

            // Tạo 2 vị trí

            var stockholm = new google.maps.LatLng(59.32522, 18.07002);

            var parliament = new google.maps.LatLng(59.327383, 18.06747);

             

            // Marker

            var marker;

             

            // Bản đồ

            var map;

 

            function initialize()

            {

                //bản đồ options

                var mapOptions = {

                    zoom: 13,

                    center: stockholm

                };

 

                // Tạo bản đồ

                map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

                 

                // Tạo marker ban đầu

                marker = new google.maps.Marker({

                    map: map,

                    draggable: true,

                    animation: google.maps.Animation.DROP,

                    position: parliament

                });

                 

                // Khi click vào marker thì gọi hàm toogleBounce

                google.maps.event.addListener(marker, 'click', toggleBounce);

            }

 

            function toggleBounce() {

                // Nếu có sử dụng animate

                if (marker.getAnimation() != null) {

                    marker.setAnimation(null);

                } else {

                    marker.setAnimation(google.maps.Animation.BOUNCE);

                }

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

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.

function drop() {

  for (var i =0; i < markerArray.length; i++) {

    setTimeout(function() {

      addMarkerMethod();

    }, i * 200);

  }

}

5. Thay đổi hình Marker (Customize a marker image)

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.

Simple icons

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

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Simple icons</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            function initialize()

            {

                var mapOptions = {

                    zoom: 4,

                    center: new google.maps.LatLng(10.771971, 106.697845)

                };

                 

                var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

                 

                // Đường dẫn đến hình icon       

                var image = 'https://freetuts.net/public/javascript/i18n/flags/vi.png';

                var myLatLng = new google.maps.LatLng(10.771971, 106.697845);

                var beachMarker = new google.maps.Marker({

                    position: myLatLng,

                    map: map,

                    icon: image

                });

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

Icon phức tạp (Complex icons)

Đô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é.

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Simple icons</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            // Trong ví dụ này ta sẽ tạo một complex markers bãi biển gần Sydney, NSW, Australia.

            function initialize() {

                var mapOptions = {

                    zoom: 10,

                    center: new google.maps.LatLng(-33.9, 151.2)

                };

                var map = new google.maps.Map(document.getElementById('map-canvas'),

                        mapOptions);

 

                setMarkers(map, beaches);

            }

 

            // Danh sahcs Địa Điểm

            var beaches = [

                ['Bondi Beach', -33.890542, 151.274856, 4],

                ['Coogee Beach', -33.923036, 151.259052, 5],

                ['Cronulla Beach', -34.028249, 151.157507, 3],

                ['Manly Beach', -33.80010128657071, 151.28747820854187, 2],

                ['Maroubra Beach', -33.950198, 151.259302, 1]

            ];

 

            function setMarkers(map, locations) {

                 

                // Icon Image Marker

                var image = {

                    url: 'https://freetuts.net/public/javascript/i18n/flags/vi.png',

                    // Kích cỡ hình

                    size: new google.maps.Size(50, 50),

                    // Gốc cho hình là oo

                    origin: new google.maps.Point(0, 0),

                    // Neo cho hình là 0, 32

                    anchor: new google.maps.Point(50, 50)

                };

                 

                // Hình dạng

                var shape = {

                    coords: [1, 1, 1, 20, 18, 20, 18, 1],

                    type: 'poly'

                };

                 

                // Lặp qua từng locations

                for (var i = 0; i < locations.length; i++)

                {

                    var beach = locations[i];

                    var myLatLng = new google.maps.LatLng(beach[1], beach[2]);

                    var marker = new google.maps.Marker({

                        position: myLatLng,

                        map: map,

                        icon: image,

                        shape: shape,

                        title: beach[0],

                        zIndex: beach[3]

                    });

                }

            }

            google.maps.event.addDomListener(window, 'load', initialize);

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

6. Di chuyển Marker (Make a marker draggable)

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.

 

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Custom controls</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

 

            function initialize() {

                var myLatlng = new google.maps.LatLng(-25.363882, 131.044922);

                var mapOptions = {

                    zoom: 4,

                    center: myLatlng

                }

                var map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);

 

                // Place a draggable marker on the map

                var marker = new google.maps.Marker({

                    position: myLatlng,

                    map: map,

                    draggable: true,

                    title: "Drag me!"

                });

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html><br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

IV.11.Google Map Window - Display Marker Info Window

1. Thêm một cửa sổ thông tin ( Add an info window )

InfoWindow được khởi tạo bởi các thông số InfoWindowOptions gồm:

  • content: nội dung muốn hiển thị, có thể sử dụng m.
  • pixelOffset: Vị trí các đỉnh của cửa sổ được neo, thông số này thường để mặc định
  • position: đối tượng LatLng gồm hai thông số tung độ và vỹ độ ()
  • maxWidth: Chiều rộng tối đa của cửa sổ và tính theo Pixel

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ụ

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Info windows</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

            #content{

                padding: 20px;

                color:red

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            function initialize()

            {

                var myLatlng = new google.maps.LatLng(10.771971, 106.697845);

                 

                var mapOptions = {

                    zoom: 12,

                    center: myLatlng

                };

 

                var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

 

                var infowindow = new google.maps.InfoWindow({

                    content: '<div id="content">Welcome to <a href="https://freetuts.net">freetuts.net</a> <br/> Đây là chợ bến thành</div>',

                    position : myLatlng

                });

 

                // Hàm Open dùng để mở window

                infowindow.open(map);

            }

             

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

2. Mở một cửa sổ window ( Open an info window )

Ở 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ápinfowindow.open(map, control);

Trong đó:

  • map: là map sẽ hiển thị
  • control: gắn vào control nào, thường là Marker

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.

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Info windows</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

            #content{

                padding: 20px;

                color:red

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            function initialize()

            {

                var myLatlng = new google.maps.LatLng(10.771971, 106.697845);

                 

                var mapOptions = {

                    zoom: 12,

                    center: myLatlng

                };

 

                var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

 

                var infowindow = new google.maps.InfoWindow({

                    content: '<div id="content">Welcome to <a href="https://freetuts.net">freetuts.net</a> <br/> Đây là chợ bến thành</div>',

                    position : myLatlng

                });

                 

                var marker = new google.maps.Marker({

                    position: myLatlng,

                    map: map,

                    title: 'Hồ Chí Minh Việt Nam'

                });

                 

                // Khi click vào Marker thì hiển thị

                google.maps.event.addListener(marker, 'click', function() {

                    infowindow.open(map,marker);

                });

            }

             

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

 

3. Tắt Cửa Sổ Window ( Close an info window )

Để tắt một cửa sổ InfoWindow thì ta dùng hàm Close().

Ví dụ: infowindow.close()

4. Thay đổi vị trí của window (Change position Info Window)

Để thay đổi vị trí của infowindow thì ta sẽ có hai cách:

  • Cách 1: dùng hàm setPosition() 
  • Cách 2: Dùng hàm open() để tạo mới một marker

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.

IV.12.Google Map Shapes - Polylines (vẽ đường thẳng)

Đô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 đồ.

1. Thêm một đường thẳng trên google map (Add a polyline)

Đố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:

  • strokeColor Quy định màu của đường thẳng, được tính theo mã màu thập lục phân d.
  • strokeOpacity độ trong suốt của đường thẳng, nằm trong khoảng từ 0 -> 1, mặc định là 1.
  • strokeWeight Chiều rộng của đường thẳng, tính theo pixels.

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.

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Simple Polylines</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

             

            function initialize()

            {

                var mapOptions = {

                    zoom: 3,

                    center: new google.maps.LatLng(0, -180),

                    mapTypeId: google.maps.MapTypeId.TERRAIN

                };

 

                var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

                 

                // Các tọa độ của đường thẳng sẽ đi qua

                var flightPlanCoordinates = [

                    new google.maps.LatLng(37.772323, -122.214897),

                    new google.maps.LatLng(21.291982, -157.821856),

                    new google.maps.LatLng(-18.142599, 178.431),

                    new google.maps.LatLng(-27.46758, 153.027892)

                ];

                 

                // Tạo polyline

                var flightPath = new google.maps.Polyline({

                    path: flightPlanCoordinates,

                    geodesic: true,

                    strokeColor: '#FFFFFF',

                    strokeOpacity: 1.0,

                    strokeWeight: 4

                });

                 

                // Dùng hàm setMap để gắn vào bản đồ

                flightPath.setMap(map);

            }

            google.maps.event.addDomListener(window, 'load', initialize);

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

2. Xóa đường thẳng đã vẽ ra khỏi bản đồ (Remove a polyline)

Để 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ụ:

// Xóa ra khỏi bản đồ hiện tại

flightPath.setMap(null);

 

// Xóa luôn khỏi bộ nhớ

flightPath = null;

3. Xử lý đường thẳng đã vẽ (Inspect a polyline)

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:

  • getAt() trả về đối tượng chứa vĩ độ và kinh độ.
  • insertAt() Chèn một vị trí vào đối tượng đường thẳng.
  • removeAt() Xóa một ví trí nào đó trong đườ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

<!DOCTYPE html>

<html>

    <head>

        <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

        <meta charset="utf-8">

        <title>Complex Polylines</title>

        <style>

            html, body, #map-canvas {

                height: 100%;

                margin: 0px;

                padding: 0px

            }

        </style>

        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDfNk5eVWmQB9e6ApnWzICLNIY5lUXpOBw&language=vi"></script>

        <script>

            // Đườn thẳng

            var poly;

             

            // Bản đồ

            var map;

 

            function initialize()

            {

                // Thông số tạo bảng đồ

                var mapOptions = {

                    zoom: 7,

                    center: new google.maps.LatLng(41.879535, -87.624333)

                };

                 

                // Tạo bản đồ

                map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

                 

                // Thông số tạo Polyline

                // chưa xác định tạo độ các điểm vẽ

                var polyOptions = {

                    strokeColor: '#000000',

                    strokeOpacity: 1.0,

                    strokeWeight: 3

                };

                 

                // Tạo Poluline mới

                poly = new google.maps.Polyline(polyOptions);

                 

                // Và gắn vào bản đồ

                poly.setMap(map);

 

                // Khi click vào bản đồ thì gọi đến hàm addLatLng

                google.maps.event.addListener(map, 'click', addLatLng);

            }

 

            // Làm xử lý

            function addLatLng(event) {

                 

                // Lấy danh sách tọa độ hiện tại của đường thẳng

                var path = poly.getPath();

 

                // Thêm một tọa độ mới (vừa click) vào danh sách

                path.push(event.latLng);

 

                // Tạo marker

                var marker = new google.maps.Marker({

                    position: event.latLng,

                    title: '#' + path.getLength(),

                    map: map

                });

            }

 

            google.maps.event.addDomListener(window, 'load', initialize);

 

        </script>

    </head>

    <body>

        <div id="map-canvas"></div>

    </body>

</html>

 


Tài liệu lập trình Javascript

Bài viết trong cùng chuyên mục

Góc games giải trí (chơi trực tiếp trên web) Đăng nhập (chơi có thưởng)



Cờ caro


Butterfly


Lật hình (luyện trí nhớ)

Cờ tướng ONLINE

Xếp hình

Ghép hình

15_PUZZLE

Kill ghosts

Banchim

Planet Defense

Tower game

Bắn bóng

Plapy Bird (NH.Đông)

Vượt chướng ngại vật

Vẽ hình cứu người

Game Phases

Game vui chơi có thưởng

Game bắn cá



0379136392

Thông tin liên hệ: Lê Văn Thuyên - ĐT: 0379136392 ; Gmail: lethuyen0379136392@gmail.com

Comment

 +   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!

Trả lời

 *   Dũng Trung-090567448:Lê Văn Thuyên0379136392--->Ok.Anh!

Trả lời

 *   Bé Nguyễn-benguyen@gmail,com:Lê Văn Thuyên0379136392--->Good job!

Trả lời

 +   -:

Trả lời

 +   -:

Trả lời

7436