一 什么是函数
1. 定义函数
函数:将一组可重用的代码组织在一起,使用给定的名字来标识。
函数声明:
function sum(a, b){
var c = a + b;
return c;
}
函数有一下几部分组成:
1.function关键字
2.函数名,如sum
3.函数的参数,如a和b。一个函数可以接受任意个数的参数或者0个参数,参数之间使用逗号分隔。
4.代码块即函数体
5.return语句。一个函数总是返回一个值。如果函数没有显式的返回值,则它隐式的返回undefined值。
注意,一个函数只能返回一个值。如果你需要返回多个值,可以将这些值放进一个数组中,并将数组返回。
2. 调用函数
调用函数的形式:
函数名([参数列表]);
如:
var result = sum(1, 2);
console.log(result);
输出:
3
3. 参数
当定义一个函数时,你可以指定函数调用时函数所期待接收的参数。一个函数可能不需要任何参数,但如果它有参数,在调用时你却忘了传递参数值,JS将会把undefined值赋给你忽略的参数。
例如:
console.log(sum(1));
输出:
NaN
因为1+undefined的结果为NaN。
从技术上讲,虽然parameters(形参)和arguments(实参)通常是被交换使用的,但是两者是有区别的。parameters(形参)是和函数一起被定义的,而arguments(实参)是函数被调用时传递给函数的。
var result = sum(1, 2);
console.log(result);
这里,a和b是parameters(形参),1和2是arguments(实参)。
对于那些已经传递进来的arguments(实参),JS是不会挑剔的。如果你传递的arguments(实参)超过了函数所期待的parameters(形参),多余的部分会被忽略。
例如:
console.log(sum(1, 2, 3, 4, 5));
输出:
3
而且,你可以创建一些接受参数个数更为灵活的函数。这得益于特殊值arguments,它在每个函数内部自动被创建。
function args() {
return arguments;
}
console.log(args());
console.log(args(1, 2, 3, true, "huhu"));
输出:
{}
{ ‘0’: 1, ‘1’: 2, ‘2’: 3, ‘3’: true, ‘4’: ‘huhu’ }
使用arguments,来完善sum()函数的功能,对任意数量的参数执行求和运算:
function sumOnSteroids() {
var result = 0 ;
var len = arguments.length;
for (var i = 0; i < len; i++){
result += arguments[i];
}
return result;
}
console.log(sumOnSteroids(1, 1, 1));
console.log(sumOnSteroids(1, 2, 3, 3, 2, 1));
console.log(sumOnSteroids(5));
console.log(sumOnSteroids(0));
输出:
3
12
5
0
表达式arguments.length返回的是函数被调用时所接收的参数个数。
arguments不是数组(虽然它看起来确实很像),而是一个类数组的对象。
function testArguments(one, two) {
console.log(Array.isArray(arguments));
}
testArguments(1, "2"); // 返回false
ES6围绕函数参数引人了许多重要的改进。ES6中函数的参数可以有默认值,剩余参数(可变参数),并且允许解构。
4. 默认值 Default parameters
函数的参数(形参)能被赋予默认值。
当调用函数时,如果某个参数被遗漏,默认值被用于赋值给参数(形参)。
function render(fog_level = 0, spark_level = 100, rain_level) {
console.log(`Fog level: ${fog_level} , spark level: ${spark_level} and rain_level: ${rain_level}`);
}
render(10);
render(undefined, 10);
输出:
Fog level: 10 , spark level: 100 and rain_level: undefined
Fog level: 0 , spark level: 10 and rain_level: undefined
注意:undeifned是一个缺省参数值
当为参数提供默认值时,默认值可以引用其他参数:
function t(fog_level = 1, spark_level = fog_level) {
console.log(`Fog level: ${fog_level} and spark level: ${spark_level}`);
}
function s(fog_level = 10, spark_level = fog_level * 10) {
console.log(`Fog level: ${fog_level} and spark level: ${spark_level}`);
}
t(10);
s(10);
输出:
Fog level: 10 and spark level: 10
Fog level: 10 and spark level: 100
默认参数有自己的作用域;这个作用域处于外层函数作用域和内嵌函数作用域之间。如果参数被内嵌作用域的变量隐藏,令人惊讶的是,内嵌作用域的变量是不可用的。如:
var scope = "outer_scope";
function scoper(val = scope) {
var scope = "inner_scope";
console.log(val);
}
scoper();
输出:
outer_scope
你可能期望val被内嵌作用域变量的定义所隐藏,但是因为默认参数有自己的作用域,默认值被赋予给val是不会被内嵌作用域所影响。
5. 剩余参数 Rest parameters
ES6引人了剩余参数(rest parameters)。
剩余参数允许我们以一个数组的形式发送任意数量的参数给一个函数。
剩余参数只能在参数列表的最后一个,且只能有一个剩余参数。
把剩余操作符(…)放在最后一个形参之前就表明这个参数是一个剩余参数。
例如:
function sayThings(tone, ...quotes){
console.log(Array.isArray(arguments))
console.log(Array.isArray(quotes));
console.log(`${tone}`);
console.log(`${quotes}`);
}
sayThings("张三", "李四", "王五", "刘六", "陈七");
输出:
false
true
张三
李四,王五,刘六,陈七
可变参数(var-args)是许多其他语言和ES6的一部分。
剩余参数可以取代稍有争议的arguments变量。
剩余参数和arguments变量 之间最主要的区别是剩余参数是真正的数组。
所有的数组方法对于剩余参数都是可用的。
6. 展开操作符 Spread operators
展开操作符和剩余操作符看起来完全一样,但是执行的是完全相反的功能(即展开操作符是剩余操作符的逆向运算)。
当在调用一个函数要提供实参时或者在定义一个数组时,展开操作符被使用。
展开操作符接收一个数组并将数组元素分割成单独的变量。
例如:当调用函数时接收一个数组作为实参,展开操作符可以提供更简洁的语法:
function sumAll(a, b, c) {
return a + b + c;
}
var numbers = [6, 7, 8];
// ES5 传递数组作为函数的实参
console.log(sumAll.apply(null, numbers));
// ES6 展开操作符
console.log(sumAll(...numbers));
在ES5中,当一个数组作为实参传递给一个函数时,通常使用apply()函数。前面的例子中,我们有一个数组需要传递给一个函数,而该函数接收的是三个变量。
ES5中传递一个数组给函数使用apply()函数,它的第二个参数允许一个数组被传递给被调用的函数。
ES6的展开操作符提供了一种更加清晰和确切的方式处理这个情况。当调用sumAll()的时候,使用展开操作符(…)并传递numbers数组给函数调用。随后这个数组被分割城单独的变量–a, b和c。
在JS中,展开操作符改进了数组的能力。如果你想去创建一个由其他数组组成的数组,已存在的数组语法是不支持的。你不得不使用push,splice和concat去完成这个任务。然而使用展开操作符,这将变得轻而易举:
// ES5
var a = [3, 4];
var b = [6, 7];
var c = [];
c.splice(0, 0, 1, 2);
c = c.concat(a);
c.push(5);
c = c.concat(b);
console.log(c);
// ES6
var c2 = [1, 2, ...a, 5, ...b];
console.log(c);
输出:
[ 1, 2, 3, 4, 5, 6, 7 ]
[ 1, 2, 3, 4, 5, 6, 7 ]
以上的例子中,我们使用两个数组a和b以及展开操作符构建了一个数组c。
二 预定义函数
在Javascript引擎中,有很多内嵌的函数对你是可用的。
注意:
通常,当你调用函数时,你的程序不需要知道函数内部是如何工作的。你可以认为函数是一个黑盒,给它一些值(作为输入参数),然后接收它返回的输出结果。这种思维适用于任何函数,既包括Javascript引擎的内嵌函数,也包括由任何个人或集体所创建的函数。
1. parseInt()
parseInt()能接收任意类型的输出(通常是一个字符串),并尝试将其转换为整型。如果转化失败,返回NaN。
>parseInt('123')
123
>parseInt('abc123')
NaN
>parseInt('1abc23')
1
>parseInt('123abc')
123
该函数接收一个可选的第二个参数:radix(基数),它告诉函数所期待的数据类型–十进制、十六进制、八进制等。
如果尝试以十进制输出字符串FF,结果会是NaN,而改为十六进制,会得到255。
>parseInt('FF', 10)
NaN
>parseInt('FF', 16)
255
将一个字符串转换为十进制和八进制:
>parseInt('0377', 10)
377
>parseInt('0377', 8)
255
如果在调用parseInt()时没有指定第二个参数,函数就会将其默认为十进制,但有两种情况例外:
1. 如果给首参数传递的字符串是以0x开头,第二个参数就会被默认为十六进制
2. 如果给首参数传递的字符串是以0开头,第二个参数就会被默认为八进制
>parseInt('377')
377
>parseInt('0377')
377
>parseInt('0x377')
887
上面的第二点在ES5之后不成立,为了避免错误,建议使用该函数提供的第二个参数radix,显式指定转换时使用的基数。
2. parseFloat()
将输入值转换为浮点型。该函数只有一个参数,且始终会忽略前导0。
该函数识别所有的浮点数值格式,包括十进制整数格式。
如果字符串包含一个可以解析为整数的数(没有小数点或者小数点后都是0),该函数会返回整数。
>parseFloat('1.23')
1.23
>parseFloat('1.23.45')
1.23
>parseFloat('1.23abc')
1.23
>parseFloat('1a23.45')
1
>parseFloat('a123.45')
NaN
>parseFloat('123')
123
>parseFloat('123blue')
123
>parseFloat('123.00')
123
>parseFloat('0xA')
0
>parseFloat('0377')
377
>parseFloat('3.12e7')
31200000
3. isNaN()
检测一个输入值是否是一个合法的能安全参与算术运算的数字。
该函数也可以用来检测parseInt(),parseFloat()或任意算术运算是否成功。
该函数视图将其接收的输入转换为数字。
NaN与任何值不等,包括NaN本身。
>isNaN(NaN)
true
>isNaN(1.23)
false
>isNaN(true)
false
>isNaN('a1.23')
true
>isNaN(parseInt('a123'))
true
>isNaN('1.23')
false
>isNaN('a1.23')
true
>NaN == NaN
false
4. isFinite()
检查输入是否是一个既不是infinity也不是NaN的数字。
>isFinite(Infinity)
false
>isFinite(-Infinity)
false
>isFinite(12)
true
>isFinite(1e308)
true
>isFinite(1e309)
false
JS中最大数字是1.7976931348623157e+308,所以1e309是infinity。
5. URI的编码与反编码
在Uniform Resource Locator (URL,统一资源定位符)和Uniform Resource Identifier (URI,统一资源标记符)中,有一些字符是具有特殊意义的。
如果想转义这些字符,可以使用encodeURI()或encodeURIComponent()。
第一个函数将字符串作为URI进行编码,不会对具有特殊意义的ASCII标点符号(如分隔URI组件的符号:;/?:@&=+$,#)进行转义。
第二个函数将字符串作为URI组件进行编码,会对具有特殊意义的ASCII标点符号(如分隔URI组件的符号:;/?:@&=+$,#)进行转义,该函数假定其参数是URI的一部分,如协议、主机名、路径或查询字符串。
>var url = 'http://www.packtpub.com/script.php?q=this and that';
undefined
>encodeURI(url)
"http://www.packtpub.com/script.php?q=this%20and%20that"
>encodeURIComponent(url)
"http%3A%2F%2Fwww.packtpub.com%2Fscript.php%3Fq%3Dthis%20and%20that"
encodeURI()和encodeURLComponent()分别对应的反转函数:decodeURI()和decodeURIComponent()。
有时,在旧的代码中,你可能看到escape()和unescape()用于编码和解码URL,但是这些方法已经被废弃了。
6. eval()
该函数接收一个字符串输入,然后将其作为Javascript代码执行。
>eval('var ii = 2;');
undefined
>ii
2
所以,这里eval(‘var ii = 2;’)和表达式var ii = 2的执行结果是相同的。
有时eval()函数是很有用的,但是如果有其他选择的话应该避免使用它。
大多数情况下,要选择更优雅且易于编写和维护的方案。
你经常能从经验丰富的JavaScript程序员那里听说这句话:“Eval is evil(Eval是魔鬼)”。
eval()有如下缺点:
1. 安全性:Javascript是很强大的,这也意味着它可以造成损坏。如果你不信任传入eval()的代码,就不要使用它。
2. 性能:执行动态代码要比直接执行脚本中的代码要慢。
7. alert()与console.log()
这两个方法均不是ECMA标准的一部分,是由宿主环境提供的。
alert():
- 显示一个文本消息对话框
- 会阻塞浏览器线程,即在alert窗口关闭前,其他代码都不会执行。
- 只能输出string,如果输出的是对象,会自动调用该对象的toString()
- 不支持多个参数的写法,如果写了多个,只输出第一个
console.log():
- 在控制台输出
- 可以打印任何类型的数据
- 支持多个参数的写法
三 变量的作用域
在JavaScript中变量不是定义在块级作用域(block scope),而是定义在函数作用域(function scope)。这意味着如果一个变量定义在一个函数内,那么它在函数外是不可见的。然而,如果一个变量定义在一个if或者for代码块中,那么它在代码块外是可见的。
全局变量:定义任意函数之外(在全局程序代码中)
局部变量:定义在一个函数中
一个函数内的代码可以访问所有的全局变量,也可以访问自身的局部变量。
例如:
函数f()可以访问global变量
在函数f()之外,local变量不存在
>var global = 1;
function f() {
var local = 2;
global++;
return global;
}
>f();
2
>f();
3
>local;
VM8409:1 Uncaught ReferenceError: local is not defined
at <anonymous>:1:1
重点注意:声明一个变量不使用var,该变量会被自动分配到全局作用域。
>function f(){local = 2};
undefined
>local;
VM8472:1 Uncaught ReferenceError: local is not defined
at <anonymous>:1:1
(anonymous) @ VM8472:1
>f();
undefined
>local;
2
在函数f()中定义了一个local变量。在调用该函数之前,local变量是不存在的。当首次调用该函数时,local变量在全局作用域中被创建。然后,如果你在函数之外访问local变量,那么它将是可用的。
最佳实践:
为了避免命名冲突,尽量减少全局变量的数量。
总是使用var语句来声明变量。建议将函数中所有的变量定义在函数的最顶端。
1. 变量提升 Variable hoisting
一个有趣的例子(显示了局部作用域和全局作用域的一个重要方面):
>var a = 123;
undefined
>function f(){
console.log(a);
var a = 1;
console.log(a);
}
undefined
>f();
VM9042:2 undefined
VM9042:4 1
undefined
你可能期望第一个log()函数打印123(全局变量a的值)和第二个log()函数打印1(局部变量a的值)。但是,事实并非如此。第一个log()函数打印undefined。因为,在函数内部,局部作用域比全局作用域更重要。所以,一个局部变量会覆盖任何同名的全局变量。在首次调用log()的时候,变量a还有没有定义(因此是undefined值),但由于被称为提升(hoisting)的特殊行为,变量a仍然存在于局部空间中。
当你的Javascript程序执行进入一个新的函数,在函数任何地方所有变量的声明会被移动,被升高或被提升到函数的顶部。这是一个重要的概念要牢记于心。而且,只有声明被提升,意味着只有变量的存在(the presence of the variable,此处感觉翻译别扭)被移动到函数顶部。任何赋值语句都保留在原来的地方。
在前面的例子中,局部变量a的声明被提升到函数顶部。只有声明被提升了,而没有提示赋值为1的语句。如下:
>var a = 123;
undefined
>function f(){
var a; //和 var a = undefined;一样
console.log(a); // undefined
a = 1;
console.log(a); // 1
}
undefined
f();
VM275:3 undefined
VM275:5 1
undefined
2. 块级作用域
声明变量时,ES6提供了额外的作用域。如果你在ES6下编写代码,块级作用域几乎取代了必须使用var的变量声明。但是,如果你仍然在使用ES5,我们希望你确保你仔细观察提升行为。
ES6引人了let和const关键字允许我们去声明变量。
使用let声明的变量是块级作用域。它们只存在于当前块。
使用var声明的变量是函数作用域。
例子:
var a = 1;
{
let a = 2;
console.log(a); // 2
}
console.log(a); // 1
在“{”和“}”之间的作用域是一个块。
如果你有Java或者C/C++的经验,块级作用域的概念对你来说将是熟悉的。在这些语言中,程序员为了定义一个作用域而引入块。然而,在Javascript中按需惯性地引入了块,由于块没一个有作用域与之关联。然而,ES6允许你使用let关键字去创建块级作用域变量。当你声明块级作用域变量时,一般推荐在块的顶部添加let声明。
例子:
>function swap(a, b) { // 函数作用域开始
if (a > 0 && b > 0){ // 块级作用域开始
let tmp = a;
a = b;
b =tmp;
} // 块级作用域结束
console.log(a, b);
console.log(tmp); // tmp没有定义,因为它只在块级作用域可用
return [a, b];
} // 函数作用域结束
>swap(1, 2);
VM13596:7 2 1
VM13596:8 Uncaught ReferenceError: tmp is not defined
实际上,你应该最大限度地使用块级作用域变量。除非有些你尝试去做的事情非常明确:这些事情对你来说做到它必须使用var声明,否则应确保你优先使用块级作用域变量。然而,不正确的使用let关键字会引起几个问题。第一个,你不能在相同的函数或块级作用域中,使用let关键字重复声明相同的变量:
>function blocker(x){
if(x){
let f;
let f; // 重复声明f
}
}
VM223:4 Uncaught SyntaxError: Identifier 'f' has already been declared
在ES6中,通过let关键字声明的变量被提升到块级作用域。但是在声明变量之前引用(使用)变量是错误的。
在ES6中引入的另一个关键字是const。使用const关键字声明的一个变量会对一个值创建一个只读引用。这并不意味着被引用持有的值是不可变的。但是,变量标识符不能被重新赋值。
常量(使用const声明的变量)是块级作用域,就像使用let关键字创建的变量。
此外,你在声明变量的同时必须给它赋予一个值。
虽然听起来很像,但是const与不变值(immutable value)无关。常量创建了不可变的绑定(即变量中保存的值或地址是不能被改变的)。这是一个重要的区别,需要去正确的理解。
例子:
>const car = {};
undefined
>car.tyres = 5;
>car
Object {tyres: 5}
>car = {name: "奔驰"};
VM950:1 Uncaught TypeError: Assignment to constant variable.
这是一段有效的代码;这里将值{}赋给了一个常量car。一旦赋值,这个引用不能被改变。
在ES6中,你应该执行一下操作:
1.尽可能使用const。对于所有值不能改变的变量使用const。
2.使用let,避免var。
四 函数也是数据
在JavaScript中的函数实际上是数据。这个概念对于我们以后的学习至关重要。这意味着你可以创建一个函数并将其赋值给一个变量,如:
var f = function () {
return 1;
};
这种定义函数的方式有时称为函数字面量标记法( function literal notation)。
function () {return 1;} 部分是一个函数表达式(function expression)。一个函数表达式可以有一个名字,在这种情况下它就变成了一个命名的函数表达式(named function expression,NFE)。所以,虽然这在实践中很少见,但这也是允许的:
var f = function myFunc() {
return 1;
};
虽然命名函数表达式和函数声明看起来没有区别。但事实上它们是不同的。区分两者的唯一方法是查看它们使用的上下文。函数声明只可能出现在一个程序代码中(在其他函数体中或者在主程序中)。
当你在一个包保存函数值的变量上使用typeof操作符时,它将字符串”function”:
>function define() {
return 1;
}
undefined
>var express = function () {
return 1;
}
undefined
>typeof define;
"function"
>typeof express;
"function"
所以,JavaScript中函数是一种数据,不过是一种具有如下两种重要特性的特殊数据类型:
1.它们包含代码
2.他们是可执行的(它们可以被调用)
一个函数的执行方式是在函数名之后加一对括号。
例子:
>var sum = function (a, b) {
return a + b;
};
undefined
>var add = sum;
undefined
>typeof add;
"function"
>add(1, 2);
3
由于函数是赋值给变量的数据,所以函数的命名规则于一般的变量相同,即函数名不能以数字开头,它可以包含字母、数字、下划线和美元符号的任意组合。
1. 匿名函数 Anonymous functions
正如你现在所知道的,有了一个函数表达式语法,你可以以如下方式定义的函数:
var f = function (a) {
return a;
};
通常这也被称为匿名函数(因为函数没有名字),尤其是当这样的函数表达式被使用,甚至不将其赋值给一个变量时。既然如此,对于这样的匿名函数就有了两种优雅的用法:
1.你可以将匿名函数作为参数传递给其他函数。接收函数可以利用你传递的匿名函数来完成有意义的事情。
2.你可以定义一个匿名函数并立即执行它。
下面具体看看匿名函数的两个应用(即回调函数和即时函数)。
2. 回调函数 Callback functions
由于函数可以像任何其他数据一样赋值给一个变量,所以它可以被定义,被复制,也能被传递给其他函数作为实参。
例1:使用函数声明定义的函数作为函数的参数
function invokeAdd(a, b) {
return a() + b();
}
function one() {
return 1;
}
function two() {
return 2;
}
console.log(invokeAdd(one, two));
输出:
3
例2:使用匿名函数(即函数表达式)作为函数的参数
console.log(invokeAdd(
function () { return 1;},
function () { return 2;}
)
);
输出:
3
当你将一个函数A传递给另一个函数B,然后在B中执行A,通常说A是一个回调函数(callback function)。如果A没有名字,那么A就是一个匿名回调函数。
回调函数什么时候有用呢?让我们来看一些展示回调函数好处的例子,即:
1.在不命名的情况下传递函数,这意味着可以节省全局变量
2.将调用一个函数的职责委托给另一个函数,这意味着可以少写代码
3.通过延时执行或者分块调用有助于性能提升
回调示例 Callback examples
看一个常见的场景:你需要将一个函数的返回值一个传递给另一个函数。
例如,定义两个函数:multiplyByTwo(),接收3个参数,遍历它们,将它们分别乘以2,然后返回一个包含该结果的数组;addOne(),接收一个值,将其加1,然后将其返回。如下:
function multiplyByTwo(a, b, c) {
var arr = [];
for (var i = 0; i < arguments.length; i++) {
arr[i] = arguments[i] * 2;
}
return arr;
}
function addOne(a) {
return a + 1;
}
console.log(multiplyByTwo(1, 2, 3)); // [ 2, 4, 6 ]
console.log(addOne(100)); // 101
现在假设你想要一个数组myarr,它包含3个元素,且其中的每一个元素都能在这两个函数之间被传递。
var myarr = [];
myarr = multiplyByTwo(10, 20, 30);
for (var i = 0; i < myarr.length; i++){
myarr[i] = addOne(myarr[i]);
}
console.log(myarr);
正如你所看到的,代码可以正常工作,但仍有改进的空间。例如,有两个循环。如果重复次数很多,循环会很昂贵。你可以只使用一个循环得到相同的结果。
// 回调函数的应用
function multiplyByTwo(a, b, c, f) {
var arr = [];
for (var i = 0; i < arguments.length-1; i++){
arr[i] = f(arguments[i] * 2);
}
return arr;
}
// 传递callback函数
console.log(multiplyByTwo(1, 2, 3, addOne)); // [ 3, 5, 7 ]
// 传递匿名callback函数
// 使用一个匿名函数能节约了一个额外的全局变量
console.log(multiplyByTwo(1, 2, 3, function (a) {
return a + 1;
})); // [ 3, 5, 7 ]
// 一个匿名函数便于按需修改
console.log(multiplyByTwo(1, 2, 3, function (a) {
return a + 2;
})); // [ 4, 6, 8 ]
3. 即时函数或者自调函数 Immediate functions
到目前为止,我们已经讨论了使用匿名函数作为回调函数的应用,让我们看看匿名函数的另一个应用,一个函数可以在定义后立即被调用。例如:
(
function () {
console.log('boo');
}
)();
这种语法看起来有点吓人,但是你所做的是简单地将一个函数表达式放在一对括号中,然后将另一对括号放在前面括号的后面。第二对括号起到“立即执行”的作用。同时也是放置匿名函数所需参数的地方。
(
function (name) {
console.log('Hello ' + name + '!');
}
)('huhu');
或者,你可以将第一对括号的右括号移到最后。
(function () {
// ...
}());
// vs.
(function () {
// ...
})();
立即(自调)匿名函数应用的好处是当你想要做一些工作时,不会创建额外的全局变量。当然,缺点是你不能重复执行相同的函数。这使得即时函数最适合执行一次性的或者初始化的任务。
即时函数也可以返回一个你需要的值。这种不常见的代码如下:
var result = (function () {
// something complex with
// temporary local variables...
// ...
// return something;
}());
在这种情况下,你不需要将函数表达式放在括号中,你只需要调用函数的括号。所以,如下代码也能工作:
var result = function () {
// something complex with
// temporary local variables
// return something;
}();
这种语法能工作,但是看起来有点混乱。如果不读到函数末尾,你就不知道result是一个函数还是立即函数的返回值。