ES6 Class 和 Babel 6 在 IE 10 及以下时候的一个坑
写 ES6+ 一定逃不开 babel,也避不开调试 babel 生成的一些代码。
当输入一段 ES6 Class 代码时:
class Person {
static baseName = 'Person'
static speakForAll() {
return this.baseName
}
speak() {
return 'Hello'
}
}
class Developer extends Person {
}
const myself = new Developer()
console.log(myself.speak() === 'Hello') // true
console.log(Person.speakForAll() === 'Person') // true
console.log(Developer.speakForAll() === 'Person') // true
问题
在开发常用的浏览器 Chrome 和 Firefox 里正常工作,但是在 IE10 下会报错 Uncaught TypeError: Developer.speakForAll is not a function
刨根问底
.babelrc
配置如下:
{
"presets": ["es2015", "stage-2"],
}
看 babel 编译出的一串代码 blahblah, 重点下面说:
var _class, _temp
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called",
)
}
return call && (typeof call === 'object' || typeof call === 'function')
? call
: self
}
function _inherits(subClass, superClass) {
if (typeof superClass !== 'function' && superClass !== null) {
throw new TypeError(
'Super expression must either be null or a function, not ' +
typeof superClass,
)
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true,
},
})
if (superClass)
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass)
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function')
}
}
var Person = ((_temp = _class = (function() {
function Person() {
_classCallCheck(this, Person)
}
Person.speakForAll = function speakForAll() {
return this.baseName
}
Person.prototype.speak = function speak() {
return 'Hello'
}
return Person
})()),
(_class.baseName = 'Person'),
_temp)
var Developer = (function(_Person) {
_inherits(Developer, _Person)
function Developer() {
_classCallCheck(this, Developer)
return _possibleConstructorReturn(this, _Person.apply(this, arguments))
}
return Developer
})(Person)
var myself = new Developer()
console.log(myself.speak() === 'Hello')
console.log(Person.speakForAll() === 'Person')
console.log(Developer.speakForAll() === 'Person')
关键是此段实现继承的部分:
function _inherits(subClass, superClass) {
if (typeof superClass !== 'function' && superClass !== null) {
throw new TypeError(
'Super expression must either be null or a function, not ' +
typeof superClass,
)
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true,
},
})
if (superClass)
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass)
}
subClass.prototype
这一段比较简单,操作原型链来实现实例方法和属性的继承。顺带还用 object descriptor 重写了 constructor
这一属性,调用 myself.constructor
时才会拿到正确的值 Developer
,而不是 Person
。
接下来的一段比较有趣。
Object.setPrototypeOf(subClass, superClass)
这个写法还是比较讨巧的,将父类的构造函数 superClass
作为子类构造函数 subClass
的原型。
知识回顾
Object.setPrototypeOf
这是个 ES2015 新提出的函数,函数签名:
Object.setPrototypeOf(obj, prototype)
对比 Object.create
,可以在对象创建出来之后替换其原型。
const p1 = {}
Object.setPrototypeOf(p1, Person.prototype)
console.log(p1.speak()) // 为'Hello'
浏览器兼容性
Feature | Chrome | Edge | Firefox | IE | Opera | Safari |
---|---|---|---|---|---|---|
Basic Support | 34 | (Yes) | 31 | 11 | (Yes) | 9 |
注意到从 IE11 才开始支持此方法。
既然第一条路行不通,那就第二条呗。
__proto__
_inherits
函数中回退到 subClass.__proto__ = superClass
。__proto__
指向的是对象构造函数的 prototype
,通过重设 subClass
的原型来使其获得父类构造函数上的方法(此例中是 class 上的静态方法)。
关键在于,__proto__
是个非标准的属性,根据微软的文档,IE10 及其以下都没有支持。
Not supported in the following document modes: Quirks, Internet Explorer 6 standards, Internet Explorer 7 standards, Internet Explorer 8 standards, Internet Explorer 9 standards, Internet Explorer 10 standards. Not supported in Windows 8.
Babel 的一个 issue 中有人提过类似问题,回答是:babel 6 不考虑兼容 IE。没碰上问题算幸运,碰上问题只好自己解决。
解决方案
就这个事情来说,添加一个 polyfill 能够解决。以 这个实现 来说:
module.exports = Object.setPrototypeOf || ({__proto__:[]} instanceof Array ? setProtoOf : mixinProperties);
function setProtoOf(obj, proto) {
obj.__proto__ = proto;
return obj;
}
function mixinProperties(obj, proto) {
for (var prop in proto) {
if (!obj.hasOwnProperty(prop)) {
obj[prop] = proto[prop];
}
}
return obj;
}
先探测 Object 上是否原生支持,然后检测更改 __proto__
是否有作用,最后回退到简单暴力的遍历赋值。