ES6 引入 rest
参数(形式为...变量名),用于获取函数的多余参数,这样就不须要使用arguments
对象了。rest
参数搭配的变量是一个数组,该变量将多余的参数放入数组中。编程
const foo = (...values) = {
console.log(values)
}
foo(1,2,3) // [1,2,3]
复制代码
arguments
对象是使用function
声明函数时自动生成的对象, 包含了函数的参数,但结构复杂。在箭头函数中被rest
代替,不可以使用,不然报错。数组
// arguments变量的写法
function f1() {
console.log(arguments)
}
const f11 = () => {
console.log(arguments)
}
// rest参数的写法
const f2 = (...numbers) => {
console.log(numbers)
};
复制代码
注意:rest
参数只能是最后一个参数,不然会报错。bash
const foo = (a, ...rest, b) => {}
// Uncaught SyntaxError: Rest parameter must be last formal parameter
复制代码
ES6 容许为函数的参数设置默认值,即直接写在参数定义的后面。app
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', undefined) // Hello World
log('Hello', '') // Hello
log('Hello', null) // Hello null
log('Hello', 'China') // Hello China
复制代码
这种写法有两个好处:函数式编程
undefined
的时候,默认值才会生效,跟解构赋值很相近。因此定义默认值的参数,最好是函数的尾参数。否则会出现一下状况:function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 报错
f(undefined, 1) // [1, 1]
复制代码
另外,一个容易忽略的地方是,参数默认值不是传值的,而是每次都从新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。函数
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100
x = 100;
foo() // 101
复制代码
当参数是一个对象时:优化
function foo({x, y = 5}) {
console.log(x, y)
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined
复制代码
若是调用函数时没有给参数就会报错.
经过提供函数参数的默认值,就能够避免这种状况。ui
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5
复制代码
要注意函数解构设置默认值的写。
如下有两种写法:this
// 写法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
复制代码
两个都是给了默认值,可是是有区别的:spa
// 函数没有参数的状况
m1() // [0, 0]
m2() // [0, 0]
// x 和 y 都有值的状况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]
// x 有值,y 无值的状况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
// x 和 y 都无值的状况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]
m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]
复制代码
一旦设置了参数的默认值,函数进行声明初始化时,参数会造成一个单独的做用域(context)。等到初始化结束,这个做用域就会消失。
let x = 1;
let y = 3;
function f1(x, y = x) {
console.log(x, 'x')
x = 3
console.log(y, 'y');
}
f1(2) // 2
复制代码
x
和 y
的值不受外界影响。函数体内的值也优先为头部的值y
在函数头就已经赋值,因此在运行时即使x
改变,也不会受影响。从 ES5 开始,函数内部能够设定为严格模式。
function foo(a, b) {
'use strict';
// code
}
复制代码
然而 ES2016 作了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符(rest),那么函数内部就不能显式设定为严格模式,不然会报错。
// 报错
function foo(a, b = a) {
'use strict';
// code
}
// 报错
const foo = function ({a, b}) {
'use strict';
// code
};
// 报错
const foo = (...a) => {
'use strict';
// code
};
const obj = {
// 报错
foo({a, b}) {
'use strict';
// code
}
};
复制代码
两种方法能够规避这种限制:
1.设定全局性的严格模式
'use strict';
function foo(a, b = a) {
// code
}
复制代码
2.把函数包在一个无参数的当即执行函数里面。
const foo = (function () {
'use strict';
return function(value = 42) {
return value;
};
}());
复制代码
特色:
=>
来定义函数const f1 = (x) => {
return x + 1
}
const f2 = x => x + 1
const f3 = x => ({ x })
function f4 (x) {
return { x }
}
复制代码
箭头函数内部,还能够再使用箭头函数。下面是一个 ES5 语法的多重嵌套函数。
function insert(value) {
return {into: function (array) {
return {after: function (afterValue) {
array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}};
}};
}
insert(2).into([1, 3]).after(1); //[1, 2, 3]
复制代码
使用箭头函数改写,明显少了不少代码:
let insert = (value) => ({into: (array) => ({after: (afterValue) => {
array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}})});
insert(2).into([1, 3]).after(1); //[1, 2, 3]
复制代码
this
对象,就是定义时所在的对象(他的外层对象),而不是使用时所在的对象。this
对象。arguments
对象,该对象在函数体内不存在。若是要用,能够用 rest
参数代替。yield
命令,所以箭头函数不能用做 Generator
函数。var a=11
function f1(){
this.a=22;
let b=function(){
console.log(this.a);
};
b();
}
function f2(){
this.a=22;
let b=()=>{console.log(this.a)}
b();
}
var x=new f1(); // 11
var y=new f2(); // 22
复制代码
this指向的固定化,并非由于箭头函数内部有绑定this的机制,实际缘由是箭头函数根本没有本身的this,致使内部的this就是外层代码块的this。正是由于它没有this,因此也就不能用做构造函数。
因此上面代码至关于这样:
function f2(){
this.a=22;
let _this = this
let b=()=>{console.log(_this.a)}
b();
}
复制代码
除了this
,如下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments
、super
、new.target
。
const foo () =>{
console.log(arguments)
}
foo() // Uncaught ReferenceError: arguments is not defined
function foo () {
return () => {
console.log(arguments)
}
}
foo()()
复制代码
另外,因为箭头函数没有本身的this,因此固然也就不能用call()、apply()、bind()这些方法去改变this的指向。
(function() {
return [
(function () { return this.x }).bind({ x: 'inner' })()
];
}).call({ x: 'outer' });
// ['inner']
(function() {
return [
(() => this.x).bind({ x: 'inner' })()
];
}).call({ x: 'outer' });
// ['outer']
复制代码
length
属性将返回该函数预期传入的参数个数。某个参数指定默认值之后,预期传入的参数个数就不包括这个参数了。同理,rest 参数也不会计入length
属性。
若是 默认参数不是尾参数,那么默认参数后面的参数也不计入 length
。
const f1 = (a,b) => {}
f1.length // 2
const f2 = (a, ...rest) => {}
f2.length // 1
const f3 = (a, b=1, c) => {}
f3.length // 1
复制代码
name
属性,返回该函数的函数名。function foo() {}
foo.name // "foo"
复制代码
1.若是将一个匿名函数赋值给一个变量.
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"
复制代码
2.若是将一个具名函数赋值给一个变量. 都返回函数本来的名字。
const bar = function baz() {};
// ES5
bar.name // "baz"
// ES6
bar.name // "baz"
复制代码
3.Function
构造函数返回的函数实例,name
属性的值为anonymous
。
const foo = new Function
foo.name // "anonymous"
复制代码
4.bind返回的函数,name属性值会加上bound前缀。
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
复制代码
尾调用(Tail Call)是函数式编程的一个重要概念,是指某个函数的最后一步是调用另外一个函数。
function f(x){
return g(x);
}
复制代码
如下三种状况,都不属于尾调用:
const g = (x) => {}
// 状况一
function f(x){
let y = g(x);
return y;
}
// 状况二
function f(x){
return g(x) + 1;
}
// 状况三
function f(x){
g(x);
}
// 等同于
function f(x){
g(x);
return undefined
}
复制代码
调用帧:函数调用会在内存造成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。
调用栈: 若是在函数A的内部调用函数B,那么在A的调用帧上方,还会造成一个B的调用帧。
等到B运行结束,将结果返回到A,B的调用帧才会消失。
若是函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。
全部的调用帧,就造成一个“调用栈”(call stack)。
尾调用:尾调用因为是函数的最后一步操做,因此不须要保留外层函数的调用帧,由于调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就能够了。
function g (x) {
console.log(x)
}
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同于
function f() {
return g(3);
}
f();
// 等同于
g(3);
复制代码
“尾调用优化”的意义: 若是全部函数都是尾调用,那么就能够作到每次执行时,调用帧只有一项,这将大大节省内存.
注意:只有再也不用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,不然就没法进行“尾调用优化”。
function addOne(a){
var one = 1;
function inner(b){
return b + one; // 这里还要使用 外层函数的 one
}
return inner(a);
}
复制代码
函数调用自身,称为递归。若是尾调用自身,就称为尾递归。
递归很是耗费内存,由于须要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。可是若是使用尾调用优化,使每次只存在一个调用帧,就不会发生“栈溢出”错误。
function f1(n) {
if (n === 1) return 1;
return n * f1(n - 1);
}
f1(5) // 120
// 尾递归
function f2(n, total) {
if (n === 1) return total;
console.log(n - 1, n * total)
return f2(n - 1, n * total);
}
f2(5,1) // 120
复制代码
这个函数更具备数学描述性:
若是输入值是1 => 当前计算数1 * 上一次计算的积total 若是输入值是x => 当前计算数x * 上一次计算的积total 计算f2(5, 1)的时候,其过程是这样的:
整个计算过程是线性的,调用一次sum(x, total)后,会进入下一个栈,相关的数据信息和跟随进入,再也不放在堆栈上保存。当计算完最后的值以后,直接返回到最上层的sum(5,0)。
这能有效的防止堆栈溢出。
普通递归改写须要在最后一步调用自身。作到这一点的方法,就是把全部用到的内部变量改写成函数的参数。这样作的缺点就是不太直观。很难看出这些参数是干什么的。 有两个方法能够解决这问题: 1. 内部要调的参数给个默认值
function f2(n, total=1) {
if (n === 1) return total;
return f2(n - 1, n * total);
}
f2(5) // 120
复制代码
2. 用另一个函数来返回这个函数。
function f2(n, total) {
if (n === 1) return total;
return f2(n - 1, n * total);
}
function f3 (n) {
return f2(n, 1)
}
f3(5) // 120
复制代码
以上只是尾调用优化的写法,可是并无实现真正的优化。
ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
这是由于在正常模式下,函数内部有两个变量,能够跟踪函数的调用栈。
func.arguments
:返回调用时函数的参数。func.caller
:返回调用当前函数的那个函数。尾调用优化发生时,函数的调用栈会改写,所以上面两个变量就会失真。严格模式禁用这两个变量,因此尾调用模式仅在严格模式下生效。
尾递归优化只在严格模式下生效,那么正常模式下,或者那些不支持该功能的环境中,有没有办法也使用尾递归优化呢。
那就只有本身是实现递归优化了。
原理:尾递归之因此须要优化,缘由是调用栈太多,形成溢出,那么只要减小调用栈,就不会溢出。怎么作能够减小调用栈呢?就是采用“循环”换掉“递归”。
下面是一个直接写的尾递归:
function sum(x, y) {
if (y > 0) {
return sum(x + 1, y - 1);
} else {
return x;
}
}
sum(1, 1000) // 1001
sum(1, 100000)
// Uncaught RangeError: Maximum call stack size exceeded(…)
复制代码
一旦指定sum递归 100000 次,就会报错,提示超出调用栈的最大次数。
有两种方法避免: 1.使用蹦床函数(trampoline) 将递归执行转为循环执行。
function trampoline(f) {
while (f && f instanceof Function) {
f = f();
}
return f;
}
复制代码
上面就是蹦床函数的一个实现,它接受一个函数f做为参数。只要f执行后返回一个函数,就继续执行。 注意,这里是返回一个函数,而后执行该函数,而不是函数里面调用函数,这样就避免了递归执行,从而就消除了调用栈过大的问题。
function sum(x, y) {
if (y > 0) {
return sum.bind(null, x + 1, y - 1);
} else {
return x;
}
}
// sum函数的每次执行,都会返回自身的另外一个版本。
trampoline(sum(1, 100000))
// 100001
复制代码
2.蹦床函数并非真正的尾递归优化,下面的实现才是
function tco(f) {
var value;
var active = false;
var accumulated = [];
// console.log(1)
return function accumulator() {
// console.log(2, arguments)
accumulated.push(arguments);
if (!active) {
active = true;
while (accumulated.length) {
// console.log(3)
value = f.apply(this, accumulated.shift());
}
active = false;
return value;
}
};
}
var sum = tco(function(x, y) {
if (y > 0) {
return sum(x + 1, y - 1)
}
else {
return x
}
});
sum(1, 10000)
// 100001
复制代码