Javascript là ngôn ngữ lập trình phổ biến nhất trên thế giới trong suốt 20 năm qua. Nó cũng là một trong 3 ngôn ngữ chính của website. Vậy người mới bắt đầu nên học từ đâu?
Hàm (function)
Function là một trong những nền tảng cơ bản trong JavaScript. Một function trong JavaScript tương tự như một thủ tục — một tập hợp các câu lệnh thực hiện một tác vụ hoặc tính toán một giá trị, nhưng để một thủ tục đủ điều kiện là một hàm, nó phải nhận một số đầu vào và trả về một đầu ra trong đó có một số mối quan hệ rõ ràng giữa đầu vào và đầu ra. Để sử dụng một hàm, bạn phải xác định nó ở đâu đó trong phạm vi mà bạn muốn gọi nó.
Các loại hàm: Gồm có 5 loại function:
- declaretion function
- expression function
- arrow function
- anonymous function
- gennerator function
Declaretion function
Một function (hàm) bao gồm từ khóa function tiếp theo là:
- Tên của hàm
- Danh sách các tham số, được đặt trong dấu ngoặc đơn và đực phần cách nhau bằng dấu phẩy.
- Các câu lệnh javascript xác định hàm, được đặt trong dấu ngoặc nhọn {…}
Ví dụ, đoạn mã sau xác định một hàm đơn giản có tên là square:
Hàm squase nhận một tham số number. Hàm bao gồm câu lệnh trả về tham số của hàm (number) được nhân với chính nó. Câu lệnh return chỉ định nghĩa giá trị được trả về bởi hàm.
Các tham số dạng nguyên thủy (VD: number) được truyền cho hàm theo giá trị, nếu hàm thay đổi giá trị của tham số thì thay đổi này không ảnh hưởng đến biến trên toàn cục hay trong hàm đang được gọi.
Nhưng nếu truyền một đối tượng (tức là giá trị không phải nguyên thủy, VD: array, object) làm tham số của hàm, khi hàm thay đổi thuộc tính của đối tượng thì thay đổi này sẽ ảnh hưởng đến đến toàn cục và sẽ hiển thị được thay đổi đó bên ngoài hàm.
ví dụ:
Expressions function
Ta có thể khai báo hàm như một biểu thức hàm, thay vì khai báo theo kiểu truyền thống.
Một function khai báo theo kiểu expressions function sẽ không cần phải đặt tên hàm.
ví dụ:
Tuy nhiên, một expressions function vẫn có thể được đặt tên, để cho phép hàm tự tham chiếu đến chính nó và cũng giúp dễ dàng xác định hàm trong ngăn xếp của trình biên dịch.
Việc dùng expressions function sẽ thuận tiện hơn khi muốn truyền một hàm làm đối số của một hàm khác.
ví dụ:
Khởi tạo hàm map()
Hàm map nhận một hàm được xác định bởi một biểu thức và thực thi nó cho mọi phần tử của mảng nhận được dưới dạng tham số thức hai:
Lưu ý: Trong javascript, một hàm có thể được định nghĩa dựa vào điều kiện, ví dụ:
Arrow function (cú pháp được ra mắt năm 2015 – ES6)
Là một biểu thức hàm mũi tên, có cú pháp ngắn hơn so với biểu thức hàm. Các arrow function luôn là hàm ẩn danh
Ưu điểm:
Cú pháp hàm ngắn hơn: trong một số mô hình chức năng, các function ngắn hơn được khuyến cáo sử dụng. Ví dụ:
Hàm không có ràng buộc của this
Trong các hàm thông thường, từ khóa this đại diện cho đối tượng là hàm, có thể là cửa sổ, tài liệu, một nút hoặc bất cứ thứ gì.
Với arrow function thì từ khóa this luôn đại diện cho đối tượng đã xác định hàm.
Xem ví dụ để thấy rõ sự khác biệt.
- Cả 2 ví dụ đều gọi đến một hàm 2 lần, lần đầu tiên khi tải trang và một lần nữa khi người dùng click vào một nút.
VD1:
VD2:
Kết luận: Kết quả cho thấy ví dụ 1 trả về hai đối số khác nhau (windows và button), ví dụ 2 trả về đối tượng windows 2 lần, vì windows là chủ sở hữu của hàm.
Anonymous function
Là hàm ẩn danh (hay hàm không tên), cái này không có gì lạ cả.
Cách khai báo:
- Dùng từ khóa function (trong trường hợp nếu function là tham só truyền vào một function khác thì chúng ta có thể không cần dùng từ khóa function)
- Danh sách tham số của hàm, được đặt trong dấu ngoặc tròn và cách nhau bằng dấu phẩy.
- Câu lệnh javascript định nghĩa hàm, được đặt trong dấu ngoặc nhọn {…}.
Vậy đặt ra câu hỏi:
- Hàm không có tên thì ta làm cách nào để gọi hàm?
- Anonymous function ra đời để làm gì?
C1: để sử dụng chúng ta có thể dùng cú pháp gán hoặc thêm dấu ngoặc tròn, và xem như đối tượng (hàm) này đã được hình thành và gọi ngay sau đó.
C2: để trả lời cho câu hàm này dùng để làm gì thì ta hãy đi vào ví dụ sau:
Ta có một hàm tính tổng 2 số ở thời điểm chạy và không dùng ở chỗ khác, thay vì định nghĩa ra một hàm riêng biệt và gọi ra thì ta dùng anonymous function ở đó. Vì đoan lệnh đó không được tái sử dụng ở nơi khác.
Ví dụ:
Hàm bình thường:
Hàm là một tham số của một hàm khác:
Hoặc:
Lại nảy sinh ra câu hỏi:
Tại sao ta không viết trực tiếp đoạn mã ra và chạy một lần mà lại dùng anonymous function?
Trả lời cho câu hỏi trên: đúng chúng ta có thể không dùng anonymous function vẫn được.
Vấn đề xảy ra khi code của chúng ta nhiều hơn thì chúng ta sẽ khó kiểm soát và khắc phục nếu có vấn đề lỗi xảy ra…
Tóm lại: Anonymous function thường được sử dụng xử lý đoạn mã chỉ cần được gọi ở một chỗ duy nhất, ta có thể viết hàm riêng rồi sau đó truyền vào hàm vào chỗ đó.
Gennerator function (cú pháp ra mắt trong bản ES6 – 2015)
Generator function là một trong những chức năng không còn mới đối với lập trình viên Javascript. Từ phiên bản ECMAScript 2015 (ES6) nó đã được nhà phát triển đưa vào sử dụng bằng cú pháp khai báo “function*”, và tất nhiên là trả về một Generator object.
Cú pháp:
Giải thích: Trong đó thì name: tên hàm. param: tham số đầu vào của hàm, tối đa 255 tham số. statements: phần thân chứa nội dung của hàm.
=> Gennerator function: đây là phần quan trọng nhưng rất ít được dùng, với loại function này mình sẽ chia sẻ ở một series khác!
Phạm vi của function
Các biến được định nghĩa bên trong một hàm không thể được truy cập từ bất kỳ đâu bên ngoài hàm, bởi vì biến chỉ được xác định trong phạm vi của hàm. Tuy nhiên, một hàm có thể truy cập tất cả các biến và hàm được xác định bên trong phạm vi mà nó được định nghĩa.
Nói cách khác, một hàm được xác định trong phạm vi toàn cục có thể truy cập tất cả các biến được xác định trong phạm vi toàn cục. Một hàm được định nghĩa bên trong một hàm khác cũng có thể truy cập vào tất cả các biến được xác định trong hàm cha của nó và bất kỳ biến nào khác mà hàm cha có quyền truy cập.
Phạm vi và ngăn xếp của function
Đệ quy: Một hàm có thể gọi lại chính nó. có 3 cách để một hàm tham chiều đến chính nó.
- Gọi thông qua tên function
- Một biến trong phạm vi trỏ đến hàm.
Trong hàm thì tất cả các phần sau đây đều tương đương:
- bar()
- arguments.callee()
- foo()
Một hàm gọi chính nó được gọi là một hàm đệ quy. Theo một số cách, đệ quy tương tự như một vòng lặp. Cả hai đều thực thi cùng một mã nhiều lần và cả hai đều yêu cầu một điều kiện (để tránh vòng lặp vô hạn, hay đúng hơn là đệ quy vô hạn trong trường hợp này).
Ví dụ, vòng lặp sau …
… có thể được chuyển đổi thành một khai báo hàm đệ quy, theo sau là lời gọi hàm đó:
Tuy nhiên, một số thuật toán không thể là các vòng lặp đơn giản. Ví dụ: lấy tất cả các nút của cấu trúc cây (chẳng hạn như ) dễ dàng hơn thông qua đệ quy:
So với hàm loop, bản thân mỗi lệnh gọi đệ quy tạo ra nhiều lệnh gọi đệ quy ở đây.
Có thể chuyển đổi bất kỳ thuật toán đệ quy nào sang thuật toán không đệ quy, nhưng logic thường phức tạp hơn nhiều và làm như vậy đòi hỏi phải sử dụng một ngăn xếp.
Trên thực tế, bản thân đệ quy sử dụng một ngăn xếp: ngăn xếp hàm. Hành vi giống như ngăn xếp có thể được nhìn thấy trong ví dụ sau:
Bạn có thể lồng một hàm trong một hàm khác. Hàm lồng nhau (bên trong) là riêng cho hàm chứa (bên ngoài) của nó.
Nó cũng tạo thành một sự đóng cửa . Bao đóng là một biểu thức (phổ biến nhất là một hàm) có thể có các biến tự do cùng với một môi trường liên kết các biến đó (“đóng” biểu thức).
Vì một hàm lồng nhau là một bao đóng, điều này có nghĩa là một hàm lồng nhau có thể “kế thừa” các đối số và biến của hàm chứa nó. Nói cách khác, hàm bên trong chứa đựng phạm vi của hàm bên ngoài.
Tóm lại:
- Hàm bên trong chỉ có thể được truy cập từ các câu lệnh trong hàm bên ngoài.
- Hàm bên trong tạo thành một bao đóng: hàm bên trong có thể sử dụng các đối số và biến của hàm bên ngoài, trong khi hàm bên ngoài không thể sử dụng các đối số và biến của hàm bên trong.
Ví dụ sau cho thấy các hàm lồng nhau:
Vì hàm bên trong tạo thành một bao đóng, bạn có thể gọi hàm bên ngoài và chỉ định các đối số cho cả hàm bên ngoài và bên trong:
Lưu ý cách x được bảo quản khi inside được trả lại. Một bao đóng phải bảo toàn các đối số và biến trong tất cả các phạm vi mà nó tham chiếu. Vì mỗi cuộc gọi cung cấp các đối số có khả năng khác nhau, một bao đóng mới được tạo cho mỗi lệnh gọi tới outside. Bộ nhớ chỉ có thể được giải phóng khi inside không còn truy cập được.
Điều này không khác với việc lưu trữ các tham chiếu trong các đối tượng khác, nhưng thường ít rõ ràng hơn vì người ta không đặt trực tiếp các tham chiếu và không thể kiểm tra chúng.
Các hàm có thể được lồng ghép nhiều lần. Ví dụ:
- Một hàm (A) chứa một hàm (B), chính nó chứa một hàm (C).
- Cả hai chức năng B và C biểu mẫu đóng ở đây. Vì vậy, B có thể truy cập A, và C có thể truy cập B.
- Ngoài ra, vì C có thể truy cập B mà có thể truy cập A, C cũng có thể truy cập A.
Do đó, các bao đóng có thể chứa nhiều phạm vi; chúng chứa một cách đệ quy phạm vi của các hàm chứa nó. Đây được gọi là chuỗi phạm vi. (Lý do nó được gọi là “chuỗi” sẽ được giải thích sau.)
Hãy xem xét ví dụ sau:
Trong ví dụ này, C truy cập B’s y và A’ x.
Điều này có thể được thực hiện bởi vì:
- B tạo thành một bao đóng bao gồm A (tức là, B có thể truy cập A các đối số và biến của).
- Chỉnh thành một sự đóng cửa bao gồm B.
- Bởi vì B bao gồm của bao gồm A, bao Ccủa bao gồm A, C có thể truy cập các đối số và biến của cả B và A ‘. Nói cách khác, C chuỗi các phạm vi của B và A, theo thứ tự mà.
Tuy nhiên, điều ngược lại là không đúng. A không thể truy cập C, vì A không thể truy cập bất kỳ đối số hoặc biến nào của B, C là biến của. Vì vậy, C vẫn còn riêng tư cho duy nhất B.
Khi hai đối số hoặc biến trong phạm vi của một bao đóng có cùng tên, sẽ xảy ra xung đột về tên . Nhiều phạm vi lồng nhau được ưu tiên hơn. Vì vậy, phạm vi bên trong nhất được ưu tiên cao nhất, trong khi phạm vi ngoài cùng có mức độ ưu tiên thấp nhất. Đây là chuỗi phạm vi. Đầu tiên trên chuỗi là phạm vi bên trong nhất và cuối cùng là phạm vi bên ngoài nhất. Hãy xem xét những điều sau:
Cuộc xung đột tên xảy ra tại báo cáo kết quả return x * 2và là giữa inside’s tham số x và outside’ biến s x. Chuỗi phạm vi ở đây là {inside, outside, toàn cầu đối tượng}. Do đó, inside’s x được ưu tiên hơn outside’ s x, và 20 (inside’s x) được trả về thay vì 10 (outside’ s x).