一.函数的默认值
(1).只有当函数提供的参数是严格等于undefined的时候,才会应用默认值
(2).函数参数中的变量不能在函数体中重复声明,也不能在函数参数作用域内重复声明
(3).函数的默认值是惰性求值的,不应用到是不会求值的
(4).函数带有默认值的参数必须放在最后一个,否则js不知道哪一个是省略的,阅读也不友好
(5).es5中函数的参数length属性失真,es6只会计算含有默认值的参数的前面的参数个数
(6).参数有默认值的情况下,函数参数会有自己的作用域
(7).可以和解构赋值一起用
// 默认值
function log(x, y = false) {
console.log(x, y);
}
/**
作用域,这里存在3个作用域,一个是函数外部作用域,一个是函数bar的参数作用域,
一个是函数内部作用域.函数bar的参数作用域中返回变量foo,但是函数bar的参数
作用域中并未定义foo变量,所以向上查找,找到外层作用域的foo,当执行的时候才返回
outer;但是,内部的console.log(foo)是先查找本作用域即函数内部作用域找到foo为
inner,所以返回inner
*/
let foo = 'outer';
function bar(func = () => foo) {
let foo = 'inner';
console.log(func());
console.log(foo);
}
bar(); // outer
// inner
// 结合解构赋值
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
console.log(method);
}
fetch('http://example.com')
// "GET"
函数默认值推荐的应用
限制参数必须提供,否则报错
function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}
foo()
// Error: Missing parameter
二.函数rest参数(扩展运算符)
(1)rest参数是为替代arguments参数的,表示剩余的参数
(2)rest参数本身是数组,可遍历
(3)rest参数只能在函数参数的最后
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
console.log(sortNumbers(2,5,1,2,20,8,7)); // [1, 2, 2, 20, 5, 7, 8]
三.严格模式
es6改变了函数体内部可以声明严格模式的限制,在es6中,只要使用函数参数默认值,解构赋值或者扩展运算符的都不能在函数内部声明严格模式
原因如下:
函数体的严格模式同样适用于函数参数和函数体,函数执行的时候,是先执行函数参数,但是要等到函数体的时候才能知道是不是严格模式,这不合理
有效的规避方式:
方法一:
// 全局严格模式
'use strict';
function doSomething(a, b = a) {
// code
}
方法二:
// 将函数包含在无参的立即执行函数中
const doSomething = (function () {
'use strict';
return function(value = 42) {
return value;
};
}());
四.name属性
es6更改了es5的函数name属性
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"
const bar = function baz() {};
// ES5
bar.name // "baz"
// ES6
bar.name // "baz"
(new Function).name // "anonymous"
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
五.箭头函数
(1)和es5不同,不会改变this对象,而是使用定义时所在的对象,本身无对象
(2)不能做构造函数,不能用new 命令
(3)不存在arguments,super,new.target,不能使用call,apply,bind方法
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
六.双冒号运算符
一个提案,不知道会不会成为标准
foo::bar;
// 等同于
bar.bind(foo);
7.尾调用优化
尾调用就是在函数执行的最后一布调用另一个函数,只能是最后一步,打不是必须放在函数的最后
已下三种不是尾调用
// 情况一,g函数执行不是最后执行的
function f(x){
let y = g(x);
return y;
}
// 情况二,g函数执行不是最后执行的
function f(x){
return g(x) + 1;
}
// 情况三,没有返回g函数,
function f(x){
g(x);
}
1.尾调用优化
原因:
函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。
// 不是尾调用优化,因为one变量被inner用了
function addOne(a){
var one = 1;
function inner(b){
return b + one;
}
return inner(a);
}
2.尾递归调用优化
非优化情况
function Fibonacci (n) {
if ( n <= 1 ) {return 1};
// 最后一步不是调用函数
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Fibonacci(10) // 89
Fibonacci(100) // 堆栈溢出
Fibonacci(500) // 堆栈溢出
优化情况
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};
return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}
Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity
至于更加深入的学习,请参考: http://es6.ruanyifeng.com/#docs/function#%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0