JavaScript中this关键词的使用技巧、工作原理以及注意事项

作者:代码拾荒

时间:2026-10-29

浏览:70

本文详细解析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) {
            ...
        }
    });
});

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章