在 JavaScript 的世界里,this是一个既强大又容易让人困惑的概念。它的指向在不同的函数调用场景下会动态变化,而call()、apply()和bind()这三个方法则为我们提供了精确控制this指向的能力。本文将从基础概念出发,结合具体案例,带大家深入理解这些核心知识点。
一、JavaScript 中 this 的本质
1. 作为普通函数调用(独立调用)
当函数作为独立函数调用时(即没有任何对象前缀),this指向全局对象(浏览器环境为window,Node 环境为global)。在严格模式下("use strict"),this会被绑定为undefined。
function sayHello() {
console.log(this); // 普通调用时指向window(非严格模式)
}
sayHello(); // window
2. 作为对象方法调用
当函数作为对象的方法被调用时(即通过对象。方法 () 的形式),this指向该对象本身:
const person = {
name: 'Alice',
greet: function() {
console.log(`Hello, ${this.name}`); // 指向person对象
}
};
person.greet(); // Hello, Alice
3. 作为构造函数调用
使用new关键字调用函数时,会创建一个新对象,此时this指向这个新创建的实例对象:
function Person(name) {
this.name = name; // 指向新创建的实例
}
const alice = new Person('Alice');
console.log(alice.name); // Alice
4. 箭头函数中的 this
箭头函数不具备自身的this,它的this继承自外层作用域(词法作用域),且无法通过call()、apply()、bind()改变:
⑴.箭头函数的 this
继承外层作用域
箭头函数的 this
值在函数 定义时 就确定,继承自外层(词法作用域)的 this
,而非运行时的调用上下文。
测试代码 1:全局作用域
// 全局作用域(非严格模式)
const globalArrow = () => {
console.log(this); // 输出:window(浏览器环境)或 global(Node.js)
};
globalArrow();
// 严格模式下
(function() {
"use strict";
const strictArrow = () => {
console.log(this); // 输出:undefined(因为外层严格模式的 this 是 undefined)
};
strictArrow();
})();
测试代码 2:对象方法中的 this
const obj = {
name: '普通函数',
regularMethod: function() {
console.log(this.name); // 输出:"普通函数"(this 指向 obj)
},
arrowMethod: () => {
console.log(this.name); // 输出:undefined(或全局对象的 name,如果存在)
}
};
obj.regularMethod(); // 普通函数:this 指向 obj
obj.arrowMethod(); // 箭头函数:this 继承自外层(全局或严格模式下的 undefined)
⑵.无法通过 bind()
、call()
、apply()
改变 this
箭头函数的 this
是 固定 的,无法通过 bind
、call
、apply
改变。
测试代码 3:尝试绑定 this
const arrowFunc = () => {
console.log(this); // 始终继承定义时的 this
};
const obj = { name: '尝试绑定' };
// 尝试绑定 this 到 obj
const boundFunc = arrowFunc.bind(obj);
boundFunc(); // 输出:全局对象(或 undefined,取决于定义时的外层 this)
// 使用 call/apply 也无效
arrowFunc.call(obj); // 输出:全局对象
⑶.构造函数中的箭头函数
箭头函数 不能作为构造函数 使用,因为没有自己的 this
,且没有 prototype
。
测试代码 4:构造函数错误
// 错误示例:尝试用箭头函数作为构造函数
const Person = (name) => {
this.name = name; // 报错!箭头函数没有 this
};
new Person('Alice'); // 抛出错误:this is undefined
⑷.嵌套函数中的 this
传递
箭头函数的 this
继承自 外层最近的非箭头函数 的 this
。
测试代码 5:嵌套函数中的 this
function outer() {
this.message = '外层函数';
const innerRegular = function() {
console.log(this.message); // 输出:undefined(因为 innerRegular 的 this 默认指向全局或严格模式下的 undefined)
};
const innerArrow = () => {
console.log(this.message); // 输出:"外层函数"(继承 outer 的 this)
};
innerRegular(); // 普通函数:this 未绑定
innerArrow(); // 箭头函数:this 继承自 outer
}
outer();
二、改变 this 指向的三种方法
1. call () 方法 - 显式指定 this 并立即执行
call()
方法是 JavaScript 中函数对象的一个内置方法,它允许你调用一个函数,并指定这个函数内部 this
的值,同时还可以传递参数给这个函数。
语法
function.call(thisArg, arg1, arg2, ...)
函数名.call(此值指向对象, 参数1, 参数2, …)
- 函数名:调用
call
方法的函数。 - 此值指向对象:调用函数时,函数内部
this
关键字所指向的值。若该参数为null
或者undefined
,那么this
会指向全局对象(在浏览器环境里是window
对象)。 - 参数 1, 参数 2, …:属于可选参数,是传递给函数的参数列表。
案例
案例一:改变 this
的指向
const person = {
name: 'Alice',
greet: function() {
console.log(`Hello, my name is ${this.name}.`);
}
};
const anotherPerson = {
name: 'Bob'
};
// 使用 call 方法改变 greet 函数的 this 指向
person.greet.call(anotherPerson);
在这个例子中,person
对象有一个 greet
方法,该方法使用 this.name
来输出信息。通过 call
方法,我们将 greet
方法内部的 this
指向了 anotherPerson
对象,所以输出的是 Bob
的名字。
案例二:传递参数
function add(a, b) {
return a + b;
}
// 使用 call 方法调用 add 函数并传递参数
const result = add.call(null, 3, 5);
console.log(result);
在这个例子中,add
函数是一个简单的加法函数。由于 add
函数不依赖于 this
,所以第一个参数可以传入 null
。然后我们通过 call
方法传递了两个参数 3
和 5
,最终得到结果 8
。
案例三:类数组对象使用数组方法
const arrayLike = {
0: 'apple',
1: 'banana',
2: 'cherry',
length: 3
};
// 使用 call 方法让类数组对象使用数组的 forEach 方法
Array.prototype.forEach.call(arrayLike, function(item) {
console.log(item);
});
在这个例子中,arrayLike
是一个类数组对象,它没有数组的方法。通过 call
方法,我们让 arrayLike
对象使用了数组的 forEach
方法,从而可以遍历它的元素。
2.apply () 方法
apply()
方法也是 JavaScript 中函数对象的一个内置方法,与 call()
方法类似,都可以用来调用函数并指定函数内部 this
的值,不同之处在于 apply()
方法接收参数的方式是一个数组或类数组对象。
语法
function.apply(thisArg, [argsArray])
参数解释:
function
:要调用apply
方法的函数。thisArg
:在调用函数时,this
关键字会指向这个值。如果该参数为null
或undefined
,在非严格模式下,this
会指向全局对象(在浏览器环境中是window
对象);在严格模式下,this
的值为null
或undefined
。argsArray
:一个包含要传递给函数的参数的数组或类数组对象。如果不需要传递参数,可以传入一个空数组[]
。
案例
案例一:改变 this
的指向
const person = {
name: 'Alice',
greet: function() {
console.log(`Hello, my name is ${this.name}.`);
}
};
const anotherPerson = {
name: 'Bob'
};
// 使用 apply 方法改变 greet 函数的 this 指向
person.greet.apply(anotherPerson);
在这个例子中,person
对象有一个 greet
方法,通过 apply
方法将 greet
方法内部的 this
指向了 anotherPerson
对象,所以输出的是 Bob
的名字。
案例二:传递参数
function add(a, b) {
return a + b;
}
// 使用 apply 方法调用 add 函数并传递参数
const result = add.apply(null, [3, 5]);
console.log(result);
这里 add
函数是一个简单的加法函数,因为 add
函数不依赖于 this
,所以第一个参数传入 null
。通过 apply
方法传递了一个包含 3
和 5
的数组作为参数,最终得到结果 8
。
案例三:求数组中的最大值
const numbers = [5, 10, 3, 8, 15];
// 使用 Math.max.apply 求数组中的最大值
const max = Math.max.apply(null, numbers);
console.log(max);
Math.max()
函数用于求多个数中的最大值,但它接收的是多个独立的参数,而不是一个数组。通过 apply
方法,将 numbers
数组作为参数传递给 Math.max()
函数,就可以求出数组中的最大值 15
。
3. bind () 方法
bind()方法会创建一个新函数,当这个新函数被调用时,this会被绑定为bind()方法的第一个参数。
语法
function.bind(thisArg, arg1, arg2, ...)
函数名.bind(此值指向对象, 参数1, 参数2, …)
参数解释
- 函数名:调用
bind
方法的原函数。 - 此值指向对象:在调用新创建的函数时,函数内部
this
关键字会指向该值。若传入null
或undefined
,在非严格模式下,this
指向全局对象(浏览器环境中是window
对象);严格模式下,this
就是null
或undefined
。 - 参数 1, 参数 2, …:可选参数,是在调用新创建的函数时预先传入的参数。
案例
案例一:改变 this
的指向
const person = {
name: 'Alice',
greet: function() {
console.log(`Hello, my name is ${this.name}.`);
}
};
const anotherPerson = {
name: 'Bob'
};
// 使用 bind 方法创建一个新函数,改变 greet 函数的 this 指向
const newGreet = person.greet.bind(anotherPerson);
newGreet();
在这个例子里,person
对象有 greet
方法,借助 bind
方法创建了一个新函数 newGreet
,并把 greet
方法内部的 this
指向了 anotherPerson
对象,调用 newGreet
时输出的就是 Bob
的名字。
案例二:预先传入参数
function multiply(a, b) {
return a * b;
}
// 使用 bind 方法创建一个新函数,预先传入第一个参数
const double = multiply.bind(null, 2);
console.log(double(5));
这里的 multiply
函数用于计算两个数的乘积。通过 bind
方法创建了新函数 double
,预先传入参数 2
作为第一个参数。调用 double(5)
时,相当于调用 multiply(2, 5)
,最终结果是 10
。
案例三:在事件处理程序中使用
const button = document.createElement('button');
button.textContent = 'Click me';
const obj = {
message: 'Button clicked!',
handleClick: function() {
console.log(this.message);
}
};
// 使用 bind 方法确保 handleClick 中的 this 指向 obj
button.addEventListener('click', obj.handleClick.bind(obj));
document.body.appendChild(button);
在这个例子中,创建了一个按钮元素,obj
对象有 handleClick
方法。通过 bind
方法将 handleClick
方法的 this
指向 obj
,这样在点击按钮触发事件时,就能正确输出 Button clicked!
。
4.bind()
与 call()
、apply()
的区别
call()
和apply()
是直接调用函数,同时可以指定this
值和参数。bind()
并不直接调用函数,而是创建一个新的函数,这个新函数在被调用时,会使用指定的this
值和预先传入的参数。