Blog icon indicating copy to clipboard operation
Blog copied to clipboard

ES6 系列之箭头函数

Open mqyqingfeng opened this issue 7 years ago • 23 comments

回顾

我们先来回顾下箭头函数的基本语法。

ES6 增加了箭头函数:

let func = value => value;

相当于:

let func = function (value) {
    return value;
};

如果需要给函数传入多个参数:

let func = (value, num) => value * num;

如果函数的代码块需要多条语句:

let func = (value, num) => {
    return value * num
};

如果需要直接返回一个对象:

let func = (value, num) => ({total: value * num});

与变量解构结合:

let func = ({value, num}) => ({total: value * num})

// 使用
var result = func({
    value: 10,
    num: 10
})

console.log(result); // {total: 100}

很多时候,你可能想不到要这样用,所以再来举个例子,比如在 React 与 Immutable 的技术选型中,我们处理一个事件会这样做:

handleEvent = () => {
  this.setState({
    data: this.state.data.set("key", "value")
  })
};

其实就可以简化为:

handleEvent = () => {
  this.setState(({data}) => ({
    data: data.set("key", "value")
  }))
};

比较

本篇我们重点比较一下箭头函数与普通函数。

主要区别包括:

1.没有 this

箭头函数没有 this,所以需要通过查找作用域链来确定 this 的值。

这就意味着如果箭头函数被非箭头函数包含,this 绑定的就是最近一层非箭头函数的 this。

模拟一个实际开发中的例子:

我们的需求是点击一个按钮,改变该按钮的背景色。

为了方便开发,我们抽离一个 Button 组件,当需要使用的时候,直接:

// 传入元素 id 值即可绑定该元素点击时改变背景色的事件
new Button("button")

HTML 代码如下:

<button id="button">点击变色</button>

JavaScript 代码如下:

function Button(id) {
    this.element = document.querySelector("#" + id);
    this.bindEvent();
}

Button.prototype.bindEvent = function() {
    this.element.addEventListener("click", this.setBgColor, false);
};

Button.prototype.setBgColor = function() {
    this.element.style.backgroundColor = '#1abc9c'
};

var button = new Button("button");

看着好像没有问题,结果却是报错 Uncaught TypeError: Cannot read property 'style' of undefined

这是因为当使用 addEventListener() 为一个元素注册事件的时候,事件函数里的 this 值是该元素的引用。

所以如果我们在 setBgColor 中 console.log(this),this 指向的是按钮元素,那 this.element 就是 undefined,报错自然就理所当然了。

也许你会问,既然 this 都指向了按钮元素,那我们直接修改 setBgColor 函数为:

Button.prototype.setBgColor = function() {
    this.style.backgroundColor = '#1abc9c'
};

不就可以解决这个问题了?

确实可以这样做,但是在实际的开发中,我们可能会在 setBgColor 中还调用其他的函数,比如写成这种:

Button.prototype.setBgColor = function() {
    this.setElementColor();
    this.setOtherElementColor();
};

所以我们还是希望 setBgColor 中的 this 是指向实例对象的,这样就可以调用其他的函数。

利用 ES5,我们一般会这样做:

Button.prototype.bindEvent = function() {
    this.element.addEventListener("click", this.setBgColor.bind(this), false);
};

为避免 addEventListener 的影响,使用 bind 强制绑定 setBgColor() 的 this 为实例对象

使用 ES6,我们可以更好的解决这个问题:

Button.prototype.bindEvent = function() {
    this.element.addEventListener("click", event => this.setBgColor(event), false);
};

由于箭头函数没有 this,所以会向外层查找 this 的值,即 bindEvent 中的 this,此时 this 指向实例对象,所以可以正确的调用 this.setBgColor 方法, 而 this.setBgColor 中的 this 也会正确指向实例对象。

在这里再额外提一点,就是注意 bindEvent 和 setBgColor 在这里使用的是普通函数的形式,而非箭头函数,如果我们改成箭头函数,会导致函数里的 this 指向 window 对象 (非严格模式下)。

最后,因为箭头函数没有 this,所以也不能用 call()、apply()、bind() 这些方法改变 this 的指向,可以看一个例子:

var value = 1;
var result = (() => this.value).bind({value: 2})();
console.log(result); // 1

2. 没有 arguments

箭头函数没有自己的 arguments 对象,这不一定是件坏事,因为箭头函数可以访问外围函数的 arguments 对象:

function constant() {
    return () => arguments[0]
}

var result = constant(1);
console.log(result()); // 1

那如果我们就是要访问箭头函数的参数呢?

你可以通过命名参数或者 rest 参数的形式访问参数:

let nums = (...nums) => nums;

3. 不能通过 new 关键字调用

JavaScript 函数有两个内部方法:[[Call]] 和 [[Construct]]。

当通过 new 调用函数时,执行 [[Construct]] 方法,创建一个实例对象,然后再执行函数体,将 this 绑定到实例上。

当直接调用的时候,执行 [[Call]] 方法,直接执行函数体。

箭头函数并没有 [[Construct]] 方法,不能被用作构造函数,如果通过 new 的方式调用,会报错。

var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor

4. 没有 new.target

因为不能使用 new 调用,所以也没有 new.target 值。

关于 new.target,可以参考 http://es6.ruanyifeng.com/#docs/class#new-target-%E5%B1%9E%E6%80%A7

5. 没有原型

由于不能使用 new 调用箭头函数,所以也没有构建原型的需求,于是箭头函数也不存在 prototype 这个属性。

var Foo = () => {};
console.log(Foo.prototype); // undefined

6. 没有 super

连原型都没有,自然也不能通过 super 来访问原型的属性,所以箭头函数也是没有 super 的,不过跟 this、arguments、new.target 一样,这些值由外围最近一层非箭头函数决定。

总结

最后,关于箭头函数,引用 MDN 的介绍就是:

An arrow function expression has a shorter syntax than a function expression and does not have its own this, arguments, super, or new.target. These function expressions are best suited for non-method functions, and they cannot be used as constructors.

翻译过来就是:

箭头函数表达式的语法比函数表达式更短,并且不绑定自己的this,arguments,super或 new.target。这些函数表达式最适合用于非方法函数(non-method functions),并且它们不能用作构造函数。

那么什么是 non-method functions 呢?

我们先来看看 method 的定义:

A method is a function which is a property of an object.

对象属性中的函数就被称之为 method,那么 non-mehtod 就是指不被用作对象属性中的函数了,可是为什么说箭头函数更适合 non-method 呢?

让我们来看一个例子就明白了:

var obj = {
  i: 10,
  b: () => console.log(this.i, this),
  c: function() {
    console.log( this.i, this)
  }
}
obj.b();
// undefined Window
obj.c();
// 10, Object {...}

自执行函数

自执行函数的形式为:

(function(){
    console.log(1)
})()

或者

(function(){
    console.log(1)
}())

利用箭头简化自执行函数的写法:

(() => {
    console.log(1)
})()

但是注意:使用以下这种写法却会报错:

(() => {
    console.log(1)
}())

为什么会报错呢?嘿嘿,如果你知道,可以告诉我~

ES6 系列

ES6 系列目录地址:https://github.com/mqyqingfeng/Blog

ES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

mqyqingfeng avatar Jun 04 '18 05:06 mqyqingfeng

跟着大佬学习,少走弯路

heyunjiang avatar Jun 05 '18 01:06 heyunjiang

this 指向的是按钮元素,那 this.element 就是 undefined

这里说 this 就是 this.element 会更好一点

savoygu avatar Jun 06 '18 06:06 savoygu

大佬大佬,产出那么恐怖的吗?才刚刚看完一篇

silent-tan avatar Jun 06 '18 06:06 silent-tan

鼓掌

liuxinqiong avatar Jun 07 '18 03:06 liuxinqiong

我要学习 我渴望学习 我爱学习!!!

wuyunqiang avatar Jun 09 '18 03:06 wuyunqiang

一如前端深似海,文章何时能写完……

default

mqyqingfeng avatar Jun 20 '18 12:06 mqyqingfeng

关于文章最后的疑问,感谢 SF 的 happy007:

default

mqyqingfeng avatar Jul 12 '18 05:07 mqyqingfeng

箭头函数还是很方便的。

Lanveer avatar Aug 10 '18 09:08 Lanveer

发现好多问题从规范的角度理解,一下就解释通了 :smile_cat:

XuToTo avatar Oct 18 '18 08:10 XuToTo

小哥哥确实想的更深一层

yanyixin avatar Dec 02 '18 11:12 yanyixin

但是注意:使用以下这种写法却会报错:

(() => { console.log(1) }()) 为什么会报错呢?嘿嘿,如果你知道,可以告诉我~

等价于这样子的: (function f(){console.log(1)}())

function f(){ console.log(1) }()

lishihong avatar Dec 11 '18 03:12 lishihong

为什么会报错呢 你得上个括号:

((() => {
    console.log(1)
})())

KaiOrange avatar Jan 03 '19 06:01 KaiOrange

关于Assignment Expression和Call Expression,可以看这里的介绍

zhoubhin avatar Jan 31 '19 06:01 zhoubhin

image 有个疑问,对于总结这一块标红的区域。首先还是没太明白methodnon-method的区别。作者给的例子很容易看懂,但看完后并不是很清楚:为什么箭头函数更适合 non-method 呢?

sfsoul avatar Jul 29 '19 03:07 sfsoul

image 有个疑问,对于总结这一块标红的区域。首先还是没太明白methodnon-method的区别。作者给的例子很容易看懂,但看完后并不是很清楚:为什么箭头函数更适合 non-method 呢?

因为它内部this的指向原因,当使用obj.b()的时候,很明显我们希望b方法里面的this指向obj,但是它却指向了obj所在上下文中的this(即window),违背了我们的意愿,所以箭头函数不适合作为对象的方法。这也是为什么vue组件里面方法不允许使用箭头函数的原因。

feiying-tf avatar Aug 05 '19 08:08 feiying-tf

感觉最后一个问题就是符号运算优先级的问题. 函数的定义应该是个整体, 然后再自运行这个函数. 否则就会是语法错误

geforcesong avatar Aug 26 '19 17:08 geforcesong

image 有个疑问,对于总结这一块标红的区域。首先还是没太明白methodnon-method的区别。作者给的例子很容易看懂,但看完后并不是很清楚:为什么箭头函数更适合 non-method 呢?

因为它内部this的指向原因,当使用obj.b()的时候,很明显我们希望b方法里面的this指向obj,但是它却指向了obj所在上下文中的this(即window),违背了我们的意愿,所以箭头函数不适合作为对象的方法。这也是为什么vue组件里面方法不允许使用箭头函数的原因。

很感谢你的回答,懂啦!

sfsoul avatar Feb 03 '20 09:02 sfsoul

关于文章最后的疑问,感谢 SF 的 happy007:

default

还是不太明白 楼主

EayCome avatar Mar 21 '20 16:03 EayCome

受益匪浅

BaoGuoSen avatar Apr 07 '20 14:04 BaoGuoSen

可以这样理解吗,我自己来捋下、 1.js中的函数遵守词法作用域,及定义时 2.普通函数中的this有比较特殊,遵守四大绑定规则,及调用时绑定this指向(有点类似动态作用域,‘’你不知道的JavaScirpt‘’也是这么描述的) 3.箭头函数有不一样,不创建局部this对象,通过词法作用域(及箭头函数定义时)寻找上级作用域(最近一层普通函数)的this

liutao2428118 avatar Dec 01 '20 02:12 liutao2428118

关于文章最后的疑问,感谢 SF 的 happy007: default

还是不太明白 楼主

我想你得疑问应该是不清楚 表达式(expression) 的定义,可以看看规范的这一章,详细的说明了不同的语法分别属于什么 表达式 https://262.ecma-international.org/6.0/#sec-left-hand-side-expressions

morningbao avatar Apr 30 '21 10:04 morningbao

坚持每日进步 ✊ 感谢大佬🙏

isJx avatar Sep 01 '22 07:09 isJx

关于文章最后的疑问,感谢 SF 的 happy007: default

还是不太明白 楼主

函数调用是 CallExpression,CallExpression 要求左侧必须是 MemberExpression 或 CallExpression,比如 obj.fn()fn()(),而箭头函数是 AssignmentExpression,不符合要求所以报错。

但是我又不太理解为什么匿名函数就可以 😂

(function(){
    console.log(1)
}())

justorez avatar Feb 08 '23 02:02 justorez