环境检测:node.js 或 浏览器

90

我正在开发一个JS应用程序,需要在客户端和服务器端(在浏览器中的JavaScript和Node.js中)工作,并且我希望能够重用两个环境使用的部分代码。

我已经发现window是仅限于浏览器访问的变量,而在Node环境下是global,因此我可以检测代码执行的环境(假设没有其他脚本声明了window变量)。

这里有两个问题:

  1. 如何检测代码运行在哪个浏览器中。例如,以下代码是否可行?(此代码为内联代码,意味着它被一些全局代码包围,用于两个环境的重用)
  2. if window?
        totalPath= "../examples/#{path}"
    else
        totalPath= "../../examples/#{path}"
    
  3. 我该如何使用全局变量来适用于两个环境?目前我正在使用以下方法,但感觉并不正确。

  4. if window?
        window.DocUtils = {}
        window.docX = []
        window.docXData= []
    else
        global.DocUtils= {}
        global.docX = []
        global.docXData = []
    

可能是重复的问题:如何检查脚本是否在 node.js 下运行? - João Pimentel Ferreira
12个回答

120

注意:这个问题有两个部分,但因为标题是“环境检测:node.js还是浏览器”,我将先回答这一部分,因为我猜很多人来这里寻找答案。另一个问题可能需要单独提问。

在JavaScript中,变量可以被内部作用域重新定义,因此假设环境没有创建名为process、global或window的变量可能会很容易失败,例如如果使用了node.js jsdom模块,则API使用示例就有这种情况。

var window = doc.defaultView;

基于存在window变量来检测环境的模块将无法在该作用域下运行。同样的逻辑,任何基于浏览器的代码都可以轻松地覆盖globalprocess,因为它们不是该环境中的保留变量。

幸运的是,有一种方法可以要求全局范围并测试它是什么 - 如果您使用new Function()构造函数创建一个新函数,则this的执行范围将绑定到全局范围,您可以直接将全局范围与预期值进行比较。 *)

因此,要创建一个函数以检查全局范围是否为“window”,可以这样做:

var isBrowser=new Function("try {return this===window;}catch(e){ return false;}");

// tests if global scope is bound to window
if(isBrowser()) console.log("running under browser");

一个用于测试全局作用域是否绑定到“global”的函数是:

var isNode=new Function("try {return this===global;}catch(e){return false;}");

// tests if global scope is bound to "global"
if(isNode()) console.log("running under node.js");

try...catch语句块将确保如果变量未定义,则返回false

isNode()函数还可以与node.js中找到的全局变量进行比较,例如this.process.title==="node",但是在实践中,与全局进行比较就足够了。

http://jsfiddle.net/p6yedbqk/

注意:不建议检测运行环境。然而,在特定的环境中,如开发和测试环境中,检测运行环境可能是有用的,因为它具有全局范围的某些已知特征。

现在 - 回答的第二部分。在进行环境检测之后,您可以选择基于环境使用哪种策略来绑定应用程序中的“全局”变量(如果有)。

我认为这里推荐的策略是使用单例模式将设置绑定在类内部。SO上已经有一个很好的替代方案列表。

JavaScript中实现单例模式的最简单/最干净的方法

所以,如果您不需要“全局”变量,并且根本不需要检测环境,请使用单例模式定义一个模块,它将为您存储值。好吧,有人可能会争辩说该模块本身就是一个全局变量,在JavaScript中实际上确实是这样,但至少在理论上,看起来更加清晰明了。

*) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function

注意:使用Function构造函数创建的函数不会创建其创建上下文的闭包;它们总是在全局范围内创建。运行它们时,它们只能访问自己的本地变量和全局变量,而不能访问从调用Function构造函数的范围中获取的变量。


2
嗨,感谢提供这个解决方案,我尝试了很多方法,但这一个完美地运作了,所以我使用它发布了一个npm包,希望你不会介意...看看这个 https://www.npmjs.com/package/detect-is-node - abhirathore2006
1
是的,我同意,但如果您想使用它100次,那么编写一个简单的函数会更容易阅读和理解。 - abhirathore2006
@TeroTolonen,为什么你说不建议检测环境?许多主要的框架都可以在Node和Web环境中工作。包含“获取文件”函数的模块在每个环境中的工作方式都非常不同,例如Web环境下使用XMLHttpRequest,而Node环境下使用http或path模块。 - McShaman
2
不使用异常:(function (){ return (typeof window !== 'undefined') && (this === window); }).call(undefined); - Semmel
1
我认为new Function()构造函数的一个限制是,如果在内容安全策略script-src指令中不允许“unsafe-eval”作为脚本源,则会失败。 - John Henderson
显示剩余5条评论

25

看起来Node.js可以同时存在(使用NW.js?),我个人的方式是通过检测process.versions对象中是否存在node入口。

var isNode = false;    
if (typeof process === 'object') {
  if (typeof process.versions === 'object') {
    if (typeof process.versions.node !== 'undefined') {
      isNode = true;
    }
  }
}

多层级条件是为了避免在搜索未定义变量时发生错误,因为一些浏览器的限制。

参考链接:https://nodejs.org/api/process.html#process_process_versions


6
一句话概括:function isNodejs() { return typeof "process" !== "undefined" && process && process.versions && process.versions.node; } 的意思是判断当前环境是否为 Node.js 平台。 - brillout
@brillout 关闭:function isNodejs() { return typeof process === 'object' && typeof process.versions === 'object' && typeof process.versions.node !== 'undefined'; } - ams
1
process.versions.node !== 'undefined' should be process.versions.node !== undefined - avalanche1
请注意,@avalanche1 此处使用 typeof 是没有问题的。 - David Sherret
3
在现代语法中:const isNode = typeof process !== "undefined" && process?.versions?.node; - Nixinova

15

有一个专门用于此的npm包,它可以在客户端和服务器端都使用。

browser-or-node

你可以这样使用它

if (isBrowser) {
  // do browser only stuff
}

if (isNode) {
  // do node.js only stuff
}

免责声明:本包的作者是我自己 :)


9

我知道这是对一个1.5年前的问题的晚回复,但为什么不复制jQuery的源代码呢?

if (typeof module === "object" && typeof module.exports === "object") {
  // node
}

if (typeof window !== "undefined" && typeof window.document !== "undefined") {
  // browser
}

祝你好运。


1
我想知道什么时候定义了 window 但是没有定义 window.document - nonopolarity
1
如果您不检查window.document并且您的Node.js定义了一个window变量,那么您将会得到一个错误的浏览器判断结果。 - Salatiel

8
您可以根据情况将变量窗口或全局附加。尽管这不是制作多平台JS应用程序的推荐方式:
var app = window ? window : global;

在应用程序的逻辑中使用全局变量,但该变量应由基于不同平台的部分组成,这样会更好。例如:

var app = {
    env: '',
    agent: ''
};

if (window) {
    app.env = 'browser';
    app.agent = navigator.userAgent;
} else if (process) {
    app.env = 'node';
}

因此,这个想法是你的主要应用程序逻辑将完全相同,并使用相同的对象,只有全局对象需要根据环境进行更改。这使得您的应用程序在平台方面更具可移植性和灵活性。


谢谢,实际上我找到了另一篇帖子处理coffeescript部分的问题https://dev59.com/42855IYBdhLWcg3wp2N0,所以这很好。 - edi9999
请注意,var app = window ? window : global; 可以简化为 var app = window || global; - Itai Steinherz
5
在Node环境中运行 var app = window ? window : global; 会产生错误 Thrown: ReferenceError: window is not defined。你是否在Node环境下测试过它的工作情况?(使用的版本是 v11.15.0 - humanityANDpeace
var app = (typeof(window) != 'undefined') ? window : global 似乎有效。 - Nathan Chappell

5

这似乎在范围不变的情况下运作良好,除非您已经命名了其他window

const isBrowser = () => typeof window !== `undefined`

if (isBrowser())
  console.log(`is browser`)
else
  console.log(`is node.js`)

1
/*
    detect global/window/self in browser, deno, nodejs
    including where 'this' is undefined
*/

const self = new Function('return this')(); // be careful, Function is like eval, use with caution
console.log( 
    (self.window && "window" || self.global && 'global'),
    self.toString().slice('[object '.length, -1).toLowerCase()
);

/*
    browser: window window
    nodejs: global global
    deno: window object
*/

1

这是一个老问题,有许多复杂的答案,甚至有一个npm包,但这个解决方案非常简单和强大,除非有人刻意破坏(顺便说一句,没有任何解决方案是100%准确的,因为你可以在两个环境中设置全局变量)。

if (typeof process === 'object' && String(process) === '[object process]') {
  // is Node
} else {
  // is Browser
}

通常(几乎总是)在浏览器上运行的脚本没有全局的process对象,即使您意外创建了一个process = {},它也会在第二个条件中失败。


0

这是我根据@TeroTolonen的答案使用的内容:

var isBrowser = (function() {
  try {
    return this === window;
  } catch (e) {
    return false;
  }
})();

if (isBrowser) {

}

不需要函数构造器,而且只需调用一次即可。


0
一种可靠的方法来检测Node和浏览器环境:

const isBrowser = typeof window === 'object' && '[object Window]' === window.toString.call(window)
const isNode = typeof global === 'object' && '[object global]' === global.toString.call(global)

console.log(`isBrowser: ${isBrowser}`)
console.log(`isNode: ${isNode}`)


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