图1.4 函数声明是独立的,是独立的JavaScript代码块(它可以被包含在其他函数中)
清单1.4展示了两条函数声明例子。
清单1.4 函数声明示例
1function samurai() {
2 return "samurai here"; ?--- 在全局代码中定义samurai函数
3}
4function ninja() { ?--- 在全局代码中定义ninja函数
5 function hiddenNinja() {
6 return "ninja here";
7 } ?--- 在ninja函数内定义hiddenNinja函数
8 return hiddenNinja();
9}
如果你对函数式语言没有太多了解,仔细看一看,你可能会发现你并不习惯这种使用方式: 一个函数被定义在另一个函数之中!
1function ninja() {
2 function hiddenNinja() {
3 return "ninja here";
4 }
5 return hiddenNinja();
6}
在JavaScript中,这是一种非常通用的使用方式,这里用它作为例子是为了再次强调JavaScript中函数的重要性。
{注意 }
让函数包含在另一个函数中可能会因为忽略作用域的标识符解析而引发一些有趣的问题,但现在可以先留下这个问题,第5章会重新回顾这个问题的细节。
函数表达式
正如我们多次所提到的,JavaScript中的函数是第一类对象,除此以外也就意味着它们可以通过字面量创建,可以赋值给变量和属性,可以作为传递给其他函数的参数或函数的返回值。正因为函数有如此的基础结构,所以JavaScript能让我们把函数和其他表达式同等看待。例如,如下例子中我们可以使用数字字面量:
1var a = 3;
2myFunction(4);
同样,在相同位置可以用函数字面量:
1var a = function() {};
2myFunction(function(){});
这种总是其他表达式的一部分的函数(作为赋值表达式的右值,或者作为其他函数的参数)叫作函数表达式。函数表达式非常重要,在于它能准确地在我们需要使用的地方定义函数,这个过程能让代码易于理解。清单1.5展示了函数声明和函数表达式的不同之处。
清单1.5 函数声明和函数表达式
1<pre class="代码无行号"><code>function myFunctionDeclaration(){ ?--- 独立的函数声明
2 function innerFunction() {} ?--- 内部函数声明
3}
4var myFunc = function(){}; ?--- 函数表达式作为变量声明赋值语句中的一部分
5myFunc(function(){ ?--- 函数表达式作为一次函数调用中的参数
6 return function(){}; ?--- 函数表达式作为函数返回值
7});
8(function <strong>namedFunctionExpression</strong> () {
9})(); ?--- 作为函数调用的一部分,命名函数表达式会被立即调用
10 function(){}();
11-function(){}();
12!function(){}();
13~function(){}(); ?--- 函数有达式可以作为一元操作符的参数立即调用</code></pre>
示例代码的开头是标准函数声明,其包含一个内部函数声明:
1function myFunctionDeclaration(){
2 function innerFunction() {}
3}
从这个示例中你能够看到,函数声明是如何作为JavaScript代码中的独立表达式的,但它也能够包含在其他函数体内。与之比较的是函数表达式,它通常作为其他语句的一部分。它们被放在表达式级别,作为变量声明(或者赋值)的右值:
1var myFunc = function(){};
或者作为另一个函数调用的参数或返回值。
1myFunc(function() {
2 return function(){};
3});
函数声明和函数表达式除了在代码中的位置不同以外,还有一个更重要的不同点是:对于函数声明来说,函数名是强制性的,而对于函数表达式来说,函数名则完全是可选的。
函数声明必须具有函数名是因为它们是独立语句。一个函数的基本要求是它应该能够被调用,所以它必须具有一种被引用方式,于是唯一的方式就是通过它的名字。
从另一方面来看,函数表达式也是其他JavaScript表达式的一部分,所以我们也就具有了调用它们的替代方案。例如,如果一个函数表达式被赋值给了一个变量,我们可以用该变量来调用函数。
1var doNothing = function(){};
2doNothing();
或者,如果它是另外一个函数的参数,我们可以在该函数中通过相应的参数名来调用它。
1function doSomething(action) {
2 action();
3}
立即函数
函数表达式可以放在初看起来有些奇怪的位置上,例如通常认为是函数标识符的位置。接下来仔细看看这个构造(如图1.5所示)。
图1.5 标准函数的调用和函数表达式的立即调用的对比
当想进行函数调用时,我们需要使用能够求值得到函数的表达式,其后跟着一对函数调用括号,括号内包含参数。在最基本的函数调用中,我们把求值得到函数的标识符作为左值(如图1.5所示)。不过用于被括号调用的表达式不必只是一个简单的标识符,它可以是任何能够求值得到函数的表达式。例如,指定一个求值得到函数的表达式的最简单方式是使用函数表达式。如图1.5中右图所示,我们首先创建了一个函数,然后立即调用这个新创建的函数。这种函数叫作立即调用函数表达式(IIFE),或者简写为立即函数。这一特性能够模拟JavaScript中的模块化,故可以说它是JavaScript开发中的重要理念。第11章中会集中讨论IIFE的应用。
{加括号的函数表达式!}
还有一件可能困扰你的是上面例子中我们立即调用的函数表达式方式:函数表达式被包裹在一对括号内。为什么这样做呢?其原因是纯语法层面的。JavaScript解析器必须能够轻易区分函数声明和函数表达式之间的区别。如果去掉包裹函数表达式的括号,把立即调用作为一个独立语句function() {}(3),JavaScript开始解析时便会结束,因为这个独立语句以function开头,那么解析器就会认为它在处理一个函数声明。每个函数声明必须有一个名字(然而这里并没有指定名字),所以程序执行到这里会报错。为了避免错误,函数表达式要放在括号内,为JavaScript解析器指明它正在处理一个函数表达式而不是语句。
还有一种相对简单的替代方案(function(){}(3))也能达到相同目标(然而这种方案有些奇怪,故不常使用)。把立即函数的定义和调用都放在括号内,同样可以为JavaScript解析器指明它正在处理函数表达式。
表1.5中最后4个表达式都是立即调用函数表达式主题的4个不同版本,在JavaScript库中会经常见到这几种形式:
1 function(){}();
2-function(){}();
3!function(){}();
4~function(){}();
不同于用加括号的方式区分函数表达式和函数声明,这里我们使用一元操作符 、-、!和~。这种做法也是用于向JavaScript引擎指明它处理的是表达式,而不是语句。从计算机的角度来讲,注意应用一元操作符得到的结果没有存储到任何地方并不重要,只有调用IIFE才重要。现在我们已经学会了JavaScript中两种基本的函数定义方式(函数声明和函数表达式)的细节。接下来开始探索JavaScript标准中的新增特性:箭头函数。
1.3.2 箭头函数
注意:
箭头函数是JavaScript标准中的ES6新增项(浏览器兼容性可参考http://mng.bz/8bnH)。
由于JavaScript中会使用大量函数,增加简化创建函数方式的语法十分有意义,也能让我们的开发者生活更愉快。在很多方式中,箭头函数是函数表达式的简化版。一起来回顾一下本文开始的排序例子。
1var values = [0, 3, 2, 5, 7, 4, 8, 1];
2values.sort(function(value1,value2){
3 return value1 – value2;
4});
这个例子中,数组对象的排序方法的参数传入了一个回调函数表达式,JavaScript引擎会调用这个回调函数以降序排序数组。现在来看看如何用箭头函数来做完全相同的工作:
1var values = [0, 3, 2, 5, 7, 4, 8, 1];
2
3values.sort((
4
5value1,value2) => value1 – value2
6
7);
看到这是多么简洁了吧?
这种写法不会产生任何因为书写function关键字、大括号或者return语句导致的混乱。箭头函数语句有着比函数表达式更为简单的方式:函数传入两个参数并返回其差值。注意这个新操作符——胖箭头符号=>(等号后面跟着大于号)是定义箭头函数的核心。
现在来解析箭头函数的语法,首先看看它的最简形式:
1param => expression
这个箭头函数接收一个参数并返回表达式的值,如下面的清单1.6就使用了这种语法。
清单1.6 比较箭头函数和函数表达式
1var greet = name => "Greetings " name; ?--- 定义箭头函数
2
3assert(greet("Oishi") === "Greetings Oishi", "Oishi is properly greeted")
4;
5
6var anotherGreet = function(nam
7e){
8 return "Greetings " n
9ame;
10}; ?--- 定义
11函数表达式
12assert(anotherGreet("Oishi") === "Greetings O
13ishi",
14 "Again, Oishi is properly greeted");
稍作欣赏,使用箭头函数的代码即简洁又清楚。这是箭头函数的最简语法,但一般情况下,箭头函数会被定义成两种方式,如图1.6所示。
稍作欣赏,使用箭头函数的代码即简洁又清楚。这是箭头函数的最简语法,但一般情况下,箭头函数会被定义成两种方式,如图1.6所示。
图1.6 箭头函数的语法
如你所见,箭头函数的定义以一串可选参数名列表开头,参数名以逗号分隔。如果没有参数或者多余一个参数时,参数列表就必须包裹在括号内。但如果只有一个参数时,括号就不是必须的。参数列表之后必须跟着一个胖箭头符号,以此向我们和JavaScript引擎指示当前处理的是箭头函数。
胖箭头操作符后面有两种可选方式。如果要创建一个简单函数,那么可以把表达式放在这里(可以是数学运算、其他的函数调用等),则该函数的返回值即为此表达式的返回值。例如,第一个箭头函数的示例如下:
1var greet = name => "Greetings " name;
这个箭头函数的返回值是字符串“Greetings”和参数name的结合。在其他案例中,当箭头函数没那么简单从而需要更多代码时,箭头操作符后则可以跟一个代码块,例如:
1var greet = name => {
2 var helloString = 'Greetings ';
3 return helloString name;
4};
这段代码中箭头函数的返回值和普通函数一样。如果没有return语句,返回值是undefined;反之,返回值就是return表达式的值。
在本文中我们会多次回顾箭头函数。除此之外,我们还会展示箭头函数的一些额外功能,它能帮助我们规避一些在很多标准函数中可能遇到的难以捉摸的缺陷。箭头函数和很多其他函数一样,可以通过接收参数来执行任务。接下来看看当向函数内传入参数后,该参数值发生了什么。
本文摘自《JavaScript忍者秘籍(第2版)》
《JavaScript忍者秘籍 第2版》
[美] John,Resig(莱西格),Bear,Bibeault(贝比奥特),Josip ... 著
点击封面购买纸书
JavaScript 正以惊人的速度成为各种应用程序的通用语言,包括 Web、桌面、云和移动设备上的应用程序。当成为 JavaScript 专业开发者时,你将拥有可应用于所有这些领域的、强大的技能集。
《JavaScript 忍者秘籍(第2版)》使用实际的案例清晰地诠释每一个核心概念和技术。本书向读者介绍了如何掌握 JavaScript 核心的概念,诸如函数、闭包、对象、原型和 promise,同时还介绍了 JavaScript API, 包括 DOM、事件和计时器。你将学会测试、跨浏览器开发,所有这些都是高级JavaScript开发者应该掌握的技能。
小福利
关注【异步社区】服务号,转发本文至朋友圈或 50 人以上微信群,截图发送至异步社区服务号后台,并在文章底下留言,分享你的JavaScript开发经验或者本书的试读体验,我们将选出3名读者赠送《JavaScript 忍者秘籍(第2版)》1本,赶快积极参与吧!
活动截止时间:2018 年3月15日
在“异步社区”后台回复“关注”,即可免费获得2000门在线视频课程;推荐朋友关注根据提示获取赠书链接,免费得异步图书一本。赶紧来参加哦!
扫一扫上方二维码,回复“关注”参与活动!
阅读原文,购买《JavaScript 忍者秘籍(第2版》
阅读原文