JavaScript:箭头函数与this关键词

2021-04-06

JavaScript:箭头函数与this关键词

那么,到底是什么=>?

你可能在这里或那里见过这些奇怪的埃及象形文字符号,尤其是在别人的代码中,你正在调试一个“this”关键字问题。经过一个小时的修补,你现在漫游谷歌搜索栏和跟踪堆栈溢出。听起来熟悉吗?

让我一起来讨论3个主题:

  • “this”关键字与=>的关系
  • 如何将函数从ES5迁移到ES6。
  • 使用=>时要注意的重要技巧。

箭头函数

创建箭头函数是为了简化函数的作用域,并使用“this”关键字更加简单。它们使用看起来像箭头的=>;语法。尽管我不认为它需要节食,但人们称之为“胖箭头”(Ruby爱好者可能更了解它为“散列火箭”)——这是需要注意的。

“this”关键字与箭头函数的关系

在深入研究ES6 arrow函数之前,首先要清楚地了解ES5代码中的“this”绑定到什么

如果“this”关键字位于对象的方法(属于对象的函数)中,那么它将指什么?

1
2
3
4
5
6
7
8
9
// Test it here: <https://jsfiddle.net/maasha/x7wz1686/>
var bunny = {
name: 'Usagi',
showName: function() {
alert(this.name);
}
};

bunny.showName(); // Usagi

对的!它指的是那个对象(bunny)。我们稍后再讨论原因。

如果’this’关键字在方法的函数中呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Test it here: <https://jsfiddle.net/maasha/z65c1znn/>
var bunny = {
name: 'Usagi',
tasks: ['transform', 'eat cake', 'blow kisses'],
showTasks: function() {
this.tasks.forEach(function(task) {
alert(this.name + " wants to " + task);
});
}
};

bunny.showTasks();
// [object Window] wants to transform
// [object Window] wants to eat cake
// [object Window] wants to blow kisses

// please note, in jsfiddle the [object Window] is named 'result' within inner functions of methods.

你得到了什么?等等,我们的bunny怎么了…?

啊,你认为‘this’是指方法的内部函数吗?

也许是对象本身?

你这么想是明智的,但事实并非如此。请允许我教你老程序员曾经教过我的:

老程序员:“啊,是的,这个代码很强,认为’this’关键字绑定到函数确实是可行的,但事实是,’this’现在已经超出了范围…它现在属于…”。他停顿了一下,好像经历了内心的混乱,“window对象”

没错。事情就是这样发生的。

“this”为什么绑定到window对象?因为“this”总是引用它所在的函数的所有者,在这种情况下,因为它现在超出了范围,所以是window/global对象。

当它位于对象的方法内部时,函数的所有者就是该对象。因此,“this”关键字被绑定到该对象。然而,当它在一个函数内部时,不管是独立的还是在另一个方法中,它总是引用window/global对象。

1
2
3
4
5
6
// Test it here: <https://jsfiddle.net/maasha/g278gjtn/>
var standAloneFunc = function(){
alert(this);
}

standAloneFunc(); // [object Window]

但是为什么…?

这被称为Java脚本怪癖,这意味着Java脚本中发生的事情并不完全简单,而且它的工作方式不符合您的想法。开发人员也认为这是一个糟糕的设计选择,他们现在正在用ES6的箭头函数来重新定义它。

在我们继续之前,重要的是要了解程序员解决ES5代码中“this”问题的两种灵巧方法,特别是因为您将继续在ES5中运行一段时间(并非所有浏览器都已完全迁移到ES6):

  1. 在方法的内部函数之外创建一个变量。现在,“for Each”方法获得了对“this”的访问,从而获得对象的属性及其值。这是因为“This”存储在变量中,而它仍然在对象的直接方法“showTasks”的范围内。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Test it here: <https://jsfiddle.net/maasha/3mu5r6vg/>
var bunny = {
name: 'Usagi',
tasks: ['transform', 'eat cake', 'blow kisses'],
showTasks: function() {
var _this = this;
this.tasks.forEach(function(task) {
alert(_this.name + " wants to " + task);
});
}
};

bunny.showTasks();
// Usagi wants to transform
// Usagi wants to eat cake
// Usagi wants to blow kisses
  1. 使用bind将引用该方法的“this”关键字附加到该方法的内部函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Test it here: <https://jsfiddle.net/maasha/u8ybgwd5/>
var bunny = {
name: 'Usagi',
tasks: ['transform', 'eat cake', 'blow kisses'],
showTasks: function() {
this.tasks.forEach(function(task) {
alert(this.name + " wants to " + task);
}.bind(this));
}
};

bunny.showTasks();
// Usagi wants to transform
// Usagi wants to eat cake
// Usagi wants to blow kisses

现在介绍…箭头函数!处理“this”问题从未如此简单和直接!简单的ES6解决方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Test it here: <https://jsfiddle.net/maasha/che8m4c1/>

var bunny = {
name: 'Usagi',
tasks: ['transform', 'eat cake', 'blow kisses'],
showTasks() {
this.tasks.forEach((task) => {
alert(this.name + " wants to " + task);
});
}
};

bunny.showTasks();
// Usagi wants to transform
// Usagi wants to eat cake
// Usagi wants to blow kisses

在ES5中,“this”指的是函数的父级,而在ES6中,箭头函数使用词法范围-“this”指的是它当前的周围范围,而不是其他范围。因此,内部函数只知道绑定到内部函数,而不绑定到对象的方法或对象本身。

如何将函数从ES5迁移到ES6。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Before
let bunny = function(name) {
console.log("Usagi");
}

// After
let bunny = (name) => console.log("Usagi")

// Step 1: Remove the word ‘function’.
let bunny = (name) {
console.log("Usagi");
}

// Step 2: If your code is less than a line, remove brackets and place on one line.
let bunny = (name) console.log("Usagi");

// Step 3. Add the hash rocket.
let bunny = (name) => console.log("Usagi");

你做到了!干得好!很简单吧?下面是更多利用胖-瘦箭头的例子,让你的眼睛习惯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// #1 ES6: if passing one argument you don't need to include parenthesis around parameter.
var kitty = name => name;

// same as ES5:
var kitty = function(name) {
return name;
};

// #2 ES6: no parameters example.
var add = () => 3 + 2;

// same as ES5:
var add = function() {
return 3 + 2;
};

// #3 ES6: if function consists of more than one line or is an object, include braces.
var objLiteral = age => ({ name: "Usagi", age: age });

// same as ES5:
var objLiteral = function(age) {
return {
name: "Usagi",
age: age
};
};

// #4 ES6: promises and callbacks.
asyncfn1().then(() => asyncfn2()).then(() => asyncfn3()).then(() => done());

// same as ES5:
asyncfn1().then(function() {
asyncfn2();
}).then(function() {
asyncfn3();
}).done(function() {
done();
});

使用箭头函数时要注意的重要技巧

如果将“new”关键字与=>函数一起使用,则会引发错误。箭头函数不能用作构造函数-普通函数通过属性原型和内部方法[[Construct]]支持“new”。箭头函数两者都不使用,因此新的(()=>{})抛出一个错误。

需要考虑的其他问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Line breaks are not allowed and will throw a syntax error
let func1 = (x, y)
=> {
return x + y;
}; // SyntaxError

// But line breaks inside of a parameter definition is ok
let func6 = (
x,
y
) => {
return x + y;
}; // Works!

// If an expression is the body of an arrow function, you don’t need braces:
asyncFunc.then(x => console.log(x));

// However, statements have to be put in braces:
asyncFunc.catch(x => { throw x });

// Arrow functions are always anonymous which means you can’t just declare them as in ES5:
function squirrelLife() {
// play with squirrels, burrow for food, etc.
}

// Must be inside of a variable or object property to work properly:
let squirrelLife = () => {
// play with squirrels, burrow for food, etc.
// another super squirrel action.
}

恭喜!你已经通过了学习ES6的Dope Way第二部分,现在你有了箭头函数知识的基础,它给’this’带来的词汇上的好处,同时你也学到了一些Java脚本的技巧!:)

保持你的智慧更新喜欢和跟随更多学习ES6的方式即将到来的!

后记

翻译自Learn ES6 The Dope Way Part II: Arrow functions and the ‘this’ keyword