- 函数执行是先执行函数参数,再执行函数体
- 如果函数参数使用了解构赋值、设置默认值、扩展运算符则不可以在函数体内显式设定为严格模式,否则会报错
- “调用记录”,又称“调用帧”。用于保存调用位置和内部变量等信息。如果A调用了B,那么在A的调用帧上形成了B的调用帧,如果在B内部海调用了C,那么在B的上方又形成了C的调用帧,等到C的调用结束以后,将结果返回给B,C的调用帧才会消失。A、B、C的调用帧便形成了调用栈
- 函数拥有name属性,可以获取函数名。
默认值
- 普通设置默认值
- 解构赋值设置默认值:其中给解构赋值的属性设置默认值和给函数参数设置默认值之间的区别。
1 | function m1({x = 0, y =0} = {}) { |
length属性
1 | 指定默认值以后,函数的length属性、函数参数的length将会失真。即如下: |
函数的length属性是:该函数预期传入的参数个数。所以length的大小是看调用该函数时传入的参数个数,而不是单纯看函数自己参数默认值的设置。
rest参数
1 | function add(...values) { // 采用...values接收所有参数 |
1 | 一、...values与arguments的区别: |
- 注意点
- rest参数之后不能有其他参数
箭头函数
箭头函数体内的
this
对象,是函数定义时所在的对象,而不是使用时所在的对象。这是因为箭头函数没有this
,不是因为它可以绑定this
,所以箭头函数的this
便是它定义的外部环境的this
,因为没有this,自然不可以调用call、apply、bind来改变this的指向问题。除
this
外,arguments
、super
、new.target
在箭头函数中也是不存在的,而且会和this
一样,指向到定义时的外部环境一样。不可作构造函数,不可以使用
new
命令不可使用
arguments
对象,该对象在函数体内不存在,可以用rest
参数代替不可使用
yield
命令,箭头函数不能用作Generator
函数关于this对象的举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45var s2 = 8; // 这里定义没个鸟用,因为调用Timer的是setTimeout,而不是全局
function Timer(){
this.s1 = 0;
this.s2 = 0;
setInterval(() => {
console.log(this.s1) // 0,也就是说这里可以取到值,因为this = timer
this.s1++
console.log(this.s1) // 1
}, 1000);
setInterval(function(){
console.log(this)
console.log(this.s2) // undefined,取不到,以为全局下没有这个变量var s2 = 8;
function Timer(){
this.s1 = 0;
this.s2 = 0;
setInterval(() => {
console.log(this.s1) // 0,也就是说这里可以取到值,因为this = timer
this.s1++
console.log(this.s1) // 1
}, 1000);
setInterval(function(){
console.log(this) // 为setTimeout,因为是在setTimeout中调用了这个函数
console.log(this.s2) // undefined,取不到,以为全局下没有这个变量
this.s2++
console.log(this.s2) // 后面执行为NaN
},1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1:', timer.s1), 1000)
setTimeout(() => console.log('s2:', timer.s2), 1000)
this.s2++
console.log(this.s2) // 后面执行为NaN
},1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1:', timer.s1), 1000)
setTimeout(() => console.log('s2:', timer.s2), 1000)多层this的嵌套:
1
2
3
4
5
6
7
8
9
10
11
12
13
14function foo(){
return () => {
return () => {
return () => {
console.log('id:',this.id);
}
}
}
}
var f = foo.call({id:1})
var t1 = f.call({id:2})()(); // id:1
var t2 = f().call({id:3})(); // id:1
var t3 = f()().call({id:4}); // id:1- 题外话,这里的call()用法,参考《call()》一文
尾调用
1 | 含义:函数的最后一步是调用另一个函数。不一定是写在函数尾部,一定是最后一步操作,,实际上很大程度上,需要时return 某函数,因为return真正的结束了这个函数的操作。 |
尾调用主要是为了实现尾调用优化,实际上是内存优化的一种方式。当执行到最后一条语句,也就是执行另一个函数的时候,因为这时候外层函数已经没有用了,不需要再去保存调用位置和变量信息,所以可以释放其调用帧来节省内存,官方语言可以说是该函数的调用帧取代了外层函数的调用帧。
所以一定要是最后一条执行语句的时候调用函数,否则就不会释放外层函数的调用帧,这样尾调用优化就不能实现,尾调用也失去了意义。
尾递归
这个就非常有意义了!不过ES6尾调用优化只在严格模式下才会开启,正常模式下是无效的。所以代码如下:
1 | function test(a,b){ |
函数调用自身称为递归,尾调用自身则是尾递归。因为普通递归需要保存很多调用帧,非常耗费内存,但是尾递归则只会保存一个调用帧,大大的节省了内存,其复杂度就是O(1)。
ES6中使用尾递归,就不会发生栈溢出(或者层层递归造成的超时),相对节省内存
蹦床函数
将递归改变为循环执行,并且传递的参数是一个函数,并立即执行它。。。。呃。。
1 | function foo(){ |