在 JavaScript 开发中,你是不是也遇到过这些困惑:
点击按钮时,this 明明该指向按钮,却变成了 window?
setTimeout 里的回调函数,this 怎么突然找不到对象了?
明明写了 obj.fn(),函数里的 this 却不是 obj?
其实这些问题的根源,都在于没搞懂 this 的动态绑定规则——this 既不是 “谁定义就是谁”,也不是 “谁调用就是谁” 这么简单,它的指向完全取决于函数执行时的场景。今天我们就从实际开发场景出发,拆解 this 绑定的核心逻辑,帮你避开 90% 的坑。
this 是函数执行时的上下文对象,它的作用是让函数能 “找到” 自己所属的环境。比如:
user.getName() 时,this 指向 user,函数才能拿到 user 里的 name;new Person() 时,this 指向新创建的实例,才能给实例添加属性。关键误区:this 不是在函数定义时确定的,而是在函数执行时确定的。哪怕同一个函数,执行方式不同,this 也会变。
我们从最常用到最冷门的场景,逐个拆解,每个场景都配 “代码例子 + 实际问题”。
当函数作为对象的属性被调用时(比如 obj.fn()),this 会隐式绑定到这个对象上。这是开发中最常见的场景,比如操作 DOM 元素、调用实例方法。
javascript
运行
// HTML:<button id="btn">点击</button>
const btn = document.getElementById('btn');
btn.textContent = '点击获取按钮文本';
// 给按钮绑定点击事件,函数作为 btn 的方法执行
btn.onclick = function() {
console.log(this.textContent); // 输出:点击获取按钮文本
// 这里 this 指向 btn,因为函数是通过 btn.onclick 调用的
};如果是多层对象嵌套(比如 obj1.obj2.fn()),this 只会绑定到最后一个调用函数的对象(也就是 obj2):
javascript
运行
const obj1 = {
name: 'obj1',
obj2: {
name: 'obj2',
sayName: function() {
console.log(this.name); // 输出:obj2
}
}
};
obj1.obj2.sayName(); // 最后调用者是 obj2,this 指向 obj2当函数没有通过任何对象调用时(比如直接写 fn()),就会触发默认绑定。
this 绑定到全局对象(浏览器里是 window,Node.js 里是 global);this 是 undefined(避免全局变量污染,但容易报错)。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
运行
const user = {
name: '张三',
getInfo: function() {
// 错误写法:setTimeout 回调是独立调用,this 指向 window
setTimeout(function() {
console.log(this.name); // 输出:undefined(window 没有 name)
}, 1000);
}
};
user.getInfo();如果想手动指定 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) | 不执行函数,返回新函数(硬绑定) |
用 bind 给回调函数显式绑定 user,就能解决默认绑定的坑:
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 等数组方法自带 “上下文参数”,本质就是显式绑定 this:
javascript
运行
const obj = { prefix: '结果:' };
const numbers = [1, 2, 3];
// forEach 第二个参数是上下文,会绑定到回调函数的 this
numbers.forEach(function(num) {
console.log(this.prefix + num); // 输出:结果:1、结果:2、结果:3
}, obj);当用 new 关键字调用函数时(比如 new Person()),这个函数就变成了 “构造函数”,this 会绑定到新创建的实例对象上。
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 关键字会隐式执行 4 步操作,这也是 this 绑定到实例的原因:
__proto__ 指向 Person.prototype);this 绑定到这个空对象上;当函数通过 “赋值表达式” 或 “逗号操作符” 调用时,会触发间接调用,本质是 “函数引用被剥离对象”,最终变成独立调用,this 指向全局。
比如这两种写法,你可能在老项目里见过:
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 指向全局或 undefined。
解决方案:
bind 显式绑定 this;this,后面讲);this 到变量(老写法,比如 const that = this)。javascript
运行
const user = {
name: '张三',
getInfo: function() {
// 方案3:老写法,保存 this 到 that
const that = this;
setTimeout(function() {
console.log(that.name); // 输出:张三
}, 1000);
}
};问题:把对象方法作为参数传给其他函数,调用时会剥离对象,变成独立调用。
解决方案:传递时用 bind 绑定 this。
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问题:通过赋值、逗号操作符等间接引用函数,调用时变成独立调用。
解决方案:直接通过对象调用,或用 bind 绑定。
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)> 隐式绑定 > 默认绑定
我们用两个对比例子验证:
new 的优先级更高,哪怕用 bind 硬绑定了 this,new 依然会把 this 指向新实例:
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 没被修改)bind 等显式绑定的优先级高于隐式绑定:
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); // 输出:obj2ES6 新增的箭头函数,完全不遵循上面的所有规则 —— 它的 this 是静态的,在函数定义时就确定了,永远继承自 “外层词法上下文” 的 this(简单说就是 “外层函数或全局的 this”)。
this,继承外层 this;new 调用(会报错,因为没有 this 可以绑定到新实例);call/apply/bind 修改 this(修改无效)。javascript
运行
const user = {
name: '张三',
getInfo: function() {
// 箭头函数继承外层 this(getInfo 的 this,即 user)
setTimeout(() => {
console.log(this.name); // 输出:张三
}, 1000);
}
};
user.getInfo();比如对象的方法不能用箭头函数,否则 this 会继承全局对象,导致错误:
javascript
运行
// 错误写法:对象方法用箭头函数,this 继承全局
const obj = {
name: 'objname',
sayName: () => {
console.log(this.name); // 输出:全局name(非严格模式)
}
};
obj.sayName();之前提到过,严格模式('use strict')会改变默认绑定的 this:
this 指向全局对象;this 是 undefined。javascript
运行
'use strict'; // 开启严格模式
function fn() {
console.log(this); // 输出:undefined(不是 window)
}
fn(); // 独立调用,this 是 undefined
// 坑点:如果访问 this.name,会报错(Cannot read property 'name' of undefined)
最后给大家一个简单的口诀,遇到 this 问题时,按这个顺序判断,保证不会错:
this 继承外层词法上下文的 this;
否 → 走下一步。new 调用?
是 → this 指向新创建的实例;
否 → 走下一步。this 指向显式指定的对象;
否 → 走下一步。this 指向调用函数的对象;
否 → 走下一步。this 指向全局对象(window/global);
严格模式 → this 是 undefined。this 绑定的核心不是 “谁调用就是谁”,而是 “执行场景决定指向”。日常开发中,最容易踩坑的是 “回调函数 this 丢失” 和 “间接调用”,记住用 bind 或箭头函数可以解决大部分问题。
如果记不住所有规则,就用最后的 “3 步口诀” 判断 —— 先看箭头函数,再看 new,再看显式绑定,最后看隐式绑定,剩下的就是默认绑定。多写几个例子测试,很快就能熟练掌握!
参考书籍:《你不知道的 JavaScript(上卷)》
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。