# 箭头函数
箭头函数有几个使用注意点:
- 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
- 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
- 箭头函数可以让 this 指向固定化,绑定定义时所在的作用域,而不是指向运行时所在的作用域。这种特性很有利于封装回调函数。
- 除了 this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments、super、new.target。
- 由于箭头函数没有自己的 this,所以当然也就不能用 call()、apply()、bind() 这些方法去改变 this 的指向。
# 普通函数和箭头函数的区别 重点
- 箭头函数的 this 指向规则(没有 this)
- 箭头函数的 arguments(没有 arguments)
- 使用 new 调用箭头函数会报错,即,不能作为构造函数
- 箭头函数不支持 new.target
- 箭头函数没有原型
- 箭头函数没有 super
- 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名
- 箭头函数不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
- 箭头函数不能直接用 call()、apply()、bind() 这些方法去改变 this 的指向,但可以间接改变
# 箭头函数的 this 指向规则
箭头函数没有 this,所以需要通过查找作用域链来确定 this 的值。这就意味着如果箭头函数被非箭头函数包含,this 绑定的就是最近一层非箭头函数的 this。因为箭头函数没有 this,所以也不能用 call()、apply()、bind() 这些方法改变 this 的指向,但可以通过修改被继承的普通函数的 this 指向间接修改。
- 箭头函数没有 prototype(原型),所以箭头函数本身没有 this。
- 箭头函数的 this 指向在定义的时候继承自外层第一个普通函数的 this。
- 箭头函数的 this 指向定义时所在的外层第一个普通函数,跟使用位置没有关系。
- 被继承的普通函数的 this 指向改变,箭头函数的 this 指向会跟着改变
- 不能直接修改箭头函数的 this 指向。但是,我们可以间接修改箭头函数的指向。去修改被继承的普通函数的 this 指向,然后箭头函数的 this 指向也会跟着改变
- 箭头函数外层没有普通函数,严格模式和非严格模式下它的 this 都会指向 window(全局对象)
箭头函数实际上可以让 this 指向固定化,绑定 this 使得它不再可变,这种特性很有利于封装回调函数。
Babel 转箭头函数产生的 ES5 代码,就能清楚地说明 this 的指向
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
// 箭头函数里面根本没有自己的 this,而是引用外层的 this
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 箭头函数的 arguments
箭头函数没有自己的 arguments 对象,这不一定是件坏事,因为箭头函数可以访问外围函数的 arguments 对象。可以通过命名参数或者 rest 参数的形式访问参数。
- 如果箭头函数的 this 指向 window(全局对象)使用 arguments 会报错,未声明 arguments
- 箭头函数的 this 指向普通函数时,它的 argumens 继承于该普通函数
- rest(...扩展符)参数获取函数的多余参数
# new 调用箭头函数
不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
JavaScript 函数有两个内部方法:[[Call]]
和 [[Construct]]
。
当通过 new 调用函数时,执行 [[Construct]]
方法,创建一个实例对象,然后再执行函数体,将 this 绑定到实例上。当直接调用的时候,执行 [[Call]]
方法,直接执行函数体。箭头函数并没有 [[Construct]]
方法,不能被用作构造函数,如果通过 new 的方式调用,会报错。
# 不支持 new.target
new.target 是 ES6 新引入的属性,普通函数如果通过 new 调用,new.target 会返回该函数的引用。此属性主要用于确定构造函数是否为 new 调用的。
- 箭头函数的 this 指向全局对象时,在箭头函数中使用
new.target
会报错 - 箭头函数的 this 指向普通函数时,它的
new.target
就是指向该普通函数的引用。
# 没有原型
由于不能使用 new 调用箭头函数,所以也没有构建原型的需求,于是箭头函数也不存在 prototype 这个属性。
# 没有 super
连原型都没有,自然也不能通过 super 来访问原型的属性,所以箭头函数也是没有 super 的,不过跟 this、arguments、new.target 一样,这些值由外围最近一层非箭头函数决定。
箭头函数与非箭头函数的区别
- 箭头函数本身没有 this。所以需要通过查找作用域链来确定 this 的值。this 绑定的就是最近一层非箭头函数的 this。箭头函数外层如果没有非箭头函数,严格模式和非严格模式下它的 this 都会指向 window(全局对象)。不可以直接修改箭头函数的 this 指向。
- 箭头函数本身没有 argument。可以通过扩展符代替 argument 获取函数的多余参数。箭头函数的 this 指向普通函数时,它的 argumens 继承于该普通函数。如果箭头函数的 this 指向 window(全局对象)使用 arguments 会报错,未声明 arguments。
- 箭头函数不能通过 new 调用。箭头函数不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
- 箭头函数不支持 new.target。new.target 会返回该函数的引用。此属性主要用于确定构造函数是否为 new 调用的。箭头函数的 this 指向普通函数时,它的
new.target
就是指向该普通函数的引用。箭头函数的 this 指向全局对象时,在箭头函数中使用new.target
会报错。 - 箭头函数没有原型。由于不能使用 new 调用箭头函数,所以也没有构建原型的需求,于是箭头函数也不存在 prototype 这个属性。
- 箭头函数不支持 super。连原型都没有,自然也不能通过 super 来访问原型的属性,所以箭头函数也是没有 super 。
- 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名
- 箭头函数不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
# 尾调用
尾调用(Tail Call) 是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数并返回,且调用发生在当前函数的末尾(即没有其他操作要执行)。
以下三种情况,都不属于尾调用。
// 情况一 在调用 g(x) 之后还有额外的操作(赋值操作),所以这不符合尾调用的定义
function f(x){
let y = g(x);
return y;
}
// 情况二 g(x) 的调用结果被加 1 后才返回,
// 这意味着在调用 g(x) 之后还有额外的操作(加法操作),所以这也不符合尾调用的定义
function f(x){
return g(x) + 1;
}
// 情况三 虽然 g(x) 是在函数 f(x) 的末尾被调用,但没有返回 g(x)
function f(x){
g(x);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
尾调用之所以与其他调用不同,就在于它的特殊的调用位置。
函数调用会在内存形成一个“调用记录”,又称 调用帧”(call frame),保存调用位置和内部变量等信息。
尾调用优化(Tail call optimization) : 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。
注意,目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持。
注意,ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。
- func.arguments:返回调用时函数的参数。
- func.caller:返回调用当前函数的那个函数。
尾递归:尾调用自身。
尾递归优化:尾递归之所以需要优化,原因是调用栈太多,造成溢出,那么只要减少调用栈,就不会溢出。怎么做可以减少调用栈呢?就是采用“循环”换掉“递归”。
# 函数的扩展其他特性
ES2017 允许函数的最后一个参数有尾逗号。这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。
function clownsEverywhere(
param1,
param2,
) { /* ... */ }
clownsEverywhere(
'foo',
'bar',
);
2
3
4
5
6
7
8
9
ES2019 对函数实例的toString()
方法做出了修改,明确要求返回一模一样的原始代码。
function /* foo comment */ foo () {}
foo.toString()
// function foo() {} 函数foo的原始代码包含注释,函数名foo和圆括号之间有空格,但是toString()方法都把它们省略了。
function /* foo comment */ foo () {}
foo.toString()
// "function /* foo comment */ foo () {}"
2
3
4
5
6
7
8
9
ES2019 允许catch
语句省略参数。
try {
// ...
} catch {
// ...
}
2
3
4
5
← var let const 异步方案 →