this 是 JavaScript 核心关键字之一,但其指向并非在定义时确定,而是由调用方式和执行上下文动态决定;
理解 this 的关键在于:谁调用函数,this 就指向谁 (核心原则),但不同场景下有特殊规则;
this 的核心特性
-
动态绑定:this 指向在函数执行时确定,而非定义时; -
上下文依赖:不同调用方式 (普通调用、对象调用、构造函数等) 对应不同 this; -
严格模式影响:严格模式下避免 this 隐式指向全局对象; -
可显式修改:通过 call/apply/bind 强制改变 this 指向;
不同场景下的 this 指向
场景 1:全局作用域中的 this
-
非严格模式:全局作用域 (浏览器 / Node.js) 中,this 指向全局对象:
- 浏览器:window;
- Node.js:global (Node.js 14+ 后为 globalThis);
-
严格模式:全局作用域的 this 仍指向全局对象 (仅函数内严格模式有差异);
// 浏览器环境 console.log(this === window); // true(非严格/严格模式均成立) // Node.js 环境 console.log(this === globalThis); // true
场景 2:普通函数调用(独立调用)
-
非严格模式:普通函数直接调用时,this 隐式指向全局对象 (浏览器 window,Node.js globalThis);
function foo() { console.log(this); // 浏览器:window;Node.js:globalThis } foo(); // 独立调用 -
严格模式:严格模式下,普通函数调用的 this 为 undefined (避免隐式全局污染);
'use strict'; function foo() { console.log(this); // undefined } foo();
场景 3:对象方法调用
-
函数作为对象的方法调用时,this 指向调用该方法的对象 (核心规则:“谁调用,指向谁”);
-
基础示例:
const obj = { name: '张三', sayHi: function() { console.log(this.name); // 张三(this 指向 obj) } }; obj.sayHi(); // obj 调用方法,this → obj -
嵌套对象 / 方法赋值的特殊情况:
- 方法被赋值给变量后独立调用:this 回退为全局对象 / undefined;
- 嵌套对象的方法:this 指向直接调用者,而非外层对象;
const obj = { name: '张三', sayHi: function() { console.log(this.name); } }; const fn = obj.sayHi; fn(); // 独立调用 → 非严格模式:undefined(全局无 name);严格模式:报错const outer = { name: '外层', inner: { name: '内层', sayHi: function() { console.log(this.name); // 内层(this 指向 inner) } } }; outer.inner.sayHi();
场景 4:构造函数调用(new 关键字)
-
使用 new 调用函数时,函数成为构造函数,this 指向新创建的实例对象;
-
构造函数执行流程 (决定 this 指向):
- 创建一个空的新对象;
- 将新对象的 __proto__ 指向构造函数的 prototype;
- 构造函数的 this 绑定到新对象;
- 若构造函数无显式返回值,返回新对象;若返回非对象类型,仍返回新对象;若返回对象类型,返回该对象;
function Person(name) { this.name = name; // this 指向新实例 } const p1 = new Person('李四'); console.log(p1.name); // 李四 console.log(this === p1); // false(this 是全局,p1 是实例) // 特殊:构造函数返回对象 function Person2(name) { this.name = name; return { age: 20 }; // 返回对象,覆盖新实例 } const p2 = new Person2('王五'); console.log(p2.name); // undefined console.log(p2.age); // 20
场景 5:箭头函数中的 this
-
箭头函数是 ES6 新增,其 this 有本质区别:
- 箭头函数无自己的 this,this 继承自定义时的外层普通函数 / 全局作用域;
- 箭头函数的 this 一旦确定,无法通过 call/apply/bind 修改;
- 箭头函数不能作为构造函数 (无 this,调用 new 会报错);
- 箭头函数无 arguments 对象 (可用剩余参数 …args 替代);
-
实例:
const obj = { name: '赵六', sayHi: () => { console.log(this.name); // undefined(this 继承全局,全局无 name) }, sayHi2: function() { const inner = () => { console.log(this.name); // 赵六(this 继承 sayHi2 的 this → obj) }; inner(); } }; obj.sayHi(); obj.sayHi2();const arrowFn = () => console.log(this); arrowFn.call({ name: '测试' }); // 仍指向全局(浏览器 window)
场景 6:DOM 事件处理函数中的 this
-
普通函数:DOM 事件回调中,this 指向触发事件的 DOM 元素;
-
箭头函数:this 继承外层作用域 (通常是全局 window);
const btn = document.getElementById('btn'); // 普通函数:this → btn 元素 btn.onclick = function() { console.log(this); // <button id="btn">点击</button> }; // 箭头函数:this → window btn.onclick = () => { console.log(this); // window };
场景 7:显式修改 this(call/apply/bind)
-
JavaScript 提供 3 个方法强制修改函数的 this 指向,核心区别:
方法 语法 执行时机 参数传递 返回值 call fn.call(thisArg, arg1, arg2...)立即执行 逐个传参 函数执行结果 apply fn.apply(thisArg, [arg1, arg2...])立即执行 数组传参 函数执行结果 bind fn.bind(thisArg, arg1, arg2...)不立即执行 逐个传参 (可柯里化) 绑定 this 后的新函数 -
示例代码:
function sum(a, b) { return this.base + a + b; } const obj = { base: 10 }; // call:立即执行,逐个传参 console.log(sum.call(obj, 2, 3)); // 15(this → obj) // apply:立即执行,数组传参 console.log(sum.apply(obj, [2, 3])); // 15 // bind:返回新函数,不立即执行 const boundSum = sum.bind(obj, 2); // 柯里化,固定第一个参数 console.log(boundSum(3)); // 15
常见坑点与解决方案
-
坑点 1:- 回调函数中 this 丢失:
const obj = { data: [1,2,3], process: function() { // 回调函数独立调用,this → 全局/undefined this.data.forEach(function(item) { console.log(this); // window/undefined }); } }; obj.process(); - 解决方案:
- 用变量保存 this:
const that = this;; - 使用箭头函数 (继承外层 this);
- forEach 第二个参数指定 this:
this.data.forEach(..., this);
- 用变量保存 this:
- 回调函数中 this 丢失:
-
坑点 2:- 对象方法作为定时器回调
const obj = { sayHi: function() { console.log(this); // window(定时器回调是普通调用) } }; setTimeout(obj.sayHi, 1000); - 解决方案:
- 包裹箭头函数:
setTimeout(() => obj.sayHi(), 1000); - bind 绑定 this:
setTimeout(obj.sayHi.bind(obj), 1000);
- 包裹箭头函数:
- 对象方法作为定时器回调
-
坑点 3:- 箭头函数作为对象方法
const obj = { name: '错误示例', fn: () => { console.log(this.name); // undefined(this 指向全局) } }; obj.fn(); - 解决方案:改用普通函数作为对象方法;
- 箭头函数作为对象方法
面试题
手写 call、apply、bind
Function.prototype.myCall = function (context, ...args) {
context === null ? context = window : null;
//=> 此处 this 为 call 前的函数,也就是调用 call 的函数
//=> 把函数赋值给需要指定 this 的对象 context
context.$fn = this;
let result = context.$fn(...args); // 这时函数执行 this 就变成了 context
delete context.$fn;
return result;
};
// 测试
const person = { name: '张三' };
function sayHi(age, gender) {
console.log(`我是${this.name},年龄${age},性别${gender}`);
return { name: this.name, age, gender };
}
console.log(sayHi.myCall(person, 20, '男')); // 我是张三,年龄20,性别男
console.log(sayHi.myCall(null, 25, '女')); // 我是 undefined,年龄25,性别女
Function.prototype.myApply = function (context, argsArr=[]) {
context === null ? context = window : null;
//=> 此处 this 为 call 前的函数,也就是调用 call 的函数
//=> 把函数赋值给需要指定 this 的对象 context
context.$fn = this;
let result = context.$fn(...argsArr); // 这时函数执行 this 就变成了 context
delete context.$fn;
return result;
};
// 测试
const animal = { type: '猫' };
function describe(color, age) {
console.log(`这是一只${color}的${this.type},年龄${age}岁`);
return { type: this.type, color, age };
}
console.log('ES6 myApply:');
console.log(describe.myApply(animal, ['橘色', 3])); // 这是一只橘色的猫,年龄3岁
console.log(describe.myApply(animal)); // 输出:这是一只undefined的猫,年龄undefined岁
Function.prototype.myBind = function (context, ...bindArgs) {
if (typeof this !== "function") {
throw new TypeError("Bind must be called on a function");
}
return (...callArgs) => this.apply(context, [...bindArgs, ...callArgs]);
};
// 测试
const person = { name: "张三" };
function sayHi(age, gender) {
console.log(`我是${this.name},${age}岁,${gender}`);
}
// 绑定 this 和预设参数
const boundSayHi = sayHi.myBind(person, 20);
boundSayHi("男"); // 输出:我是张三,20岁,男
阿里原题
function fn1() {
console.log(1);
}
function fn2() {
console.log(2);
}
// fn2.$fn = fn1;
// fn2.$fn() => fn2.fn1();this 指向 fn2,执行 fn1,输出 1
fn1.call(fn2); // 1
// 把 fn1.call 看成 AF0,AF0.call(fn2)
// fn2.$fn() => fn2.AF0(); fn1.call 和 fn1.call.call 都是同一个 call 函数
// 则 AF0 可以看成 call 函数,即 fn2.call 执行,this 为 window,输出 2
fn1.call.call(fn2); // 2
// 同上
Function.prototype.call.call(fn2); // 2
剑指 Offer 53 - II.0~n-1中缺失的数字
上一篇