0

Bắt Đầu với Javascript (P6)

Functions là một trong những khối xây dựng cơ bản trong JavaScript. Một function là một thủ tục JavaScript-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ị. Để sử dụng một function, bạn phải định nghĩa nó ở đâu đó trong phạm vi mà bạn muốn gọi nó. Trong phần này sẽ tìm hiểu các kiến thức về function trong javascript.

1. Định nghĩa function - hàm

Function declarations

Một định nghĩa hàm (hay còn gọi là khai báo hàm, hoặc câu lệnh hàm) bao gồm các từ khóa function, tiếp theo là:

  • Tên của hàm.
  • Một danh sách các tham số cho hàm, được bao gồm trong ngoặc đơn và được phân tách bằng dấu phẩy.
  • Các lệnh JavaScript định nghĩa hàm, được bao gồm trong ngoặc nhọn, {}.

Ví dụ, đoạn mã sau định nghĩa một hàm đơn giản có tên là square:

function square(number) {
  return number * number;
}

function square có một tham số là number. Thân hàm chứa 1 câu lệnh gọi return - trả lại tham số của hàm nhân với chính nó (number * number). Lệnh trả về chỉ định giá trị trả về của hàm.

return number * number;

Các tham số nguyên thủy (như number) được truyền đến các hàm theo giá trị; giá trị được truyền đến hàm, nhưng nếu hàm thay đổi giá trị của tham số, sự thay đổi này sẽ không được phản ánh trên toàn cầu hoặc trong hàm gọi.

Nếu bạn truyền một đối tượng (tức là giá trị không phải là nguyên thủy, chẳng hạn như Array hoặc một đối tượng người dùng xác định) như là một tham số và chức năng sẽ thay đổi thuộc tính của đối tượng, thay đổi đó sẽ hiển thị bên ngoài hàm, như thể hiện trong ví dụ sau:

function myFunc(theObject) {
  theObject.make = 'Toyota';
}

var mycar = {make: 'Honda', model: 'Accord', year: 1998};
var x, y;

x = mycar.make; // x gets the value "Honda"

myFunc(mycar);
y = mycar.make; // y gets the value "Toyota"
                             // (the make property was changed by the function)

Function expressions

Trong khi khai báo function ở trên là cú pháp một tuyên bố, các hàm cũng có thể được tạo ra bởi một biểu thức hàm. Hàm như vậy có thể được gọi là ẩn danh; nó không cần phải có tên. Ví dụ, function square có thể đã được định nghĩa là:

var square = function(number) { return number * number; };
var x = square(4); // x gets the value 16

Tuy nhiên, một tên có thể được cung cấp với một biểu thức function và có thể được sử dụng bên trong các function để tham khảo chính nó, hoặc trong một trình sửa lỗi để xác định các function trong stack:

var factorial = function fac(n) { return n < 2 ? 1 : n * fac(n - 1); };

console.log(factorial(3));

Các biểu thức function rất thuận tiện khi truyền một hàm như một đối số cho một hàm khác. Ví dụ sau cho thấy một hàm maps được định nghĩa và sau đó được gọi với một hàm biểu thức như là tham số đầu tiên của nó:

function map(f, a) {
  var result = [], // Create a new Array
      i;
  for (i = 0; i != a.length; i++)
    result[i] = f(a[i]);
  return result;
}

Hãy xem đoạn code sau:

var numbers = [0,1, 2, 5,10];
var cube= numbers.map(function(x) {
   return x * x * x;
});
console.log(cube);

Kết quả trả về là [0, 1, 8, 125, 1000].

Trong JavaScript, một function có thể được xác định dựa trên một điều kiện. Ví dụ, định nghĩa function sau định nghĩa myFunc chỉ khi num bằng 0:

var myFunc;
if (num === 0) {
  myFunc = function(theObject) {
    theObject.make = 'Toyota';
  }
}

Ngoài việc xác định các function như mô tả ở đây, bạn cũng có thể sử dụng hàm constructor để tạo các hàm từ một chuỗi trong thời gian chạy, giống như eval().

Một method là một hàm có thuộc tính của một đối tượng. Đọc thêm về các đối tượng và method trong Working with objects.

2. Calling functions

Định nghĩa một hàm không thực hiện nó. Định nghĩa hàm chỉ đơn giản đặt tên hàm và chỉ định phải làm gì khi hàm được gọi. Khi gọi hàm mới thực sự thực hiện các hành động được chỉ định với các tham số chỉ định. Ví dụ, nếu bạn xác định hàm square, bạn có thể gọi nó như sau:

square(5);

Lệnh trước gọi hàm với một đối số là 5. Hàm thực thi câu lệnh của nó và trả về giá trị 25.

Các hàm phải ở trong phạm vi khi chúng được gọi, nhưng khai báo function có thể được hoisted (tức là khai báo dưới lời gọi trong mã), như trong ví dụ này:

console.log(square(5));
/* ... */
function square(n) { return n * n; }

Phạm vi của một function là function mà nó được khai báo, hoặc toàn bộ chương trình nếu nó được khai báo ở cấp cao nhất.

Chú ý: Chỉ hoạt động khi xác định hàm bằng cách sử dụng cú pháp ở trên (tức là hàm funcName () {}). Mã dưới đây sẽ không hoạt động. Điều đó có nghĩa, function hoisting chỉ làm việc với khai báo function và không phải với biểu thức function.

console.log(square); // square is hoisted with an initial value undefined.
console.log(square(5)); // TypeError: square is not a function
var square = function(n) { 
  return n * n; 
}

Đối số của một hàm không giới hạn ở chuỗi và số. Bạn có thể truyền toàn bộ đối tượng đến một hàm. Hàm show_props() là một ví dụ về một hàm lấy đối tượng làm đối số.

Một hàm có thể gọi chính nó. Ví dụ, đây là một hàm tính toán các giai thừa đệ quy:

function factorial(n) {
  if ((n === 0) || (n === 1))
    return 1;
  else
    return (n * factorial(n - 1));
}

Sau đó bạn có thể tính toán factorials từ 1 đến 5 như sau:

var a, b, c, d, e;
a = factorial(1); // a gets the value 1
b = factorial(2); // b gets the value 2
c = factorial(3); // c gets the value 6
d = factorial(4); // d gets the value 24
e = factorial(5); // e gets the value 120

Có nhiều cách khác để gọi function. Thường có những trường hợp một chức năng cần phải được gọi tự động, hoặc số lượng đối số cho hàm khác nhau, hoặc trong đó bối cảnh của cuộc gọi chức năng cần phải được thiết lập để một đối tượng cụ thể được xác định tại thời gian chạy. Nó chỉ ra rằng các hàm là, bản thân, các đối tượng, và các đối tượng này lần lượt có các phương thức. Một trong số này, phương pháp apply(), có thể được sử dụng để đạt được mục tiêu này.

3. Function scope

Các biến được xác định bên trong một hàm không thể truy cập từ bất cứ nơi nào 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 vào tất cả các biến và các 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 định nghĩa trong phạm vi toàn cầu có thể truy cập vào tất cả các biến được định nghĩa trong phạm vi toàn cầu. Một hàm được xác định 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 định nghĩa trong hàm cha và bất kỳ biến nào khác mà hàm cha mẹ truy cập.

// The following variables are defined in the global scope
var num1 = 20,
    num2 = 3,
    name = 'Chamahk';

// This function is defined in the global scope
function multiply() {
  return num1 * num2;
}

multiply(); // Returns 60

// A nested function example
function getScore() {
  var num1 = 2,
      num2 = 3;
  
  function add() {
    return name + ' scored ' + (num1 + num2);
  }
  
  return add();
}

getScore(); // Returns "Chamahk scored 5"

4. Scope and the function stack

Recursion

Một hàm có thể tham khảo và gọi chính nó. Có ba cách để một hàm đề cập đến chính nó:

  • tên của chức năng
  • arguments.callee
  • một trong phạm vi biến mà đề cập đến các chức năng

Ví dụ, xem xét các định nghĩa hàm sau đây:

var foo = function bar() {
   // statements go here
};

Trong nội dung function, sau đây là tương đương:

  • bar()
  • arguments.callee()
  • foo()

Một hàm gọi chính nó được gọi là hàm đệ quy. Trong một số cách, đệ quy tương tự như một vòng lặp. Cả hai đều thực hiện 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 một vòng lặp vô hạn, hoặc đúng hơn, đệ quy vô hạn trong trường hợp này). Ví dụ, vòng lặp sau đây:

var x = 0;
while (x < 10) { // "x < 10" is the loop condition
   // do stuff
   x++;
}

Có thể được chuyển đổi thành một hàm đệ quy và gọi hàm đó:

function loop(x) {
  if (x >= 10) // "x >= 10" is the exit condition (equivalent to "!(x < 10)")
    return;
  // do stuff
  loop(x + 1); // the recursive call
}
loop(0);

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ụ: nhận được tất cả các nút của cấu trúc cây (ví dụ DOM) được thực hiện dễ dàng hơn bằng cách sử dụng đệ quy:

function walkTree(node) {
  if (node == null) // 
    return;
  // do something with node
  for (var i = 0; i < node.childNodes.length; i++) {
    walkTree(node.childNodes[i]);
  }
}

So với các vòng lặp hàm, mỗi cuộc gọi đệ quy tự làm cho nhiều cuộc gọi đệ quy ở đây.

Có thể chuyển đổi bất kỳ thuật toán đệ quy sang 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. Trong thực tế, đệ quy chính nó sử dụng một stack: ngăn xếp chức năng.

Các hành vi giống như ngăn xếp có thể được nhìn thấy trong ví dụ sau:

function foo(i) {
  if (i < 0)
    return;
  console.log('begin: ' + i);
  foo(i - 1);
  console.log('end: ' + i);
}
foo(3);

// Output:

// begin: 3
// begin: 2
// begin: 1
// begin: 0
// end: 0
// end: 1
// end: 2
// end: 3

5. Nested functions and closures

Bạn có thể tạo một hàm trong một hàm. Hàm lồng nhau (bên trong) là riêng tư cho hàm chứa (ngoài) của nó. Nó cũng tạo thành một sự closure. closure là một biểu thức (thường 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 đó ("closes" biểu thức đó).

Vì một hàm lồng nhau là một closure, đ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à các biến của hàm chứa nó. Nói cách khác, hàm bên trong chứa phạm vi của hàm bên ngoài.

Tóm tắt:

  • Hàm bên trong có thể được truy cập chỉ từ các câu lệnh ở hàm bên ngoài.
  • Hàm bên trong tạo thành một closure: hàm bên trong có thể sử dụng các đối số và các biến của hàm bên ngoài, còn hàm ngoài không thể sử dụng các đối số và các biến của hàm bên trong.

Ví dụ sau cho thấy các hàm lồng nhau:

function addSquares(a, b) {
  function square(x) {
    return x * x;
  }
  return square(a) + square(b);
}
a = addSquares(2, 3); // returns 13
b = addSquares(3, 4); // returns 25
c = addSquares(4, 5); // returns 41

Kể từ khi hàm bên trong tạo thành một closure, bạn có thể gọi hàm ngoài và chỉ định đối số cho cả hàm bên ngoài và bên trong:

function outside(x) {
  function inside(y) {
    return x + y;
  }
  return inside;
}
fn_inside = outside(3); // Think of it like: give me a function that adds 3 to whatever you give it
result = fn_inside(5); // returns 8

result1 = outside(3)(5); // returns 8

Name conflicts

Khi hai đối số hoặc biến trong phạm vi của một closure có cùng một tên, có một xung đột tên. Các phạm vi bên trong hơn được ưu tiên, do đó, phạm vi bên trong nhất chiếm ưu tiên cao nhất, trong khi phạm vi bên ngoài nhiều nhất có mức thấp nhất. Đây là chuỗi phạm vi. Đầu tiên trong chuỗi là phạm vi bên trong nhất, và cuối cùng là phạm vi bên ngoài nhiều nhất. Xem xét những đoạn code sau đây:

function outside() {
  var x = 5;
  function inside(x) {
    return x * 2;
  }
  return inside;
}

outside()(10); // returns 20 instead of 10

Xung đột tên xảy ra ở câu lệnh return x và nằm giữa tham số x của inside và biến x của outside. Chuỗi phạm vi ở đây là {inside, outside, global object}. Do đó, x trong của inside có quyền ưu tiên so với x outside, và 20 (x của intside) được trả về thay vì 10 (x của outside).

Kết luận:

Trên đây là một số kiến thức cơ bản về function của javaScript. Cảm ơn các bạn đã theo dõi bài viết. Mong rằng nó có ích cho bạn!

Tham khảo: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí