摘要:起因某天,某測試說這個頁面在下白屏,也白。。某前端開發吭哧吭哧。。。一上午的時間就過去了,搞定了。第二天,某測試說又白了。。某前端開發吭哧吭哧。。。誰用的,出來我保證削不屎你。原諒我不禁又黑了一把。
起因
某天,某測試說:“這個頁面在 IE8 下白屏,9也白。。”
某前端開發: 吭哧吭哧。。。一上午的時間就過去了,搞定了。
第二天,某測試說:“IE 又白了。。”
某前端開發: 吭哧吭哧。。。誰用的 Object.assign,出來我保證削不屎你。
原諒我不禁又黑了一把 IE。
有人可能會想,都要淘汰了,還有什么好講的?
也許幾年后,確實沒用了,但目前我們的系統還是要對 ie8+ 做兼容,因為確實還有個別用戶,盡管他沒朋友。。。
記錄下本次在 IE 下踩得坑,讓后面的同學能夠不再在這上面浪費時間了。
經過 測試首先,看下面代碼(以下測試在 IE9)
class Test extends React.Component { constructor(props) { super(props); } render() { return{this.props.content}; } } module.exports = Test;
這段代碼跑的妥妥的,沒什么問題。
一般來說,babel 在轉換繼承時,可能會出現兼容問題,那么,再看這一段
class Test extends React.Component { constructor(props) { super(props); } test() { console.log("test"); } render() { return{this.props.content}; } } 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{this.props.content}; } } 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 模式即可解決第一個問題
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 屬性或方法,這種情況一般是使用了 es6、es7 的高級語法,Object.assgin Object.keys 等,這種情況在移動端的一些 ‘神機’ 也一樣會掛。
第一點本文已經分析,預知第二點講解請見下篇。
備注:下篇會主要介紹下如何讓 用了 Object.assign 的那位同學可以繼續用,又不會被削。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80399.html
摘要:在上篇,我們主要拋出了兩個問題,并給出了第一個問題的解決方案。沒有的實例方法可以采用方案三委屈下。放棄模式,放棄上篇中提到了開啟了模式來解決低版本瀏覽器無法繼承到在構造函數里定義的屬性或方法。 回顧 起因: 某天,某測試說:這個頁面在 IE8 下白屏,9也白。。某前端開發: 吭哧吭哧。。。一上午的時間就過去了,搞定了。第二天,某測試說:IE 又白了。。某前端開發: 嘿咻嘿咻。。。誰用的...
摘要:本文記錄如下起因在準備提測的那天,順便打開看一眼注意,這里是原生不是用模擬的,排查后發現,原來是因為構造函數中使用了。簡寫如下老司機們肯定能一眼發現問題構造函數中不應該使用而是傳入的應該改為改正之后,問題確實解決了。 雖然過了兼容IE6的噩夢時代,IE依舊陰魂不散,因為你可能還要兼容IE9。在ES6已經普及的今天,用ES6寫react已經成了標配。但是babel編譯的js語法,由于某些...
摘要:本文記錄如下起因在準備提測的那天,順便打開看一眼注意,這里是原生不是用模擬的,排查后發現,原來是因為構造函數中使用了。簡寫如下老司機們肯定能一眼發現問題構造函數中不應該使用而是傳入的應該改為改正之后,問題確實解決了。 雖然過了兼容IE6的噩夢時代,IE依舊陰魂不散,因為你可能還要兼容IE9。在ES6已經普及的今天,用ES6寫react已經成了標配。但是babel編譯的js語法,由于某些...
摘要:簡稱已經更新之版本也更新至版本裝飾器語法雖然還不是標準但是借助于也能在項目里愉快的玩耍時代如何啟用裝飾器語法呢我們依舊采用的是通過劫持對象達到修改的目的修改安裝裝飾器語法所需的插件也可以順帶升級在項目 create-react-app(簡稱cra)已經更新之2.0.3版本, babel也更新至7.x版本, JavaScript裝飾器語法雖然還不是標準, 但是借助于babel, 也能在項...
閱讀 1601·2023-04-26 01:54
閱讀 1630·2021-09-30 09:55
閱讀 2652·2021-09-22 16:05
閱讀 1866·2021-07-25 21:37
閱讀 2628·2019-08-29 18:45
閱讀 1891·2019-08-29 16:44
閱讀 1890·2019-08-29 12:34
閱讀 1352·2019-08-23 14:02