signed

QiShunwang

“诚信为本、客户至上”

ES6 箭头函数、回调函数,和 This

2021/3/21 0:46:40   来源:

普通函数基本用法

function fn(name,age=17){
 console.log(name+","+age);
}
fn("Amy",18);  // Amy,18
fn("Amy","");  // Amy,
fn("Amy");     // Amy,17

只有在未传递参数,或者参数为 undefined 时,才会使用默认参数,null 值被认为是有效的值传递。

function fn(name,age=17){
    console.log(name+","+age);
}
fn("Amy",null); // Amy,null

不定参数:不定参数用来表示不确定参数个数,形如,...变量名,由 ... 加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。

function f(...values){
    console.log(values.length);
}
f(1,2);      // 2
f(1,2,3,4);  // 4

this 和箭头函数

箭头函数提供了一种更加简洁的函数书写方式。基本语法是:

参数 => 函数体

var f = v => v;
// 等价于
var f = function(v){
 return v;
}
f(1);  // 1
  • 当箭头函数没有参数或者有多个参数,要用 () 括起来。

    var f = (a,b) => {
     let result = a+b;
     return result;
    }
    f(6,2);  // 8
    
  • 当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来。

    // 报错
    var f = (id,name) => {id: id, name: name};
    f(6,2);  // SyntaxError: Unexpected token :
     
    // 不报错
    var f = (id,name) => ({id: id, name: name});
    f(6,2);  // {id: 6, name: 2}
    

学习如何在 JavaScript 里正确使用 this 就好比一场成年礼。

JavaScript 里,this 的值在普通函数被调用的时候才会指定(箭头函数恰恰相反)。这是个既强大又灵活的特点,但是你需要花点时间弄清楚函数调用的上下文是什么。但众所周知,这不是一件很简单的事,尤其是在返回一个函数或将函数当做参数传递的时候。

  • 箭头函数能保存函数创建时的 this 值,而不是调用时的值。

    function fn(){
      setTimeout(()=>{
        // 定义时,this 绑定的是 fn 中的 this 对象
        console.log(this.a);
      },0)
    }
    var a = 20;
    // fn 的 this 对象为 {a: 19}
    fn.call({a: 18});  // 18
    
    • 箭头函数根本没有自己的 this,导致箭头函数体内部的 this 就是外层函数体的 this
    • ES6 之前,JavaScript 的 this 对象一直很令人头大,回调函数,经常看到 var self = this 这样的代码,为了将外部 this 传递到回调函数中,那么有了箭头函数,就不需要这样做了,直接使用 this 就行。
    /**普通回调函数 */
    function fn1() {
        // var self = this
        setTimeout(function() {
            console.log("id:", this.id)
        }, 0)
    }
    
    fn1.call({id: 12})  // 如果注释掉 `var self = this`, 则:id: undefined
    
    /**箭头函数 */
    var age = 20;
    var Person1 = {
        'age': 18,
        'sayHello': function () {
          setTimeout(()=>{
            console.log(this.age);
          });
        }
    };
    
    Person1.sayHello();  // 18
    
  • 下面看一个例子

    let deck = {
        suits: ["hearts", "spades", "clubs", "diamonds"],
        cards: Array(52),
        createCardPicker: function() {
            return function() {
                let pickedCard = Math.floor(Math.random() * 52);
                let pickedSuit = Math.floor(pickedCard / 13);
    
                return {suit: this.suits[pickedSuit], card: pickedCard % 13};
            }
        }
    }
    
    let cardPicker = deck.createCardPicker();
    let pickedCard = cardPicker();
    
    console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
    

    可以看到 createCardPicker 是个函数,并且它又返回了一个函数。 如果我们尝试运行这个程序,会发现它并没有弹出对话框而是报错了。 因为 createCardPicker 返回的函数里的 this 被设置成了 window 而不是 deck 对象。 因为我们只是独立的调用了 cardPicker()。 顶级的非方法式调用会将 this 视为 window。 (注意:在严格模式下,thisundefined 而不是 window)。

    为了解决这个问题,我们可以在函数被返回时就绑好正确的 this。 这样的话,无论之后怎么使用它,都会引用绑定的 deck 对象。 我们需要改变函数表达式来使用 ECMAScript 6 箭头语法。 箭头函数能保存函数创建时的 this 值,而不是调用时的值:

    let deck = {
        suits: ["hearts", "spades", "clubs", "diamonds"],
        cards: Array(52),
        createCardPicker: function() {
            // NOTE: 箭头函数能保存函数创建时的 `this` 值
            return () => {
                let pickedCard = Math.floor(Math.random() * 52);
                let pickedSuit = Math.floor(pickedCard / 13);
    
                return {suit: this.suits[pickedSuit], card: pickedCard % 13};
            }
        }
    }
    
    let cardPicker = deck.createCardPicker();
    let pickedCard = cardPicker();
    
    console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
    

    还有:

    class Handler {
        info: string;
        onClickGood = (e: Event) => { this.info = e.message }
    }
    
    let h = new Handler();
    uiElement.addClickListener(h.onClickGood);
    

箭头函数不适用的场景

  • 在对象里定义的函数,且该函数内部包括 this 的时候,不适用。

    若对象里面的函数使用箭头函数。

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

    若对象里面的函数使用普通函数。

    const cat = {
        lives: 9,
        jumps() {
            this.lives--;
        }
    };
    
    cat.jumps();
    console.log(cat.lives);     // 8
    

    第一个代码中,cat.jumps() 方法是一个箭头函数,这是错误的。调用 cat.jumps() 时,如果是普通函数,该 this 指向 cat 对象;如果写成上面那样的箭头函数,使得 this 指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致 jumps 箭头函数定义时指的是 window。

  • 回调函数(callback)里的 this 参数

    当你将一个函数传递到另一个函数里调用时,你可能见到过回调函数里的 this 会报错。 因为当回调函数被调用时,它会被当成一个普通函数调用,this 将为 undefined 或者 window

    var clientData = {
        id: 096545,
        fullName: "Not Set",
        // setUsrName 是一个在 clientData 对象中的方法
        setUserName: function (firstName, lastName){
            this.fullName = firstName + " " + lastName;
        }
    } 
    
    function getUserInput(firstName, lastName, callback){
        // 调用回调函数
        callback(firstName, lastName);
    }
    
    getUserInput("Barack","Obama",clientData.setUserName);
    
    console.log(clientData.fullName);  // Not Set
    
    console.log(window.fullName);  // Barack Obama
    

    使用 CallApply 函数来改变 this 指向:

    每个 Javascript 中的函数都有两个方法:callapply。这些方法被用来设置函数内部的 this 对象以及给此函数传递变量。

    这里我们演示 apply 函数实现,call 函数类似。(call 接收的第一个参数默认被用来在函数内部当做 this 的对象(类似 python 类方法的 self),传递给函数的其他参数被挨个传递。)

    注:apply 函数的第一个参数也是在函数内部作为 this 的对象,然而后面的参数却是传递给函数的参数组成的数组。

    //注意到我们增加了新的参数作为回调对象,叫做“callbackObj”
    function getUserInput(firstName, lastName, callback ,callbackObj){
             //code .....
    
            callback.apply(callbackObj, [firstName, lastName]);
            //callback.call(callbackObj, firstName, lastName);
    }
    
    getUserInput("Barack", "Obama", clientData.setUserName, clientData);
    
    console.log(clientData.fullName); //Barack Obama
    
  • 需要动态this时,也不应该使用动态函数。

    var btn = document.getElementById('btn');
    btn.addEventListener('click', () => {
      console.log(this);
    });
    

    因为 btn 的监听函数是一个箭头函数,导致里面的 this 就是全局对象,而不符合我们想操作按钮本身的需求。如果改成普通函数,this 就会动态指向被点击的按钮对象。


参考:

  • 菜鸟教程:ES6 函数
  • 箭头函数需要注意的点
  • 📖TypeScript 中文手册:函数(function)
  • ⭐️JavaScript中的回调函数(callback)echozzh