JavaScript this 指向详解:从默认绑定到箭头函数的完整指南
本文深入解析 JavaScript 中 this 的四种绑定规则:默认绑定、隐式绑定、显式绑定(call/apply/bind)以及 new 绑定,并详细探讨 ES6 箭头函数的词法 this 特性。通过对比不同环境下的表现与绑定优先级,帮助开发者彻底掌握 this 的工作机制。
前言
JavaScript 中 this 的指向机制是一个经典且常新的话题。由于其行为方式较为灵活多变,本文将对其核心逻辑进行拆解分析,帮助读者深入理解。
这句话的核心含义是:this 的值取决于当前的执行上下文(context)。
简单来说,this 指向的是拥有当前执行上下文的对象,即上下文对象(context object)。
换言之,this 的具体指向取决于函数被调用时,是谁在调用它。
默认绑定与全局对象
全局对象在 this 的指向规则中扮演着“兜底”的角色。当没有更明确的绑定时,全局对象作为默认目标存在。
通常情况下,当 this 指向全局对象时,这被称为默认绑定。那么,什么是默认绑定呢?
通俗地讲,默认绑定发生在函数调用时没有任何调用前缀对象,或者函数没有特定绑定的情况下。在非严格模式下,此时 this 会指向全局对象。
在非严格模式下,不同运行环境的全局变量对象有所区别:
• 在浏览器环境中,this 指向 Window 对象;
• 在 Node.js 环境中,this 指向 global 对象;
• 在函数内部环境中,this 指向绑定当前函数的作用域(具体视调用方式而定,见下文);
在严格模式下:
• 在使用 'use strict' 的环境中,this 指向 undefined;
示例:在非严格模式下
{
/* 在非严格模式下,this默认绑定 */
console.log('window global this: ', this); // window
function fnOuter() {
console.log('fnOuter: ', this); // window
}
function windowThis() {
console.log('windowThis: ', this); // window
function fnInner() {
console.log('fnInner: ', this); // window
fnOuter();
}
fnInner();
}
windowThis();
}
在上述示例中,无论函数声明在哪里,也无论在哪里被调用,只要函数调用时前面没有指定任何对象,this 均指向全局对象 window。
需要注意的是,在严格模式下,默认绑定中的 this 会指向 undefined。
示例:在严格模式下,再来看几个例子,请在心中记下答案
{
/* 在非严格模式下,this默认绑定 */
var mode = '在非严格模式下,this默认绑定';
function windowThis() {
console.log('windowThis: ', this);
console.log('windowThis: ', this.mode);
function fnInner() {
console.log('fnInner: ', this);
console.log('fnInner: ', this.mode);
}
fnInner();
}
function windowStrictThis() {
'use strict';
windowThis();
function fnInner() {
console.log('windowStrictThis: ', this);
console.log('windowStrictThis: ', this.mode);
}
fnInner();
}
windowStrictThis();
}建议先得出答案再看下文解析,
一起来倒数吧,“花栗鼠。。。。。。”。
🌸🌰🐿️ 🌸🌰🐿️ 🌸🌰🐿️ 🌰🐿️🌸 🌰🐿️🌸 🌰🐿️🌸 🐿️🌸🌰 🐿️🌸🌰 🐿️🌸🌰
好啦,来看正确输出吧,都答对了吧~
// windowThis: Window{}
// windowThis: 在非严格模式下,this默认绑定
// fnInner: Window{}
// fnInner: 在非严格模式下,this默认绑定
// windowStrictThis: undefined
// windowStrictThis: TypeError: Cannot read property 'mode' of undefined
可见,在函数内部使用严格模式声明后,this 指向变为 undefined。同时,在函数内声明严格模式仅对该函数内定义的变量与函数有效,不影响其调用的外部函数。
点石成金:隐式绑定
什么是隐式绑定呢?
this 的隐式绑定规则:如果在函数调用时,函数前面存在调用它的对象,那么 this 就会隐式绑定到这个调用对象上。
因此,隐式绑定的关键点在于函数被调用的对象是谁。简单来说,就是看调用这个函数前面的点(.)前面的对象是谁,谁就是 this 所绑定的对象。
示例:
{
var mode = 'window';
var boss1 = {
mode: 'boss1',
fn() {
console.log(this.mode);
},
};
var boss2 = {
mode: 'boss2',
call: boss1.fn,
o: boss1,
};
boss2.o.fn(); // boss1
boss2.call(); // boss2
var boss1Copy = boss1.fn;
boss1Copy(); // window
}当函数进行隐式绑定时,如果调用时涉及多个对象,this 指向距离自己最近的对象,也就是点(.)前面的对象是谁,this 就指向谁。
那么问题来了,如果删除 boss2 上的 mode 属性,会有什么不同呢?
示例:
{
var mode = 'window';
var boss1 = {
mode: 'boss1',
fn() {
console.log(this.mode);
},
};
var boss2 = {
call: boss1.fn,
o: boss1,
};
boss2.call(); // undefined
}答案是输出 undefined。因为此时 boss1 只是 boss2 的一个属性,boss1 与 boss2 的原型链各不相同,不属于父子关系。因此遵循作用域链查找规则,this 必须从 boss2 上查找 mode 属性,当 boss2 上不存在 mode 属性时则返回 undefined。注意不要与作用域链混淆了。
下面这个例子就要小心点咯,能想出答案么?
{
var mode = 'window';
var boss1 = {
mode: 'boss1 mode',
fn() {
console.log(this.mode);
},
};
function Fn() {}
Fn.prototype.mode = 'Fn mode';
Fn.prototype.fnProto = function() {
console.log(this.mode);
};
var boss2 = {
mode: 'boss2 mode',
fn: function() {
return boss1.fn();
},
proto: new Fn(),
};
boss2.fn(); // boss1 mode
boss2.proto.fnProto(); // Fn mode
}答案是 boss1 mode 和 Fn mode 哦,猜对了吗。
涉及原型链与作用域链的一些区别,基本这里就不做解析了,请各自查漏补缺~。
隐式绑定丢失
细心的同学可能已经发现,上述例子中存在将函数赋值给变量再调用的情景。当函数被赋值给变量后再调用,原本 this 指向会发生改变,函数的 this 不会指向其原对象,从而引起隐式绑定丢失问题。
常见引起隐式丢失的方式:
1. 函数赋值给变量再调用
示例:
{
var mode = 'window';
var boss1 = {
mode: 'boss1',
fn() {
console.log(this.mode);
},
};
var boss2 = {
mode: 'boss2',
call: boss1.fn,
o: boss1,
};
boss2.o.fn(); // boss1
boss2.call(); // boss2
var boss1Copy = boss1.fn;
boss1Copy(); // window
}上述案例中 boss1Copy 和 boss2.call 就是函数赋值给变量再调用的情况。
1. 函数以形参传递
示例:
{
var mode = 'window';
var boss1 = {
mode: 'boss1',
fn() {
console.log(this.mode);
},
};
function exce(params) {
params && params();
}
exce(boss1.fn); // window
}
上述例子中我们将 boss1.fn 也就是一个函数传递进 exce 中执行,这里只是单纯传递了一个函数而已,this 并没有跟函数绑在一起,此时 this 指向原对象发生丢失从而指向了 window。
可见,隐式丢失本质上是因为函数赋值引起的。在函数被赋值给变量或另一个函数形参 Fn 后,在调用 Fn 时 this 会指向离其最近的对象(即全局对象)。
指腹为婚:显式绑定
this 的显式绑定:指通过 Object.prototype.call、Object.prototype.apply、Object.prototype.bind 方法改变 this 指向。
这里我将显式绑定细分为:
• 显式绑定:在运行时改变 this 指向
• call
• apply
• 显式硬绑定:一次绑定后,永久不能改变 this 指向
• bind
接下来看个例子,分别通过 call、apply、bind 改变了函数 log 的 this 指向。
示例:
{
function log() {
console.log(this.name);
}
var boss1 = { name: 'boss1' };
var boss2 = { name: 'boss2' };
var boss3 = { name: 'boss3' };
log.call(boss1); // boss1
log.apply(boss2); // boss2
log.bind(boss3)(); // boss3
var logBind = log.bind(boss3);
logBind.apply(boss1); // boss3
logBind.bind(boss2); // boss3
}在 JavaScript 中,当调用一个函数时,我们习惯称之为函数调用,此时函数处于一个被动的状态;而 bind、call 与 apply 让函数从被动变主动,函数能主动选择自己的上下文,所以这种写法我们又称之为函数应用。
注意,如果在使用 bind、call 与 apply 之类的方法改变 this 指向时,指向参数提供的是 null 或者 undefined 时,this 将指向全局对象。
示例:
{
var name = 'window';
function log() {
console.log(this.name);
}
var boss1 = { name: 'boss1' };
var boss2 = { name: 'boss2' };
var boss3 = { name: 'boss3' };
log.call(null); // window
log.apply(undefined); // window
log.bind(undefined)(); // window
}同样值得注意的是,bind 在显式改变 this 指向之后会返回一个新的绑定函数(bound function,BF)。绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语),它包装了原函数对象。调用绑定函数通常会导致执行包装函数。
另外简明补充一下 call、apply、bind 的区别:
• bind:函数硬绑定 this 指向并返回一个全新函数 BF,且返回的 BF 无法再次被 call、apply、bind 改变 this 指向,且需要执行 BF 才会运行函数。
• function.bind(thisArg[, arg1[, arg2[, ...]]])()
• call:改变 this 指向的同时还会执行函数,以散列形式的形参。
• function.bind(thisArg[, arg1[, arg2[, ...]]])
• apply:改变 this 指向的同时还会执行函数,可接受一个数组形式的形参。
• function.apply(thisArg,[param1,param2...])
call 与 apply 主要区别在于传参形式不同。在传参的情况下,call 的性能要高于 apply,因为 apply 在执行时还要多一步解析数组。
内有乾坤:new 绑定
严格来说,JavaScript 中的构造函数只是使用关键字 new 调用的普通函数,它并不是一个类,最终返回的对象也不是一个实例,只是为了便于理解习惯这么说罢了。
一个比较容易忽略的会绑定 this 指向的方法就是使用 new。当我们 new 一个函数时,就会自动把 this 绑定在新对象上,然后再调用这个函数。new 会覆盖 bind 的绑定让其无法生效。
那么 new 对函数到底起到什么作用呢,大致分为四步:
1. 创建一个空的简单 JavaScript 对象(即{});
2. 为步骤 1 新创建的对象添加属性 __proto__,将该属性链接至构造函数的原型对象;
3. 将步骤 1 新创建的对象作为 this 的上下文;
4. 如果该函数没有返回对象,则返回 this。
这个过程我们称之为构造调用。
示例:那么 new 在构造调用时对 this 产生什么影响呢?请看栗子:
{
function log() {
console.log(this);
}
log(); // window{}
new log(); // log{}
var boss1 = { name: 'boss1' };
log.call(boss1); // boss1{}
new log.call(boss1); // Uncaught TypeError: log.call is not a constructor
new log.bind(boss1); // Uncaught TypeError: log.call is not a constructor
var logBind = log.bind(boss1);
logBind(); // boss1{}
new logBind(); // log{}
}当 new 一个函数时,this 会被绑定为函数本身,即使函数在 bind 改变 this 指向的情况下,关键字 new 依旧会将 this 指向为函数本身。且 new 绑定与显式绑定互不兼容。
军令如山:箭头函数
ES6 的箭头函数是另类的存在,为什么要单独说呢,这是因为箭头函数中的 this 不适用上面介绍的几种绑定规则。
准确来说,箭头函数中没有 this,箭头函数的 this 指向取决于外层作用域中的 this,外层作用域或函数的 this 指向谁,箭头函数中的 this 便指向谁。
因为箭头函数里的 this 是永远指向到当前词法作用域(Lexical this)之中,在代码编码时就可以确定。没有其它 this 绑定方式可以覆盖。
这样的好处就是方便让回调函数的 this 使用当前的作用域,不怕引起混淆。
所以对于箭头函数,只要看它在哪里创建的就行。
示例:有点吃软饭的嫌疑,一点都不硬朗,我们来看看栗子:
{
function fn() {
return () => {
console.log('efnArrow: ', this);
};
}
function callback(cb) {
cb();
}
var boss1 = {
name: 'boss1',
fn() {
console.log('fn: ', this);
},
fnArrow: () => {
console.log('fnArrow: ', this);
},
ret() {
return function() {
console.log('ret: ', this);
};
},
retArrow() {
return () => {
console.log('retArrow: ', this);
};
},
cb() {
callback(function() {
console.log('cb: ', this);
});
},
cbArrow() {
callback(() => {
console.log('cbArrow: ', this);
});
},
};
var boss2 = {
name: 'boss2',
fn: boss1.retArrow,
};
boss1.fn(); // fn: boss1{}
boss1.fnArrow(); // fnArrow: window{}
boss1.ret()(); // ret: window{}
boss1.retArrow()(); // retArrow: boss1{}
boss1.cb(); // cb: window{}
boss1.cbArrow(); // cbArrow: boss1{}
boss1.fn.call(boss2); // fn: boss2{}
boss1.fnArrow.call(boss2); // fnArrow: window{}
boss1.ret.call(boss2)(); // ret: window{}
boss1.retArrow.call(boss2)(); // retArrow: boss2{}
boss1.ret().call(boss2); // ret: boss2{}
boss1.retArrow().call(boss2); // retArrow: boss1{}
boss1.cb.call(boss2); // cb: window{}
boss1.cbArrow.call(boss2); // cbArrow: boss2{}
var bar = boss1.retArrow.call(boss2);
bar(); // returnArrowLog: boss2{}
bar.call(boss1); // returnArrowLog: boss2{}
bar.bind(boss1)(); // returnArrowLog: boss2{}
}对 boss1.retArrow 为啥我们第一次绑定 this 并返回箭头函数后,再次改变 this 指向没生效呢?
前面说了,箭头函数的 this 取决于外层作用域的 this,boss1.retArrow 函数执行时 this 指向了 boss1,所以箭头函数的 this 也指向 boss1。除此之外,箭头函数 this 还有一个特性,那就是一旦箭头函数的 this 绑定成功,也无法被再次修改,有点硬绑定的意思。
当然,箭头函数的 this 也不是真的无法修改。我们知道箭头函数的 this 就像作用域继承一样从上层作用域找,因此我们可以修改外层函数 this 指向达到间接修改箭头函数 this 的目的,如 boss1.retArrow.call(boss2)() 成功将 this 指向 boss2。
this 绑定优先级
前面已经介绍了几种 this 绑定规则,那么问题来了,如果一个函数调用存在多种绑定方法,this 最终指向谁呢?这里直接先上答案。
this 绑定优先级:
• 显式绑定 > 隐式绑定 > 默认绑定
• new 绑定 > 隐式绑定 > 默认绑定
为什么显式绑定不和 new 绑定比较呢?因为不存在这种绑定同时生效的情景,如果同时写这两种代码会直接抛错,所以大家只用记住上面的规律即可。
总结
文章到这里,对于 this 的几种绑定场景就全部总结完毕了,如果你有跟着例子一起练习下来,相信聪明的你现在对于 this 的理解一定更上一层楼了。
那么通过本文,我们学习了默认绑定在严格模式与非严格模式下 this 指向会有所不同。
也知道了隐式绑定的触发条件与隐式丢失的几种情况。
相对于隐式绑定的不可见,我们还学习到显式绑定以及硬绑定,并提到非严格模式下,当绑定指向为 null 或 undefined 时 this 会指向全局。
接着重新认识了 new 绑定。
最后我们了解了不太合群的箭头函数中的 this 绑定,了解到箭头函数的 this 由外层作用域的 this 指向决定,并有一旦绑定成功也无法再修改的特性。
好了,内容如有不妥之处请指正,希望你能受益良多~
相关文献
• MDN-this[1]
• MDN-Function.prototype.bind()[2]
• MDN-Function.prototype.call()[3]
• MDN-Function.prototype.apply()[4]
• MDN-new 运算符[5]
• MDN-箭头函数[6]
引用链接
[1] MDN-this: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this
[2] MDN-Function.prototype.bind(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
[3] MDN-Function.prototype.call(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call
[4] MDN-Function.prototype.apply(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
[5] MDN-new 运算符: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new
[6] MDN-箭头函数: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions
到此这篇关于你可能不知道的JavaScript之this指向详解的文章就介绍到这了,更多相关JS this指向内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
ES6模块化中js文件怎么用import和export交互
2026-11-05 17:00:48
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53
