某天,某測試說:「這個頁面在 IE8 下白屏,9也白。。」javascript
某前端開發: 吭哧吭哧。。。一上午的時間就過去了,搞定了。前端
次日,某測試說:「IE 又白了。。」java
某前端開發: 吭哧吭哧。。。誰用的 Object.assign
,出來我保證削不屎你。git
原諒我不由又黑了一把 IE。es6
有人可能會想,都要淘汰了,還有什麼好講的?github
也許幾年後,確實沒用了,但目前咱們的系統仍是要對 ie8+ 作兼容,由於確實還有個別用戶,儘管他沒朋友。。。瀏覽器
記錄下本次在 IE 下踩得坑,讓後面的同窗可以再也不在這上面浪費時間了。babel
首先,看下面代碼(如下測試在 IE9)async
class Test extends React.Component { constructor(props) { super(props); } render() { return <div>{this.props.content}</div>; } } module.exports = Test;
這段代碼跑的妥妥的,沒什麼問題。函數
通常來講,babel 在轉換繼承時,可能會出現兼容問題,那麼,再看這一段
class Test extends React.Component { constructor(props) { super(props); } test() { console.log('test'); } render() { return <div>{this.props.content}</div>; } } Test.defaultProps = { content: "測試" }; class Test2 extends Test { constructor(props) { super(props); this.test(); } } Test2.displayName = 'Test2'; module.exports = Test2;
這段代碼一樣也能夠正常運行
也就是說在上述這兩種狀況下,不作任何處理(前提是已經加載了 es5-shim/es5-sham),在 IE9 下均可以正常運行。
而後咱們再看下會跑掛的代碼
class Test extends React.Component { constructor(props) { super(props); this.state = { test: 1, }; } test() { console.log(this.state.value); } render() { return <div>{this.props.content}</div>; } } Test.defaultProps = { content: "測試" }; class Test2 extends Test { constructor(props) { super(props); // SCRIPT5007: 沒法獲取屬性 "value" 的值,對象爲 null 或未定義 this.test(); // SCRIPT5007: 沒法獲取屬性 "b" 的值,對象爲 null 或未定義 this.a = this.props.b; } } // undefined console.log(Test2.defaultProps); Test2.displayName = 'Test2'; module.exports = Test2;
這段代碼在高級瀏覽器中是沒問題的,在 IE9 中會出現註釋所描述的問題
從這些問題分析,可得出3個結論
在構造函數裏的定義的屬性沒法被繼承
在構造函數裏不能使用 this.props.xx
類屬性或方法是沒法被繼承的
也就是說,只要規避了這三個條件的話,不作任何處理(前提是已經加載了 es5-shim/es5-sham),在 IE9 下均可以正常運行。
第二點,是徹底能夠避免的,切記在
constructor
直接使用props.xxx
, 不要再用this.props.xxx
第三點,也是能夠徹底避免的,由於從理論上來講,類屬性就不應被繼承,若是想使用父類的類屬性能夠直接
Test2.defaultProps = Test.defaultProps;
第一點,可避免,但沒法徹底避免
第一點,有時是沒法徹底避免的,那麼就要查詢緣由,才能找到解決方案
咱們把 babel 轉義後的代碼放出來就能查出緣由了
'use strict'; var _createClass = function () { ... }(); function _classCallCheck(instance, Constructor) { ... } function _possibleConstructorReturn(self, call) { ... // 這個方法只是作了下判斷,返回第一個或第二參數 return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { ...; // 這裏的 _inherits 是經過將子類的原型[[prototype]]指向了父類,因此若是在高級瀏覽器下,子類的能夠繼承到類屬性 // 根本問題也是出在這裏,IE9 下既沒有 `setPrototypeOf` 也沒有 `__proto__` if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var Test = function (_React$Component) { ... return Test; }(React.Component); Test.defaultProps = { content: "測試" }; var Test2 = function (_Test) { _inherits(Test2, _Test); function Test2(props) { _classCallCheck(this, Test2); // 這裏的 this 會經過 _possibleConstructorReturn,來獲取父類構造函數裏定義的屬性 // _possibleConstructorReturn 只是作了下判斷,若是第二個參數獲得了正確執行,則返回執行結果,不然返回第一個參數,也就是子類的 this // 也就是說問題出在 Object.getPrototypeOf // 在 _inherits 中將子類的原型指向了父類, 這裏經過 getPrototypeOf 來獲取父類,其實就是 _Test // Object.getPrototypeOf 不能正確的執行,致使了子類沒法繼承到在構造函數裏定義的屬性或方法,也沒法繼承到類屬性或方法 var _this2 = _possibleConstructorReturn(this, Object.getPrototypeOf(Test2).call(this, props)); _this2.test(); console.log(_this2.props.children); return _this2; } return Test2; }(Test); console.log(Test2.defaultProps); Test2.displayName = 'Test'; module.exports = Test2;
經過上述的代碼註釋,能夠得出有兩處問題須要解決
正確的獲取父類(解決沒法繼承到在構造函數裏定義的屬性或方法)
正確的將子類的原型指向了父類(解決沒法繼承到類屬性或方法)
經過文檔的查詢,發現只要開啓 es2015-classes 的 loose 模式便可解決第一個問題
Babel have two modes:
A normal mode follows the semantics of ECMAScript 6 as closely as possible.
A loose mode produces simpler ES5 code.
Babel 有兩種模式:
儘量符合 ES6 語義的 normal 模式。
提供更簡單 ES5 代碼的 loose 模式。
儘管官方是更推薦使用 normal 模式,但爲了兼容 IE,咱們目前也只能開啓 loose 模式。
在 babel6 中,主要是經過 babel-preset-2015 這個插件,來進行轉義的
咱們看下 babel-preset-2015
plugins: [ require("babel-plugin-transform-es2015-template-literals"), require("babel-plugin-transform-es2015-literals"), require("babel-plugin-transform-es2015-function-name"), ... require("babel-plugin-transform-es2015-classes"), ... require("babel-plugin-transform-es2015-typeof-symbol"), require("babel-plugin-transform-es2015-modules-commonjs"), [require("babel-plugin-transform-regenerator"), { async: false, asyncGenerators: false }], ]
是一堆對應轉義的插件,從命名上也可看出了大概,好比 babel-plugin-transform-es2015-classes 就是作類的轉義的,也就是咱們只需把它開啓 loose 模式,便可解決咱們的一個問題
[require('babel-plugin-transform-es2015-classes'), {loose: true}],
看下開啓了 loose 模式的代碼,你會發現它的確更接近 ES5
var Test = function (_React$Component) { ... // 這裏是 ES5 的寫法 Test.prototype.test = function test() { console.log(this.state.value); }; /* normal 模式是這樣的 { key: 'test', value: function test() { console.log(this.state.value); } } */ return Test; }(React.Component); var Test2 = function (_Test) { _inherits(Test2, _Test); function Test2(props) { _classCallCheck(this, Test2); // 這裏直接拿到了父類 _Test, 即解決了沒法繼承到在構造函數裏定義的屬性或方法 var _this2 = _possibleConstructorReturn(this, _Test.call(this, props)); _this2.test(); return _this2; } return Test2; }(Test);
咱們能夠經過去安裝 babel-preset-es2015-loose, 這個插件來開啓 loose 模式。
但從咱們團隊的 老司機 口中
獲得了一個更好插件babel-preset-es2015-ie,看下這個插件的代碼,發現它和原來的 babel-preset-2015 只有兩行區別
[ [require('babel-plugin-transform-es2015-classes'), {loose: true}], require('babel-plugin-transform-proto-to-assign'), ]
恰好解決咱們上述碰到的兩個問題
這個 babel-plugin-transform-proto-to-assign
插件會生成一個 _defaults 方法來處理原型
function _inherits(subClass, superClass) { ...; if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : _defaults(subClass, superClass); }
function _defaults(obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; }
這個插件正確的將子類的原型指向了父類(解決沒法繼承到類屬性或方法)
本文講述低版本瀏覽器報錯的緣由和解決方案
一方面是提示下在構造函數裏不要使用 this.props.xx
另外一方面也對繼承的機制有了更好的理解
在此次項目中發如今低版本瀏覽器跑不起來的兩點主要緣由:
SCRIPT5007: 沒法獲取屬性 xxx 的值,對象爲 null 或未定義
,這種狀況通常是組件繼承後,沒法繼承到在構造函數裏定義的屬性或方法,一樣類屬性或方法也一樣沒法繼承
SCRIPT438: 對象不支持 xxx 屬性或方法
,這種狀況通常是使用了 es六、es7 的高級語法,Object.assgin
Object.keys
等,這種狀況在移動端的一些 ‘神機’ 也同樣會掛。
第一點本文已經分析,預知第二點講解請見下篇。
備註:下篇會主要介紹下如何讓 用了 Object.assign
的那位同窗能夠繼續用,又不會被削。