当前位置:资讯中心>技术文章
公司动态 行业新闻 技术文章

用Node.js学JavaScript(三)语言函数、参数与闭包

发布时间:2017-05-26 点击数:3206

在本期当中,重庆逍遥子科技有限公司将介绍 JavaScript 当中最有趣的一个领域,那就是「函数」(function) 的用法,还有「闭包」 (closure) 这种相当特别的概念。

函数的宣告

在 JavaScript 当中,函数的宣告方法大致有两种,第一种的宣告方法就和一般程序语言 (C/C++, Python, Java) 等差不多,是采用 f(a,b,c…) 这种方式宣告的,但是必须在前面加上 function 这个关键词。

在以下范例中, sub(a,b) 就是采用这种方式宣告的一个范例。

档案:function.js

// 第一种写法,直接宣告函数

function sub(a,b) {

return a-b;

}


// 第二种写法,将匿名函数指定给变量。

var add = function(a,b) {

return a+b;

}

console.log("add(3,5)=", add(3,5), " sub(7,2)=", sub(7,2));

执行结果:

D:\js\code>node function.js

add(3,5)= 8  sub(7,2)= 5

但是、在 JavaScript 当中,还有一种比较特别函数宣告方式,是在宣告了一个「匿名函数」之后,再把这个函数「塞给」一个变量。就像上述 `var add = function(a,b) …` 的做法,这样我们就可以用 add(3,5) 这样的方式去呼叫该函数了。

函数型态的参数

在上面的 add 范例中,我们将「函数」塞给一个变量,而且还可以直接把该变量当作函数来呼叫。

那么、我们能不能将函数当作参数来传递呢?

关于这点、当然是可以的,以下是一个将「函数当作参数」的范例。

档案: fptr.js

function sub(a,b) {

return a-b;

}


function f5(f, a) {

return f(a, 5);

}


console.log("sub(8,5)="+sub(8, 5));

console.log("f5(sub,8)="+f5(sub,8));

执行结果

D:\Dropbox\Public\web\js\code>node fptr

sub(8,5)=3

f5(sub,8)=3

您可以看到,函数 `f5(f, a)` 的参数 f,其实又是一个函数,因为我们在 f(a,5) 当中把 f 当作函数来呼叫。

所以、当我们呼叫 f5(sub, 8) 的时候,该函数会传回 3,因为当我们将 f5(sub, 8) 的内容 return f(a, 5) 里面的 f 取代为 sub,而 a 取代为 8 时,就会发现 return 语句的 f(a,5) 其实就是呼叫 sub(8,5),所以当然就会传回 3 啰!

参数的存取

对于一般的函数,参数个数是固定的,例如上述范例的 add(a,b) 与 sub(a,b) ,都很明确的有两个参数,因此直接用 a, b 就可以存取该参数。

但是、对于那种有不确定参数个数的函数,就没有对应名称可以用来存取这些参数了。

还好,javascript 在呼叫每个函数时,都会将参数放到一个称为 arguments 的变量里,arguments 是一个类似数组形态,我们可以透过 arguments 来存取每一个参数,以下是一个范例。

档案:arg.js

function print() {

for (var i in arguments) {

console.log(i, ":", arguments[i]);

}

}


print(3, 2.71828, "hello");

执行结果:

D:\js\code>node arg.js

0 : 3

1 : 2.71828

2 : hello

这种变动参数个数的函数,有时候很有用。例如、若我们要写一个可以找出最小值的函数,就可以用下列的 min() 函数。

档案:min.js

function min() {

var m = arguments[0];

for (var i in arguments) {

if (arguments[i] < m)

m = arguments[i];

}

return m;

}


var x = min(3, 7, 2, 9, 1, 5, 8);

console.log("x=min(3, 7, 2, 9, 1, 5, 8)=", x);

执行结果

D:\Dropbox\Public\web\js\code>node min.js

x=min(3, 7, 2, 9, 1, 5, 8)= 1

变量的领域范围

在上述的 min.js 程序中,您可以看到我们经常会用 var 这个关键词来宣告变量。但事实上,即使我们不用 var 宣告,该程序也能正常运作。以下是一个完全没有 var 宣告的版本。

档案:min2.js

function min() {

m = arguments[0];

for (i in arguments) {

if (arguments[i] < m)

m = arguments[i];

}

return m;

}


x = min(3, 7, 2, 9, 1, 5, 8);

console.log("x=min(3, 7, 2, 9, 1, 5, 8)=", x);

但是、上述这个没有 var 的版本 min2.js ,与那个有 var 的版本其实在某些细微处有所不同,因为采用 var 宣告时,该变量将会是一个局部变量,而没有采用 var 宣告就直接指定的方式,则会是一个「全局」变量,这种全局变量有可能造成更多的冲突问题,所以在一般的情况下,我们都会加上 var 宣告。

关于是否该为变量加上 var 的更详细描述,可以参考下列文章:

JavaScript 语言核心(3)你的变数 var 了吗?

闭包 (Closure)

对于很多 C/C++、Java、C#、VB 等语言的「程序人」而言,「闭包」是个很奇特而难以理解的概念,但对于 JavaScript、Lua、Python、Ruby 等动态语言来说,「闭包」却是个很自然的用法,一点都不神秘。

其实、是「闭包」 (Closure) 这个词给人的感觉太深奥了,我们不需要迷惑于这个名词的神秘感,请让我们先来看一个范例。

档案: closure.js

function sub(a,b) {

return a-b;

}


function sub5(a) {

return sub(a, 5);

}


function fsub5(a) {

return function() {

return sub(a, 5);

};

}


console.log("sub(8,5)="+sub(8, 5));

console.log("sub5(8)="+sub5(8));

console.log("fsub5(8)="+fsub5(8));

console.log("fsub5(sub,8)()="+fsub5(8)());

执行结果:

D:\Dropbox\Public\web\js\code>node closure

sub(8,5)=3

sub5(8)=3

fsub5(8)=function () {

return sub(a, 5);

}

fsub5(sub,8)()=3

在上述范例中,我们看到 sub(a,b) 是个很正常的函数,当我们呼叫 sub(8,5) 时会传回 3。

如果我们运用 sub(a,b) 定义一个传回 sub(a,5) 的函数为 sub5(a),那么 sub5(8) 同样也会传回 3。

上述程是最后的 fsub5 函数,则不像前面的 sub5 一样传回一个值,而是传回一个函数,这个函数的内容如下:

return function() {

return sub(a, 5);

}

这下问题就来了,fsub5 所传回的是一个函数,而这个函数里的 a 到底是什么东西呢?

这时,请让我们把眼光放大一点点:

function fsub5(a) {

return function() {

return sub(a, 5);

};

}

您会发现,原来所传回来的那个函数里的 a ,应该就是 fsub5(a) 的参数 a,这种「把外层变数一起包进来」的机制,就称为「闭包」。

换句话说、只要直接在函数里引用外层的变量,然后当我们将「函数封闭起来传回」时,该函数仍然可以正常使用,这就是闭包的概念了。

结语

在本文中,我们介绍了 JavaScript 中的「函数、参数与闭包」等观念,这些观念在我们进行模块化或撰写大型程序的时候,将会是非常重要的根基。

JavaScript 当中的函数,可以被塞进变量里,然后再将变量当作函数来呼叫。也可以放在参数里,拿来传递给另一个函数使用,这种方式有点像 C 语言当中的函数指针,只是感觉更精简,更有弹性而已。

而那个感觉有点神秘的「闭包」观念,也只不过是「在传回一整个函数时、顺便把外层的变量给包进来而已」,并不真的那么神秘啊!