一个JS函数的定义由function关键字开头,然后加上函数名、参数列表和函数体。
- 函数名与变量等JS标识符命名规则一致,以字母、下划线或者$开头,后面可以跟数字;
- 参数列表是一个包含在括号中,以逗号分离的列表,例如:(a, b, c);
- 函数体是函数的主体内容,以大括号{}将JS语句包含起来,可通过return返回结果;
最简单的函数定义如下所示(函数名自定义):
function foo(){}
可以添加一些语句到函数体中:
function foo(){
console.log("Hello World!");
}
再添加一些参数,并返回两个参数的总和:
function foo(a, b){
return a + b;
}
var sum = foo(1, 2);
console.log(sum); //输出3
上面定义了一个求和的函数,并使用函数名加参数列表(可为空)调用它。
OK,一般的函数定义和调用比较简单,但在JS中函数的灵活用法有很多,可以再看看下面。
匿名函数
定义函数时不一定需要函数名:
var test = function(){}
像上面这样的匿名函数,以后可以通过test变量来调用它。
另外,可以定义一个匿名函数并立即运行它:
(function(){ return 1; })();
如果不需要重复调用这个函数并立即执行的话,可以像上面这样定义匿名函数。
但是直接定义一个匿名函数,却不将它赋值给某个变量或者立即执行,那么它会报错:
function() {return 1;} // SyntaxError: function statement requires a name
默认参数
JS函数中的参数默认值是undefined,有时候给参数一个默认值是比较好的选择,这样不用判断一个值是否为未定义而手动设置初始值。
不使用默认参数的话,只能像下面那样判断参数b是否为undefined(外部调用是未传入此参数),如果是则赋值为1,否则直接传入b的值:
function multiply(a, b) {
b = (typeof b !== 'undefined') ? b : 1;
return a*b;
}
multiply(5); // 5
使用默认参数可以这样:
function multiply(a, b = 1) {
return a*b;
}
multiply(5); // 5
上面设置默认参数的方式,可以设后面多个参数。
参数个数不确定
函数的参数个数不确定的情况下,有两种处理方法:
第一种,arguments对象:
function myConcat(separator) {
var result = ''; // 把值初始化成一个字符串,这样就可以用来保存字符串了!!
var i;
// iterate through arguments
for (i = 1; i < arguments.length; i++) {
result += arguments[i] + separator;
}
return result;
}
arguments对象是一个类数组的对象,但它不是数组。因为它有length属性,也可以通过下标索引的方式来访问各参数,所以叫类数组的对象。简单来说,它的值是根据调用函数时的任意数量参数来确定的。
可以给这个函数传递任意数量的参数,它会将各个参数连接成一个字符串“列表”:
// returns "red, orange, blue, "
myConcat(", ", "red", "orange", "blue");
// returns "elephant; giraffe; lion; cheetah; "
myConcat("; ", "elephant", "giraffe", "lion", "cheetah");
// returns "sage. basil. oregano. pepper. parsley. "
myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");
第二种,剩余参数语法:
function multiply(multiplier, ...theArgs) {
return theArgs.map(x => multiplier * x);
}
var arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]
在这个例子,theArgs参数是一个真数组,它包含了从第二个参数开始的所有参数(除了第一个)。
那么上面这两种方法有什么不同?
剩余参数与arguments对象的三个不同点:
1. 剩余参数只包含那些没有对应形参的实参,而 arguments
对象包含了传给函数的所有实参。
2. arguments
对象不是一个真正的数组,而剩余参数是真正的 Array
实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort
,map
,forEach
或pop
。
3. arguments
对象还有一些附加的属性 (如callee
属性)。
闭包
闭包,这个名词第一次听有点头大,以为又是什么复杂的设计,经过了解之后,发现其实闭包就是函数,不过它是一个特殊的函数。
介绍闭包之前需要介绍JS里函数另一个特性——嵌套。
JS支持嵌套函数,也就是函数里面包含另一个函数的定义:
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
由于内部函数形成了闭包,因此可以调用外部函数并为外部函数和内部函数指定参数:
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
注意上面这里有两种方式调用,一种是先调用outside获取内部函数并村委fn_inside,然后再调用fn_inside来获取结果;第二种是直接电泳outside(3)(5)来获取结果。
由此可见,一个函数内部的另一个函数(嵌套函数)就是一个闭包,一个闭包可以“继承”容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。
总结如下:
1. 内部函数只可以在外部函数中访问,或者通过外部函数暴露给更外部(此处只outside函数外部的作用域)。
2. 内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。
作用域链
由于嵌套函数的层数不限,从里到外形成了一个作用域链:{最里层函数,较内层函数,...,较外层函数,最外层函数}。
当在这个作用域链中定义了同名的变量,那么会发送命名冲突,更近的作用域有更高的优先权,所以最近的优先级最高,最远的优先级最低。链的第一个元素就是最里面的作用域,最后一个元素便是最外层的作用域。
看下面例子:
function outside() {
var x = 5;
function inside(x) {
return x * 2;
}
return inside;
}
outside()(10); // returns 20 instead of 10
很明显,outside定义的变量x与inside定义的变量x发生了命名冲突,那么该选择哪个变量来做最后的运算呢?这里是作用域链出场的地方了,根据上面的规则作用域链是{inside
, outside
, 全局对象},inside处于最高优先级位置,因此x变量应该是10,结果是10*2即20。
箭头函数
ES6规范新作了一个新的函数定义语法,箭头函数。它主要有两个应用点,简洁函数声明和闭包this传递。
简洁的函数
一般它是用在简短的函数上,以是整个函数的定义更加简洁,它抛弃了function关键字,也不需要函数名,甚至还可以连函数体的大括号都可以省略!
下面看一个例子:
var a = [
"Hydrogen",
"Helium",
"Lithium",
"Beryllium"
];
var a2 = a.map(function(s){ return s.length });
console.log(a2); // logs [ 8, 6, 7, 9 ]
var a3 = a.map( s => s.length );
console.log(a3); // logs [ 8, 6, 7, 9 ]
上面a2和a3的结果是一样的,但是a3使用了箭头函数,同时函数体的大括号也省略了,这种情况下直接返回箭头右边的表达式的结果。
闭包this传递
在箭头函数出现之前(ES6前),由于每个函数都有自己的this值,所以闭包的this关键字不能访问到外部函数的this:
function Person() {
this.age = 0;
setInterval(function growUp() {
// 这里的this是growUp这个闭包,而不是外部Person函数的this
this.age++;
}, 1000);
}
var p = new Person();
为了解决上面的问题,需要先将this赋值给其他变量,再在闭包里访问改变了来达成目的:
function Person() {
var self = this; // Some choose `that` instead of `self`.
// Choose one and be consistent.
self.age = 0;
setInterval(function growUp() {
// 此处self是Person函数的的this
self.age++;
}, 1000);
}
上面是我总结了关于JS中函数应用的关键点,更多其他的特性功能以后再补充。