this 关键字
this 关键字原理
this 是指包含它的函数作为方法被调用时所属的对象。
简而言之, this 代表存在于函数的一个对象,而这个对象就是调用这个函数的那个对象。
this的值取决于函数调用时的上下文(运行时绑定)因为函数是在一定的环境中运行的,调用函数时肯定需要知道是谁调用的,就用到了this进行指向
严格模式与非严格模式
1 | // 严格模式 |
使用场景
全局上下文
在全局执行上下文中(即,在任何函数之外),this 无论在严格模式还是非严格模式下,都引用全局对象。
在 web 浏览器中,全局对象是
window在 Node.js 环境中,全局对象不是
window而是global
在 web 浏览器中,全局对象是 window,所以 this 将引用 window 对象:
1 | console.log(this); // 在浏览器上下文中会记录 "[object Window]" |
函数上下文
普通函数
在普通函数内部,this 的值取决于函数的调用方式。如果函数在全局上下文中调用,this 在严格模式下将为 undefined,在非严格模式下将引用全局对象。
1 | var name = 'globalName'; |
函数充当对象的方法
当函数充当对象的方法时,this 将引用调用该方法的对象。这展示了 this 的值不绑定于函数本身,而是由函数被调用的方式和位置决定,这个概念称为执行上下文。
一层作用域链
this 指的该对象
1 | var prop = 'globalHello'; |
多层作用域链
this 指的是距离方法最近的一层对象。
1 | var prop = "globalHello"; |
注意:如果
obj.obj1.func这个结果赋值给一个变量obj2,则obj2()的值是多少呢?
1
2 var func1 = obj.obj1.func;
func1(); // this.prop => "globalHello"答案是 “globalHello”,这个是因为经过赋值操作时,并未发起函数调用,
obj2()这个才是真正的调用,而 **发起这个调用的是根对象 **window,所以this指的就是window
匿名函数自调【立即调用(IIFE)】
this 指向的是全局对象
即便在多层作用域链当中,也指向全局对象
注意:在 立即调用(IIFE) 当中
1
2
3
4
5 var obj = {
func: (function(){
console.log(this.prop);
})()
}此时的
obj.func并不是一个函数,而是立即执行函数(IIFE)的结果。
1 | var prop = "globalHello"; |
立即调用的匿名函数有几种常见写法
传统的括号包裹法
- 这是最常见的 IIFE 写法,通过在函数表达式外面加一对圆括号
(),然后再在最后加一对圆括号()来调用它。
1
2
3
4// 先用()包起来,然后再后面跟 (参数)
(function(data) {
console.log(data);
})("111");- 这是最常见的 IIFE 写法,通过在函数表达式外面加一对圆括号
通过
()直接调用- 这种方式与第一种非常相似,只是将调用部分的括号放在函数表达式内部。
1
2
3
4先后面跟(参数),然后再()包起来
(function(data) {
console.log(data);
}("222"));使用一元操作符
- 可以使用一元操作符
!、+、-、~来触发函数的立即调用。尽管不常用,但这是合法的 JavaScript 语法。
1
2
3
4
5
6
7
8// 正常函数格式,前面加 一元操作符
!function(data) {
console.log(data);
}("333");
+function(data) {
console.log(data);
}("333");- 可以使用一元操作符
通过
void操作符- 使用
void操作符也可以立即调用匿名函数。void操作符返回undefined,因此常用于不关心返回值的场景。
1
2
3
4// 正常函数格式,前面加 void
void function(data) {
console.log(data);
}("444");- 使用
自执行箭头函数(ES6)
- 在 ES6 中,也可以用箭头函数来创建 IIFE,不过由于箭头函数的语法特点,需要注意将整个函数表达式包裹起来。
1
2
3((data) => {
console.log(data);
}) ("111")
定时器中调用
this 指向的是全局变量
其实定时器的本质,也是一种匿名函数的形式。
1 | var prop = "globalHello"; |
箭头函数调用
箭头函数不具有自己的 this。相反,它们从创建时非箭头函数的父作用域继承 this
在其周围的作用域上创建一个 this 值的闭包:
- 如果箭头函数被非箭头函数包含,则
this绑定的是最近一层非箭头函数的this - 否则
this的值则被设置为全局对象
1 | var prop = "globalHello"; |
回调函数调用
this 指向的全局对象
因为本质上是在函数内 callback ,是以普通函数调用的方式执行的。【存疑,网上并未找到直接相关结果。GPT给出的答案】
下面的 ”勘误“ 部分给出了具体情境的不同详细例子,希望能有所帮助。
勘误
有些博主文章说:this 指向的执行上下文并 不是回调函数定义时 的那个上下文,而是 调用它的函数 所在的上下文。
乍一看有点道理,但是实际代码验证一下就发现不对。
1 | var funObj = { |
所有的例子都在
obj.func()内部完成。可以看到所有回调函数当中的this都指向全局对象excuteCallback():该全局函数接受一个匿名回调函数的参数并直接调用它。- 符合 “this指向调用它的函数所在的上下文” 的说法。
- 此时的
excuteCallback()函数处于全局上下文,因此this指向 全局对象
funObj.excuteCallbackInner():该方法中的函数接受一个匿名回调函数的参数并直接调用它。- 不符合 “this指向调用它的函数所在的上下文” 的说法。
- 排除 调用者 上下文的影响
- 此时调用它的函数应该是
funObj.excuteCallbackInner(),调用者内this指向funObj对象的上下文。 - 然而回调函数当中
this仍然指向全局对象。
excuteCallback(funObj.callback):该全局函数接受一个funObj.callback的参数 并直接调用它。- 排除 回调函数自身 上下文的影响
- 此时传入的参数为
funObj对象的函数,回调函数自身内部this指向funObj对象的上下文。【严格来说此时还不叫回调函数】 - 然而回调函数当中
this仍然指向全局对象。
funObj.excuteCallbackInner(funObj.callback):该方法中的函数接受一个funObj.callback的参数 并直接调用它。- 排除 回调函数自身 和 调用者 上下文的影响
funObj.callback():函数充当对象的方法,this 指向距离最近的对象。详见上文。
得出结论:无论从什么角度,都可以说明回调函数当中
this指向全局对象。如有异议,欢迎联系博主讨论。
this指向问题解决
使用箭头函数:利用箭头函数的继承机制【绑定最近一层非箭头函数的
this】1
2
3
4
5
6
7
8
9
10function excuteCallback(callback) {
callback();
}
var obj = {
data: 100,
func: function () {
excuteCallback(() => console.log(this)); // this => { data: 100, func: [Function: func] }
}
}
obj.func();- 箭头函数内的
this绑定成了function()内的this:指向对象obj
- 箭头函数内的
使用
var self = this:在回调函数调用之前,保存一下this1
2
3
4
5
6
7
8
9
10
11
12
13function excuteCallback(callback) {
callback();
}
var obj = {
data: 100,
func: function () {
var self = this;
excuteCallback(function(){
console.log(self); // self => { data: 100, func: [Function: func] }, this => global
});
}
}
obj.func();- 使用变量
self提前保存this以供后续调用。
- 使用变量
使用
.bind()方法永久绑定1
2
3
4
5
6
7
8
9
10
11
12function excuteCallback(callback) {
callback();
}
var obj = {
data: 100,
func: function () {
excuteCallback(function(){
console.log(this); // this => { data: 100, func: [Function: func] }
}.bind(this));
}
}
obj.func();- 在参数传入之前,对匿名回调函数进行处理。
- 使用
.bind()方法永久绑定 该回调函数内部 的this
构造函数上下文
this 引用实例化的新对象
在构造函数内部,this 引用新创建的对象。但是,这里的“新创建”是什么意思呢?要理解这一点,我们需要探讨 JavaScript 中的 new 关键字。
当你在函数调用之前使用 new 时,它告诉 JavaScript 进行四个操作:
- 创建一个新的空对象。这不是一个函数、数组或 null,只是一个空对象。
- 使函数内部的
this引用这个新对象。新对象与构造函数内的this关联起来。这就是为什么Person(name)内的this.name实际上修改了新对象。- 正常执行函数。它像通常情况下执行函数代码一样执行。
- 如果函数没有返回自己的对象,则返回新对象。如果构造函数返回一个对象,那个对象将被返回,而不是新对象。如果返回其他任何内容,将返回新对象。
1 | var name = "globalName"; |
原型方法上下文
this 指向调用该方法的实例
1 | function Person(name) { |
类上下文
在类中,方法内部的 this 引用类的实例
1 | class ExampleClass { |
显式绑定 this
可以使用函数上的 .call()、.apply() 或 .bind() 方法来明确设置 this 的上下文
call 和 apply 是函数的内置方法,显式的修改了prototype原型,用于调用一个函数并显式地设置该函数内部的 this 值。这两个方法的功能非常相似,区别在于它们处理参数的方式不同。
.call()方法- 语法:
function.call(thisArg, arg1, arg2, ...) - 用途:
call方法可以调用一个函数,并且可以指定this的值,还可以直接传递多个参数。
- 语法:
.apply()方法用于调用一个函数,并显式设置
this的值,同时传递参数。语法:
function.apply(thisArg, [argsArray])用途:
apply方法与call类似,但不同的是,apply接受一个包含多个参数的数组,而不是单独传递参数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//此处声明若用let,则第一个调用函数输出undefined,
//因为此处let声明的变量虽然也是全局变量但其不会成为全局对象window的属性,故say()直接调用时为undefined
var word = "我是window";
function say(params1,params2){
console.log(params1 + " " + params2 + ","+ this.word)
}
let obj = {
word: "我是obj"
}
let newObj= {
word: "我是newObj"
}
say("Hi","friend"); // Hi friend,我是window
say.call(obj,"Hi","friend") // Hi friend,我是obj
say.apply(newObj,["Hi","friend"]) // Hi friend,我是newObj
.bind()方法- 语法:
function.bind(thisArg, arg1, arg2, ...) - 用途:返回一个新的函数,这个新函数的
this永远绑定到提供的值,并且可以预设部分参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var word = "我是window";
function say(params1,params2){
console.log(params1 + " " + params2 + ","+ this.word)
}
let obj = {
word: "我是obj"
}
var bindSay = say.bind(obj, "Hi"); // "a1" 是预设的参数,会在 bindFunc 被调用时作为第一个参数传递给 func。
bindSay("friend"); // Hi friend,我是obj
let obj1 = {
word: "我是obj1"
}
// 尝试使用 call() 方法更改上下文;但是,它仍然使用 obj 作为 `this` 上下文。并且预设参数已经确定
console.log(bindSay.call(obj1, "Hi", "friend")); // Hi Hi,我是obj- 语法:
另外的,.apply() 方法有一些典型的使用场景
借用对象的方法
apply()让person.greet使用anotherPerson的this上下文。
1
2
3
4
5
6
7
8const person = {
name: 'Alice',
greet: function(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`; // 模板字符串
}
};
const anotherPerson = { name: 'Bob' };
console.log(person.greet.apply(anotherPerson, ['Hello', '!'])); // Hello, Bob!应用于函数参数的转换
apply可以方便地将数组元素作为函数参数传递,常用于需要将参数数组展开的情况,比如Math.max。1
2const numbers = [1, 2, 3, 4, 5];
const max = Math.max.apply(null, numbers); // 输出: 5可以用于合并数组
1
2
3
4
5
6
7
8
9var array1 = [1, 2, 3];
var array2 = [4, 5, 6];
var mergedArray1 = array1.push.apply(array1, array2); // 第一个参数是 this 值,即 push 方法的上下文
console.log(mergedArray1)
// 其他合并方式: 使用拓展运算符 ...
var mergedArray2 = [...array1, ...array2];
console.log(mergedArray2); // mergedArray2 => [1, 2, 3, 4, 5, 6]
事件处理程序上下文
this 引用附加了事件监听器的元素,与 event.currentTarget 相同。
1 | <button class="button">onclick</button> |
注意!
它不引用常用的 event.target 属性。让我们澄清 event.currentTarget 和 event.target 之间的区别。
event.currentTarget:该属性引用附加了事件处理程序(如addEventListener)的元素。这是在事件处理程序函数的上下文中 this 引用的内容。event.target:该属性引用引发事件的实际 DOM 元素。对于会冒泡的事件特别重要。如果你点击内部元素,事件将冒泡到外部元素,触发它们的事件监听器。对于这些外部元素,event.target将是实际被点击的最内层元素,而event.currentTarget(或this)将是当前处理程序附加到的元素。1
2
3
4
5
6
7
8
9
10
11<div id="outer">点击我
<div id="inner">或者点击我</div>
</div>
<script>
document.getElementById('outer').addEventListener('click', function(event) {
console.log("currentTarget: ", event.currentTarget.id);
console.log("this: ", this.id);
console.log("target: ", event.target.id);
});
</script>- 在这种情况下,如果你点击外部 div,所有三个日志都将打印 “outer”,因为点击的元素(
target)和处理程序附加的元素(currentTarget或this)是相同的。 - 但是,如果你点击内部 div 中的 “或者点击我” 文本,
event.target将是 “inner”(因为这是你点击的元素),而event.currentTarget(或this)仍将是 “outer”(因为这是事件处理程序附加的元素)。
- 在这种情况下,如果你点击外部 div,所有三个日志都将打印 “outer”,因为点击的元素(
this 指针判定
这段代码会输出什么?
1 | var funObj = { |
Output:
200
undefined
200
200
代码解析
funObj.func();- 这里直接调用了
funObj.func();方法。 - 由于
func是通过funObj调用的,因此this指向funObj对象。 this.data访问的是funObj中的data属性,其值为200。- 输出:
200
- 这里直接调用了
const o = funObj.func; o();- 这里将
funObj.func赋值给了变量o,然后调用了o()。 - 当
o()被独立调用时,this不再指向funObj,而是指向全局对象(在非严格模式下)或undefined(在严格模式下)。 - 在浏览器环境中,如果
this指向全局对象,全局对象的data是undefined(因为全局对象通常没有data属性)。 - 输出:
undefined
- 这里将
obj.func();- 这里调用了
obj.func方法。 obj.func()内部调用funObj.func(),而funObj.func()是通过funObj调用的,因此this仍然指向funObj对象。- 输出:
200
- 这里调用了
const f = obj.func; f();- 这里将
obj.func赋值给了变量f,然后调用了f()。 - 由于
f()被独立调用,因此在obj.func()中this指向全局对象或undefined(在严格模式下)。 - 然而,
func内部调用funObj.func(),此调用依然通过funObj对象,因此funObj.func内部的this指向funObj对象。 - 输出:
200
- 这里将


