- vừa được xem lúc

[JavaScript] Bài 11 - Hàm & Vùng

0 0 6

Người đăng: Semi Art

Theo Viblo Asia

Trong bài viết này, chúng ta sẽ gặp lại 2 khái niệm: Hàm function và Vùng scope - đã được giới thiệu trong bài viết JavaScript số 3 mà chúng ta đã thực hiện trước đây. Chúng ta đã có một phần giới thiệu về 2 khái niệm này ở một giao diện bề mặt đơn giản, phù hợp với thời điểm khởi đầu. Tuy nhiên ở thời điểm hiện tại, chúng ta đã khá quen với JavaScript và công việc lập trình nói chung; Và tất nhiên là đã có thể tự tin hơn để tìm hiểu về các khái niệm ở một giao diện có chiều sâu và chi tiết hơn. Hãy cùng gặp gỡ những điều mới mẻ của những khái niệm cũ. 😄

Một cách khác để khởi tạo hàm trong JavaScript

Bên cạnh cú pháp khai báo hàm bằng từ khóa function mà chúng ta đã biết, JavaScript còn cung cấp cho chúng ta một cú pháp khác để khởi tạo một hàm như sau:

function.js

const doubleIt = (num) => num * 2;
console.log( doubleIt(9) );
// kết quả: 18

Như bạn thấy thì cú pháp mới không sử dụng từ khóa mà thay vào đó chúng ta có một ký hiệu => ở sau cặp ngoặc đơn khai báo các biến nhận dữ liệu vào hàm (num). Cũng vì ký hiệu => này mà các hàm được tạo ra bằng cú pháp mới được gọi là các hàm mũi tên arrow function. Nó có tên gọi riêng để phân biệt với cái cũ thì hiển nhiên cũng sẽ có những điểm khác biệt nào đó nữa để người ta cần phải đặt tên mới như vậy. Chúng ta sẽ xem có những gì khác nữa nào. 😄

Hàm doubleIt của chúng ta sẽ trả về một giá trị được khuếch đại lên gấp 2 lần như tên hàm đã mô tả. Vậy ở đây chúng ta có phần phía sau ký hiệu => chính là phần thân hàm num * 2. Chúng ta không thấy từ khóa return để trả về giá trị và cũng không có cặp ngoặc đơn {} thường thấy để khoanh vùng phần thân hàm. Vậy có lẽ cú pháp này được thiết kế để viết hàm ở những vị trí nào đó mà chúng ta cần sự ngắn gọn, xúc tích. 😄

Bây giờ giả sử chúng ta có một mảng các giá trị số học và muốn tạo ra một mảng mới có các phần tử được nhân đôi so với các giá trị ở mảng cũ. Có lẽ cú pháp mới này sẽ rất phù hợp. 😄

function.js

var numArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var newArray = numArray.map((num) => num * 2);
console.log(newArray);
// kết quả: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Ồ... như vậy là đối với những trường hợp cần truyền một hàm đơn giản vào một hàm khác, có lẽ chúng ta sẽ không cần phải cố gắng nghĩ ra thêm một cái tên cho một hàm mà chúng ta không có nhu cầu sử dụng lại. Thật tuyệt. 😄

Tuy nhiên, đó - chưa phải là tất cả. Cú pháp => còn cho phép chúng ta viết hàm ngắn gọn hơn trong trường hợp chúng ta muốn tạo ra một hàm nhận vào nhiều giá trị, và muốn áp dụng các giá trị đầu vào theo từng phần.

partial.js

const multiply = (a) => (b) => a * b;
const doubleIt = multiply(2);
const tripleIt = multiply(3); console.log( doubleIt(0) ); // kết quả: 0
console.log( doubleIt(1) ); // kết quả: 2
console.log( tripleIt(0) ); // kết quả: 0
console.log( tripleIt(1) ); // kết quả: 3

Ở đây hàm multiply được định nghĩa là một hàm nhận vào duy nhất một giá trị (a) và trả về một giá trị nào đó sau ký hiệu => đầu tiên. Sau đó giá trị trả về ở đây lại là một hàm được định nghĩa là nhận vào duy nhất một giá trị (b) và trả về kết quả là a * b. Do hàm thứ 2 được tạo ra xếp chồng ở bên trong hàm đầu tiên, nên có thể tham chiếu tới biến a được khai báo ở hàm thứ nhắt.

Trong trường hợp sử dụng cú pháp function để khởi tạo hàm multiply như trên, chúng ta sẽ phải viết dài hơn khá nhiều.

partial.js

const multiply = function(a) { return function(b) { return a * b; };
}; // multiply

Tuy nhiên thì mọi thứ đều có ưu điểm và nhược điểm riêng. Nếu đứng từ góc độ của một người chưa từng viết code, rõ ràng từ khóa function có tính mô tả tốt hơn và chỉ ra rất rõ: đây là một hàm; Và không yêu cầu người đọc phải tìm hiểu thông tin về ý nghĩa của cú pháp đang sử dụng là để làm gì? Trong khi đó thì ký hiệu => chỉ nói được lên rằng: có một sự liên quan giữa ký hiệu a với vế bên phải, và tương tự với ký hiệu b sau đó.

Vì vậy nên trường hợp sử dụng với các hàm lặp của mảng thì cú pháp mới khá phù hợp, còn ở trường hợp thứ 2 thì chúng ta sẽ cần phải cân nhắc xem code mà chúng ta viết ra sẽ chia sẻ cho những ai nữa. Bản thân mình thì rất ưa thích cú pháp => để sử dụng cho trường hợp số 2 vì nó cho phép định dạng code với các tham số thẳng hàng và rất dễ theo dõi. Tuy nhiên trong Series Tự Học Lập Trình Web Một Cách Thật Tự Nhiên thì chúng ta cần dự trù thêm trường hợp có ai đó bất chợt ghé qua và tham gia học cùng; Do đó bạn thông cảm là các ví dụ từ đây về sau này mình vẫn sẽ dùng chủ yếu là cú pháp function nhé. 😄

partial.js

const multiply = (a = 0) => (b = 0) => { return a * b }

Các giá trị đầu vào mặc định

Một điểm mới nữa về các hàm, đó là chúng ta sẽ có thể thiết lập giá trị mặc định cho các biến đầu vào của hàm.

Điều này sẽ giúp chúng ta đảm bảo là hàm sẽ hoạt động tốt trong trường hợp không có dữ liệu truyền vào hàm ở những tình huống cụ thể, chứ không báo lỗi hay trả về một giá trị vô nghĩa đối với đoạn code tiếp theo sau lời gọi hàm đó.

Bên cạnh đó thì việc thiết lập giá trị mặc định cho các biến đầu vào sẽ giúp cho các trình soạn thảo code nhận diện được kiểu giá trị của biến đó, và sẽ gợi ý cho chúng ta các hàm liên quan có thể muốn sử dụng khi chúng ta gõ tên biến. Tới đây thì mình muốn giới thiệu tới bạn một trình soạn thảo code miễn phí khác là Visual Studio Code của Microsoft. Nó thực sự rất tuyệt khi làm việc với JavaScript; Còn với Atom thì chúng ta sẽ cần phải cài thêm một vài plug-in hỗ trợ để được gợi ý code JavaScript tốt hơn.

parameter.js

const fixNum =
function(a = 0) { return a.toFixed(2);
}; console.log( fixNum() );
// kết quả: '0.00' console.log( fixNum(10.0123456789) );
// kết quả: '10.01'

Thực ra vẫn còn một vài điều nữa mà chúng ta chưa tìm hiểu hết về các hàm được tạo ra bởi function=>. Tuy nhiên những kiến thức đó lại có phần liên quan tới object nên chúng ta sẽ để dành sang bài viết tiếp theo về Object & Everything. Còn bây giờ thì chúng ta sẽ gặp gỡ một kiểu hàm mới. 😄

Các hàm function*

Đây là một kiểu hàm mới chứ không phải là một cú pháp thay thế như cú pháp =>. Các hàm function* còn được gọi là các generator - các hàm sản xuất ra các giá trị. Trước khi nói thêm về kiểu hàm mới này, chúng ta cần một đoạn code ví dụ:

generator.js

function* createNumbers() { yield 1; yield 2; yield 3;
}; var numberGenerator = createNumbers(); var one = numberGenerator.next().value;
console.log(one);
// kết quả: 1 var two = numberGenerator.next().value;
console.log(two);
// kết quả: 2 var three = numberGenerator.next().value;
console.log(three);
// kết quả: 3

Thay vì trả về một giá trị cố định, một hàm function* sẽ trả về một object thực thể của class Generator. Hàm createNumbers() ban đầu sẽ được lưu lại trong object numberGenerator này nhưng code bên trong hàm chưa được thực thi ngay.

Khi chúng ta gọi hàm next() (đi tiếp) lần đầu tiên, hàm createNumbers() mới thực sự được thực thi và dừng lại ở câu lệnh yield đầu tiên trả về giá trị là 1 và được lưu vào biến value của object numberGenerator. Lời gọi hàm next() sau khi chạy hàm createNumbers() xong sẽ trả về chính object numberGenerator và chúng ta tiếp tục truy xuất tới biến value để lấy ra kết quả của lần chạy hàm đầu tiên.

Ở lần gọi hàm next() tiếp theo thì hàm numberGenerator() bắt đầu chạy tiếp từ điểm dừng lần trước và đi tới câu lệnh yield tiếp theo để trả về giá trị là 2 và được lưu tiếp vào biến value của numberGenerator. Và cứ như vậy...

Như bạn thấy thì các hàm function* sẽ rất hữu ích khi chúng ta muốn trả về các giá trị theo từng phần. Ngược lại với logic truyền các giá trị vào hàm từng phần ở phía trên. 😄

Hoặc, trong trường hợp chúng ta muốn trả về một tập giá trị vô hạn, nhưng chỉ khi chúng ta cần sử dụng tới đâu thì generator mới thực hiện tính toán và cung cấp ra giá trị tới đó. Tuy nhiên để làm vậy thì chúng ta sẽ cần tới sự hỗ trợ của một cấu trúc lặp có thể lặp vô hạn mà chúng ta chưa được gặp gỡ. Hãy tạm ghi nhớ điểm này và nhắc mình khi chúng ta tìm hiểu về các cấu trúc lặp trong một bài viết sau này nhé. 😄

Lưu ý cuối cùng về các object Generator, đó là để kiểm tra trạng thái của hàm thực thi bên trong generator, chúng ta truy xuất tới biến done. Nếu thu được giá trị true thì có nghĩa là hàm thực thi bên trong đã được chạy đến hết code định nghĩa. Còn nếu là false thì chúng ta vẫn còn có thể tiếp tục gọi hàm next() để lấy ra giá trị tiếp theo.

Để để kết thúc generator trước khi hàm thực thi bên trong được chạy xong thì chúng ta có thể gọi hàm return(oneValue), giá trị oneValue sẽ được gán cho biến value của generator.

generator

function* createNumbers() { yield 1; yield 2; yield 3;
}; var numberGenerator = createNumbers(); var one = numberGenerator.next().value;
console.log(one);
// kết quả: 1 numberGenerator.return(10);
console.log( numberGenerator.value ); // 10
console.log( numberGenerator.done ); // true

Nói thêm về khái niệm Vùng scope

Lần này gặp lại khái niệm Vùng scope chúng ta sẽ đi qua nhanh thôi vì căn bản chúng ta đã hiểu scope là gì rồi. 😄

Trong bài viết trước thì chúng ta được gặp 2 từ khóa mới constlet giúp chúng ta tạo ra những chiếc hộp lưu giá trị - bên cạnh từ khóa var đã biết. Điểm khác biệt cơ bản là constlet có khả năng tạo vùng hoạt động nhỏ hơn so với cấp độ của hàm function. Hãy cùng xem xét ví dụ sau.

scope.js

const printNumbers = function() { var theVar = 1; let theLet = 2; if (true) { var theVar = 3; let theLet = 4; } console.log(theVar); // 3 console.log(theLet); // 2
}; printNumbers();

Như bạn thấy trong kết quả được in ra, biến theVar ban đầu được ghi đè lại bởi đoạn khai báo bên trong khối if nhưng biến theLet ban đầu thì không bị ghi đè. Lý do là vì đoạn khai báo theLet bên trong khối if sẽ tạo ra một biến mới với phạm vi hoạt động scope giới hạn bên trong cặp ngoặc xoắn {} của khối if.

Vì vậy nên các khóa constlet được xem là các giá trị hỗ trợ block scope (các khối lệnh được khoanh vùng bởi một cặp ngoặc xoắn {} bất kỳ), còn var thì không.

scope.js

var theVar = 1;
let theLet = 2; { var theVar = 3; // ghi đè let theLet = 4; // biến mới
}

Vùng scope lớn nhất

Code JavaScript ban đầu, được viết ở bên ngoài tất cả các cặp ngoặc xoắn {}, thuộc scope lớn nhất và thuộc về object mô tả môi trường chạy JavaScript. Trong các trình duyệt web thì đó là window, còn ở môi trường NodeJS thì là một object rỗng.

Khi chúng ta khai báo các biến varscope này thì nó trở thành thuộc tính gắn với object môi trường và có thể truy xuất ở bất kỳ đâu. Do đó còn được gọi là các biến toàn cục global. Tuy nhiên khóa constlet ở mặt khác lại được thiết kế để không hoạt động như vậy.

global.js

var globalBox = 'something';
console.log( window.globalBox );
// 'something'

Vậy chúng ta nên dùng var, let, hay const?

Tùy vào mục đích sử dụng trong từng tình huống và người viết code thôi. 😄 Bạn có thể dùng bất kỳ cái nào bạn ưa thích và với mục đích nào đó để đem lại tiện ích nhất định cho code của bạn. 😄

Mình thì hay dùng const để khai báo hàm ở global scope để tránh bị khai báo đè, hoặc ghi đè giá trị nào đó vào ở đoạn code xa xa sau khi đã hơi quên đoạn code trước đó; Và dùng var cho các biến khai báo trong hàm để sử dụng vì mình thường viết các hàm ngắn nên có ít biến và dễ theo dõi nên không ngại bị khai báo lặp; Và vì từ khóa var có tính mô tả tốt hơn let nên người đọc không biết gì về JavaScript thì cũng lơ mơ đoán ra được var nó là variable cũng giống như const thì chắc là constant. 😄 Việc sử dụng var thay vì let cũng là một dạng ràng buộc mình muốn tạo ra để bản thân buộc phải suy nghĩ về việc chia nhỏ tác vụ cần thực hiện thành các hàm nhỏ hơn, như vậy nó giúp mình tư duy tốt hơn khi viết code nữa. 😄

Ok, như vậy là bài viết gặp lại các khái niệm Hàm & Vùng của chúng ta tới đây đã hoàn thành. Trong bài viết tiếp theo, chúng ta sẽ được gặp lại Object & Everything. Hãy nghỉ giải lao một chút trước khi tiếp tục. Hẹn gặp lại bạn trong bài viết tiếp theo.

(Sắp đăng tải) [JavaScript] Bài 12 - Object & Everything

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giới thiệu Typescript - Sự khác nhau giữa Typescript và Javascript

Typescript là gì. TypeScript là một ngôn ngữ giúp cung cấp quy mô lớn hơn so với JavaScript.

0 0 525

- vừa được xem lúc

Bạn đã biết các tips này khi làm việc với chuỗi trong JavaScript chưa ?

Hi xin chào các bạn, tiếp tục chuỗi chủ đề về cái thằng JavaScript này, hôm nay mình sẽ giới thiệu cho các bạn một số thủ thuật hay ho khi làm việc với chuỗi trong JavaScript có thể bạn đã hoặc chưa từng dùng. Cụ thể như nào thì hãy cùng mình tìm hiểu trong bài viết này nhé (go).

0 0 433

- vừa được xem lúc

Một số phương thức với object trong Javascript

Trong Javascript có hỗ trợ các loại dữ liệu cơ bản là giống với hầu hết những ngôn ngữ lập trình khác. Bài viết này mình sẽ giới thiệu về Object và một số phương thức thường dùng với nó.

0 0 153

- vừa được xem lúc

Tìm hiểu về thư viện axios

Giới thiệu. Axios là gì? Axios là một thư viện HTTP Client dựa trên Promise.

0 0 145

- vừa được xem lúc

Imports và Exports trong JavaScript ES6

. Giới thiệu. ES6 cung cấp cho chúng ta import (nhập), export (xuất) các functions, biến từ module này sang module khác và sử dụng nó trong các file khác.

0 0 110

- vừa được xem lúc

Bài toán đọc số thành chữ (phần 2) - Hoàn chỉnh chương trình dưới 100 dòng code

Tiếp tục bài viết còn dang dở ở phần trước Phân tích bài toán đọc số thành chữ (phần 1) - Phân tích đề và những mảnh ghép đầu tiên. Bạn nào chưa đọc thì có thể xem ở link trên trước nhé.

0 0 245