在node.js模块和函数中,“this”的含义是什么?

56
我有一个JavaScript文件,它被require加载。
// loaded by require()

var a = this; // "this" is an empty object
this.anObject = {name:"An object"};

var aFunction = function() {
    var innerThis = this; // "this" is node global object
};

aFunction();

(function(anyParameter){
    console.log(anyParameter.anObject);
})(
    this // "this" is same having anObject. Not "global"
);

我的问题是:在变量a = this;中,this是一个空对象,而函数中的this语句却是node.js全局对象的影子。我知道this关键字在函数中有所不同,但我无法理解为什么第一个this不等于全局对象,而函数中的this等于全局对象。

Node.js如何将全局对象global注入到函数作用域中的this中,为什么它不将其注入到模块作用域中?


4
在 JavaScript 语言中,this 的值是由其核心特性决定的(尽管 NodeJS 可以通过 JavaScript 语言特性设置该值)。你可能需要阅读一些 this 文档。请注意,这些文档通常假设 JS 运行在浏览器中,因此“全局对象”将是 window 而不是 NodeJS 全局对象,但概念相同。一些更相关的文档是 NodeJS 的 this 文档 - ajp15243
1
请查看这个问题 - Two-Bit Alchemist
2
我知道为什么两个 this 值不同。我的问题是为什么和如何 node.js 在函数作用域中将 global 注入到 this 而不是外部作用域。它可以将 global 注入到两个 this 中,使它们保持不同。 - Gökçer Gökdal
你怎么调用 aFunction - ajp15243
@ajp15243,你指出的文档给了我更多关于this的见解,谢谢。据我所知,this根据函数被调用的位置而不同。但是我仍然无法找出为什么和如何node.js以不同的方式注入global。注意:我已经添加了对aFunction的调用,就像我调试时一样。 - Gökçer Gökdal
1
@GökçerGökdal:我认为这个答案至少部分回答了你的问题。 - go-oleg
4个回答

99
以下是您需要了解的一些基本事实,以澄清情况:
  • 在Node模块的顶级代码中,thismodule.exports等价。你看到的是一个空对象。

  • 当你在函数内使用this时,this的值在每次执行函数之前都会重新确定,并且其值由函数的执行方式决定。这意味着,如果调用机制不同(例如aFunction() vs. aFunction.call(newThis) vs. emitter.addEventListener("someEvent", aFunction);等),��同的函数对象的两次调用可能具有不同的this值。在非严格模式下,aFunction()会将this设置为全局对象。

  • 当JavaScript文件作为Node模块require时,Node引擎会在包装函数(wrapper function)中运行模块代码。该模块包装函数以module.exportsthis调用。(回想一下上面的话,一个函数可以用任意的this值来运行。)

因此,您会得到不同的this值,因为每个this位于不同的函数内部:第一个位于由Node创建的模块包装器函数内部,第二个位于aFunction内部。

1
我想我也找到了module.export出现的代码。在module.js(node.js源代码)中,执行代码使用JavaScript的apply方法,如return compiledWrapper.apply(self.exports, args);。非常感谢大家的帮助。 - Gökçer Gökdal

43
为了理解这一点,您需要了解Node.js实际上将您的模块代码包装在一个函数中,就像这样。
(function (exports, require, module, __filename, __dirname) {
    var test = function(){
        console.log('From test: '  + this);
    };
    console.log(this);
    test();
});

更详细的解释可以在这个答案中找到。


现在,这个包装函数实际上是这样被调用的

var args = [self.exports, require, self, filename, dirname];
return compiledWrapper.apply(self.exports, args);

所以,模块级别的this实际上是exports对象。

您可以像这样确认

console.log(this, this === module.exports);
// {} true

1
可能值得指出的是,test 可以以不同的方式被调用,将 this 设置为其他内容。 - Ruan Mendes
1
据我所知,调用类似于fn()的函数将使this指向全局对象,或者在使用use strict时指向null,不管当前的this是什么...当设置this时,Node和浏览器是否不同?我认为正确的答案只是将全局对象设置为exports - Ruan Mendes
你能否对我的评论(以及我的答案)发表一下意见?我认为你的答案是误导性的,因为调用test()函数的where值并不影响test()函数内部this的值,这完全取决于它的调用方式。如果我有所遗漏,我很想了解。 - Ruan Mendes

16

概述:

Javascript中this的值是在函数被调用时确定的,而不是在创建函数时。在NodeJS中,在模块的最外层范围内,this的值为当前module.exports对象。当一个函数作为对象的属性被调用时,this的值将改变为该对象被调用。您可以通过点号左边规则来简单地记住这一点:

当一个函数被调用时,您可以通过查看函数调用的位置来确定this的值。点号左侧的对象是this的值。如果点号左侧没有对象,则this的值是module.exports对象(在浏览器中为window)。

警告:

  • 这条规则不适用于es2015箭头函数,它们没有自己的绑定this
  • callapplybind函数可以扭曲有关this值的规则。

示例(NodeJS):

console.log(this);  // {} , this === module.exports which is an empty object for now

module.exports.foo = 5;

console.log(this);  // { foo:5 }

let obj = {
    func1: function () { console.log(this); },
    func2: () => { console.log(this); }
}

obj.func1();  // obj is left of the dot, so this is obj
obj.func2();  // arrow function don't have their own this
              // binding, so this is module.exports, which is{ foo:5 } 

输出:

这里输入图片描述


这是一个显示图片的示例,图片链接在标签中指定。

1
当我运行 module.exports = {a:4}console.log(this) 时,我得到一个空对象。 - Pranav Singhal

-1

这是因为Node.js模块中的默认全局对象是exports对象,而您调用的test()没有指定this。在传统的JS中,this指向全局对象,使用严格模式,this将为null。

this可以指向任何东西,这取决于您如何调用它。

  • test():使用全局对象(exports)作为this,除非在严格模式下,此时this将为null;
  • test.call({})test.apply({}):您正在指定用作this的内容(第一个参数)
  • var obj = {testRef: test}; obj.testRef()this设置为.左侧的对象,也就是obj

反驳thefourtheye的回答

确实,在模块的顶层中,thisexports,但这并不一定意味着test()内部的this也会指向与调用它的地方相同的东西。


试图证明this和全局对象都指向exports

 myGLobal = 5;
 this.myGlobal; // 5

3
抱歉纠正你,但是在REPL模式下,this只指向global - Oleg
1
Node.js 中的全局对象是 exports 对象,这是不正确的。每个模块都有自己的 exports 对象。 - thefourtheye
@Curious 谢谢,这就是我问的原因,我从命令行运行了我的测试。 - Ruan Mendes
@thefourtheye 我从文档中了解到这一点,这是否意味着没有全局变量?在我上面的示例中,myGlobal在另一个模块中不存在吗? - Ruan Mendes
2
@JuanMendes 在Node中全局变量确实存在,但与客户端JS不同,您应该访问global对象。例如,global.myName ='Mike';将通过global.myNamemyName在所有模块中看到。但是var myName ='Mike';myName ='Mike'仅在当前模块中可见。请查看文档 - Oleg
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接