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

之前對瀏覽器兼容性問題只是大概知道一些點,沒想到此次真正着手去作的時候,仍是碰到了不少問題。剛開始的時候一邊解決問題,一邊想着:用 IE8 的都是神經病,到後來,我發現完了,I LOVE IE。javascript

0x00 起源

在此次作小蜜 PC 版的時候,因爲早於 PC 版,無線版已經從新設計了全新版,作了不少架構上的優化調整。因此在作的時候把無線版的前端架構拿了過來,主要的考慮就是品牌和功能保持跟無線版統一的同時,技術上也可相互支持以及組件複用。php

無線版技術上主要採用 ES6 + Webpack + Babel 的方式,因爲項目的獨特性和特殊需求,並無使用任何框架,只引入 zepto 做爲一個標準支撐庫。html

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

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

{
  "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 來模擬出這樣一個提供原生支持功能的瀏覽器環境。webpack

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

babel-runtime

babel-runtime 的做用是模擬 ES2015 環境,包含各類分散的 polyfill 模塊,咱們能夠在本身的模塊裏單獨引入,好比 promise:github

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

它們不會在全局環境添加未實現的方法,只是這樣手動引用每一個 polyfill 會很是低效,咱們能夠藉助 Runtime transform 插件來自動化處理這一切。web

首先使用 npm 安裝:chrome

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 瀏覽器,只要確保網頁在 IE六、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 版的兼容更側重於對特定瀏覽器的特性的瞭解,相比而言更爲明確,而非由於某一款手機的詭異表現。

參考文檔

相關文章
相關標籤/搜索