Function trong javascript

Function trong javascript

Function là 1 khái niệm cơ bản trong javascript, nhưng cũng chứa lắm thứ rối rắm. Bài viết này sẽ tập trung vào những thứ dễ gây nhầm lẫn cần chú ý với function trong javascript và 1 số thủ thuật thú vị với function

1. Các cách định nghĩa function trong javascript

Chúng ta có thể khai báo rõ tên của function hoặc không. Những cách khai báo function dưới đây đều hợp lệ

function test(){ return true; }
var a = function(){ return true; };
window.b = function(){ return true; };
console.log(test, a, b);
=>
function test(){ return true; }
function (){ return true; }
function (){ return true; }

2. Vị trí của định nghĩa function

Đối với function được nêu rõ tên cụ thể, ta có thể được định nghĩa trước hoặc sau lời gọi nó, đối với ví dụ trên thì chúng ta có thể thực hiện lòi gọi console.log trước định nghĩa hàm test và kết quả không thay đổi.

var a = function(){ return true; };
window.b = function(){ return true; };
console.log(test, a, b);
function test(){ return true; }

=>
function test(){ return true; }
function (){ return true; }
function (){ return true; }

Tuy nhiên thứ tự của lời gọi function và định nghĩa function lại ảnh hưởng đến việc xác định kiểu dữ liệu khi gán giá trị 1 biến là 1 function

var a = function(){ return true; };
window.b = function(){ return true; };
console.log("a: " + typeof(a), "b: " + typeof(b));
=>
"a: function"
"b: function"

Nhưng

console.log("a: " + typeof(a), "b: " + typeof(b));
var a = function(){ return true; };
window.b = function(){ return true; };
=>
"a: undefined"
"b: undefined"

Tóm lại là dù định nghĩa trước hay sau thì lời gọi function vẫn được thực hiện mặc dù việc kiểm tra xem 1 biến có phải kiểu function hay không thì lại trả về những kết quả khác biệt =))

Vậy nếu lời gọi function đặt sau lệnh return thì sao ?

Thông thường thì câu lệnh return sẽ chấm dứt chuỗi thực thi đặt sau nó nhưng như đã thấy ở ví dụ trên thì function có thể được định nghĩa sau cả lời gọi nó. Vậy rốt cục thì đặt 1 định nghĩa function sau lệnh return sẽ thế nào ? (mặc dù sẽ gần như chẳng có ai làm thế để mua rắc rối vào người)

function test(){
  console.log(a(), "first call" );
  return a();
  function a(){ return true; };
  console.log(a(), "second call" );
}
test();
=>
true
"first call"

Kiểm tra thử thì mặc dù function a được định nghĩa sau cả lệnh return nhưng chúng ta vẫn có thể sử dụng được, tuy nhiên chỉ lời gọi nằm trước lệnh return là được thực hiện (bằng chứng là chỉ có first call được in ra)

3. Tên của function

Tên của fucntion có thể được sử dụng ngay trong định nghĩa của nó, giúp chúng ta ta có thể thực hiện các function đệ qui. Một ví dụ đơn giản là hàm tính tổng của n số tự nhiên đầu tiên:

function sum(n){
  return n > 0 ? sum(n-1) + n : 0;
}
console.log(sum(4));
=> 10

Vậy với những anonymous function - những function không tên chúng ta thực hiện đệ quy như thế nào. Bằng cách gán function cho 1 thuộc tính của object chúng ta có thể thực hiện hàm tương tự hàm tính tổng ở trên

var test = {
  sum: function(n){
    return n > 0 ? test.sum(n-1) + n : 0;
  }
};
console.log(test.sum(4));
=> 10

Nhưng nếu chúng ta không muốn khai báo thêm 1 object có thuộc tính là function mà chỉ đơn thuần muốn gán 1 hàm đệ qui vào 1 biến cho trước thì sao. Hãy sử dụng arguments.callee bên trong định nghĩa của function để gọi lại chính nó

var sum = function(n){
  return n > 0 ? arguments.callee(n-1) + n : 0;
};
console.log(sum(4));
=> 10

Bên trong định nghĩa của function chúng ta có thể sử dụng tên function như 1 biến có kiểu function nhưng không thể làm điều tương tự khi ở ngoài function khi gán function trực tiếp cho 1 biến như ví dụ đưới đây. Khi đó tên của function sẽ bị coi là 1 biến chưa được định nghĩa.

var fa = function fb(){
  console.log(typeof(fb));
  console.log(fa == fb);
};
fa();
console.log(typeof(fb));
console.log(typeof(fa));
=>
"function"
true
"undefined"
"function"

Chú ý rằng nếu chỉ khai báo function mà không thực hiện gán ngay thì không vấn đề gì

function fb(){
  return true;
};
console.log(fb());
=> true

4. Sử dụng function như 1 object

Trong javascript, function và object có khá nhiều điểm tương đồng. Thực tế thì function cũng là 1 object và có thể chứa thuộc tính (properrty)

var test = function (){
  return true;
};
test.my_prop = "A prop";
console.log(test.my_prop);
=> "A prop"

Do function có thể được dùng như object nên chúng ta có thể lợi dụng để cache giá trị của function khi thực hiện những function đệ qui. Hãy xem xét ví dụ in ra 1 đoạn trong chuỗi fibonaci dưới đây

function fb(i) {
  console.log(i);
  if (i < 3) {
    fb.cache[i] = 1;
    return 1;
  } else {
    if (fb.cache[i-1] && fb.cache[i-2]){
      fb.cache[i] = fb.cache[i-1] + fb.cache[i-2];
    } else {
      fb.cache[i] =  fb(i-1) + fb(i-2);
    }
    return fb.cache[i];
  }
}
fb.cache = {};
for(i=5; i<9; i++){
  console.log("fb(" + i +")= " + fb(i));
}

Nếu không cache lại giá trị của function fb mà chỉ đơn thuần thực hiện phép toán đệ qui như bình thường thì việc hàm fb bị gọi nhiều lần với cùng 1 giá trị tham số sẽ xảy ra và nếu số i đủ lớn thì thời gian tính toán sẽ bị chậm đi. Bẵng việc cache lại giá trị thì việc tính toán sẽ nhanh hơn.

5
4
3
2
1
2
3
"fb(5)= 5"
6
"fb(6)= 8"
7
"fb(7)= 13"
8
"fb(8)= 21"

Có thể thấy khi tính toán giá trị fb(5) thì do chưa có các giá trị cache việc lặp lại tính toán các giá trị fb(1), fb(2), fb(3) xảy ra (nhìn vào dãy giá trị của tham số i được log ra). Còn với các giá trị fb(6), fb(7), fb(8) do lơi dụng giá trị được cache trước đó nên thực tế việc tính toán chỉ đơn thuần là cộng 2 giá trị fibonaci trước nó.

To be continued