搞懂 JavaScript 的 this 绑定:从踩坑到举一反三

搞懂 JavaScript 的 this 绑定:从踩坑到举一反三

在 JavaScript 开发中,你是不是也遇到过这些困惑:

点击按钮时,this 明明该指向按钮,却变成了 window?

setTimeout 里的回调函数,this 怎么突然找不到对象了?

明明写了 obj.fn(),函数里的 this 却不是 obj?

其实这些问题的根源,都在于没搞懂 this 的动态绑定规则——this 既不是 “谁定义就是谁”,也不是 “谁调用就是谁” 这么简单,它的指向完全取决于函数执行时的场景。今天我们就从实际开发场景出发,拆解 this 绑定的核心逻辑,帮你避开 90% 的坑。

一、先搞懂:this 的本质是什么?this 是函数执行时的上下文对象,它的作用是让函数能 “找到” 自己所属的环境。比如:

当你写 user.getName() 时,this 指向 user,函数才能拿到 user 里的 name;当你用 new Person() 时,this 指向新创建的实例,才能给实例添加属性。关键误区:this 不是在函数定义时确定的,而是在函数执行时确定的。哪怕同一个函数,执行方式不同,this 也会变。

二、日常开发中,5 种常见的 this 绑定场景我们从最常用到最冷门的场景,逐个拆解,每个场景都配 “代码例子 + 实际问题”。

场景 1:对象方法调用(隐式绑定)—— 最常用也最容易踩坑当函数作为对象的属性被调用时(比如 obj.fn()),this 会隐式绑定到这个对象上。这是开发中最常见的场景,比如操作 DOM 元素、调用实例方法。

例子:DOM 按钮点击事件javascript

运行

代码语言:javascript复制// HTML:

const btn = document.getElementById('btn');

btn.textContent = '点击获取按钮文本';

// 给按钮绑定点击事件,函数作为 btn 的方法执行

btn.onclick = function() {

console.log(this.textContent); // 输出:点击获取按钮文本

// 这里 this 指向 btn,因为函数是通过 btn.onclick 调用的

};关键细节:只有 “最后一层对象” 影响 this如果是多层对象嵌套(比如 obj1.obj2.fn()),this 只会绑定到最后一个调用函数的对象(也就是 obj2):

javascript

运行

代码语言:javascript复制const obj1 = {

name: 'obj1',

obj2: {

name: 'obj2',

sayName: function() {

console.log(this.name); // 输出:obj2

}

}

};

obj1.obj2.sayName(); // 最后调用者是 obj2,this 指向 obj2场景 2:独立函数调用(默认绑定)—— 最容易忽略的坑当函数没有通过任何对象调用时(比如直接写 fn()),就会触发默认绑定。

非严格模式下:this 绑定到全局对象(浏览器里是 window,Node.js 里是 global);严格模式下:this 是 undefined(避免全局变量污染,但容易报错)。例子:函数内部调用另一个函数javascript

运行

代码语言:javascript复制// 非严格模式

const name = '全局name';

function outer() {

const name = 'outername';

inner(); // 独立调用 inner,触发默认绑定

}

function inner() {

console.log(this.name); // 输出:全局name(this 指向 window)

}

outer();实际坑点:回调函数里的默认绑定很多人以为 setTimeout、forEach 里的回调函数会 “继承” 外层 this,其实不然 —— 回调函数是独立调用的,默认绑定全局对象:

javascript

运行

代码语言:javascript复制const user = {

name: '张三',

getInfo: function() {

// 错误写法:setTimeout 回调是独立调用,this 指向 window

setTimeout(function() {

console.log(this.name); // 输出:undefined(window 没有 name)

}, 1000);

}

};

user.getInfo();场景 3:显式绑定(call/apply/bind)—— 主动控制 this如果想手动指定 this 的指向,就用显式绑定。JavaScript 给函数提供了三个方法:call、apply、bind,它们的核心作用都是 “强制绑定 this”。

三者的区别很简单:

方法

语法

特点

call

fn.call(thisObj, a, b)

直接执行函数,参数逐个传入

apply

fn.apply(thisObj, [a,b])

直接执行函数,参数用数组传入

bind

const newFn = fn.bind(thisObj, a)

不执行函数,返回新函数(硬绑定)

例子:修复 setTimeout 的 this 问题用 bind 给回调函数显式绑定 user,就能解决默认绑定的坑:

javascript

运行

代码语言:javascript复制const user = {

name: '张三',

getInfo: function() {

// 正确写法:用 bind 绑定 this 为 user

setTimeout(function() {

console.log(this.name); // 输出:张三

}.bind(this), 1000); // this 此时是 user(因为 getInfo 是 user 的方法)

}

};

user.getInfo();实际用途:数组 forEach 的上下文参数forEach 等数组方法自带 “上下文参数”,本质就是显式绑定 this:

javascript

运行

代码语言:javascript复制const obj = { prefix: '结果:' };

const numbers = [1, 2, 3];

// forEach 第二个参数是上下文,会绑定到回调函数的 this

numbers.forEach(function(num) {

console.log(this.prefix + num); // 输出:结果:1、结果:2、结果:3

}, obj);场景 4:构造函数调用(new 绑定)—— 创建实例时的 this当用 new 关键字调用函数时(比如 new Person()),这个函数就变成了 “构造函数”,this 会绑定到新创建的实例对象上。

例子:创建用户实例javascript

运行

代码语言:javascript复制function Person(name) {

// new 调用时,this 指向新实例(比如下面的 zhangsan)

this.name = name;

this.sayHi = function() {

console.log('你好,我是' + this.name);

};

}

// new 绑定:this 指向 zhangsan 实例

const zhangsan = new Person('张三');

zhangsan.sayHi(); // 输出:你好,我是张三关键细节:new 做了什么?new 关键字会隐式执行 4 步操作,这也是 this 绑定到实例的原因:

创建一个全新的空对象;让这个空对象继承构造函数的原型(__proto__ 指向 Person.prototype);把构造函数的 this 绑定到这个空对象上;如果构造函数没有返回其他对象,就返回这个新对象。场景 5:间接调用(逗号 / 赋值表达式)—— 冷门但容易踩的坑当函数通过 “赋值表达式” 或 “逗号操作符” 调用时,会触发间接调用,本质是 “函数引用被剥离对象”,最终变成独立调用,this 指向全局。

比如这两种写法,你可能在老项目里见过:

javascript

运行

代码语言:javascript复制const name = '全局name';

const obj = {

name: 'objname',

sayName: function() {

console.log(this.name);

}

};

// 1. 赋值表达式:obj.sayName 赋值给 fn,然后调用 fn()

const fn = obj.sayName;

fn(); // 输出:全局name(独立调用)

// 2. 逗号操作符:(0, obj.sayName) 返回函数本身,然后调用

(0, obj.sayName)(); // 输出:全局name(独立调用)为什么会这样?因为 “赋值表达式” 和 “逗号操作符” 会返回 “函数本身”,而不是 “对象的方法引用”。比如 obj.sayName 本身是一个函数,赋值给 fn 后,fn 就和 obj 没关系了,调用时自然是独立调用。

三、this 丢失的 3 个高频坑,以及避坑方案前面的场景里其实已经提到了 this 丢失的问题,这里集中总结 3 个最常见的坑,以及对应的解决方案。

坑 1:回调函数里的 this 丢失(setTimeout/forEach)问题:回调函数独立调用,this 指向全局或 undefined。

解决方案:

方案 1:用 bind 显式绑定 this;方案 2:用箭头函数(继承外层 this,后面讲);方案 3:保存 this 到变量(老写法,比如 const that = this)。javascript

运行

代码语言:javascript复制const user = {

name: '张三',

getInfo: function() {

// 方案3:老写法,保存 this 到 that

const that = this;

setTimeout(function() {

console.log(that.name); // 输出:张三

}, 1000);

}

};坑 2:函数作为参数传递时丢失 this问题:把对象方法作为参数传给其他函数,调用时会剥离对象,变成独立调用。

解决方案:传递时用 bind 绑定 this。

javascript

运行

代码语言:javascript复制const obj = {

value: 10,

double: function() {

return this.value * 2;

}

};

// 问题:obj.double 作为参数传递,调用时 this 丢失

function calculate(fn) {

return fn(); // 独立调用,this 指向全局

}

calculate(obj.double); // 输出:NaN(全局没有 value)

// 解决方案:传递时用 bind 绑定 this

calculate(obj.double.bind(obj)); // 输出:20坑 3:间接引用导致的 this 丢失问题:通过赋值、逗号操作符等间接引用函数,调用时变成独立调用。

解决方案:直接通过对象调用,或用 bind 绑定。

javascript

运行

代码语言:javascript复制const obj = {

name: 'objname',

sayName: function() {

console.log(this.name);

}

};

// 问题:间接引用

const indirectFn = obj.sayName;

indirectFn(); // 输出:全局name

// 解决方案1:直接通过对象调用

obj.sayName(); // 输出:objname

// 解决方案2:bind 绑定

const boundFn = obj.sayName.bind(obj);

boundFn(); // 输出:objname四、绑定优先级:谁的权力更大?如果一个函数同时满足多种绑定场景(比如 new + bind),this 会听谁的?

这里给大家一个明确的优先级顺序(从高到低):

new 绑定 > 显式绑定(bind)> 隐式绑定 > 默认绑定

我们用两个对比例子验证:

对比 1:new 绑定 vs 显式绑定(bind)new 的优先级更高,哪怕用 bind 硬绑定了 this,new 依然会把 this 指向新实例:

javascript

运行

代码语言:javascript复制function Person(name) {

this.name = name;

}

const obj = { name: 'objname' };

// 用 bind 绑定 this 为 obj

const BoundPerson = Person.bind(obj);

// new 调用 BoundPerson:this 指向新实例,不是 obj

const person = new BoundPerson('张三');

console.log(person.name); // 输出:张三(new 优先级更高)

console.log(obj.name); // 输出:objname(obj 没被修改)对比 2:显式绑定 vs 隐式绑定bind 等显式绑定的优先级高于隐式绑定:

javascript

运行

代码语言:javascript复制const obj1 = { name: 'obj1', sayName: function() { console.log(this.name); } };

const obj2 = { name: 'obj2' };

// 隐式绑定:this 指向 obj1

obj1.sayName(); // 输出:obj1

// 显式绑定:用 call 把 this 改成 obj2,优先级更高

obj1.sayName.call(obj2); // 输出:obj2五、特殊情况:箭头函数的 thisES6 新增的箭头函数,完全不遵循上面的所有规则 —— 它的 this 是静态的,在函数定义时就确定了,永远继承自 “外层词法上下文” 的 this(简单说就是 “外层函数或全局的 this”)。

箭头函数的核心特点:没有自己的 this,继承外层 this;不能用 new 调用(会报错,因为没有 this 可以绑定到新实例);不能用 call/apply/bind 修改 this(修改无效)。例子:用箭头函数修复回调函数 this 问题javascript

运行

代码语言:javascript复制const user = {

name: '张三',

getInfo: function() {

// 箭头函数继承外层 this(getInfo 的 this,即 user)

setTimeout(() => {

console.log(this.name); // 输出:张三

}, 1000);

}

};

user.getInfo();注意:箭头函数不要滥用比如对象的方法不能用箭头函数,否则 this 会继承全局对象,导致错误:

javascript

运行

代码语言:javascript复制// 错误写法:对象方法用箭头函数,this 继承全局

const obj = {

name: 'objname',

sayName: () => {

console.log(this.name); // 输出:全局name(非严格模式)

}

};

obj.sayName();六、严格模式下的 this 差异之前提到过,严格模式('use strict')会改变默认绑定的 this:

非严格模式:独立函数调用,this 指向全局对象;严格模式:独立函数调用,this 是 undefined。例子:严格模式下的默认绑定javascript

运行

代码语言:javascript复制'use strict'; // 开启严格模式

function fn() {

console.log(this); // 输出:undefined(不是 window)

}

fn(); // 独立调用,this 是 undefined

// 坑点:如果访问 this.name,会报错(Cannot read property 'name' of undefined)七、判断 this 的 3 步口诀最后给大家一个简单的口诀,遇到 this 问题时,按这个顺序判断,保证不会错:

看函数是不是箭头函数?

是 → this 继承外层词法上下文的 this;

否 → 走下一步。看函数是不是用 new 调用?

是 → this 指向新创建的实例;

否 → 走下一步。看函数是不是显式绑定(call/apply/bind)?

是 → this 指向显式指定的对象;

否 → 走下一步。看函数是不是对象方法调用(隐式绑定)?

是 → this 指向调用函数的对象;

否 → 走下一步。默认绑定:

非严格模式 → this 指向全局对象(window/global);

严格模式 → this 是 undefined。总结this 绑定的核心不是 “谁调用就是谁”,而是 “执行场景决定指向”。日常开发中,最容易踩坑的是 “回调函数 this 丢失” 和 “间接调用”,记住用 bind 或箭头函数可以解决大部分问题。

如果记不住所有规则,就用最后的 “3 步口诀” 判断 —— 先看箭头函数,再看 new,再看显式绑定,最后看隐式绑定,剩下的就是默认绑定。多写几个例子测试,很快就能熟练掌握!

参考书籍:《你不知道的 JavaScript(上卷)》

更多尼泊尔内容

唐门玩家为何一出山门就摔死?老玩家:来唐家堡一日游,你就懂了
Android设备用指令查看设备mac地址的两种方法
casino365sport365

Android设备用指令查看设备mac地址的两种方法

🗓️ 10-25 👁️ 8659
〈繁殖〉9.玫瑰扦插繁殖技
3658188

〈繁殖〉9.玫瑰扦插繁殖技

🗓️ 08-20 👁️ 5930
皇明太阳能价格与质量
casino365sport365

皇明太阳能价格与质量

🗓️ 09-25 👁️ 7148
足球界史无前例变革:世界杯变欧洲杯引发热议
最全面的 Markdown 语法参考手册
casino365sport365

最全面的 Markdown 语法参考手册

🗓️ 10-31 👁️ 9666
2023年全球及中国铝土矿行业储量、产量、进出口、产业链、重点企业及趋势分析「图」
华硕AX92U组AiMesh效果测试,对比三频Velop(上篇)
驾驶证怎么考最快?如何快速拿到驾驶证?