1. 函数参数的默认值
[ 基本用法 ]
在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
上面的代码检查函数log的参数y有没有赋值,如果没有,则指定默认值为world。这种写法的缺点在于,如果参数y赋值了,但是对应的布尔值为false,则该赋值不起作用。就像以上代码的最后一行,参数y等于空字符,结果被改为默认值。
ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
[ 与解构赋值默认值结合使用 ]
参数默认值可以与解构赋值的默认值结合起来使用。
[ 参数默认值的位置 ]
通常情况下,定义了默认值的参数应该是函数的尾参数。如果非尾部的参数设置默认值,实际上这个参数是无法省略的。
有默认值的参数都不是尾参数。这时,无法只省略该参数而不省略其后的参数,除非显式输入undefined。如果传入undefined,将触发该参数等于默认值,null没有这个结果。
[ 函数的length属性 ]
指定了默认值之后,函数的length属性将返回没有指定默认值的参数个数。
length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值之后,预期传入的参数个数就不包括这个参数了。
[ 作用域 ]
如果参数默认值是一个变量,则该变量所处的作用域与其他变量的作用域规则是一样的,即先是当前函数的作用域,然后才是全局作用域。
如果调用时函数作用域内部的变量x没有生成,结果就会不一样。
函数调用时y的默认值变量x尚未在函数内部生成,所以x指向全局变量。
[ 应用 ]
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
以上代码中的foo函数,如果调用的时候没有参数,就会调用默认值throwIfMissing函数,从而抛出一个错误。
2. rest参数
ES6引入了rest参数(形式为"...变量名"),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入其中。
以上代码中的add函数是一个求和函数,利用rest参数可以向该函数传入任意数目的参数。下面是rest参数代替arguments变量的例子。
// arguments 变量的写法
// rest 变量的写法
注意:rest参数之后不能有其他参数,函数的length属性不包括rest参数。
3. 扩展运算符
[ 含义 ]
扩展运算符是三个点(...)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
扩展运算符与正常的函数参数可以结合使用。
function f(v,w,x,y,z){}
var args = [0,1];
f(-1,...args,2,...[3]);
[ 替代数组的apply方法 ]
由于扩展运算符可以展开数组,所以不再需要apply方法将数组转为函数的参数了。下面是扩展运算符取代apply方法的一个实际的例子,应用Math.max方法简化求数组最大元素的写法:
// ES5的写法
Math.max.apply(null,[14,3,77])
// ES6的写法
Math.max(...[14,3,77])
[ 扩展运算符的应用 ]
- 合并数组
扩展运算符提供了数组合并的新写法。
- 与解构赋值结合
扩展运算符可以与解构赋值结合起来用于生成数组。
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,不然会报错。
- 函数的返回值
JavaScript的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。
var dateFields = readDateFields(database);
var d = new Date(...dateFields);
上面的代码用于从数据库中取出一行数据,通过扩展运算符直接将其传入构造函数Date。
- 字符串
扩展运算符还可以将字符串转为真正的数组。
[ 类似数组的对象 ]
任何类似数组的对象都可以用扩展运算符转为真正的数组。
var nodeList = document.querySelectorAll("div");
var array = [...nodeList];
上面的代码中,querySelectorAll方法返回的是一个nodeList对象,扩展运算符可以将其转为真正的数组。
[ Map 和 Set 结构,Generator 函数 ]
扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。
[ name属性 ]
函数的name属性返回该函数的函数名。
如果将一个匿名函数赋值给一个变量,ES5的name属性会返回空字符串,而ES6的name属性会返回实际的函数名。
如果将一个具名函数赋值给一个变量,则ES5和ES6的name属性都返回这个具名函数原本的名字。
5. 箭头函数
[ 基本用法 ]
ES6允许使用"箭头"(=>)定义函数。
var f = v => v;
// 上面的箭头函数等同于
var a = function(v){
return v;
};
如果箭头函数不需要参数或需要多个参数,就使用圆括号代表参数部分。
var f = () => 5;
// 等同于
var f = function(){ return 5 };
var sum = (num1,num2) => num1 + num2;
// 等同于
var sum = function(num1,num2){
return num1+num2;
};
如果箭头函数的代码块部分多于一条语句,就要使用大括号将其括起来,并使用return语句返回。
var sum = (num1,num2) => { return num1 + num2 }
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。
var getTempItem = id => ({ id: id, name: "Temp" });
箭头函数可以与变量解构结合使用。
const full = ({ first, last }) => first + '' + last;
// 等同于
function full(person){
return person.first + '' + person.name;
}
箭头函数使得表达更加简洁。
const isEven = n => n % 2 == 0;
箭头函数的一个用处是简化回调函数。
// 正常函数的写法
[1,2,3].map(function(x){
return x*x;
});
// 箭头函数的写法
[1,2,3].map(x => x*x);
箭头函数还能与rest参数结合。
[ 使用注意点 ]
箭头函数有几个使用注意点。
● 函数体内的this对象就是定义时所在的对象,而不是使用时所在的对象。
● 不可以当做构造函数。也就是说,不可以使用new命令,否则会抛出一个错误。
● 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用rest参数代替。
● 不可以使用yield命令,因此箭头函数不能用作Generator函数。
6.函数绑定
箭头函数没有自己的this,导致内部的this就是外层代码块的this。正因为它没有this,所以也就不能用作构造函数。当然也不能使用call()、apply()、bind()这些方法去改变this的指向。
函数绑定运算符是并排的双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象作为上下文环境绑定到右边的函数上。
foo::bar
// 等同于
bar.bind(foo)
foo::bar(...arguments)
// 等同于
bar.bind(foo,arguments)
如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上。
var method = obj::obj.foo
// 等同于
var method = ::obj.foo
由于双冒号运算符返回的还是原对象,因此可以采用链式写法。
import {map,takeWhild,forEach} from "iterlib";
getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100);
::forEach(x => console.log(x));
7.尾调用优化
[ 尾调用概念 ]
尾调用是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
function f(x){
return g(x)
}
以下情况都不属于尾调用:
// 情况一
function f(x){
let y = g(x);
return y;
}
// 情况二
function f(x){
return g(x)+1;
}
// 情况三
function f(x){
g(X);
}
上面的代码中,情况一是调用函数g之后还有赋值操作,所以不属于尾调用,即使语义完全一样;情况二也属于调用后还有操作,即使写在一行内;情况三等同于下面的代码。
function f(x){
f(x);
return undefined;
}