第八章 函数
函数的实参和形参
当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参将设置为undefined值。所以比较好的做法是赋予一个默认值。在ES5中可以通过短路运算赋予一个默认值,比如a = a || []
,当然这个a是一个形参。在ES6中的做法是直接给函数参数声明默认值:
let a = function (page = 1,pageLimit = 10) {
//
}
当调用函数的时候传入实参的个数超过定义时的形参个数时,没有办法直接获得未命名值得引用。可以使用arguments来获得所有传入的参数。因为实参对象是一个类数组对象,可以通过下标访问传入函数的实参。在ES6中可以使用剩余参数...
来获取所有的参数。
作为值的函数
在JS中,函数不仅是一种语法,也是值。可以将函数赋值给变量,存储在对象的属性或数组的元素中,作为参数传入另一个函数。
function square(x) {return x * x;}
这个定义创建一个新的函数对象,并将其赋值给变量square。函数的名字实际上是看不见的,square仅仅是变量的名字,这个变量指代函数对象。函数还可以赋值给其他的变量,并正常工作:
var s = square; // s和square指代同一个函数
square(4); // => 16
s(4); // => 16
函数同样可以赋值给对象的属性。当函数作为对象的属性调用时,函数就称为方法。
函数甚至不需要名字,当把他们赋值给数组元素时:
var a = [function(x) {return x * x},20];
a[0](a[1]); // => 400
上式虽然看起来怪怪的,但是是合法的函数调用表达式!
自定义函数属性
如果需要写一个依赖函数自身返回值的函数,最好的办法就是给函数对象添加一个属性,而不是把这个信息放到全局变量中去污染环境。举个例子:
function sum () {
return sum.counter++;// 先返回计数器的值,然后计数器自增1
}
sum.counter = 0;
闭包
JS采用词法作用域,也就是说,函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。为了实现这种词法作用域,JS函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学中称为“闭包”。
来看一个官方示例:
var scope = "global scope"; //全局变量
function checkscope() {
var scope = "local scope"; //局部变量
function f() {return scope;} //返回scope
return f;
}
checkscope()() // 返回???
回想一下词法作用域的基本规则:JS函数的执行用到了作用域链,这个作用域链是函数定义的时候创建的。嵌套的函数f()定义在这个作用域里,其中的变量scope一定是局部变量,不管在何时何地执行函数f()。所以返回local scope
,而不是global scope
。
函数属性、方法和构造函数
bind()方法
这个方法的作用就是将函数绑定至某个对象。例如:
function f(y) {return this.x + y}
var o = {x : 1};
var g = f.bind(o); //通过调用g(x)来调用o.f(x)
g(2) // => 3
不仅如此,还附带一些其他应用:除了第一个实参之外,传入bind()的实参也会绑定至this.举个例子:
var sum = function(x,y) {return x + y};
var succ = sum.bind(null,1);
// this的值绑定到null,并且第一个参数绑定到1
succ(2) // 3;x绑定到1,并传入2作为实参y
function f(y,z) {return this.x + y + z};
var g = f.bind({x:1},2);
g(3) // 6;this.x绑定到1,y绑定到2,z绑定到3
Function()构造函数
Function()构造函数可以传入任意数量的字符串实参,最后一个实参表示的文本就是函数体;它可以包含任意的JS语句,每两条语句之间用分号分隔。如果不包含任何参数,只需传入函数体字符串即可。
注意:Function()构造函数不需要通过传入实参指定函数名,也就是创建的是一个匿名函数。
关于Function()构造函数有几点需要注意:
- Function()构造函数允许JS在运行时动态地创建并编译函数。
- 每次调用Function()构造函数都会解析函数体,并创建新的函数对象。如果是在一个循环或者多次调用的函数中执行这个构造函数,效率会受影响。而循环中的嵌套函数和函数定义表达式不会每次执行都重新编译。
- Function()构造函数所创建的函数并不是使用词法作用域,函数体代码的编译总是会在顶层函数执行。 如下所示:
var scope = "global";
function cons() {
var scope = 'local';
return new Function("return scope");
//Function()构造函数只传入一个字符串表示该匿名函数没有参数,只有返回体。
//上面说到并不是使用词法作用域,所以此时会去顶层函数所处作用域的变量scope,所以结果返回“global”
}
可以将Function()构造函数认为是在全局作用域执行的eval()。eval()可以在自己的私有作用域内定义新变量和函数。在很多JS编码规范里是不建议使用eval()和Function构造函数。具体可参考Airbnb的编码规范。
函数式编程
高阶函数
所谓高阶函数就是操作函数的函数,它接收一个或多个函数作为参数,并返回一个新函数。举个:
function mapper(f) {
return function(a) {return map(a,f);};
}
var increment = function(x) {return x + 1};
var incrementer = mapper(increment);
incrementer([1,2,3]) // [2,3,4]
// 调用mapper会传入一个map()方法里的回调函数,然后会返回一个函数,这个函数的参数就是需要处理的数组。
//可以理解为mapper(increment)([1,2,3])