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
:在回调函数调用之前,保存一下this
1
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
- 这里将