年味儿渐散,收拾下心情,继续敲代码吧。前端
对于即将到来金三银四的求职季,相信很多同窗都在默默地作着准备。本系列旨在梳理前端庞杂的知识点,并尽量通俗易懂地表述出来,也但愿能帮到有须要的同窗。react
这是前端面试题系列的第 5 篇,你可能错过了前面的篇章,能够在这里找到:面试
面试中,我常常会问及 ES6 的知识点,由于平时工做中用得不少。当问到箭头函数时,很多候选人都会赞叹地说:箭头函数很好用,并且不再用操心 this 的指向了。segmentfault
我接着问:箭头函数是挺好用的,可是你有没有遇到过,不适合使用箭头函数的场景呢?浏览器
这时,能回答得上来的候选人就不多了。箭头函数在大多数状况下,是很好用的,可是为何在有些场景,使用箭头函数后会产生问题?是否是箭头函数还不够完善?又有哪些场景会发生问题?该如何解决呢?这,正是本文想要一块儿探讨的。函数
为何叫箭头函数( Arrow Function )?由于它的写法,看上去就是一个箭头:布局
const multiply = num => num * num;
它等价于:this
const multiply = function (num) { return num * num; };
此外,还能够传多个参数,以及可变参数。spa
// 多参数 const multiply = (num1, num2) => num1 * num2; // 可变参数 const sum = (num1, num2, ...rest) => { let result = num1 + num2; for (let i = 0; i < rest.length; i++) { result += rest[i]; } return result; };
当有多条语句时,须要配上 {...}
和 return
。prototype
另外,若是返回的结果是对象,则须要配上 ()
,像下面这样:
const func = val => ({ value: val });
从上述的写法来看,相较普通函数而言,箭头函数的确简便了不少,提高了咱们代码的易用性。但它并不是在任何场景下都适用,接下来,将会介绍几种不适合箭头函数的场景,并会提出可行的解决方案。
看下面这个例子:
const obj = { x: 1, print: () => { console.log(this === window); // => true console.log(this.x); // undefined } }; obj.print();
this.x 打印出来是 undefined。为何?而后,我在上面加了一行,发现 this 指向了 window。
解析:print 方法用了箭头函数,其内部的 this 指向的仍是上下文 window,上下文中并无定义 x,因此 this.x 输出为 undefined。
解决办法:用 ES6 的短语法,或者传统的函数表达式均可以。因此,print 要这样写:
print () { console.log(this === test); // => true console.log(this.x); // 1 }
一样的规则也适用于原型方法的定义,使用箭头函数会致使运行时的执行上下文错误。
function Cat (name) { this.name = name; } Cat.prototype.sayCatName = () => { console.log(this === window); // => true return this.name; }; const cat = new Cat('Miao'); cat.sayCatName(); // => undefined
解决办法是:用回传统的函数表达式,像下面这样:
Cat.prototype.sayCatName = function () { console.log(this === cat); // => true return this.name; };
sayCatName 变回传统的函数表达式以后,被调用时的执行上下文就会指向新建立的 cat 实例。
看下面这个例子:
const btn = document.getElementById('myButton'); btn.addEventListener('click', () => { console.log(this === window); // => true this.innerHTML = 'Clicked button'; });
这里会有问题,由于 this 指向了 window。
解析:当为一个 DOM 事件绑定回调函数后,触发回调函数时的 this,须要指向当前发生事件的 DOM 节点,也就是这里的 btn。当回调发生时,浏览器会用 btn 的上下文去调用处理函数。因此最后的 this.innerHTML 等价于 window.innerHTML,问题就在这里。
解决办法:用函数表达式代替箭头函数。像这样:
btn.addEventListener('click', function() { console.log(this === btn); // => true this.innerHTML = 'Clicked button'; });
另外,在 react 中的事件回调,也常常会遇到相似的问题。
// jsx render <Button onClick={this.handleClickButton.bind(this)}> ... </Button> // callback handleClickButton () { ... }
注意:这里 onClick 的回调函数,并不是字符串,而是一个实实在在的函数。能够将 onClick 理解为一个中间变量,因此 react 在处理函数时的 this 指向就会丢失。
为了解决这个问题,咱们须要为回调函数绑定 this,使得事件处理函数不管如何传递,this 都指向咱们实例化的那个对象。
在这里,若是用箭头函数,能够这样改写:
<Button onClick={ event => this.handleClickButton(event) }> ... </Button>
箭头函数并无本身的 this,因此事件处理函数的调用者并不受影响。
箭头函数不能经过 new 关键字调用。
const Message = (text) => { this.text = text; }; var helloMessage = new Message('Hello World!'); // Uncaught TypeError: Message is not a constructor
解析:从报错信息能够看出,箭头函数没有 constructor 方法,因此不能用做构造函数。 JavaScript 会经过抛出异常的方式,进行隐式地预防。
解决方法:用函数表达式代替箭头函数。
回顾 MDN 给出的解释:箭头函数表达式的语法比函数表达式更短,而且没有本身的this,arguments,super或 new.target。这些函数表达式更适用于那些原本须要匿名函数的地方,而且它们不能用做构造函数。
因此说,箭头函数无疑是 ES6 带来的重大改进,在正确的场合使用箭头函数,能让代码变得更加简洁短小。但箭头函数也不是万能的,不能用的时候,千万别硬往上套。好比,在须要动态上下文的场景中,使用箭头函数须要格外地当心,这些场景包括:对象的方法、原型方法、事件的回调、构造函数。并不是必定要用箭头函数,才能解决问题。
PS:欢迎关注个人公众号 “超哥前端小栈”,交流更多的想法与技术。