前言
当一个函数调用时,会创建一个执行上下文,这个上下文包括函数调用的一些信息(调用栈,传入参数,调用方式), this 就指向这个执行上下文。
this不是静态的,也并不是在编写的时候绑定的,而是在 运行时绑定 的。它的绑定和函数声明的位置没有关系,只取决于函数调用的方式。
本篇文章有点长,涉及到很多道面试题,有难有简单,如果能耐心的通读一编,我相信以后this都不成问题。 在文章的最开始,陈列一下本篇文章涉及的内容,保证让大家不虚此行。
-
默认绑定
-
隐式绑定
-
隐式绑定丢失
-
显式绑定
-
显式绑定应用
-
new绑定
-
箭头函数绑定
-
综合题
-
总结
this指向哪里
在 JavaScript 中,要想完全理解 this ,首先要理解 this 的绑定规则, this 的绑定规则一共有5种:
-
默认绑定
-
隐式绑定
-
显式(硬)绑定
-
new绑定 -
ES6新增箭头函数绑定
下面来一一介绍以下 this 的绑定规则。
1.默认绑定
默认绑定通常是指函数独立调用,不涉及其他绑定规则。 非严格模式下, this 指向 window ,严格模式下, this 指向 undefined 。
题目1.1:非严格模式
var foo = 123;
function print(){
this.foo = 234;
console.log(this); // window
console.log(foo); // 234
}
print();
非严格模式, print() 为默认绑定, this 指向 window ,所以打印 window 和 234 。
这个 foo 值可以说道两句:如果学习过预编译的知识,在预编译过程中, foo 和 print 函数会存放在全局 GO 中(即 window 对象上),所以上述代码就类似下面这样:
window.foo = 123
function print() {
this.foo = 234;
console.log(this);
console.log(window.foo);
}
window.print()
题目1.2:严格模式
把 题目1.1 稍作修改,看看严格模式下的执行结果。
"use strict" 可以开启严格模式
"use strict";
var foo = 123;
function print(){
console.log('print this is ', this);
console.log(window.foo)
console.log(this.foo);
}
console.log('global this is ', this);
print();
注意事项:开启严格模式后,函数内部 this 指向 undefined ,但全局对象 window 不会受影响
答案
global this is Window{...}
print this is undefined
123
Uncaught TypeError: Cannot read property 'foo' of undefined
题目1.3:let/const
let a = 1;
const b = 2;
var c = 3;
function print() {
console.log(this.a);
console.log(this.b);
console.log(this.c);
}
print();
console.log(this.a);
let/const 定义的变量存在暂时性死区,而且不会挂载到 window 对象上,因此 print 中是无法获取到 a和b 的。
答案
undefined
undefined
3
undefined
题目1.4:对象内执行
a = 1;
function foo() {
console.log(this.a);
}
const obj = {
a: 10,
bar() {
foo(); // 1
}
}
obj.bar();
foo 虽然在 obj 的 bar 函数中,但 foo 函数仍然是独立运行的, foo 中的 this 依旧指向 window 对象。
题目1.5:函数内执行
var a = 1
function outer () {
var a = 2
function inner () {
console.log(this.a) // 1
}
inner()
}
outer()
这个题与 题目1.4 类似,但要注意,不要把它看成闭包问题
题目1.6:自执行函数
a = 1;
(function(){
console.log(this);
console.log(this.a)
}())
function bar() {
b = 2;
(function(){
console.log(this);
console.log(this.b)
}())
}
bar();
默认情况下,自执行函数的 this 指向 window
自执行函数只要执行到就会运行,并且只会运行一次, this 指向 window 。
答案
Window{...}
1
Window{...}
2 // b是imply global,会挂载到window上
2.隐式绑定
函数的调用是在某个对象上触发的,即调用位置存在上下文对象,通俗点说就是**XXX.func()**这种调用模式。
此时 func 的 this 指向 XXX ,但如果存在链式调用,例如 XXX.YYY.ZZZ.func ,记住一个原则: this永远指向最后调用它的那个对象 。
题目2.1:隐式绑定
var a = 1;
function foo() {
console.log(this.a);
}
// 对象简写,等同于 {a:2, foo: foo}
var obj = {a: 2, foo}
foo();
obj.foo();
-
foo(): 默认绑定,打印1 -
obj.foo(): 隐式绑定,打印2
答案
obj 是通过 var 定义的, obj 会挂载到 window 之上的, obj.foo() 就相当于 window.obj.foo() ,这也印证了 this永远指向最后调用它的那个对象 规则。
题目2.2:对象链式调用
感觉上面总是空谈链式调用的情况,下面直接来看一个例题:
var obj1 = {
a: 1,
obj2: {
a: 2,
foo(){
console.log(this.a)
}
}
}
obj1.obj2.foo() // 2
3.隐式绑定的丢失
隐式绑定可是个调皮的东西,一不小心它就会发生绑定的丢失。一般会有两种常见的丢失:
-
使用另一个变量作为函数别名,之后使用别名执行函数
-
将函数作为参数传递时会被隐式赋值
隐式绑定丢失之后, this 的指向会启用默认绑定。
具体来看题目:
题目3.1:取函数别名
a = 1
var obj = {
a: 2,
foo() {
console.log(this.a)
}
}
var foo = obj.foo;
obj.foo();
foo();
JavaScript 对于引用类型,其地址指针存放在栈内存中,真正的本体是存放在堆内存中的。
上面将 obj.foo 赋值给 foo ,就是将 foo 也指向了 obj.foo 所指向的堆内存,此后再执行 foo ,相当于直接执行的堆内存的函数,与 obj 无关, foo 为默认绑定。笼统的记, 只要fn前面什么都没有,肯定不是隐式绑定 。
答案
不要把这里理解成 window.foo 执行,如果 foo 为 let/const 定义, foo 不会挂载到 window 上,但不会影响最后的打印结果
题目3.2:取函数别名
如果取函数别名没有发生在全局,而是发生在对象之中,又会是怎样的结果呢?
var obj = {
a: 1,
foo() {
console.log(this.a)
}
};
var a = 2;
var foo = obj.foo;
var obj2 = { a: 3, foo: obj.foo }
obj.foo();
foo();
obj2.foo();
obj2.foo 指向了 obj.foo 的堆内存,此后执行与 obj 无关(除非使用 call/apply 改变 this 指向)
答案
题目3.3:函数作为参数传递
function foo() {
console.log(this.a)
}
function doFoo(fn) {
console.log(this)
fn()
}
var obj = { a: 1, foo }
var a = 2
doFoo(obj.foo)
用函数预编译的知识来解答这个问题:函数预编译四部曲前两步分别是:
-
找形参和变量声明,值赋予
undefined -
将形参与实参相统一,也就是将实参的值赋予形参。
obj.foo 作为实参,在预编译时将其值赋值给形参 fn ,是将 obj.foo 指向的地址赋给了 fn ,此后 fn 执行不会与 obj 产生任何关系。 fn 为默认绑定。
答案
Window {…}
2
题目3.4:函数作为参数传递
将上面的题略作修改, doFoo

本文详尽探讨JavaScript中的this绑定规则,包括默认绑定、隐式绑定、显式绑定、new绑定和箭头函数绑定。通过38道面试题,帮助读者彻底理解this的指向问题,覆盖了函数调用的各种场景,如对象方法、自执行函数、回调函数、new操作符和箭头函数等。此外,还介绍了this在实际应用中的常见问题和避免使用场景。
最低0.47元/天 解锁文章
13万+

被折叠的 条评论
为什么被折叠?



