本文详细解析JavaScript中this的指向规则,涵盖全局环境、普通函数、对象方法、构造函数、事件处理及箭头函数中的this行为。深入讲解call、apply、bind三种改变this指向的方法及其区别,并探讨回调函数中this的常见陷阱与解决方案,帮助开发者准确掌握this的使用。
前言
在JavaScript中,this是一个特殊的关键字,解析器在每次调用函数时都会自动向其内部传递一个隐含的参数,即this。this通常指向一个对象,该对象被称为函数执行的上下文对象。具体指向哪个对象,取决于函数的调用方式。
本文将围绕以下几个核心方面,详细解析this的指向问题:
- 全局环境中的this
- 普通函数中的this
- 对象方法中的this
- 构造函数中的this
- 事件处理函数中的this
- 箭头函数中的this
1、全局环境中的this
在全局环境中(即不在任何函数或对象内部),this指向全局对象。在浏览器环境中,全局对象是window;而在Node.js环境中,全局对象是global。
console.log(this === window); // 在浏览器中输出 true
2、函数中的this
当一个函数被直接调用时,在非严格模式下,this指向全局对象(window);而在严格模式下,this的值为undefined。
JavaScript严格模式:JavaScript在语法和行为上存在一些模糊特性,可能导致不易察觉的错误。为提高代码质量和可维护性,JS引入了严格模式,通过启用额外规则来强制执行更严格的语法和行为。在严格模式下,代码中的潜在问题会被捕获并抛出错误,有助于提前发现和修复潜在Bug。
function regularFunction() {
console.log(this);
}
regularFunction(); // 非严格模式下指向 window,严格模式下为 undefined
// 演示严格模式下的情况
(function () {
"use strict";
regularFunction();
})();
3、对象方法中的this
当函数作为对象的方法被调用时,this指向调用该方法的对象。
var person = {
name: "John",
sayName: function () {
console.log(this.name);
}
};
person.sayName(); // 输出 "John",这里的 this 指向 person 对象
4、构造函数中的this
使用new关键字(实例化)调用函数时,该函数被视为构造函数(或类),此时this会指向新创建的对象实例。
function Person(name) {
this.name = name;
this.sayHello = function () {
console.log("Hello, I'm " + this.name);
};
}
var john = new Person("John");
john.sayHello(); // 输出 "Hello, I'm John",这里 this 指向 john 实例
构造函数执行创建对象的过程如下:
- 调用构造函数时,它会立即创建一个新对象。
- 将新建的对象设置为函数内部的this,在构造函数中可以使用this来引用这个新建的对象。
- 逐行执行函数中的代码。
- 将新建的对象作为返回值返回。
在构造函数中,创建对象和返回对象的过程对开发者是隐藏的。使用同一个构造函数创建的对象称为一类对象,构造函数也称为类。通过一个构造函数创建的对象,称为该类的实例。
5、事件处理函数中的this
在DOM事件处理函数中,this通常指向触发事件的DOM元素。
<button id="myButton">Click me</button>
<script>
var button = document.getElementById("myButton");
button.onclick = function () {
console.log(this); // 点击按钮时,这里的 this 指向按钮元素
//打印 :<button id="myButton">Click me</button>
};
</script>
7、箭头函数中的this
箭头函数没有自己的this,它的this值继承自外层作用域的this。
// 普通函数
function outerFunction() {
this.name = "Outer";
var innerFunction = function () {
console.log(this.name);
};
innerFunction();
}
// 箭头函数
function outerFunctionWithArrow() {
this.name = "Outer with Arrow";
var innerFunction = () => {
console.log(this.name);
};
innerFunction();
}
new outerFunction(); // 输出 undefined,因为 innerFunction 中的 this 指向全局对象,全局对象没有 name 属性
new outerFunctionWithArrow(); // 输出 "Outer with Arrow",箭头函数的 this 继承自 outerFunctionWithArrow 的 this
二、改变this指向的方法
由于箭头函数的this来源于外层作用域的继承,因此无法使用以下三种方法来改变箭头函数内部的this指向。
1、call()方法
call方法附加在函数调用后面使用,可以显式指定函数内部的this指向。- 语法: 函数名.call(要改变的 this 指向,要给函数传递的参数1,要给函数传递的参数2, ...)
- 使用 call 方法时注意: 1、会立即执行函数 2、第一个参数指定函数内部this的指向 3、从第二个参数开始,依次是传递给函数的参数
var obj = { name: 'Jack' }
function fn(a, b) {
console.log(this)
console.log(a)
console.log(b)
}
fn(1, 2)
fn.call(obj, 1, 2)
- 调用
fn(1,2)时,函数内部的this指向window(即函数被直接调用)。 - 调用
fn.call(obj, 1, 2)时,函数内部的this指向obj对象。
2、apply()方法
apply方法附加在函数调用后面使用,可以显式指定函数内部的this指向。- 语法: 函数名.apply(要改变的 this 指向,[要给函数传递的参数1, 要给函数传递的参数2, ...])
- 使用 apply 方法时注意: 1、会立即执行函数 2、第一个参数指定函数内部this的指向 3、第二个参数是一个数组,数组中的每一项依次是传递给函数的参数(这是与call方法的主要区别)。
var obj = { name: 'Jack' }
function fn(a, b) {
console.log(this)
console.log(a)
console.log(b)
}
fn(1, 2)
fn.apply(obj, [1, 2])
- 调用
fn(1,2)时,函数内部的this指向window(即函数被直接调用)。 - 调用
fn.call(obj, 1, 2)时,函数内部的this指向obj对象。
3、bind()方法
bind方法附加在函数调用后面使用,可以显式指定函数内部的this指向。- 与 call / apply 不同,bind 不会立即执行函数,而是返回一个已经改变了this指向的新函数。
- 语法: var newFn = 函数名.bind(要改变的 this 指向); newFn(传递参数)
var obj = { name: 'Jack' }
function fn(a, b) {
console.log(this)
console.log(a)
console.log(b)
}
fn(1, 2)
var newFn = fn.bind(obj)
newFn(1, 2)
- 调用 bind 时,不会执行原函数fn,而是返回一个新的函数。
- 这个新函数是原函数fn的一个副本,但其内部的this指向已被修改。
- 调用
fn(1, 2)时,this指向window。 - 调用
newFn(1, 2)时,执行的是一个与fn完全相同的函数,只是内部的this指向被改为了obj。
三、回调函数中this指向
这里补充说明在回调函数运用中this的指向问题,这也是容易混淆的知识点。
1、对象方法作为回调函数
如果回调函数是一个对象的方法,并且以对象方法的形式传递进去,那么this通常会指向该对象。
var myObject = {
value: 10,
callbackFunction: function () {
console.log(this.value);
}
};
[1,2,3].forEach(() => {
myObject.callbackFunction()
}); //输出三个10
在示例中,forEach是数组的方法,myObject.callbackFunction作为回调函数传递给forEach。当forEach调用这个回调函数时,this仍然指向myObject,因为该函数本质上是myObject的一个方法。
2、箭头函数作为回调函数
箭头函数没有自己的this,它会继承外层作用域的this(依据词法作用域规则)。
function outerFunction() {
this.name = "Outer";
var array = [1, 2, 3];
array.forEach(() => {
console.log(this.name); //打印三次Outer
});
}
new outerFunction();
基于上述两个例子,这里介绍普通函数和箭头函数在确定this指向时的主要区别:
1、普通函数
普通函数在定义时确定其作用域,但不会明确this的指向。普通函数中this的指向是在函数被调用时确定的,通常指向调用者或全局对象。
2、箭头函数
箭头函数由于本身不生成this,其this继承自外层作用域。箭头函数在定义时不仅确定作用域,还会捕获外层作用域的this作为自身的this。因此,箭头函数的this在定义时就已经确定,后续无论谁调用箭头函数,其this指向都不会改变。
以上面这个例子为例:
箭头函数定义在outerFunction函数内部,注意不是定义在forEach方法内。具体可了解函数传参的步骤:箭头函数先在outerFunction内部定义,之后才作为参数传给forEach方法。箭头函数继承了outerFunction的this,并在后续被forEach调用时保持不变。
3、回调函数指向全局对象的常见情况
当普通函数作为回调函数,且该普通函数被全局函数(如setTimeout、setInterval)或在全局作用域中独立调用的函数(未通过对象调用)调用时,在非严格模式下,this通常指向全局对象。
//被一个全局函数调用
setTimeout(function () {
console.log(this);
}, 1000);
//在全局作用域中独立调用
function outer(){
inner()
}
function inner(){
console.log(this);
}
outer()
四、总结与常见错误示例
this指向总结: 1、普通函数:谁调用函数,this就指向谁;如果没有明确的调用者,则指向全局对象Window。 2、箭头函数:箭头函数不会创建自己的this,它的this继承自上层作用域中的this。
1、案例一
//回调函数中错误使用this
var person = {
name: "Eve",
greetLater: function () {
setTimeout(function () {
console.log(this.name); // 此处的function为普通函数,作为setTimeout的参数this指向全局对象
}, 1000);
}
};
person.greetLater();
此处的function为普通函数,被一个全局函数调用,其this指向全局对象Window。
若要输出Eve,可将此处的普通函数改为箭头函数。
var person = {
name: "Eve",
greetLater: function () {
setTimeout( ()=> {
console.log(this.name); // 输出Eve
}, 1000);
}
};
person.greetLater();
2、案例二
//在嵌套函数中混淆 this 指向
var outer = {
name: "Outer Object",
innerFunction: function () {
var inner = {
name: "Inner Object",
nestedFunction: function () {
console.log(this.name); // 这里 this 指向 inner,而不是 outer
}
};
inner.nestedFunction();
}
};
outer.innerFunction();
nestedFunction是作为inner对象的方法被调用,this指向inner对象(根据普通函数this指向规则,当函数作为对象的方法被调用时,this会指向调用该函数的对象)。
4.2.1、使用外层作用域this的方法
若要访问outer对象的name属性,可使用以下两种方法:
1、保存外层this的引用
var outer = {
name: "Outer Object",
innerFunction: function () {
var self = this;
var inner = {
name: "Inner Object",
nestedFunction: function () {
console.log(self.name); // 现在可以访问到 outer 的 name 属性,输出 "Outer Object"
}
};
inner.nestedFunction();
}
};
outer.innerFunction();
在方法开头先将this保存到一个变量中(通常命名为self或that等),然后在inner对象中使用该变量来访问outer对象的属性。
2、使用箭头函数
var outer = {
name: "Outer Object",
innerFunction: function () {
var inner = {
name: "Inner Object",
nestedFunction: () => {
console.log(this.name); // 输出 "Outer Object"
}
};
inner.nestedFunction();
}
};
outer.innerFunction();
将nestedFunction改为箭头函数,因为箭头函数会继承外层作用域的this。在此例中,外层作用域是outer.innerFunction(),其this指向outer对象,因此箭头函数里的this也能指向outer对象。
总结
到此这篇关于JavaScript中this(普通函数、箭头函数、函数运用)的文章就介绍到这了,更多相关JS中this普通函数、箭头函数、回调函数运用内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!