用 globalThis 訪問全局對象

做者:Faraz Kelhini

翻譯:瘋狂的技術宅javascript

https://blog.logrocket.com/wh...前端

未經容許嚴禁轉載java

JavaScript 語言愈來愈被普遍地用於各類環境中。除了 Web 瀏覽器(這是 JavaScript 的最多見的宿主環境類型)以外,你還能夠在服務器,智能手機甚至機器人硬件中運行 JavaScript 程序。node

每一個環境都有其本身的對象模型,並提供了不一樣的語法來訪問全局對象。例如,在Web瀏覽器中,能夠經過 windowselfframes 訪問全局對象。可是在 Node.js 中,這些屬性不存在,而你必須使用 global。在 Web Worker 中,只有 self 可用。git

這些引用全局對象的不一樣方式使編寫可以在多個環境中工做的可移植 JavaScript 代碼變得很是困難。幸運的是,有一個正在開發中的提案打算經過引入一個名爲 globalThis 的標準屬性來解決這個問題,該屬性將在全部環境中可用。程序員

在本文中,咱們將首先研究流行的 JavaScript 環境中的全局對象,而後看看 globalThis 是如何提供一種統一的機制來訪問它。github

window

window 屬性用於在瀏覽器環境中引用當前文檔的全局對象。在代碼的頂層,使用 var 關鍵字聲明的變量將成爲 window 的屬性,而且可可以在代碼中的任何位置訪問:web

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 === windowsegmentfault

除了標準的 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 的值會根據使用環境的不一樣而變化,因此有時最好使用 Windowself 在 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 屬性,該屬性的做用相似於 selfwindow

// browser environment
console.log(frames);    // => Window {...}

這個只讀屬性一般用於獲取當前窗口的子幀列表。例如你能夠用 window.frames [0]frames [0] 訪問第一幀。

global

在 Node.js 中,你可使用 global 關鍵字訪問全局對象:

// node environment
console.log(global);    // => Object [global] {...}

windowselfframes 在 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 模塊中,頂層的 thisundefined

介紹 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 提到的那樣,修改ObjectObject.definePropertyObject.prototype.__defineGetter__ 可能會破壞 polyfill。

總結

可以用在多種環境中的可移植 JavaScript 代碼很難編寫。每一個主機環境都有一個略有不一樣的對象模型。所以,要訪問全局對象,你須要在不一樣的 JavaScript 環境中使用不一樣的語法。

經過引入 globalThis 屬性,訪問全局對象將變得更加簡單,而且再也不須要去檢測代碼所運行的環境。

乍一看 globalThis 彷佛很容易實現。可是實際上,正確地進行操做是很是複雜的。現有的解決方法都不完美,若是不當心就可能會引入錯誤。

ECMAScript 正在迅速發展,你能夠指望它可以更多地引入新功能。要獲取有關規範最新添加的更新,請查看完成的提案列表。


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章


歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索