在 ES6 中,箭头函数是其中最有趣也最受欢迎的新增特性。javascript
本文会分为三个部分来介绍:前端
第一部主要介绍箭头函数的基本语法与使用方式,其中关于this的指向
问题会着重介绍。java
第二部分探究一下箭头函数在自执行函数
中的奇怪现象。node
第三部分将提供一些面试题目
,用于帮助你们理解。git
顾名思义,箭头函数是一种使用 (=>) 定义函数的新语法,它与传统的 ES5 函数有些许不一样。es6
这是一个用 ES5 语法编写的函数:github
function addTen(num){
return num + 10;
}
addTen(5); // 15
复制代码
有了 ES6 的箭头函数后,咱们能够用箭头函数这样表示:面试
var addTen = num => num + 10
addTen(5); // 15
复制代码
箭头函数的写法短的多!因为隐式返回,咱们能够省略花括号和 return 语句。浏览器
与常规 ES5 函数相比,了解箭头函数的行为方式很是重要。微信
基础语法以下:
(参数)=> { statements }
复制代码
接下来,拆解一下箭头函数的各类书写形式:
当没有参数时,使用一个圆括号表明参数部分
let f = ()=> 5;
f(); // 5
复制代码
当只有一个参数时,能够省略圆括号。
let f = num => num + 5;
f(10); // 15
复制代码
当有多个参数时,在圆括号内定义多个参数用逗号分隔。
let f = (a,b) => a + b;
f(1,2); // 3
复制代码
当箭头函数的代码块部分多余一条语句,就须要使用大括号括起来,而且使用 return 语句。
// 没有大括号,默认返回表达式结果
let f1 = (a,b) => a + b
f1(1,2) // 3
// 有大括号,无return语句,没有返回值
let f2 = (a,b) => {a + b}
f2(1,2) // undefined
// 有大括号,有return语句,返回结果
let f3 = (a,b) => {return a + b}
f3(1,2) // 3
复制代码
因为大括号被解释为代码块,因此若是箭头函数直接返回一个对象,必须在对象外面加上括号,不然会报错。
//报错
let f1 = num => {num:num}
//不报错
let f2 = num => ({num:num})
复制代码
箭头函数没有[[Construct]]方法,因此不能被用做构造函数。
let F = ()=>{};
// 报错 TypeError: F is not a constructor
let f = new F();
复制代码
因为不能够经过 new 关键字调用,于是没有构建原型的需求,因此箭头函数不存在 prototype 这个属性。
let F = ()=>{};
console.log(F.prototype) // undefined
复制代码
在箭头函数中,不可使用 yield 命令,所以箭头函数不能用做 Generator 函数。
箭头函数中是没有 arguments、super、new.target 的绑定,这些值由外围最近一层非箭头函数决定。
以 arguments 为例,看以下代码:
let f = ()=>console.log(arguments);
//报错
f(); // arguments is not defined
复制代码
因为在全局环境下,定义箭头函数 f,对于 f 来讲,没法获取到外围非箭头函数的 arguments 值,因此此处报错。
再看一个例子:
function fn(){
let f = ()=> console.log(arguments)
f();
}
fn(1,2,3) // [1,2,3]
复制代码
上面的代码,箭头函数 f 内部的 arguments,实际上是函数 fn 的 arguments 变量。
若想在箭头函数中获取不定长度的参数列表,可使用 ES6 中的 rest 参数解决:
let f = (...args)=>console.log(args)
f(1,2,3,4,5) // [1,2,3,4,5]
复制代码
在理解箭头函数中的this指向问题以前,咱们先来回看在 ES5 中的一个例子:
var obj = {
value:0,
fn:function(){
this.value ++
}
}
obj.fn();
console.log(obj.value); // 1
复制代码
这段代码很简单,在每次调用 obj.fn() 时,指望的是将 obj.value 加 1。
如今咱们将代码修改一下:
var obj = {
value:0,
fn:function(){
var f = function(){
this.value ++
}
f();
}
}
obj.fn();
console.log(obj.value); // 0
复制代码
咱们将代码修改了一下,在 obj.fn 方法内增长了一个函数 f ,并将 obj.value 加 1 的动做放到了函数 f 中。可是因为 javascript 语言设计上的一个错误,函数 f 中的 this 并非 方法 obj.fn 中的 this,致使咱们无法获取到 obj.value 。
为了解决此类问题,在 ES5 中,咱们一般会将外部函数中的 this 赋值给一个临时变量(一般命名为 that、_this、self),在内层函数中若但愿使用外层函数的 this 时,经过这个临时变量来获取。修改代码以下:
var obj = {
value:0,
fn:function(){
// 本人喜欢定义为 _this,也有不少人喜欢定义成 that 或 self
var _this = this;
var f = function(){
_this.value ++
}
f();
}
}
obj.fn();
console.log(obj.value); // 1
复制代码
从这个例子中,咱们知道了在 ES5 中如何解决内部函数获取外部函数 this 的办法。
而后咱们来看看箭头函数相对于 ES5 中的函数来讲,它的 this 指向有和不一样。
先看一段定义,来源于ES6标准入门
箭头函数体内的 this 对象就是定义时所在的对象,而不是使用时所在的对象。
那么,如何来理解这句话呢?
咱们尝试用babel来将以下代码转换成 ES5 格式的代码,看看它都作了什么。
function fn(){
let f = ()=>{
console.log(this)
}
}
复制代码
来看看转化后的结果,直接上图:
咱们发现了什么,竟然和咱们以前在 ES5 中解决内层函数获取外层函数 this 的方法同样,定义一个临时变量 _this ~
那么,箭头函数本身的 this 哪里去了?
答案是,箭头函数根本没有本身的 this !
那么,咱们能够总结一下,将晦涩难懂的定义转化成白话文:
让咱们用几个例子,来验证一下咱们总结的规则:
let obj = {
fn:function(){
console.log('我是普通函数',this === obj)
return ()=>{
console.log('我是箭头函数',this === obj)
}
}
}
console.log(obj.fn()())
// 我是普通函数 true
// 我是箭头函数 true
复制代码
从上面的例子,咱们可以看出,箭头函数的 this 与外层函数的 this 是相等的。
在看一个多层箭头函数嵌套的例子:
let obj = {
fn:function(){
console.log('我是普通函数',this === obj)
return ()=>{
console.log('第一个箭头函数',this === obj)
return ()=>{
console.log('第二个箭头函数',this === obj)
return ()=>{
console.log('第三个箭头函数',this === obj)
}
}
}
}
}
console.log(obj.fn()()()())
// 我是普通函数 true
// 第一个箭头函数 true
// 第二个箭头函数 true
// 第三个箭头函数 true
复制代码
在这个例子中,咱们可以知道,对于箭头函数来讲,箭头函数的 this 与外层的第一个普通函数的 this 相等,与嵌套了几层箭头函数无关。
再来看一个没有外层函数的例子:
let obj = {
fn:()=>{
console.log(this === window);
}
}
console.log(obj.fn())
// true
复制代码
这个例子,证实了,在箭头函数外层没有普通函数时,箭头函数的 this 与全局对象相等。
须要注意的是,浏览器环境下全局对象为 window,node 环境下全局对象为 global,验证的时候须要区分一下。
看到这里,相信你们已经知道了,箭头函数中根本没有本身的 this ,那么当箭头函数碰到 call、apply、bind 时,会发生什么呢?
咱们知道,call 和 apply 的做用是改变函数 this 的指向,传递参数,并将函数执行, 而 bind 的做用是生成一个绑定 this 并预设函数参数的新函数。
然而因为箭头函数根本没有本身的 this ,因此:
咱们来验证一下:
window.name = 'window_name';
let f1 = function(){return this.name}
let f2 = ()=> this.name
let obj = {name:'obj_name'}
f1.call(obj) // obj_name
f2.call(obj) // window_name
f1.apply(obj) // obj_name
f2.apply(obj) // window_name
f1.bind(obj)() // obj_name
f2.bind(obj)() // window_name
复制代码
上面代码中,声明了普通函数 f1,箭头函数 f2。
普通函数的 this 指向是动态可变的,因此在对 f1 使用 call、apply、bind 时,f1 内部的 this 指向会发生改变。
箭头函数的 this 指向在其定义时就已肯定,永远不会发生改变,因此在对 f2 使用 call、apply、bind 时,会忽略传入的上下文参数。
在 ES6 的箭头函数出现以前,自执行函数通常会写成这样:
(function(){
console.log(1)
})()
复制代码
或者写成这样:
(function(){
console.log(1)
}())
复制代码
箭头函数固然也能够被用做自执行函数,能够这样写:
(() => {
console.log(1)
})()
复制代码
可是,令大多数人想不到的是,下面这种写法会报错:
(() => {
console.log(1)
}())
复制代码
那么,为何会报错呢?
这个问题,曾困扰了我好久,直到我翻阅了ECMAScript® 2015 规范,从中得知箭头函数是属于 AssignmentExpression 的一种,而函数调用属于 CallExpression,规范中要求当 CallExpression 时,左边的表达式必须是 MemberExpression 或其余的 CallExpression,而箭头函数不属于这两种表达式,因此在编译时就会报错。
原理就是这样了,具体可参见ECMAScript® 2015 规范
在面试中关于箭头函数的考察,主要集中在 arguments 关键字的指向和箭头函数的this指向上,下面几道题目,由浅入深,供你们参考一下。
function foo(n) {
var f = () => arguments[0] + n;
return f();
}
let res = foo(2);
console.log(res); // 问 输出结果
复制代码
答案: 4
箭头函数没有本身的 arguments ,因此题中的 arguments 指代的是 foo 函数的 arguments 对象。因此 arguments[0] 等于 2 ,n 等于 2,结果为 4。
function A() {
this.foo = 1
}
A.prototype.bar = () => console.log(this.foo)
let a = new A()
a.bar() // 问 输出结果
复制代码
答案: undefined
箭头函数没有本身的 this,因此箭头函数的 this 等价于外层非箭头函数做用域的this。 因为箭头函数的外层没有普通函数,因此箭头函数中的 this 等价于全局对象,因此输出为 undefined。
let res = (function pt() {
return (() => this.x).bind({ x: 'inner' })();
}).call({ x: 'outer' });
console.log(res) // 问 输出结果
复制代码
答案:'outer'
此题稍微复杂一点,求 res 的输出结果。
分析以下:
window.name = 'window_name';
let obj1 = {
name:'obj1_name',
print:()=>console.log(this.name)
}
let obj2 = {name:'obj2_name'}
obj1.print() // 问 输出结果
obj1.print.call(obj2) // 问 输出结果
复制代码
答案:'window_name' 'window_name'
箭头函数没有本身的 this ,也没法经过 call、apply、bind 改变箭头函数中的 this。 箭头函数的 this 取决于外层是否有普通函数,有普通函数 this 指向普通函数中的this,外层没有普通函数,箭头函数中的 this 就是全局对象。
此题中,箭头函数外层没有普通函数,因此 this 指向全局对象,因此结果为 'window_name'、'window_name'。
let obj1 = {
name:'obj1_name',
print:function(){
return ()=>console.log(this.name)
}
}
let obj2 = {name:'obj2_name'}
obj1.print()() // 问 输出结果
obj1.print().call(obj2) // 问 输出结果
obj1.print.call(obj2)() // 问 输出结果
复制代码
答案: 'obj1_name' 'obj1_name' 'obj2_name'
箭头函数的 this 与其外层的普通函数的 this 一致,与 call、apply、bind 无关。
此题,obj1.print 返回一个箭头函数,此箭头函数中的 this 就是 obj1.print 调用时的 this。
欢迎关注微信公众号
【前端小黑屋】
,每周1-3篇精品优质文章推送,助你走上进阶之旅