2020年大前端面试题库+答案(第四章)关于箭头函数

405 阅读6分钟

1.基本语法回顾

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

    var f = v => v
    
    //等同于
    var f = function (v){ return v;}

如果箭头函数不需要参数或者需要多个参数,就使用一个圆括号代表参数部分。

    var f = () => 5;
    //等同于
    var f = function () {return 5};
    
    var sum = (num1,num2) => num1 + num2;
    //等同于
    var sum = function(num1,num2) {
        return num1 + num2;
    }

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则就会报错

    let getTempItem = id => {id : id, name : 'lcy' }; //报错
    let getTempItem = id => ({id : id, name : 'lcy'}); //不报错

下面是一种特殊情况,虽然可以运行,但会得到错误的结果。

    let foo = () => { a : 1};
    foo() //undefined

上面代码中,原始意图是返回一个对象{a:1},但是由于引擎认为大括号是代码块,所以执行了一行代码a:1。这时,a可以被解释为语句的标签,因此实际执行的语句是1;然后函数就结束了,没有返回值。

关于作用域
  箭头函数内定义的变量及其作用域  
  
  //常规写法
  var greeting = () => {
    let now = new Date()
    return ("good" + ((now.getHours() > 17) ? "evening." : "day."))  
  }
  greeting() // "good day."
  console.log(now) //ReferenceError: now is not defined 标准的let作用域
  
  //参数括号内定义的变量是局部变量(默认参数)
  var preson = (now = new Date()) => "good" + (now.getHours() > 17 ? "evening." : "day.")
  preson(); //good day.
  console.log(now); // ReferenceError: now is not defined
  
  //对比:函数体内{}不使用 var 定义的变量是全局变量
  var preson = () => {now = new Date(); 
    return ("good" + ((now.getHours() > 17) ? "evening." : "day."))
  }
  preson(); //"good day."
  console.log(now);  // Fri Dec 22 2017 10:01:00 GMT+0800 (中国标准时间)
  
  // 对比:函数体内{}用var 定义的变量是局部变量
  var preson = () => {
      var now = new Data();
      return ("good" +((now.getHours() > 17) ? "evenings" : "day."))
  }
  preson() //"good day."
  console.log(now) //ReferenceError: now is not defined

2.关于this

2.1 默认绑定外层this

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

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

    function foo(){
        setTimeout(() => {
            console.log(id,this.id);
        },100);
    }
    var id = 21;
    foo.call({id : 42}) //id:42

上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到100毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id:42})所以输入结果是42。

箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。

所以箭头函数转换成ES5的代码如下。

    //es6
    function foo(){
        setTimeout(() => {console.log('id:',this.id);},100)
    }
    //es5
    function foo(){
        var _this = this;
        setTimeout(function () {console.log('id:',_this.id)},100);
    }

2.2 不能用call()、apply()、bind()方法修改里面的this

(function () {
    return [
        (() => this.x).bind({x:'inner'})() //无效的bind,最终this还是指向外层
    ]
}).call({x:'outer'})   //['outer']

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

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

    function constant(){
        return () => arguments[0]
    }
    var result = constant(1);
    console.log(result());  //1

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

我们可以通过明明参数或者rest参数的形式访问参数:

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

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

javascript函数有两个内部方法:[[Call]][[Construct]]. 当通过new调出函数时,执行[Construct]方法,创建一个实例对象,然后再执行函数体,将this绑定到实例上。

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

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

    let foo1 = () => {};
    let foo2 = new foo1(); //TypeError: foo1 is not a constructor

5.没有原型

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

    let foo = () => {};
    console.log(foo.prototype); //undefined

不适用场景

第一个场景是定义函数的方法,且该方法内部包括this。

const cat = {lives:9,jumps:() => {this.lives--;}}

上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps()时,如果是普通函数,这个方法内部的this指向cat;可是写成上面这样的箭头函数,使得this指向全局对象,因此不会得到预期的结果。

第二个场景是需要动态this的时候,也不应使用箭头函数。

var btn = document.getElementById('submit');
btn.addEventListener('click', () => {
    this.classList.toggle('on');
})

上面代码运行时,点击按钮会报错,因为btn的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。

6.使用场景

下面这个是我们开发中经常遇到的代码,我们一般会通过this赋值给一个变量,然后再通过变量访问。

    class Test{
        constuctor(){
            this.birth = 10;
        }
        submit(){
            let self = this;
            $.ajax({
                type:"POST",
                dataType:"json",
                url:"xxxx",
                data:"xxxxx",
                success:function(result){
                    console.log(self.birth); //10
                },
                error:function(){}
            })
        }
    }
    let text = new Test()
    text.submit() // undefined
    
    这里我们就可以通过箭头函数来解决
    .......
    success:(result) => {
        console.log(this.birth) //10
    }
    ......

箭头函数在react中的运用场景

    class foo extends Component{
        constructor(props){
            super(props)
        }
        handleClick(){
            console.log('Click happened',this);
            this.setState({a:1})
        }
        render(){
            return <button onClick={this.handleClick}>点击我</button>
        }
    }

这里通过this.handleClick.bind(this)给函数绑定this。但是这样写起来有些麻烦,有没有简单的方法呢?这时候箭头函数就该出场了

    class foo extends Component{
       // Note: this syntax is experimental and not standardized yet.
        handleClick = () => {
            console.log('Click happened',this);
            this.setState({a:1})
        }
        render(){
            return <button onClick={this.handleClick}>点击我</button>
        }
    }

箭头函数中this的值是继承自身外围作用域,很好的解决了这个问题

除此之外我们还可以用箭头函数传参(这个不是必须的),而且会有性能问题。具体可参考这里:reactjs.org/docs/faq-fu…

    const A = 65 // ASCII character code
    
    class Alphabet extends React.Component {
      constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
        this.state = {
          justClicked: null,
          letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i)).
        };
      }
      handleClick(letter) {
        this.setState({ justClicked: letter });
      }
      render() {
        return (
          <div>
            Just clicked: {this.state.justClicked}
            <ul>
              {this.state.letters.map(letter =>
                <li key={letter} onClick={() => this.handleClick(letter)}>
                  {letter}
                </li>
              )}
            </ul>
          </div>
        )
      }
    }

总结:

  • 没有this
  • 不能绑定arguments, 用rest参数 ... 解决
  • 捕捉其所在的上下文的this值,作为自己的this
  • 箭头函数没有原型属性
  • 箭头函数不能当作Generator函数,不能使用yield关键字

箭头函数与传统的函数表达式有很大不同。它们的一些特性,在作为回调函数时非常好用。但是它们很难用做对象方法和构造函数。还有一些其他的区别,例如,箭头函数不能是生成器。可以看去MDN上看到更多细节。

本文摘自:zhuanlan.zhihu.com/p/62482741