【前端】深入理解 JavaScript 箭头函数的 `this` 指向
kimi代写
在 JavaScript 的开发中,this 的指向一直是让许多开发者感到困惑的地方。尤其是当涉及到箭头函数时,其 this 的行为与普通函数大不相同。本文将通过一个具体的例子,深入探讨箭头函数的 this 指向机制,帮助你更好地理解这一特性。
问题引入
最近在调试一段代码时,我遇到了一个有趣的现象。代码如下:
var b = 1; function a() { let b = 2; return () => { return () => { console.log(this.b); }; }; } console.log(a()()());
运行这段代码后,输出结果是:
undefined undefined
这个结果让我感到困惑:为什么箭头函数的 this 不是指向 a 函数的 this,而是指向全局上下文的 window 呢?接下来,我们将逐步分析这个问题。
箭头函数的 this 捕获机制
在 JavaScript 中,箭头函数和普通函数在 this 的处理上有很大的不同:
普通函数的 this
普通函数的 this 是动态绑定的,取决于函数的调用方式。常见的调用方式包括:
- 作为方法调用:this 指向调用它的对象。
const obj = { name: "Kimi", greet: function() { console.log(this.name); // 输出 "Kimi" } }; obj.greet();
- 作为普通函数调用:在非严格模式下,this 指向全局对象(window),在严格模式下,this 是 undefined。
function greet() { console.log(this); // 在非严格模式下输出 `window`,在严格模式下输出 `undefined` } greet();
- 通过 call、apply 或 bind 调用:this 可以被显式绑定到指定的对象。
const obj = { name: "Kimi" }; function greet() { console.log(this.name); } greet.call(obj); // 输出 "Kimi"
箭头函数的 this
箭头函数没有自己的 this,它会捕获其定义时所在上下文的 this 值。换句话说,箭头函数的 this 是在其定义时就已经确定的,而不是在调用时动态绑定的。
代码分析
我们再来看之前的代码:
var b = 1; function a() { let b = 2; return () => { return () => { console.log(this.b); }; }; } console.log(a()()());
逐层分析
-
函数 a 的定义和执行:
- a 是一个普通函数,它的 this 在调用时会根据调用方式动态绑定。
- 当你调用 a() 时,a 是作为普通函数调用的。在非严格模式下,this 指向全局对象 window;在严格模式下,this 是 undefined。
-
第一个箭头函数:
- a 返回了一个箭头函数:
() => { return () => { console.log(this.b); }; }
- 这个箭头函数捕获了其定义时所在上下文的 this。由于它定义在 a 函数内部,而 a 的 this 是全局上下文的 this,因此这个箭头函数的 this 也是全局上下文的 this。
-
第二个箭头函数:
- 第一个箭头函数返回了另一个箭头函数:
() => { console.log(this.b); }
- 这个箭头函数同样捕获了其定义时所在上下文的 this,即全局上下文的 this。
- 第一个箭头函数返回了另一个箭头函数:
- a 返回了一个箭头函数:
关键点
- 箭头函数的 this 是在其定义时捕获的,而不是在调用时动态绑定的。
- 在你的代码中,两个箭头函数都定义在 a 函数内部,而 a 的 this 是全局上下文的 this,因此两个箭头函数的 this 都是全局上下文的 this。
为什么不是 a 的 this?
箭头函数的 this 捕获机制决定了它不会绑定到其直接包含它的函数(如 a),而是绑定到其定义时所在上下文的 this。在你的代码中,a 函数的 this 是全局上下文的 this,因此箭头函数捕获的也是全局上下文的 this。
总结
- 普通函数的 this 是动态绑定的,取决于函数的调用方式。
- 箭头函数的 this 是在其定义时捕获的,不会绑定到其直接包含它的函数,而是绑定到其定义时所在上下文的 this。
- 在你的代码中,箭头函数的 this 捕获了全局上下文的 this,而不是 a 函数的 this。
希望这篇文章能帮助你更好地理解箭头函数的 this 捕获机制。如果你还有其他疑问,欢迎在评论区留言,我们一起探讨!
作者简介:我是 Kimi,一名热爱技术的开发者。如果你喜欢这篇文章,别忘了点赞和关注哦!
更多
特殊的this指向:
除了上面几种this指向的规则之外,还有一些特殊的情况,他们的this指向与上述情况有所不同,下面就来看看这些情况。
(1)、箭头函数
const foo = { fn: function () { setTimeout(function() { console.log(this) }) } } console.log(foo.fn()) // window
这里,this 出现在 setTimeout() 中的回调函数里,因此 **this 指向 window 对象**。如果需要 this 指向 foo 这个 object 对象,可以使用箭头函数解决:
const foo = { fn: function () { setTimeout(() => { console.log(this) }) } } console.log(foo.fn()) // {fn: ƒ}
(2)、数组方法
来看下面的代码,在属性 arr 的 forEach 回调函数中输出 this,指向的是什么呢?
var obj = { arr: [1] } obj.arr.forEach(function() { console.log(this) })
其实输出的仍然是全局对象。
forEach 方法语法如下:
array.forEach(function(currentValue, index, arr), thisValue)
其参数如下:
1)function(currentValue, index, arr):必需。 数组中每个元素需要调用的函数。
● currentValue:必需,当前元素
● index:可选,当前元素的索引值
● arr:可选,当前元素所属的数组对象
2)thisValue:可选,传递给函数的值一般用 “this” 值。如果这个参数为空, “undefined” 会传递给 “this” 值
可以看到,forEach方法有两个参数,第一个是回调函数,第二个是 this 指向的对象,这里只传入了回调函数,第二个参数没有传入,默认为 undefined,所以会输出全局对象。 除了forEach方法,需要传入 this 指向的函数还有:**every()、find()、findIndex()、map()、some()**,在使用的时候需要注意。
(3)、立即执行函数
立即执行函数就是定义后立刻调用的匿名函数:
var name = 'hello' var obj = { name: 'world', sayHello: function() { console.log(this.name) }, hello: function() { (function(cb) { cb() })(this.sayHello) } } obj.hello() // hello
执行结果是 hello,是 window.name 的值。立即执行函数作为一个匿名函数,通常就是直接调用,而不会通过属性访问器(obj.fn)的形式来给它指定一个所在对象,所以它的 this 是确定的,就是默认的全局对象 window。
(4)、setTimeout 和 setInterval
setTimeout 和 setInterval 中函数的 this 指向规则是一样的:
var name = 'hello' var obj = { name: 'world', hello: function() { setTimeout(function() { console.log(this.name) }) } } obj.hello() // hello
this.name 是在 obj.hello () 里被调用的,结果却输出了 window.name。其实,延时效果(setTimeout)和定时效果(setInterval)都是在全局作用域下实现的。无论是 setTimeout 还是 setInterval 里传入的函数,都会首先被交到全局对象手上。因此,函数中 this 的值,会被自动指向 window。
-