作者:Faraz Kelhini翻译:疯狂的技术宅
https://blog.logrocket.com/wh...
未经允许严禁转载
JavaScript 语言越来越被广泛地用于各种环境中。除了 Web 浏览器(这是 JavaScript 的最常见的宿主环境类型)之外,你还可以在服务器,智能手机甚至机器人硬件中运行 JavaScript 程序。
每个环境都有其自己的对象模型,并提供了不同的语法来访问全局对象。例如,在Web浏览器中,可以通过 window
,self
或 frames
访问全局对象。但是在 Node.js 中,这些属性不存在,而你必须使用 global
。在 Web Worker 中,只有 self
可用。
这些引用全局对象的不同方式使编写能够在多个环境中工作的可移植 JavaScript 代码变得非常困难。幸运的是,有一个正在开发中的提案打算通过引入一个名为 globalThis
的标准属性来解决这个问题,该属性将在所有环境中可用。
在本文中,我们将首先研究流行的 JavaScript 环境中的全局对象,然后看看 globalThis
是如何提供一种统一的机制来访问它。
window
window
属性用于在浏览器环境中引用当前文档的全局对象。在代码的顶层,使用 var
关键字声明的变量将成为 window
的属性,并且可能够在代码中的任何位置访问:
var a = [10, 20];
console.log(window.a); // → [10, 20]
console.log(a === window.a); // → true
通常在使用 window
的属性时,由于隐含引用的缘故不必直接引用 window
。但是当有一个与全局变量同名的局部变量时,使用 window
是唯一的选择:
var a = 10;
(function() {
var a = 20;
console.log(a); // → 20
console.log(window.a); // → 10
})();
如你所见,无论代码在什么作用域内运行,window
对于引用全局对象都非常有用。注意,`window
实际上引用了 window.window
。因此,window.window === window
。
除了标准的 JavaScript 属性和方法之外,window
对象还包含其他一些属性和方法,这些属性和方法使我们能够控制 Web 浏览器窗口以及文档本身。
self
Web Workers API没有 window
对象,因为它没有浏览上下文。相反,它提供了 WorkerGlobalScope
接口,其中包含通常由 WorkerGlobalScope
承载的数据。
为了访问 Web Workers 中的全局对象,我们需要使用 self
,它是 Window
对象的 window
属性的同义词。与 window
类似,self
是对全局对象的引用,可用于显式引用:
// a web worker
console.log(self); // => DedicatedWorkerGlobalScope {...}
var a = 10;
console.log(self.a); // → 10
console.log(a === self.a); // → true
在浏览器环境中,此代码将记录 Window
而不是 DedicatedWorkerGlobalScope
。由于 self
的值会根据使用环境的不同而变化,所以有时最好使用 Window
。 self
在 web worker 上下文中引用 WorkerGlobalScope.self
,而在浏览器上下文中引用 window.self
。
重要的是不要将 self
属性与声明局部变量(用于维护对上下文的引用)的常见 JavaScript 模式混淆。例如:
const obj = {
myProperty: 10,
myMethod: function(){
console.log(this === obj); // => true
// store the value of this in a variable for use in nested functions
const self = this;
const helperFunction = (function() {
console.log(self === obj); // => true (self refers to the outer this value)
console.log(this === obj); // => false (this refers to the global object. In strict mode, it has a value of undefined)
})();
}
};
// invoke myMethod on the object obj.
obj.myMethod();
frames
另一种在浏览器环境中访问全局对象的方法是使用 frames
属性,该属性的作用类似于 self
和 window
:
// browser environment
console.log(frames); // => Window {...}
这个只读属性通常用于获取当前窗口的子帧列表。例如你可以用 window.frames [0]
或 frames [0]
访问第一帧。
global
在 Node.js 中,你可以使用 global
关键字访问全局对象:
// node environment
console.log(global); // => Object [global] {...}
window
、 self
或 frames
在 Node 环境中不起作用。请记住,Node.js 中的顶级作用域不是全局作用域。在浏览器中,var abc = 123
将创建一个全局变量。但是在 Node.js 中变量是模块本身的局部变量。
this
在浏览器中,可以在程序的顶层使用 this
关键字来引用全局对象:
this.foo = 123;
console.log(this.foo === window.foo); // => true
this
在非严格模式下在函数或箭头函数内也引用全局对象。但是在严格模式下运行的函数就不是这种情况了,其中 this
的值为 undefined
:
(function() {
console.log(this); // => Window {...}
})();
(() => {
console.log(this); // => Window {...}
})();
(function() {
"use strict";
console.log(this); // => undefined
})();
在 Node 模块中,顶层的 this
不引用全局对象。相反,它与 module.exports
具有相同的值。在函数内部(Node 环境),this
的值取决于函数的调用方式。在 JavaScript 模块中,顶层的 this
是 undefined
。
介绍 globalThis
globalThis
旨在通过定义标准的全局属性来整合越来越分散的访问全局对象的方式。 globalThis
提案目前处于第 4 阶段,这意味着它已准备好纳入 ES2020 标准。所有流行的浏览器,包括 Chrome 71 +,Firefox 65+和Safari 12.1+,都已支持该功能。你也可以在 Node.js 12+ 中使用它。
// browser environment
console.log(globalThis); // => Window {...}
// node.js environment
console.log(globalThis); // => Object [global] {...}
// web worker environment
console.log(globalThis); // => DedicatedWorkerGlobalScope {...}
通过使用 globalThis
,你的代码能够在窗口和非窗口上下文中工作,而无需编写其他检查或测试代码。在大多数环境中, globalThis
直接引用该环境的全局对象。但是在浏览器中,内部需要使用代理来考虑 iframe 和跨窗口安全性。实际上,它并不会改变你编写代码的方式。
通常,当你不确定要在哪种环境中使用代码时,或者当你想使代码在不同环境中可执行时,可以用 globalThis
属性。不过你必须用 polyfill 在不支持该功能的旧版浏览器上实现该功能。
另一方面,如果需要你确定要在什么环境中使用代码,请使用前面列举引用环境全局对象的现有方法之一,避免为 globalThis
添加 polyfill 的麻烦。
创建一个 globalThis
polyfill
在引入 globalThis
之前,一种常用的跨环境访问全局对象的方法是使用以下模式:
function getGlobalObject() {
return Function('return this')();
}
if (typeof getGlobalObject().Promise.allSettled !== 'function') {
// the Promise.allSettled() method is not available in this environment
}
这段代码的问题在于,在强制执行内容安全策略(CSP)的网站中不能用 Function
构造函数和 eval
。由于CSP的缘故,Chrome 的扩展程序系统也不允许此类代码运行。
引用全局对象的另一种模式如下:
function getGlobalObject() {
if (typeof globalThis !== 'undefined') { return globalThis; }
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('cannot find the global object');
};
if (typeof getGlobalObject().Promise.allSettled !== 'function') {
// the Promise.allSettled() method is not available in this environment
}
这种模式通常在 web 上使用。但也有几个缺陷,使其在某些情况下不可靠。幸运的是 Chrome DevTools 团队的Mathias Bynens 提出了一种创意模式,它没有这些缺点:
(function() {
if (typeof globalThis === 'object') return;
Object.defineProperty(Object.prototype, '__magic__', {
get: function() {
return this;
},
configurable: true // This makes it possible to `delete` the getter later.
});
__magic__.globalThis = __magic__; // lolwat
delete Object.prototype.__magic__;
}());
// Your code can use `globalThis` now.
console.log(globalThis);
与其他方法相比,polyfill 是更可靠的解决方案,但仍然不够完美。正如 Mathias 提到的那样,修改Object
、 Object.defineProperty
或 Object.prototype.__defineGetter__
可能会破坏 polyfill。
总结
能够用在多种环境中的可移植 JavaScript 代码很难编写。每个主机环境都有一个略有不同的对象模型。因此,要访问全局对象,你需要在不同的 JavaScript 环境中使用不同的语法。
通过引入 globalThis
属性,访问全局对象将变得更加简单,并且不再需要去检测代码所运行的环境。
乍一看 globalThis
似乎很容易实现。但是实际上,正确地进行操作是非常复杂的。现有的解决方法都不完美,如果不小心就可能会引入错误。
ECMAScript 正在迅速发展,你可以期望它能够更多地引入新功能。要获取有关规范最新添加的更新,请查看完成的提案列表。
本文首发微信公众号:前端先锋
欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章
欢迎继续阅读本专栏其它高赞文章:
- 深入理解Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13个帮你提高开发效率的现代CSS框架
- 快速上手BootstrapVue
- JavaScript引擎是如何工作的?从调用栈到Promise你需要知道的一切
- WebSocket实战:在 Node 和 React 之间进行实时通信
- 关于 Git 的 20 个面试题
- 深入解析 Node.js 的 console.log
- Node.js 究竟是什么?
- 30分钟用Node.js构建一个API服务器
- Javascript的对象拷贝
- 程序员30岁前月薪达不到30K,该何去何从
- 14个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩展插件
- Node.js 多线程完全指南
- 把HTML转成PDF的4个方案及实现