JavaScript中this关键词的使用技巧、工作原理以及注意事项
本文详细解析JavaScript中this关键词在不同上下文(函数、全局、eval)下的指向规则,剖析实函数、构造器与方法中this的行为差异。重点探讨忘记使用new、方法作为回调等常见陷阱,并提供bind、that变量及ES6箭头函数等解决方案,帮助开发者编写更安全的代码。
this关键词的三种基本场景
理解this关键字的关键在于其所在的上下文环境,主要可分为以下三种情况:
1、在函数内部:this通常作为一个隐含参数存在。
2、在函数外部(即顶级作用域):在浏览器环境中,this指向全局对象;而在Node.js环境中,this则指向模块的导出对象(module.exports)。
3、在传递给eval()函数的字符串中:若eval()被直接调用,this指向当前对象;若被间接调用,this则指向全局对象。
函数中this的行为分析
针对上述分类,我们进行了相应的测试验证: 1、在函数中的this表现
函数代表了JavaScript中所有可被调用的结构,因此这是this最常见的使用场景。函数又可细分为以下三种角色:
普通函数(实函数) 构造器 方法
1.1 在普通函数(实函数)中的this
在普通函数中,this的值取决于代码运行的模式。
非严格模式(Sloppy mode):this指向全局对象(在浏览器中即为window)。
代码如下:
function sloppyFunc() {
console.log(this === window); // true
}
sloppyFunc();
严格模式(Strict mode):this的值为undefined。
代码如下:
function strictFunc() {
'use strict';
console.log(this === undefined); // true
}
strictFunc();
由于this是函数的隐含参数,其值在函数调用时是固定的。但可以通过call()或apply()方法显式地指定this的值。
代码如下:
function func(arg1, arg2) {
console.log(this); // 1
console.log(arg1); // 2
console.log(arg2); // 3
}
func.call(1, 2, 3); // (this, arg1, arg2)
func.apply(1, [2, 3]); // (this, arrayWithArgs)
1.2 构造器中的this
你可以使用new操作符将函数作为构造器来调用。new操作会创建一个新的对象,并通过this将该对象传入构造器中。
代码如下:
var savedThis;
function Constr() {
savedThis = this;
}
var inst = new Constr();
console.log(savedThis === inst); // true
JavaScript中new操作符的实现原理大致如下代码所示(更准确的实现请参考相关文档,该实现较为复杂):
代码如下:
function newOperator(Constr, arrayWithArgs) {
var thisValue = Object.create(Constr.prototype);
Constr.apply(thisValue, arrayWithArgs);
return thisValue;
}
1.3 方法中的this
在方法中,this的行为更接近传统面向对象语言:this指向接收者,即包含该方法的对象。
代码如下:
var obj = {
method: function () {
console.log(this === obj); // true
}
}
obj.method();
作用域与eval中的this
2、作用域中的this
在浏览器中,顶级作用域即为全局作用域,this指向全局对象(如window):
代码如下:
<script>
console.log(this === window); // true
</script>
在Node.js中,你通常在模块(module)中执行函数。因此,顶级作用域是一个特殊的模块作用域(module scope):
代码如下:
// `global` (not `window`) refers to global object:
console.log(Math === global.Math); // true
// `this` doesn't refer to the global object:
console.log(this !== global); // true
// `this` refers to a module's exports:
console.log(this === module.exports); // true
3、eval()中的this
eval()可以被直接调用(通过函数名'eval')或间接调用(通过其他方式如call())。更多细节请参考相关文档。
代码如下:
// Real functions
function sloppyFunc() {
console.log(eval('this') === window); // true
}
sloppyFunc();
function strictFunc() {
'use strict';
console.log(eval('this') === undefined); // true
}
strictFunc();
// Constructors
var savedThis;
function Constr() {
savedThis = eval('this');
}
var inst = new Constr();
console.log(savedThis === inst); // true
// Methods
var obj = {
method: function () {
console.log(eval('this') === obj); // true
}
}
obj.method();
this相关的常见陷阱与解决方案
4、与this有关的陷阱
需警惕以下三种与this相关的常见陷阱。值得注意的是,在示例中使用严格模式(strict mode)能提高代码安全性。因为在普通函数中,this的值为undefined,出现问题时会抛出警告。
4.1 忘记使用new
如果未使用new调用构造器,则相当于在调用普通函数。此时this不会是你预期的值。在非严格模式下,this指向window,从而创建全局变量:
代码如下:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p = Point(7, 5); // 忘记使用new!
console.log(p === undefined); // true
// 全局变量已被创建:
console.log(x); // 7
console.log(y); // 5
如果使用严格模式,你会得到警告(this===undefined):
代码如下:
function Point(x, y) {
'use strict';
this.x = x;
this.y = y;
}
var p = Point(7, 5);
// TypeError: Cannot set property 'x' of undefined
4.2 不恰当地使用方法
如果你直接获取方法的值(而非调用它),实际上是将方法作为函数使用。这常发生在将方法作为参数传入其他函数或调用方法时。setTimeout()和注册事件句柄(event handlers)即属此类情况。我将使用callIt()方法来模拟此场景:
代码如下:
/** Similar to setTimeout() and setImmediate() */
function callIt(func) {
func();
}
如果在非严格模式下将方法作为函数调用,this指向全局对象,因此后续创建的都是全局变量。
代码如下:
var counter = {
count: 0,
// Sloppy-mode method
inc: function () {
this.count++;
}
}
callIt(counter.inc);
// 未生效:
console.log(counter.count); // 0
// 相反,创建了一个全局变量
// (NaN是undefined应用++的结果):
console.log(count); // NaN
如果在严格模式下这样做,this为undefined,虽然仍得不到预期结果,但至少会收到警告:
代码如下:
var counter = {
count: 0,
// Strict-mode method
inc: function () {
'use strict';
this.count++;
}
}
callIt(counter.inc);
// TypeError: Cannot read property 'count' of undefined
console.log(counter.count);
要获得预期结果,可以使用bind():
代码如下:
var counter = {
count: 0,
inc: function () {
this.count++;
}
}
callIt(counter.inc.bind(counter));
// 生效!
console.log(counter.count); // 1
bind()创建了一个始终将this设置为counter的函数。
4.3 隐藏this
当在方法中使用内部函数时,常忽略函数拥有自己的this。该this与方法中的this不同,因此不能混用。具体见以下代码:
代码如下:
var obj = {
name: 'Jane',
friends: [ 'Tarzan', 'Cheeta' ],
loop: function () {
'use strict';
this.friends.forEach(
function (friend) {
console.log(this.name+' knows '+friend);
}
);
}
};
obj.loop();
// TypeError: Cannot read property 'name' of undefined
上述例子中,函数内的this.name无法使用,因为函数的this值为undefined,这与方法loop()中的this不同。以下提供三种解决思路:
1、that=this,将this赋值给一个变量,从而显式地表现this(除了that,self也是存放this的常见变量名),随后使用该变量:
代码如下:
loop: function () {
'use strict';
var that = this;
this.friends.forEach(function (friend) {
console.log(that.name+' knows '+friend);
});
}
2、bind()。使用bind()创建一个函数,该函数的this始终包含你想要传递的值(如下例中方法的this):
代码如下:
loop: function () {
'use strict';
this.friends.forEach(function (friend) {
console.log(this.name+' knows '+friend);
}.bind(this));
}
3、使用forEach的第二个参数。forEach的第二个参数会被传入回调函数中,作为回调函数的this使用。
代码如下:
loop: function () {
'use strict';
this.friends.forEach(function (friend) {
console.log(this.name+' knows '+friend);
}, this);
}
ES6箭头函数与最佳实践
5、最佳实践
理论上,我认为普通函数并不拥有自己的this,上述解决方案也基于此思想。ECMAScript 6通过箭头函数(arrow function)实现了这一效果,箭头函数是没有自己this的函数。在这样的函数中,你可以随意使用this,无需担心隐式存在。
代码如下:
loop: function () {
'use strict';
// The parameter of forEach() is an arrow function
this.friends.forEach(friend => {
// `this` is loop's `this`
console.log(this.name+' knows '+friend);
});
}
我不喜欢某些API将this作为普通函数的附加参数:
代码如下:
beforeEach(function () {
this.addMatchers({
toBeInRange: function (start, end) {
...
}
});
});
将隐性参数显式传入,代码会更易理解,且与箭头函数的要求一致:
代码如下:
beforeEach(api => {
api.addMatchers({
toBeInRange(start, end) {
...
}
});
});JavaScript异步发送HTTP请求时怎么优雅地处理错误
2026-11-05 17:10:44
5款主流JS在线运行工具对比:CodePen、JSFiddle与StackBlitz怎么选
2026-11-05 17:04:04
JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00
