什麼是 globalThis,爲何要學會使用它?

做者: Dmitri Pavlutin
譯者:前端小智
來源:news
點贊再看,養成習慣

本文 GitHub https://github.com/qq44924588... 上已經收錄,更多往期高贊文章的分類,也整理了不少個人文檔,和教程資料。歡迎Star和完善,你們面試能夠參照考點複習,但願咱們一塊兒有點東西。前端


JS 語言愈來愈多被用於各類環境中。除了最多見的瀏覽器以外,它還能夠在服務器、智能手機甚至機器人硬件上運行。node

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

這些不一樣的全局對象引用方法讓 JS 實現跨平臺變得很是困難。幸運的是,有一個提案正在制定中,旨在經過引入一個名爲globalThis的標準屬性來解決這個問題,該屬性將在全部環境中可用。github

在本文中,咱們首先研究一下 JS 環境中的全局對象,而後瞭解globalThis如何提供統一的機制來訪問它。web

window

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

var a = [10, 20];

console.log(window.a);          // → [10, 20]
console.log(a === window.a);    // → true

一般,在使用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安全

除了標準的 JS 屬性和方法外,window對象還包含幾個額外的屬性和方法,這些屬性和方法容許咱們控制web瀏覽器窗口和文檔自己。服務器

self

Web Workers API沒有Window對象,由於它沒有瀏覽上下文。相反,它提供WorkerGlobalScope接口,其中包含相似window攜帶的數據。微信

要訪問Web Workers中的全局對象,咱們使用self,它是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更可取。 selfWeb worker上下文中引用WorkerGlobalScope.self時,在瀏覽器上下文中引用window.self

不要將self屬性與聲明局部變量(用於維護對上下文的引用)的通用 JS 模式相混淆,這一點很重要。例如

const obj = {
  myProperty: 10,
  myMethod: function(){
    console.log(this === obj);    // => true

    // 將 this 值存儲在變量中,以便在嵌套函數中使用
    const self = this;

    const helperFunction = (function() {
      console.log(self === obj);  // => true (self 指的是外部的 this 值)
      console.log(this === obj);  // => false (this 指的是全局對象。在嚴格模式下,它的值爲undefined)
    })();
  }
};

obj.myMethod();

frames

在瀏覽器環境中訪問全局對象的另外一種方法是使用frames屬性,其工做方式相似於selfwindow:

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

此只讀屬性一般用於獲取當前窗口的子幀列表。例如,咱們可使用window.frames[0]frames[0]來訪問第一幀。

global

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

// node 環境
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值是根據調用函數的方式肯定的。在 JS 模塊中,this 在頂層是undefined的。

globalThis 的引入

globalThis旨在經過定義一個標準的全局屬性來整合日益分散的訪問全局對象的方法。該提案目前處於第四階段,這意味着它已經準備好被歸入ES2020標準。全部流行的瀏覽器,包括Chrome 71+、Firefox 65+和Safari 12.1+,都已經支持這項功能。你也能夠在Node.js 12+中使用它。

// 瀏覽器環境
console.log(globalThis);    // => Window {...}

// node.js 環境
console.log(globalThis);    // => Object [global] {...}

// web worker 環境
console.log(globalThis);    // => DedicatedWorkerGlobalScope {...}

經過使用globalThis,你的代碼將在 window 和非 window 上下文中工做,而無需編寫額外的檢查或測試。在大多數環境中,globalThis直接引用該環境的全局對象。可是,在瀏覽器中,內部使用代理來考慮iframe和跨 window 安全性。 實際上,它並不會改變咱們編寫代碼的方式。

另外一方面,若是你肯定你的代碼將在什麼環境中使用,那麼可使用現有的方法來引用環境的全局對象,這樣就沒必要爲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
}

此模式一般在網絡上使用。 但這也有一些缺陷,使其在某些狀況下不可靠。 幸運的是,Chrome開發者工具(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。

總結

編寫可在多種環境下工做的可移植 JS 代碼是很困難的。每一個宿主環境都有稍微不一樣的對象模型。所以,要訪問全局對象,須要在不一樣的 JS 環境中使用不一樣的語法。

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

乍一看,globalThis 的實現很容易,可是在實踐中,倒是很複雜的。全部現有的實現方案都是不完美的,若是不當心的話可能會引入 bug。


代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

原文:https://news.ycombinator.com/...


交流

文章每週持續更新,能夠微信搜索「 大遷世界 」第一時間閱讀和催更(比博客早一到兩篇喲),本文 GitHub https://github.com/qq449245884/xiaozhi 已經收錄,整理了不少個人文檔,歡迎Star和完善,你們面試能夠參照考點複習,另外關注公衆號,後臺回覆福利,便可看到福利,你懂的。

相關文章
相關標籤/搜索