JavaScript 中的 this 绑定机制深入解析
深入理解 JavaScript 中的 this 绑定机制
在 JavaScript 学习路径中,this 关键字绝对是绕不开的核心知识点,也是最容易让人困惑的概念之一。很多开发者会被其“变幻莫测”的指向搞得晕头转向,甚至戏称其为“JavaScript 中的薛定谔”。但实际上,this 的指向并非毫无规律,其核心原则只有一个:this 是函数执行时的上下文对象,指向谁完全取决于函数的调用方式,而非定义位置。
本文将从核心原则出发,按优先级拆解 this 的绑定规则,结合实际场景与案例,帮你彻底理清 this 的指向逻辑。
一、核心认知:this 指向的本质
在探讨具体规则前,我们需要先建立一个关键认知:this 是动态绑定的,它的指向只在函数执行的那一刻才最终确定。
举个直观的例子:
function sayHi() {
console.log(this.name);
}
const person1 = { name: "张三", sayHi };
const person2 = { name: "李四", sayHi };
person1.sayHi(); // 张三
person2.sayHi(); // 李四同一个 sayHi 函数,只因调用者不同,this 就指向了不同的对象。这正是 this 的核心特性——执行上下文决定指向。
而所有 this 绑定场景,都遵循一套明确的优先级规则(从高到低):
new 绑定 > 显式绑定(call/apply/bind)> 隐式绑定(对象调用)> 默认绑定(全局/严格模式)
二、分场景拆解 this 绑定规则
1. 默认绑定:函数独立调用时
当函数没有任何调用前缀(不是对象属性、不是 new 调用、未使用 call/apply/bind),即为独立调用,此时触发默认绑定。
- 非严格模式:
this指向全局对象(浏览器中是window,Node.js 中是global); - 严格模式(
'use strict'):this指向undefined(ES5 引入严格模式的核心目的之一,就是避免意外修改全局变量)。
代码示例:
// 非严格模式
function foo() {
console.log(this); // window(浏览器环境)
}
foo(); // 独立调用,触发默认绑定
// 严格模式
function bar() {
'use strict';
console.log(this); // undefined
}
bar();
// 嵌套函数的默认绑定
const obj = {
fn() {
// 这里的 this 是 obj(隐式绑定)
function inner() {
console.log(this); // window(非严格模式,独立调用触发默认绑定)
}
inner(); // 无调用前缀,独立执行
}
};
obj.fn();注意:嵌套函数的 this 与外层函数的 this 无关,只要是独立调用,就遵循默认绑定。
2. 隐式绑定:函数作为对象属性调用时
当函数作为某个对象的属性,通过 对象.函数() 的形式调用时,this 会指向这个调用者对象(即 . 前面的对象)。这是日常开发中最常见的绑定场景之一。
代码示例:
const user = {
name: "王五",
age: 28,
introduce() {
console.log(`我是${this.name},今年${this.age}岁`);
},
address: {
city: "北京",
showLocation() {
console.log(`我在${this.city}`); // this 指向 address 对象
}
}
};
user.introduce(); // 我是王五,今年28岁(this = user)
user.address.showLocation(); // 我在北京(this = address)常见陷阱:隐式绑定丢失
隐式绑定的核心是“函数作为对象属性被调用”,如果函数被抽离出来独立执行,或作为回调函数传递,就会丢失原对象绑定,退化为默认绑定。
典型场景:
const user = {
name: "赵六",
sayHi() {
console.log(this.name);
}
};
// 陷阱1:函数赋值给独立变量
const hi = user.sayHi; // 仅获取函数引用,未执行
hi(); // undefined(this = window,非严格模式)
// 陷阱2:作为回调函数传递
setTimeout(user.sayHi, 1000); // undefined(定时器回调独立执行)
[1,2,3].forEach(user.sayHi); // undefined(forEach 回调独立执行)3. 显式绑定:用 call/apply/bind 强制指定 this
当我们需要手动控制 this 指向时,可以使用函数原型上的 call、apply、bind 方法,这种方式称为显式绑定,优先级高于隐式绑定。
三者的核心区别与用法如下表所示:
| 方法 | 核心作用 | 语法格式 | 执行时机 |
|---|---|---|---|
| call | 强制绑定 this,传递参数 | fn.call(thisArg, arg1, arg2, ...) | 立即执行 |
| apply | 强制绑定 this,数组形式传递参数 | fn.apply(thisArg, [arg1, arg2, ...]) | 立即执行 |
| bind | 强制绑定 this,返回新函数(不立即执行) | const newFn = fn.bind(thisArg, arg1, arg2, ...) | 后续手动执行 |
代码示例:
function printInfo(job) {
console.log(`${this.name},从事${job}工作`);
}
const personA = { name: "钱七" };
const personB = { name: "孙八" };
// call:立即执行,参数逐个传递
printInfo.call(personA, "前端开发"); // 钱七,从事前端开发工作
// apply:立即执行,参数以数组传递
printInfo.apply(personB, ["后端开发"]); // 孙八,从事后端开发工作
// bind:返回新函数,this 永久绑定
const printTeacher = printInfo.bind(personA, "讲师");
printTeacher(); // 钱七,从事讲师工作
// 即使后续用 call 尝试修改,也无效
printTeacher.call(personB, "产品经理"); // 钱七,从事讲师工作特殊情况:如果给 call/apply/bind 传递的 thisArg 是 null 或 undefined,则该参数会被忽略,this 遵循默认绑定:
printInfo.call(null, "设计师"); // undefined,从事设计师工作(非严格模式)
4. new 绑定:构造函数调用时
当函数通过 new 关键字调用时(此时函数称为“构造函数”),this 会指向新创建的实例对象,这是优先级最高的绑定规则。
new 关键字执行时,会依次完成 4 件事:
- 创建一个空对象(
const instance = {}); - 让空对象的
__proto__指向构造函数的prototype(实现原型继承); - 构造函数的
this指向这个空对象; - 若构造函数没有返回对象,则默认返回这个新对象。
代码示例:
function Person(name, age) {
this.name = name;
this.age = age;
console.log(this); // Person { name: '周九', age: 30 }(this 指向新实例)
}
const person1 = new Person("周九", 30);
console.log(person1.name); // 周九(this 绑定到 person1)
// new 绑定优先级高于显式绑定
const person2 = { name: "吴十" };
const BoundPerson = Person.bind(person2); // 显式绑定到 person2
const person3 = new BoundPerson("郑十一", 25);
console.log(person3.name); // 郑十一(this 指向新实例 person3,而非 person2)5. 特殊场景:箭头函数的 this
ES6 引入的箭头函数(() => {})是一个特例——箭头函数没有自己的 this。它的 this 是定义时所在的外层作用域的 this,且一旦绑定就永久固定,无法通过 call/apply/bind/new 修改。
箭头函数的设计初衷,就是为了解决“嵌套函数 this 丢失”的问题。
代码示例:
const obj = {
name: "王十二",
asyncTask() {
// 普通函数作为回调:this 指向 window(默认绑定)
setTimeout(function() {
console.log(this.name); // undefined
}, 500);
// 箭头函数作为回调:this 继承外层 asyncTask 的 this(即 obj)
setTimeout(() => {
console.log(this.name); // 王十二
}, 1000);
}
};
obj.asyncTask();
// 箭头函数无法用 new 调用(会报错)
const ArrowFn = () => {};
new ArrowFn(); // TypeError: ArrowFn is not a constructor
// 箭头函数的 this 无法通过 call 修改
const test = () => console.log(this.name);
test.call({ name: "李十三" }); // undefined(this 仍为外层作用域的 this)适用场景:箭头函数适合作为回调函数(如定时器、数组方法、Promise 回调),避免 this 指向混乱;不适合作为构造函数或对象方法(会导致 this 指向不符合预期)。
三、this 指向判断流程图(优先级从高到低)
掌握以下流程图,可快速判断任意场景下 this 的指向:
- 函数是用
new调用的吗?→this= 新创建的实例; - 函数是用
call/apply/bind调用的吗?→this= 手动指定的对象; - 函数是作为对象的属性调用的吗?→
this= 调用者对象; - 函数是独立调用的吗?→ 非严格模式
this = 全局对象,严格模式this = undefined; - 若是箭头函数?→
this= 定义时外层作用域的this。
四、经典面试题解析(巩固理解)
题1:隐式绑定丢失与箭头函数对比
const user = {
name: "小明",
fn1() {
setTimeout(function() {
console.log(this.name); // 答案:undefined(默认绑定 window)
}, 0);
},
fn2() {
setTimeout(() => {
console.log(this.name); // 答案:小明(继承 fn2 的 this = user)
}, 0);
}
};
user.fn1();
user.fn2();题2:new 与 bind 优先级
function Foo() {
this.value = 100;
}
const obj = { value: 200 };
const BoundFoo = Foo.bind(obj); // 显式绑定到 obj
const instance = new BoundFoo();
console.log(instance.value); // 答案:100(new 优先级高于 bind,this 指向实例)
题3:嵌套对象的隐式绑定
const company = {
name: "字节跳动",
department: {
name: "前端研发部",
getDepartmentName() {
console.log(this.name);
}
}
};
const getDeptName = company.department.getDepartmentName;
getDeptName(); // 答案:undefined(独立调用,默认绑定 window)
company.department.getDepartmentName(); // 答案:前端研发部(this 指向 department)
五、总结
JavaScript 中的 this 并非“玄学”,而是遵循明确的绑定规则:
- 核心原则:
this指向由函数调用方式决定,而非定义位置; - 优先级顺序:
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定; - 特殊情况:箭头函数无自身
this,继承外层作用域的this,适合解决回调函数this丢失问题; - 常见陷阱:隐式绑定丢失(函数赋值给独立变量、作为回调传递),需通过
bind或箭头函数规避。
掌握以上规则后,无论遇到多么复杂的 this 场景,都能按优先级逐步拆解,精准判断其指向。
到此这篇关于深入理解 JavaScript 中的 this 绑定机制的文章就介绍到这了,更多相关js this绑定机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
IE view-source 无法查看看源码 JavaScript看网页源码
查看网页源代码的方法其实有好几种,最常用的我们就是在浏览器中直接选择“查看网页源代码”就可以了,但是在有些时候这种方法却不能见效,以下再介绍几种简单的方法供大家参考!2009-07-07
深入JavaScript高级程序设计之对象、数组(栈方法,队列方法,重排序方法,迭代方法)
这篇文章主要介绍了深入JavaScript高级程序设计之对象、数组(栈方法,队列方法,重排序方法,迭代方法)的相关资料,需要的朋友可以参考下2015-12-12


最新评论