ES6 + Webpack + React + Babel 如安在低版本浏览器上兴奋的游玩(上)

原由

某天,某测试说:“这个页面在 IE8 下白屏,9也白。。”

某前端开辟: 吭哧吭哧。。。一上午的时候就过去了,搞定了。

第二天,某测试说:“IE 又白了。。”

某前端开辟: 吭哧吭哧。。。谁用的 Object.assign,出来我保证削不屎你。

《ES6 + Webpack + React + Babel 如安在低版本浏览器上兴奋的游玩(上)》

谅解我不禁又黑了一把 IE。

有人能够会想,都要镌汰了,另有什么好讲的?

或许几年后,确切没用了,但如今我们的体系照样要对 ie8+ 做兼容,由于确切另有平常用户,只管他没朋侪。。。

记录下本次在 IE 下踩得坑,让背面的同砚能够不再在这上面浪费时候了。

经由

测试

起首,看下面代码(以下测试在 IE9)

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个结论

  1. 在组织函数里的定义的属性没法被继承

  2. 在组织函数里不能运用 this.props.xx

  3. 类属性或要领是没法被继承的

也就是说,只需规避了这三个前提的话,不做任何处置惩罚(前提是已加载了 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;

经由过程上述的代码解释,能够得出有两处题目须要处理

  1. 准确的猎取父类(处理没法继承到在组织函数里定义的属性或要领)

  2. 准确的将子类的原型指向了父类(处理没法继承到类属性或要领)

处理方案

经由过程文档的查询,发明只需开启 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 形式。

但从我们团队的 老司机 口中

《ES6 + Webpack + React + Babel 如安在低版本浏览器上兴奋的游玩(上)》

得到了一个更好插件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

  • 另一方面也对继承的机制有了更好的明白

在这次项目中发明在低版本浏览器跑不起来的两点重要缘由:

  1. SCRIPT5007: 没法猎取属性 xxx 的值,对象为 null 或未定义,这类状况平常是组件继承后,没法继承到在组织函数里定义的属性或要领,一样类属性或要领也一样没法继承

  2. SCRIPT438: 对象不支持 xxx 属性或要领,这类状况平常是运用了 es6、es7 的高等语法,Object.assgin Object.keys 等,这类状况在挪动端的一些 ‘神机’ 也一样会挂。

第一点本文已剖析,预知第二点解说请见下篇。

备注:下篇会重要引见下怎样让 用了 Object.assign 的那位同砚能够继承用,又不会被削。

    原文作者:matianming
    原文地址: https://segmentfault.com/a/1190000006929961
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞