Error message消失之谜

在前端项目中,自定义Error类并使用ES6的extend语法继承内置Error时,message属性在某些情况下消失。经过排查,发现是由于Babel编译时的配置问题,具体涉及'babel-plugin-transform-builtin-extend'插件的'approximate'配置项。禁用此配置后,问题得到解决,使得new CustomError()能够正确挂载message。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

在写业务代码时,前端与服务端约定了多个code,为了更好的辨识和使用Error,前端自定义了多种Error类,因此自定义了多个新的Error类,并且使用ES6的extend语法继承内置Error

class CustomError extends Error {
	constructor ({ code, message }) {
		super(message);
		this.code = code;
	}
}

遇到的问题

在项目里使用CustomError的过程中,出现了以下的情况

const error = new CustomError({ code: -1, message: 'custom error' })
console.log(error instanceof CustomError) // true
console.log(error instanceof Error) // true
console.log(error.code) // -1
console.log(error.message) // ''

??? message竟然消失了

解决之路

1.难道继承代码没写对?

创建一个HTML文件(保证环境一致),将源代码粘贴过去,使用Chrome打开,结果如下

const error = new CustomError({ code: -1, message: 'custom error' })
console.log(error instanceof CustomError) // true
console.log(error instanceof Error) // true
console.log(error.code) // -1
console.log(error.message) // 'custom error'

??? 结果如此,意味着代码本身是没有问题的,那项目里的message消失的原因是什么呢?

2.寻找原因

一步步分析,项目的代码和直接运行的demo区别在于项目代码因为考虑浏览器兼容性,使用了babel进行编译,那难道是babel编译得有问题吗?于是将代码直接复制到babel的网址上看编译后的文件(项目的代码使用了webpack打包,不是很好查看)

"use strict";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

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; }

var CustomError = function (_Error) {
	_inherits(CustomError, _Error);

	function CustomError(_ref) {
		var code = _ref.code,
		    message = _ref.message;

		_classCallCheck(this, CustomError);

		var _this = _possibleConstructorReturn(this, (CustomError.__proto__ || Object.getPrototypeOf(CustomError)).call(this, message));

		_this.code = code;
		return _this;
	}

	return CustomError;
}(Error);

分析编译后的代码,new CustomError()返回的实例即代码中_this,这个_this取决于Error.call(this, message)是否返回function or object,此处便涉及到一个知识点:Error、Array等函数在设计上和其他函数有一个小区别,它们提供直接当普通function使用返回实例能力,即new Error(‘err’)与Error(‘err’)表现一致,均返回Error的实例,因此Error.call(this, message)返回了一个Error实例,所以new CustomError()最终得到的是一个Error实例。

测试:

const error = new CustomError({ code: -1, message: 'custom error' })
console.log(error instanceof CustomError) // false
console.log(error instanceof Error) // true
console.log(error.code) // -1
console.log(error.message) // 'custom error'

这个结果和项目中的结果更是不同,于是还是直接看项目build后的代码了

function _extendableBuiltin(cls) {
  function ExtendableBuiltin() {
    cls.apply(this, arguments);
  }

  ExtendableBuiltin.prototype = (0, _create2.default)(cls.prototype, {
    constructor: {
      value: cls,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });

  if (_setPrototypeOf2.default) {
    (0, _setPrototypeOf2.default)(ExtendableBuiltin, cls);
  } else {
    ExtendableBuiltin.__proto__ = cls;
  }

  return ExtendableBuiltin;
}

var BusinessError = function (_extendableBuiltin2) {
  (0, _inherits3.default)(BusinessError, _extendableBuiltin2);

  function BusinessError(_ref) {
    var code = _ref.code,
        message = _ref.message;
    (0, _classCallCheck3.default)(this, BusinessError);

    var _this = (0, _possibleConstructorReturn3.default)(this, (BusinessError.__proto__ || (0, _getPrototypeOf2.default)(BusinessError)).call(this, message));

    _this.code = code;
    return _this;
  }

  return BusinessError;
}(_extendableBuiltin(Error));

对比项目中编译后的代码与babel网站上编译的代码,可以发现项目中多了一个“_extendableBuiltin”,这个函数返回一个继承Error的类,而函数本身只是调用了cls.apply(this, arguments),没有返回值,因此new CustomError()返回的直接是this,又妄图使用superClass.call(this, message)来为this挂上message属性,而由于Error的函数中没有使用this,而是直接创建了一个实例返回,因此this上并没有挂上message,这就导致了我们项目里message丢失的问题。

解决

问题发现了,的确是babel编译引起的问题,于是网上搜了搜extendBuiltin,发现这是一个叫“babel-plugin-transform-builtin-extend”的babel plugin编译的结果,去到这个plugin的github,看到了一个配置项:“approximate”,额,好吧,这是为了兼容IE<=10的配置项,添加之后就会以Babel5的风格进行编译,而项目中使用了该插件,使用时也没有仔细查看说明,以致于出现了问题,考虑可以不兼容IE,便将该配置项去除,重新打包,可以看到打包后的代码结果如下:

      function _extendableBuiltin(cls) {
        function ExtendableBuiltin() {
          var instance = (0, _construct2.default)(cls, (0, _from2.default)(arguments));
          (0, _setPrototypeOf2.default)(instance, (0, _getPrototypeOf2.default)(this));
          return instance;
        }
       // ...
        return ExtendableBuiltin;
      }

可以看到现在的_extendableBuiltin中返回的ExtendableBuiltin在调用时,第一步通过construct创建一个instance(使用Reflect.construct()),第二步将instance的__ptoto__设置为this,第三步返回instance,如此便可解决以上出现的系列问题。(IE<=10下,不支持Object.setPrototypeOf(),也不支持直接对__proto__的修改,因此第二步不起作用;第一步中默认使用的construct为Reflect.construct,直接不支持IE系列~~,想在IE下玩耍还需要打polyfill以及其他一些操作~)

扩展

在babel7中,继承内置函数使用的插件推荐“”,该插件内置在了babel-core中,开发者不需要另行安装,有兴趣的小伙伴可以研究两个插件打包后的代码有什么区别,为什么会换?

参考文献:

  • https://hacks.mozilla.org/2015/08/es6-in-depth-subclassing/
  • http://speakingjs.com/es5/ch28.html
  • https://github.com/babel/babel/issues/4480
  • https://github.com/loganfsmyth/babel-plugin-transform-builtin-extend
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值