高效能迷你 React 框架 anu 在低版本IE的實踐

發表於2017-06-14

理想是豐滿的,現實是骨感的,react早期的版本雖然號稱支援IE8,但是頁面總會不自覺切換到奇異模式下,導致報錯。因此必須讓react連IE6,7都支援,這才是最安全。但React本身並不支援IE6,7,因此anu使有用武之地了。

https://github.com/RubyLouvre…

但光是anu不行,相容IE是一個系統性的工程,涉及到打包壓縮,各種polyfill墊片。

首先說一下anu如何支援低版本瀏覽器。anu本身沒有用到太高階的API,像Object.defineProperty, Object.seal, Object.freeze, Proxy, WeakMap等無法 模擬的新API,anu一個也沒有用,而const, let, 箭頭函式,es6模組,通過babel編譯就可以搞定了。

而框架用到的一些es5,es6方法,我已經提供了一個叫polyfill的檔案為大家準備好,大家也可以使用bable.polyfill實現相容。

  1. Array.prototype.forEach
  2. Function.prototype.bind
  3. JSON
  4. window.console
  5. Object.keys
  6. Object.is
  7. Object.assign
  8. Array.isArray

https://github.com/RubyLouvre…

剩下就是事件系統的相容。React為了實現一個全能的事件系統,3萬行的react-dom,有一半是在搞事件的。事件系統之所以這麼難寫,是因為React要實現整個標準事件流,從捕獲階段到target階段再到冒泡階段。如果能獲取事件源物件到document這一路經過的所有元素,就能實現事件流了。但是在IE下,只有冒泡階段,並且許多重要的表單事件不支援冒泡到document。為了事件冒泡,自jQuery時代起,前端高手們已經摸索出一套方案了。使用另一個相似的事件來偽裝不冒泡事件,冒泡到document後,然後變成原來的事件觸發對應的事件。

比如說IE下,使用focusin冒充focus, focusout冒充blur。chrome下,則通過addEventListener的第三個參加為true,強制讓focus, blur被document捕獲到。

低版本的oninput, onchange事件是一個麻煩,它們最多冒泡到form元素上。並且IE也沒有oninput,只有一個相似的onpropertychange事件。IE9,IE10的oninput其實也有許多BUG,但大家要求放低些,我們也不用理會IE9,IE10的oninput事件。IE6-8的oninput事件,我們是直接在元素上繫結onpropertychange事件,然後觸發一個datasetchanged 事件冒泡到document上,並且這個datasetchanged事件物件帶有一個__type__屬性,用來說明它原先冒充的事件。

addEvent.fire這個方法在不同瀏覽器的實現是不一樣的,這裡顯示的IE6-8的版本,IE9及標準瀏覽器是使用document.createEvent, initEvent, dispatchEvent等API來建立事件物件與觸發事件。在IE6-8中,則需要用document.createEventObject建立事件物件,fireEvent來觸發事件。

ondatasetchanged事件是IE一個非常偏門的事件,因為IE的 fireEvent只能觸發它官網上列舉的幾十個事件,不能觸發自定義事件。而ondatasetchanged事件在IE9,chrome, firefox等瀏覽器中是當成一個自定義事件來對待,但那時它是使用elem.dispatchEvent來觸發了。ondatasetchanged是一個能冒泡的事件,只是充作信使,將我們要修改的屬性帶到document上。

此是其一,onchange事件也要通過ondatasetchanged也冒充,因為IE下它也不能冒泡到document。onchange事件在IE還是有許多BUG(或叫差異點)。checkbox, radio的onchange事件必須在失去焦點時才觸發,因此我們在內部用onclick來觸發,而select元素在單選時候下,使用者選中了某個option, select.value會變成option的value值,但在IE6-8下它竟然不會發生改變。最絕的是select元素也不讓你修改value值,後來我奠出修改HTMLSelectElement原型鏈的大招搞定它。

此外,滾動事件的相容性也非常多,但在React官網中,統一大家用onWheel介面來呼叫,在內部實現則需要我們根據瀏覽器分別用onmousewheel, onwheel, DOMMouseScroll來模擬了。

當然還有很多很多細節,這裡就不一一列舉了。為了防止像React那樣程式碼膨脹,針對舊版本的事件相容,我都移到ieEvent.js檔案中。然後基於它,打包了一個專門針對舊版本IE的ReactIE

https://github.com/RubyLouvre…

大家也可以通過npm安裝,1.0.2就擁有這個檔案

下面通過一個示例介紹如何使用ReactIE.

首先建立一個頁面,裡面有三個JS,其實前兩個檔案也能單獨打包的。

index.js的原始碼是這樣的,業務線開發時是直接上JSX與es6,為了相容IE6-8,請不要在業務程式碼上用Object.defineProperty與Proxy

然後我們建一個webpack.config.js,用的是webpack1

es3ify-webpack-plugin是專門將es5程式碼轉換為es3程式碼,因為es5是允許用關鍵字,保留字作為物件的方法與屬性,而es3不能。萬一碰上module.default,我們就坑大了。es3ify是一個利器。

babel是通過.babelrc來配置,裡面用到一個

babel-plugin-transform-es2015-classes記使用loose模式。

babel-preset-es2015後面這樣設定是禁用生成 “use strict”,也建議直接換成babel-preset-avalon,這是個preset生成的程式碼相容性更好。

如果大家用 uglify-js進行程式碼上線,這也要注意一下,這裡有許多坑,它預設會把es3ify乾的活全部白做了。詳見 https://github.com/zuojj/fedl… 這篇文章

最後大家可以通過加Q 79641290 聯絡我。

相關文章