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

以前對(duì)瀏覽器兼容性問題只是大概知道一些點(diǎn),沒想到這次真正著手去做的時(shí)候,還是碰到了很多問題。剛開始的時(shí)候一邊解決問題,一邊想著:用 IE8 的都是神經(jīng)病,到后來,我發(fā)現(xiàn)完了,I LOVE IE。

0x00 起源

在這次做小蜜 PC 版的時(shí)候,由于早于 PC 版,無線版已經(jīng)重新設(shè)計(jì)了全新版,做了很多架構(gòu)上的優(yōu)化調(diào)整。所以在做的時(shí)候把無線版的前端架構(gòu)拿了過來,主要的考慮就是品牌和功能保持跟無線版統(tǒng)一的同時(shí),技術(shù)上也可相互支持以及組件復(fù)用。

無線版整個(gè)架構(gòu)設(shè)計(jì)是同事做的,技術(shù)上主要采用 ES6 + Webpack + Babel 的方式,由于項(xiàng)目的獨(dú)特性和特殊需求,并沒有使用任何框架,只引入 zepto 作為一個(gè)標(biāo)準(zhǔn)支撐庫(kù)。

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

下面是一些基本的開發(fā)依賴:

{
  "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 默認(rèn)只轉(zhuǎn)換轉(zhuǎn)各種 ES2015 語法,而不轉(zhuǎn)換新的 API,比如 Promise,以及 Object.assign、Array.from 這些新方法,這時(shí)我們需要提供一些 ployfill 來模擬出這樣一個(gè)提供原生支持功能的瀏覽器環(huán)境。

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

babel-runtime

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

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

它們不會(huì)在全局環(huán)境添加未實(shí)現(xiàn)的方法,只是這樣手動(dòng)引用每個(gè) polyfill 會(huì)非常低效,我們可以借助 Runtime transform 插件來自動(dòng)化處理這一切。

首先使用 npm 安裝:

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

然后在 webpack 配置文件的 babel-loader 增加選項(xiàng):

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

babel-polyfill

babel-polyfill 是針對(duì)全局環(huán)境的,引入它瀏覽器就好像具備了規(guī)范里定義的完整的特性,一旦引入,就會(huì)跑一個(gè) babel-polyfill 實(shí)例。用法如下:

1.安裝 babel-polyfill

npm install babel-polyfill --save

2.在入口文件中引用:

import 'babel-polyfill'

小結(jié):

其實(shí)做到這些,在大部分瀏覽器就可以正常跑了,但我們做的是一個(gè)用戶環(huán)境很不確定的產(chǎn)品,對(duì)一些年代久遠(yuǎn)但又不容忽視的運(yùn)行環(huán)境,比如 IE8,我們做的還不夠。

接下來將開始講述我們?cè)诩嫒菪苑矫嬗龅降囊恍﹩栴},和解決方法。

0x02 開始在 IE8 運(yùn)行

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

第一步,我們把 jQuery 換成 1.12.1 ,因?yàn)?2.X 已經(jīng)不再支持 IE8。

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

0x03 default or catch

這是遇到的第一個(gè)問題。在兼容性測(cè)試過程中,對(duì)下面的代碼:

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

或者這種:

module.exports = _main2.default;

在 IE8 下會(huì)直接報(bào)”缺少標(biāo)識(shí)符、字符串或數(shù)字”的錯(cuò)。

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

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

module.exports = _main2['default'];

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

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

總之,在平時(shí)寫代碼的時(shí)候避免使用關(guān)鍵字,或者保留字作為對(duì)象的屬性值,尤其是在習(xí)慣不加引號(hào)的情況下。相關(guān)討論:Allow reserved words for properties

0x04 es5-shim、es5-sham

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

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

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.

其實(shí) es5-shim 明確說明,這個(gè)方法的 polyfill 在 IE8 會(huì)失敗,因?yàn)?IE8 已經(jīng)有個(gè)同名的方法,但只是用于 DOM 對(duì)象。

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

0x05 addEventListener

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

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

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

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

這個(gè)問題是在特定場(chǎng)景下【轉(zhuǎn)人工】出現(xiàn)的,出現(xiàn)問題的不是 IE8,而是 IE9 和 IE10。

原因是 ocs 實(shí)例創(chuàng)建失敗,因?yàn)闆]有調(diào)用父類的構(gòu)造函數(shù)。

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

在配置項(xiàng)加上這兩個(gè)插件的配置:

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

  ]
}

0x07 postMessage

雖然 postMessage 是 HTML5 的特性,但 IE8 和 Firefox3 很早就實(shí)現(xiàn)了這個(gè) API,當(dāng)然,跟后來的標(biāo)準(zhǔn)并不一致。這其實(shí)也不能怪 IE8。

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

我們可能會(huì)這樣去使用:

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

但是為了兼容 IE8,我們得轉(zhuǎn)成字符串:

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

另外一個(gè)需要注意的點(diǎn)是:在 IE8 下 window.postMessage 是同步的。

window.postMessage is syncronouse in IE 8

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

0x08 IE8/IE9 的控制臺(tái)

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

第一反應(yīng)就是應(yīng)該又是某個(gè)函數(shù)在 IE 下不支持,準(zhǔn)備打開控制臺(tái)看看到底哪里報(bào)錯(cuò),結(jié)果卻什么事都沒有了,頁面竟然順暢地加載出來了,這下該怎么調(diào)試好呢?

開始思考:什么東西是依賴控制臺(tái)而存在的,到底會(huì)是什么呢。。。其實(shí)就是控制臺(tái)本身。

原因就是我們?cè)诖a中添加了一些控制信息會(huì)打印在控制臺(tái),而 IE8/IE9 要開啟 IE Dev Tools 才能使用 console 對(duì)象。

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

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 檢查還是不能少的

事實(shí)上,IE8/9 從未死去,所以

就像這樣:


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

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

寫個(gè)簡(jiǎn)單的 console polyfill 吧,檢測(cè)是否存在 console,不存在可以常見一個(gè)同名的空方法達(dá)到不報(bào)錯(cuò)的目的。當(dāng)然,生產(chǎn)環(huán)境的代碼其實(shí)也不會(huì)有那么多奇奇怪怪的 console。

0x09 定義文檔兼容性

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

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

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

為什么要用 X-UA-Compatible?

在 IE8 剛推出的時(shí)候,很多網(wǎng)頁由于重構(gòu)的問題,無法適應(yīng)較高級(jí)的瀏覽器,所以使用 X-UA-Compatible 強(qiáng)制 IE8 采用低版本方式渲染。

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

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

而這段代碼:

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

IE=edge 告訴 IE 使用最新的引擎渲染網(wǎng)頁,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.

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

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

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

常用變量如下:

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

Internet Explorer 11 之前的所有版本的 Internet Explorer 都支持條件編譯。 從 Internet Explorer 11 標(biāo)準(zhǔn)模式開始,Windows 8.x 應(yīng)用商店應(yīng)用不支持條件編譯。

后:

之前一直在做移動(dòng)端的開發(fā),沒想到做 PC 端也會(huì)遇到這么多的兼容性問題。不同于移動(dòng)端設(shè)備的繁雜和不確定性,PC 版的兼容更側(cè)重于對(duì)特定瀏覽器的特性的了解,相比而言更為明確,而非因?yàn)槟骋豢钍謾C(jī)的詭異表現(xiàn)。

參考文檔:

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 工具控制臺(tái)查看錯(cuò)誤和狀態(tài)

定義文檔兼容性

條件編譯 (JavaScript)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容