一文搞定JavaScript不同场景中 this 的指向问题

this 是 JavaScript 中最容易混淆的核心关键字之一,它不是在定义函数时确定,而是在函数执行调用时动态绑定 。严格模式、普通函数、对象方法、构造函数、事件处理、call/apply/bind、箭头函数都会彻底改变 this 的指向,本文结合完整示例代码拆解每一种场景,理清所有边界。

一、基础前置知识:严格模式与全局对象

1. 全局 window 与变量声明差异

js

运行

javascript 复制代码
"use strict"; // 严格模式
var name = "王五"; // var声明变量会挂载到window全局对象
let test = "测试"; // let/const 不会污染window
console.log(window.name); // 王五
console.log(window.test); // undefined
  • var:全局声明自动绑定 window,造成全局污染;
  • let / const:存在块级作用域,不挂载全局;
  • 严格模式 "use strict" :禁止普通函数中 this 指向 window,普通函数直接调用时 this = undefined,避免全局篡改。

2. 核心底层规则

this 绑定优先级(从低到高):全局默认绑定 < 对象隐式绑定 < call/apply 显式绑定 < bind 硬绑定 < 构造函数 new < 箭头函数(不绑定 this,继承外层)

二、场景 1:普通独立函数调用(默认绑定)

非严格模式

普通函数直接执行,this 指向全局 window

js

运行

javascript 复制代码
function normalFn() {
  console.log(this); // window
  console.log(this.name); // 王五
}
normalFn();

严格模式

开启 "use strict" 后,普通函数 thisundefined,访问全局变量会直接报错,防止误操作全局:

js

运行

javascript 复制代码
"use strict";
function normalFn() {
  console.log(this); // undefined
  console.log(this.name); // Uncaught TypeError
}
normalFn();

坑点:对象方法提取为独立函数

把对象方法赋值给变量后调用,会降级为普通函数,丢失原有对象绑定:

js

运行

javascript 复制代码
let obj = {
  name: "张三",
  say: function () {
    console.log(this.name);
  }
};
const fn = obj.say; // 仅拿到函数引用,脱离对象
fn(); // 严格模式下undefined,非严格模式输出全局name

三、场景 2:作为对象方法调用(隐式绑定)

当函数作为对象的属性(方法)执行时,this 直接指向调用该方法的对象

js

运行

javascript 复制代码
let obj = {
  name: "张三",
  say: function () {
    console.log(this); // {name: "张三", say: ƒ}
    console.log(this.name); // 张三
  }
};
obj.say();

关键规则

谁调用方法,this 就指向谁,链式调用只看最后调用的对象:

js

运行

css 复制代码
let a = { name: "A", fn: function() { console.log(this.name) } }
let b = { name: "B", a: a }
b.a.fn(); // 输出A,this指向a,不是b

四、场景 3:手动修改 this 指向 --- call /apply/bind(显式绑定)

三个方法都能手动指定函数内 this,核心区别在是否立即执行传参格式

1. call (obj, 参数 1, 参数 2...)

立即执行函数,参数逐个传递

js

运行

ini 复制代码
let obj2 = { name: "李四" };
obj.say.call(obj2); // this指向obj2,输出 李四
obj.speak.call(obj2, "你好", "我是李四");

2. apply (obj, 参数数组)

立即执行函数,参数统一放进数组传递,其余逻辑和 call 完全一致

js

运行

arduino 复制代码
obj.speak.apply(obj2, ["你好", "我是李四"]);

3. bind(obj)

不会立即执行,返回一个全新永久绑定 this 的函数,适合异步场景(事件、定时器)

js

运行

arduino 复制代码
const fn2 = obj.speak.bind(obj2);
fn2("你好", "我是李四"); // 新函数执行,this永远固定obj2

实战:事件绑定中 bind 的使用

事件监听器会自动修改函数 this 为触发 DOM 元素,如果需要固定 this,只能用 bind:

js

运行

javascript 复制代码
const oForm = document.querySelector('.add-items');
function addItem(e) {
  console.log(this.name); // 想要拿到obj的name
  e.preventDefault();
}
// call/apply会直接执行函数,不能丢给addEventListener
const addItemBind = addItem.bind(obj); 
oForm.addEventListener('submit', addItemBind);

call /apply/bind 对比表

表格

方法 是否立即执行 传参形式 返回值 使用场景
call 逐个传入 函数执行结果 参数数量少、临时调用
apply 数组传入 函数执行结果 参数为数组、动态传参
bind 新函数调用时传参 永久绑定 this 的新函数 事件、定时器、异步回调

五、场景 4:DOM 事件处理函数中的 this

给 DOM 绑定事件,触发时处理函数内 this 固定指向触发事件的 DOM 元素 ,等同于 e.target(特殊冒泡场景除外):

js

运行

javascript 复制代码
document.querySelector('.lnk').addEventListener('click', goBaidu);
function goBaidu(e) {
  console.log(this); // <a class="lnk"> 触发点击的a标签
  e.preventDefault(); // 阻止a标签默认跳转
}
  • 不使用 bind:this = DOM 元素
  • 使用 bind 绑定自定义对象:this = bind 传入的对象

六、场景 5:构造函数 new 调用

函数通过 new 关键字当作构造函数执行时,底层做 4 件事:

  1. 创建空新对象;
  2. 将新对象绑定为函数内 this
  3. 执行构造函数;
  4. 若无手动 return 对象,自动返回新对象。

js

运行

javascript 复制代码
function Person(name) {
  this.name = name; // this指向新建实例
}
const p = new Person("小明");
console.log(p.name); // 小明

七、场景 6:箭头函数 --- 无独立 this

箭头函数不存在自己的 this ,不会绑定上述任何规则,会直接继承定义时外层作用域的 this

  1. 不能用作构造函数(new 会报错);
  2. call/apply/bind 无法修改箭头函数 this;
  3. 事件回调、定时器中常用,保留外层 this;

js

运行

javascript 复制代码
let testObj = {
  name: "箭头测试",
  fn: () => {
    console.log(this.name); // 外层全局name:王五,不会指向testObj
  }
}
testObj.fn();

八、完整优先级总结(判断 this 万能流程)

拿到函数调用代码,从上到下匹配第一条规则即可:

  1. 是否为箭头函数:this = 定义时外层 this;
  2. 是否用 new 构造调用:this = 新建实例;
  3. 是否使用 bind 硬绑定:this = bind 传入对象;
  4. 是否使用 call/apply:this = 传入的第一个参数;
  5. 是否作为对象方法调用:this = 调用方法的对象;
  6. 普通独立调用:严格模式 this=undefined,否则 window。

九、完整可运行示例代码(整合文中全部逻辑)

js

运行

javascript 复制代码
"use strict";
// 全局var挂载window
var name = "王五";
let oForm = document.querySelector('.add-items');

let obj = {
  name: "张三",
  say: function () {
    console.log("say的this:", this);
    console.log("name:", this.name)
  },
  speak: function (a, b) {
    console.log("参数:", a, b);
    console.log("speak的this:", this);
  }
}
let obj2 = { name: "李四" };

// 1. 对象方法调用
obj.say();

// 2. 提取为普通函数调用
const fn = obj.say;
// fn(); // 严格模式 this = undefined

// 3. call / apply 临时修改this
obj.say.call(obj2);
obj.say.apply(obj2);
obj.speak.call(obj2, '你好', '我是李四');
obj.speak.apply(obj2, ['你好', '我是李四']);

// 4. bind 返回新函数,永久绑定this
const fn2 = obj.speak.bind(obj2);
fn2('你好', '我是李四');

// 5. bind用于事件监听
function addItem(e) {
  console.log("表单提交this:", this);
  e.preventDefault();
}
const addItemBind = addItem.bind(obj);
oForm.addEventListener('submit', addItemBind);

// 6. DOM事件原生this指向DOM
document.querySelector('.lnk').addEventListener('click', goBaidu);
function goBaidu(e) {
  console.log("点击链接this:", this);
  e.preventDefault();
}

// 7. 箭头函数无自身this
const arrowTest = () => {
  console.log("箭头函数this:", this);
}
arrowTest();

十、写在最后

this 的难点不在于概念复杂,而在于多场景绑定规则互相覆盖。日常开发记住两个核心技巧:

  1. 事件、定时器需要固定自定义 this → 使用 bind
  2. 需要复用外层上下文 this(如定时器、嵌套回调)→ 使用箭头函数;
  3. 临时切换对象调用函数 → 使用 call / apply。分清每种调用方式对应的绑定规则,就能彻底规避 this 指向错乱的 bug。
相关推荐
用户298698530141 小时前
在 React 中使用 JavaScript 合并 Excel 文件
前端·javascript·react.js
大流星1 小时前
LangChainJs之基础模型(一)
javascript·langchain
橘子星1 小时前
JavaScript this 指向全解实战指南
前端·javascript
weedsfly1 小时前
JS垃圾回收:从原理到项目实战,彻底根治内存泄漏
前端·javascript·面试
万少13 小时前
万少的博客 - 技术分享与解决方案
前端·javascript·后端
尘世中一位迷途小书童16 小时前
用 Cesium 撸了一个森林火情监控大屏,弧线、粒子、发光效果都齐了
前端·javascript
先吃饱再说17 小时前
JavaScript中`this` 的“千层套路”:从默认绑定到箭头函数的五种指向
javascript
foxire17 小时前
基于nodejs实现服务端内核引擎
javascript
触底反弹19 小时前
🧠 搞懂 Token,才算真正入门大模型——从分词原理到 Embedding 语义实战
javascript·人工智能·算法