以前對瀏覽器兼容性問題只是大概知道一些點(diǎn)透绩,沒想到這次真正著手去做的時候寻馏,還是碰到了很多問題棋弥。剛開始的時候一邊解決問題,一邊想著:用 IE8 的都是神經(jīng)病诚欠,到后來顽染,我發(fā)現(xiàn)完了,I LOVE IE轰绵。
0x00 起源
在這次做小蜜 PC 版的時候粉寞,由于早于 PC 版,無線版已經(jīng)重新設(shè)計(jì)了全新版左腔,做了很多架構(gòu)上的優(yōu)化調(diào)整唧垦。所以在做的時候把無線版的前端架構(gòu)拿了過來,主要的考慮就是品牌和功能保持跟無線版統(tǒng)一的同時液样,技術(shù)上也可相互支持以及組件復(fù)用振亮。
無線版整個架構(gòu)設(shè)計(jì)是同事做的,技術(shù)上主要采用 ES6 + Webpack + Babel 的方式鞭莽,由于項(xiàng)目的獨(dú)特性和特殊需求双炕,并沒有使用任何框架,只引入 zepto 作為一個標(biāo)準(zhǔn)支撐庫撮抓。
而 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 這些新方法死相,這時我們需要提供一些 ployfill 來模擬出這樣一個提供原生支持功能的瀏覽器環(huán)境融求。
主要有兩種方式:babel-runtime
和 babel-polyfill
。
babel-runtime
babel-runtime 的作用是模擬 ES2015 環(huán)境,包含各種分散的 polyfill 模塊,我們可以在自己的模塊里單獨(dú)引入厢破,比如 promise:
import 'babel-runtime/core-js/promise'
它們不會在全局環(huán)境添加未實(shí)現(xiàn)的方法吧雹,只是這樣手動引用每個 polyfill 會非常低效笆包,我們可以借助 Runtime transform
插件來自動化處理這一切。
首先使用 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
是針對全局環(huán)境的,引入它瀏覽器就好像具備了規(guī)范里定義的完整的特性,一旦引入莱睁,就會跑一個 babel-polyfill
實(shí)例。用法如下:
1.安裝 babel-polyfill
npm install babel-polyfill --save
2.在入口文件中引用:
import 'babel-polyfill'
小結(jié):
其實(shí)做到這些芒澜,在大部分瀏覽器就可以正常跑了仰剿,但我們做的是一個用戶環(huán)境很不確定的產(chǎn)品,對一些年代久遠(yuǎn)但又不容忽視的運(yùn)行環(huán)境痴晦,比如 IE8南吮,我們做的還不夠。
接下來將開始講述我們在兼容性方面遇到的一些問題誊酌,和解決方法部凑。
0x02 開始在 IE8 運(yùn)行
最開始做的時候并沒有針對 IE 做一些兼容性方面的處理,結(jié)果在 IE8 上一跑一堆問題术辐。
第一步,我們把 jQuery
換成 1.12.1 施无,因?yàn)?2.X 已經(jīng)不再支持 IE8辉词。
但并沒有像我們想象中的那樣,只是簡單換一下 jQuery
版本就可以正常運(yùn)行了猾骡。
0x03 default or catch
這是遇到的第一個問題瑞躺。在兼容性測試過程中,對下面的代碼:
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
或者這種:
module.exports = _main2.default;
在 IE8 下會直接報”缺少標(biāo)識符兴想、字符串或數(shù)字”的錯幢哨。
我們得在對象的屬性上加 ''
才可以。就像下面這樣:
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { ‘default’: obj };
}
module.exports = _main2['default'];
至于原因嫂便,并不是 IE8 下對象的屬性必須得加 ''
才行捞镰,而是 default
的問題,作為一個關(guān)鍵字,同樣的問題還包括 catch
岸售。
這兩種情況践樱,可以通過使用 transform-es3-property-literals
和 transform-es3-member-expression-literals
這兩個插件搞定。
總之凸丸,在平時寫代碼的時候避免使用關(guān)鍵字拷邢,或者保留字作為對象的屬性值,尤其是在習(xí)慣不加引號的情況下屎慢。相關(guān)討論: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.
其實(shí) es5-shim 明確說明环肘,這個方法的 polyfill 在 IE8 會失敗,因?yàn)?IE8 已經(jīng)有個同名的方法妖枚,但只是用于 DOM 對象廷臼。
同樣的問題還包括 Object.create
,上述問題可以再引入 es5-sham 解決.
0x05 addEventListener
項(xiàng)目中有部分代碼直接使用 addEventListener
這個 API绝页,但在 IE8 下的事件綁定并不是這個方法荠商。
這個問題很容易解決,也無需去寫額外的 polyfill续誉。我們已經(jīng)把 jQuery 換成 1.x莱没,所以只需把代碼中 addEventListener
換成 jQuery
的寫法就 Okay 了。
jQuery
其實(shí)為我們封裝了很多 API酷鸦,并做了很多兼容性的封裝饰躲,類似的只要使用封裝好的就可以了。
0x06 無法獲取未定義或 null 引用的屬性
這個問題是在特定場景下【轉(zhuǎn)人工】出現(xiàn)的臼隔,出現(xiàn)問題的不是 IE8嘹裂,而是 IE9 和 IE10。
原因是 ocs 實(shí)例創(chuàng)建失敗摔握,因?yàn)闆]有調(diào)用父類的構(gòu)造函數(shù)寄狼。
通過安裝 transform-es2015-classes
和 transform-proto-to-assign
解決。
在配置項(xiàng)加上這兩個插件的配置:
{
"plugins": [
["transform-es2015-classes", { "loose": true }],
"transform-proto-to-assign"
]
}
0x07 postMessage
雖然 postMessage
是 HTML5 的特性氨淌,但 IE8 和 Firefox3 很早就實(shí)現(xiàn)了這個 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.
我們可能會這樣去使用:
parent.postMessage({success: 'ok', name: ‘mirreal’}, ‘*’);
但是為了兼容 IE8,我們得轉(zhuǎn)成字符串:
parent.postMessage(JSON.stringify({success: 'ok', name: "mirreal"}), ‘*’);
另外一個需要注意的點(diǎn)是:在 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 的控制臺
遇到一個奇怪的問題痰滋,在剛開始遇到的時候(其實(shí)搞清楚原因摘能,好像也挺正常的),小蜜在 IE8 IE9 無法加載即寡。在 IE8 那個古老瀏覽器的左下角徊哑,好像也是唯一會在頁面提示腳本錯誤的瀏覽器,提示 script error
聪富。
第一反應(yīng)就是應(yīng)該又是某個函數(shù)在 IE 下不支持莺丑,準(zhǔn)備打開控制臺看看到底哪里報錯,結(jié)果卻什么事都沒有了墩蔓,頁面竟然順暢地加載出來了梢莽,這下該怎么調(diào)試好呢?
開始思考:什么東西是依賴控制臺而存在的奸披,到底會是什么呢昏名。。阵面。其實(shí)就是控制臺本身轻局。
原因就是我們在代碼中添加了一些控制信息會打印在控制臺,而 IE8/IE9 要開啟 IE Dev Tools 才能使用 console
對象样刷。
切忌把 IE8/9 想成 Chrome/Firefox仑扑,以為永遠(yuǎn)有 window.console
可用.終于,IE10 改邪歸正置鼻,console
不再像段譽(yù)的六脈神劍時有時無镇饮。
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
,... 這樣去寫嘶是,那還不把人寫到惡心死钙勃。
寫個簡單的 console polyfill 吧,檢測是否存在 console
聂喇,不存在可以常見一個同名的空方法達(dá)到不報錯的目的辖源。當(dāng)然,生產(chǎn)環(huán)境的代碼其實(shí)也不會有那么多奇奇怪怪的 console
授帕。
0x09 定義文檔兼容性
X-UA-Compatible
當(dāng)初是針對 IE8 新加的一個配置同木。用于為 IE8 指定不同的頁面渲染模式浮梢,比如使用 IE7 兼容模式跛十,或者是采用最新的引擎。
現(xiàn)在基本也不需要前者的降級模式秕硝,更多的是寫入 IE=edge
支持最新特性芥映。而 chrome=1
則會激活 Google Chrome Frame,前提是你的 IE 安裝過這個插件。
有什么用呢奈偏,當(dāng)然有用坞嘀,有些 API 是作為新特性存在于 IE8 中的,比如 JSON
惊来,不開啟的話就用不了丽涩。
為什么要用 X-UA-Compatible?
在 IE8 剛推出的時候裁蚁,很多網(wǎng)頁由于重構(gòu)的問題矢渊,無法適應(yīng)較高級的瀏覽器,所以使用 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.
另外一個類似的東西是在 Javascript 中的條件編譯(conditional compilation)。我們可以使用這段簡單的代碼來做瀏覽器嗅探:
var isIE = /*@cc_on!@*/false
在其他瀏覽器中倒脓,false 前的被視為注釋撑螺,而在 IE 中,/*@cc_on .... @*/
之間的部分可以被 IE 識別并作為程序執(zhí)行崎弃,同時啟用 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 腳本引擎的生成號枣宫。
* @_jscript_version 包含 major.minor 格式的 JavaScript 版本號。
Internet Explorer 11 之前的所有版本的 Internet Explorer 都支持條件編譯吃环。 從 Internet Explorer 11 標(biāo)準(zhǔn)模式開始也颤,Windows 8.x 應(yīng)用商店應(yīng)用不支持條件編譯。
后:
之前一直在做移動端的開發(fā)郁轻,沒想到做 PC 端也會遇到這么多的兼容性問題翅娶。不同于移動端設(shè)備的繁雜和不確定性,PC 版的兼容更側(cè)重于對特定瀏覽器的特性的了解好唯,相比而言更為明確故觅,而非因?yàn)槟骋豢钍謾C(jī)的詭異表現(xiàn)。
參考文檔:
Allow reserved words for properties
IE8 defineProperty/getOwnPropertyDescriptor clash with shim
babel-plugin-transform-runtime definitions
super() not calling parent's constructor on IE9