使用 ES6 的瀏覽器兼容性問題

以前對瀏覽器兼容性問題只是大概知道一些點,沒想到這次真正著手去做的時候,還是碰到了很多問題。剛開始的時候一邊解決問題,一邊想著:用 IE8 的都是神經病,到后來,我發現完了,I LOVE IE。

0x00 起源

在這次做小蜜 PC 版的時候,由于早于 PC 版,無線版已經重新設計了全新版,做了很多架構上的優化調整。所以在做的時候把無線版的前端架構拿了過來,主要的考慮就是品牌和功能保持跟無線版統一的同時,技術上也可相互支持以及組件復用。

無線版整個架構設計是同事做的,技術上主要采用 ES6 + Webpack + Babel 的方式,由于項目的獨特性和特殊需求,并沒有使用任何框架,只引入 zepto 作為一個標準支撐庫。

而 PC 版的架構跟無線版基本保持一致,主要是把 zepto 換成了 jQuery。

下面是一些基本的開發依賴:

{
  "devDependencies": {
    "babel-core": "~6.3.15",
    "babel-loader": "~6.2.0",
    "babel-preset-es2015": "~6.3.13",
    "babel-preset-stage-0": "~6.3.13",
    "babel-runtime": "~6.3.13",
    "extract-text-webpack-plugin": "~0.9.1",
    "less-loader": "~2.2.1",
    "nunjucks-loader": "~1.0.7",
    "style-loader": "~0.10.2",
    "webpack": "~1.12.9",
    "webpack-dev-server": "^1.10.1"
  }
}

0x01 polyfill

由于 Babel 默認只轉換轉各種 ES2015 語法,而不轉換新的 API,比如 Promise,以及 Object.assign、Array.from 這些新方法,這時我們需要提供一些 ployfill 來模擬出這樣一個提供原生支持功能的瀏覽器環境。

主要有兩種方式:babel-runtimebabel-polyfill

babel-runtime

babel-runtime 的作用是模擬 ES2015 環境,包含各種分散的 polyfill 模塊,我們可以在自己的模塊里單獨引入,比如 promise:

import 'babel-runtime/core-js/promise'

它們不會在全局環境添加未實現的方法,只是這樣手動引用每個 polyfill 會非常低效,我們可以借助 Runtime transform 插件來自動化處理這一切。

首先使用 npm 安裝:

npm install babel-plugin-transform-runtime --save-dev

然后在 webpack 配置文件的 babel-loader 增加選項:

loader: ["babel-loader"],
query: {
  plugins: [
    "transform-runtime"
  ],
  presets: ['es2015', 'stage-0']
}

babel-polyfill

babel-polyfill 是針對全局環境的,引入它瀏覽器就好像具備了規范里定義的完整的特性,一旦引入,就會跑一個 babel-polyfill 實例。用法如下:

1.安裝 babel-polyfill

npm install babel-polyfill --save

2.在入口文件中引用:

import 'babel-polyfill'

小結:

其實做到這些,在大部分瀏覽器就可以正常跑了,但我們做的是一個用戶環境很不確定的產品,對一些年代久遠但又不容忽視的運行環境,比如 IE8,我們做的還不夠。

接下來將開始講述我們在兼容性方面遇到的一些問題,和解決方法。

0x02 開始在 IE8 運行

最開始做的時候并沒有針對 IE 做一些兼容性方面的處理,結果在 IE8 上一跑一堆問題。

第一步,我們把 jQuery 換成 1.12.1 ,因為 2.X 已經不再支持 IE8。

但并沒有像我們想象中的那樣,只是簡單換一下 jQuery 版本就可以正常運行了。

0x03 default or catch

這是遇到的第一個問題。在兼容性測試過程中,對下面的代碼:

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

或者這種:

module.exports = _main2.default;

在 IE8 下會直接報”缺少標識符、字符串或數字”的錯。

我們得在對象的屬性上加 '' 才可以。就像下面這樣:

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { ‘default’: obj };
}

module.exports = _main2['default'];

至于原因,并不是 IE8 下對象的屬性必須得加 '' 才行,而是 default 的問題,作為一個關鍵字,同樣的問題還包括 catch

這兩種情況,可以通過使用 transform-es3-property-literalstransform-es3-member-expression-literals 這兩個插件搞定。

總之,在平時寫代碼的時候避免使用關鍵字,或者保留字作為對象的屬性值,尤其是在習慣不加引號的情況下。相關討論:Allow reserved words for properties

0x04 es5-shim、es5-sham

為了兼容像 IE8 這樣的老版本瀏覽器,我們引入 es5-shim 作為 polyfill。

但在遇到 Object.defineProperty 仍提示 "對象不支持此操作"

As currently implemented, the Object.defineProperty shim will not install on IE8 because IE8 already has such a method. However, the built-in IE8 method only works when applied to DOM objects.

其實 es5-shim 明確說明,這個方法的 polyfill 在 IE8 會失敗,因為 IE8 已經有個同名的方法,但只是用于 DOM 對象。

同樣的問題還包括 Object.create,上述問題可以再引入 es5-sham 解決.

0x05 addEventListener

項目中有部分代碼直接使用 addEventListener 這個 API,但在 IE8 下的事件綁定并不是這個方法。

這個問題很容易解決,也無需去寫額外的 polyfill。我們已經把 jQuery 換成 1.x,所以只需把代碼中 addEventListener 換成 jQuery 的寫法就 Okay 了。

jQuery 其實為我們封裝了很多 API,并做了很多兼容性的封裝,類似的只要使用封裝好的就可以了。

0x06 無法獲取未定義或 null 引用的屬性

這個問題是在特定場景下【轉人工】出現的,出現問題的不是 IE8,而是 IE9 和 IE10。

原因是 ocs 實例創建失敗,因為沒有調用父類的構造函數。

通過安裝 transform-es2015-classestransform-proto-to-assign 解決。

在配置項加上這兩個插件的配置:

{
  "plugins": [
      ["transform-es2015-classes", { "loose": true }],
      "transform-proto-to-assign"

  ]
}

0x07 postMessage

雖然 postMessage 是 HTML5 的特性,但 IE8 和 Firefox3 很早就實現了這個 API,當然,跟后來的標準并不一致。這其實也不能怪 IE8。

The postMessage method is supported in Internet Explorer from version 8, Firefox from version 3 and Opera from version 9.5.

我們可能會這樣去使用:

parent.postMessage({success: 'ok', name: ‘mirreal’}, ‘*’);

但是為了兼容 IE8,我們得轉成字符串:

parent.postMessage(JSON.stringify({success: 'ok', name: "mirreal"}), ‘*’);

另外一個需要注意的點是:在 IE8 下 window.postMessage 是同步的。

window.postMessage is syncronouse in IE 8

var syncronouse = true;
window.onmessage = function () {
  console.log(syncronouse); // 在 IE8 下會在控制臺打印 true
};
window.postMessage('test', '*');
syncronouse = false;

0x08 IE8/IE9 的控制臺

遇到一個奇怪的問題,在剛開始遇到的時候(其實搞清楚原因,好像也挺正常的),小蜜在 IE8 IE9 無法加載。在 IE8 那個古老瀏覽器的左下角,好像也是唯一會在頁面提示腳本錯誤的瀏覽器,提示 script error

第一反應就是應該又是某個函數在 IE 下不支持,準備打開控制臺看看到底哪里報錯,結果卻什么事都沒有了,頁面竟然順暢地加載出來了,這下該怎么調試好呢?

開始思考:什么東西是依賴控制臺而存在的,到底會是什么呢。。。其實就是控制臺本身。

原因就是我們在代碼中添加了一些控制信息會打印在控制臺,而 IE8/IE9 要開啟 IE Dev Tools 才能使用 console 對象。

切忌把 IE8/9 想成 Chrome/Firefox,以為永遠有 window.console 可用.終于,IE10 改邪歸正,console 不再像段譽的六脈神劍時有時無。

console.log is there in IE8, but the console object isn't created until you open DevTools. Therefore, a call to console.log may result in an error, for example if it occurs on page load before you have a chance to open the dev tools.

但只要 IE8/9 還在一天,console 檢查還是不能少的

事實上,IE8/9 從未死去,所以

就像這樣:


if (window.console) {
  console.log('log here');
}

要是有一堆 console.log, console.count, console.error, console.time, console.profile,... 這樣去寫,那還不把人寫到惡心死。

寫個簡單的 console polyfill 吧,檢測是否存在 console,不存在可以常見一個同名的空方法達到不報錯的目的。當然,生產環境的代碼其實也不會有那么多奇奇怪怪的 console

0x09 定義文檔兼容性

X-UA-Compatible 當初是針對 IE8 新加的一個配置。用于為 IE8 指定不同的頁面渲染模式,比如使用 IE7 兼容模式,或者是采用最新的引擎。

現在基本也不需要前者的降級模式,更多的是寫入 IE=edge 支持最新特性。而 chrome=1 則會激活 Google Chrome Frame,前提是你的 IE 安裝過這個插件。

有什么用呢,當然有用,有些 API 是作為新特性存在于 IE8 中的,比如 JSON,不開啟的話就用不了。

為什么要用 X-UA-Compatible?

在 IE8 剛推出的時候,很多網頁由于重構的問題,無法適應較高級的瀏覽器,所以使用 X-UA-Compatible 強制 IE8 采用低版本方式渲染。

比如:使用下面這段代碼后,開發者無需考慮網頁是否兼容 IE8 瀏覽器,只要確保網頁在 IE6、IE7 下的表現就可以了。

<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />

而這段代碼:

<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

IE=edge 告訴 IE 使用最新的引擎渲染網頁,chrome=1 則可以激活 Chrome Frame[1]。

0x0a 條件注釋 or 條件編譯

最后說說 IE 的條件注釋,用法如下:

!   [if !IE]    The NOT operator. This is placed immediately in front of the feature, operator, or subexpression to reverse the Boolean meaning of the expression.

lt  [if lt IE 5.5]  The less-than operator. Returns true if the first argument is less than the second argument.

lte [if lte IE 6]   The less-than or equal operator. Returns true if the first argument is less than or equal to the second argument.

gt  [if gt IE 5]    The greater-than operator. Returns true if the first argument is greater than the second argument.

gte [if gte IE 7]   The greater-than or equal operator. Returns true if the first argument is greater than or equal to the second argument.

( ) [if !(IE 7)]    Subexpression operators. Used in conjunction with boolean operators to create more complex expressions.

&   [if (gt IE 5)&(lt IE 7)]    The AND operator. Returns true if all subexpressions evaluate to true

|   [if (IE 6)|(IE 7)]  The OR operator. Returns true if any of the subexpressions evaluates to true.

另外一個類似的東西是在 Javascript 中的條件編譯(conditional compilation)。我們可以使用這段簡單的代碼來做瀏覽器嗅探:

var isIE = /*@cc_on!@*/false

在其他瀏覽器中,false 前的被視為注釋,而在 IE 中,/*@cc_on .... @*/ 之間的部分可以被 IE 識別并作為程序執行,同時啟用 IE 的條件編譯。

常用變量如下:

* @_win32 如果在 Win32 系統上運行,則為 true。
* @_win16 如果在 Win16 系統上運行,則為 true。
* @_mac 如果在 Apple Macintosh 系統上運行,則為 true。
* @_alpha 如果在 DEC Alpha 處理器上運行,則為 true。
* @_x86 如果在 Intel 處理器上運行,則為 true。
* @_mc680x0 如果在 Motorola 680x0 處理器上運行,則為 true。
* @_PowerPC 如果在 Motorola PowerPC 處理器上運行,則為 true。
* @_jscript 始終為 true。
* @_jscript_build 包含 JavaScript 腳本引擎的生成號。
* @_jscript_version 包含 major.minor 格式的 JavaScript 版本號。

Internet Explorer 11 之前的所有版本的 Internet Explorer 都支持條件編譯。 從 Internet Explorer 11 標準模式開始,Windows 8.x 應用商店應用不支持條件編譯。

后:

之前一直在做移動端的開發,沒想到做 PC 端也會遇到這么多的兼容性問題。不同于移動端設備的繁雜和不確定性,PC 版的兼容更側重于對特定瀏覽器的特性的了解,相比而言更為明確,而非因為某一款手機的詭異表現。

參考文檔:

Allow reserved words for properties

IE8 defineProperty/getOwnPropertyDescriptor clash with shim

Runtime transform

babel-plugin-transform-runtime definitions

super() not calling parent's constructor on IE9

postMessage method (window) Javascript

使用 F12 工具控制臺查看錯誤和狀態

定義文檔兼容性

條件編譯 (JavaScript)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容