logo

鱼肚的博客

Don't Repeat Yourself

不是所有的new都能instanceof

问题

JS里面有个 instanceof 方法,可以用来判断一个对象是否是某个类的实例。

1class A {}
2class B {}
3
4var a = new A()
5
6a instanceof A
7// => true
8
9a instanceof B
10// => false

但是在Typescript之中,这个并不是一直成立的。

假设有这样一段JS代码:

1class MyError extends Error {
2    constructor () {
3        super()
4        this.someAttr = 'hello'
5    }
6}
7
8const err = new MyError()
9
10console.log('err instanceof MyError: ', (err instanceof MyError))
11// err instanceof MyError:  true

但是在将其转换成TS之后:

1class MyError extends Error {
2    filename: string
3    constructor () {
4        super()
5        this.filename = 'hello'
6    }
7}
8
9const err = new MyError()
10
11console.log('err instanceof MyError: ', (err instanceof MyError))

使用ts-node(with typescript >= 2)运行上述代码,却会得到下面的结果:

err instanceof MyError: false

更神奇的是:如果加上以下的一段 tsconfig.json

1{
2  "compilerOptions": {
3    "target": "es6"
4  }
5}

ts-node运行的结果就又变成// err instanceof MyError: true

解决方案

一般搜索之后,在Typescript的Breaking Changes 中找到了一段说明。

里面有提到相应的解决方案:

1class MyError extends Error {
2    filename: string
3    constructor () {
4        super()
5        this.filename = 'hello'
6        Object.setPrototypeOf(this, MyError.prototype)
7    }
8}
9
10const err = new MyError()
11
12console.log('err instanceof MyError: ', (err instanceof MyError))
13// err instanceof MyError:  true

或者像上面提到的,将tsconfig.json中的target改成es6,也可解决这个问题。

相关原理

刚刚发的Breaking Changes 中有提到:

As part of substituting the value of this with the value returned by a super(...) call, subclassing Error, Array, and others may no longer work as expected. This is due to the fact that constructor functions for Error, Array, and the like use ECMAScript 6's new.target to adjust the prototype chain; however, there is no way to ensure a value for new.target when invoking a constructor in ECMAScript 5.