Subclassing native class and instanceof operator

# Gray Zhang (8 years ago)

Recently I encountered an issue about subclassing Error and instance operator, my simplified code is:

class MyError extends Error {
  constructor() {
    super('my error');
  }
}

let error = new MyError();
console.log(error instanceof MyError);
console.log(error.constructor);

Surprisingly the output is false and function Error() { [native code] }

I dived into the ECMAScript 2015 spec and find the behavior is correct, chapter 12.3.5.1 says: SuperCall : super Arguments

  1. Let newTarget be GetNewTarget www.ecma-international.org/ecma-262/6.0/index.html#sec-getnewtarget ().
  2. If newTarget is undefined, throw a ReferenceError exception.
  3. Let func be GetSuperConstructor www.ecma-international.org/ecma-262/6.0/index.html#sec-getsuperconstructor ().
  4. ReturnIfAbrupt www.ecma-international.org/ecma-262/6.0/index.html#sec-returnifabrupt (func).
  5. Let argList be ArgumentListEvaluation of Arguments.
  6. ReturnIfAbrupt www.ecma-international.org/ecma-262/6.0/index.html#sec-returnifabrupt (argList).
  7. Let result be Construct www.ecma-international.org/ecma-262/6.0/index.html#sec-construct (func, argList, newTarget).
  8. ReturnIfAbrupt www.ecma-international.org/ecma-262/6.0/index.html#sec-returnifabrupt (result).
  9. Let thisER be GetThisEnvironment www.ecma-international.org/ecma-262/6.0/index.html#sec-getthisenvironment( ).
  10. Return thisER.BindThisValue www.ecma-international.org/ecma-262/6.0/index.html#sec-bindthisvalue (result).

Since Error can be called as a function without new operator, the result of step 7 is an error instance rather than undefined, so this error instance becomes this value of thisER and then returned as the result of new MyError constructor.

The problem is, the spec also said "The Error constructor is designed to be subclassable." so I think instanceof should work correctly on subclasses, however it fails now.

This also comes to Map, Set, Array and Object constructors.

Babel 6.7.7 has the correct behavior which fails on instanceof operator but it really introduces some troubles, is this by design and how could I avoid such issues?

# Jordan Harband (8 years ago)

In which engine did you try this? Please refer to kangax.github.io/compat-table/es6 under "Subclassing" to see if your browser supports subclassing builtins yet.

# Gray Zhang (8 years ago)

I know NodeJS outputs true in this case, but my problem is from the spec it seems false is the correct result, or am I missing some chapters in spec?

Jordan Harband <ljharb at gmail.com>于2016年5月31日周二 下午2:47写道:

# Logan Smyth (8 years ago)

The correct spec behavior is to return an instance of MyError. Step 7 in your example is not equivalent to calling Error without new, which seems to be your assumption. The newTarget parameter passed to Construct is used to determine the prototype of the final object, and in this context newTarget would be MyError.

Babel does not support extending builtins by default because it is difficult to transpile properly and would add more boilerplate in all class cases, even those which do not require logic to handle native extension. If you need builtin extension support, the only way at the moment is to explicitly enable it via a plugin like www.npmjs.com/package/babel

# Allen Wirfs-Brock (8 years ago)

On May 31, 2016, at 3:25 AM, Logan Smyth <loganfsmyth at gmail.com> wrote:

The correct spec behavior is to return an instance of MyError. Step 7 in your example is not equivalent to calling Error without new, which seems to be your assumption. The newTarget parameter passed to Construct is used to determine the prototype of the final object, and in this context newTarget would be MyError.

Babel does not support extending builtins by default because it is difficult to transpile properly and would add more boilerplate in all class cases, even those which do not require logic to handle native extension. If you need builtin extension support, the only way at the moment is to explicitly enable it via a plugin like www.npmjs.com/package/babel-plugin-transform-builtin-extend, www.npmjs.com/package/babel-plugin-transform-builtin-extend

On Mon, May 30, 2016 at 11:56 PM, Gray Zhang <otakustay at gmail.com <mailto:otakustay at gmail.com>> wrote: I know NodeJS outputs true in this case, but my problem is from the spec it seems false is the correct result, or am I missing some chapters in spec?

Jordan Harband <ljharb at gmail.com <mailto:ljharb at gmail.com>>于2016年5月31日周二 下午2:47写道: In which engine did you try this? Please refer to kangax.github.io/compat-table/es6, kangax.github.io/compat-table/es6 under "Subclassing" to see if your browser supports subclassing builtins yet.

On Mon, May 30, 2016 at 11:32 PM, Gray Zhang <otakustay at gmail.com <mailto:otakustay at gmail.com>> wrote: Recently I encountered an issue about subclassing Error and instance operator, my simplified code is:

class MyError extends Error {
  constructor() {
    super('my error’);
  }
}

let error = new MyError();
console.log(error instanceof MyError);
console.log(error.constructor);

Surprisingly the output is false and function Error() { [native code] }

I dived into the ECMAScript 2015 spec and find the behavior is correct, chapter 12.3.5.1 says: SuperCall : super Arguments Let newTarget be GetNewTarget www.ecma-international.org/ecma-262/6.0/index.html#sec-getnewtarget(). If newTarget is undefined, throw a ReferenceError exception. Let func be GetSuperConstructor www.ecma-international.org/ecma-262/6.0/index.html#sec-getsuperconstructor(). ReturnIfAbrupt www.ecma-international.org/ecma-262/6.0/index.html#sec-returnifabrupt(func). Let argList be ArgumentListEvaluation of Arguments. ReturnIfAbrupt www.ecma-international.org/ecma-262/6.0/index.html#sec-returnifabrupt(argList). Let result be Construct www.ecma-international.org/ecma-262/6.0/index.html#sec-construct(func, argList, newTarget). ReturnIfAbrupt www.ecma-international.org/ecma-262/6.0/index.html#sec-returnifabrupt(result). Let thisER be GetThisEnvironment www.ecma-international.org/ecma-262/6.0/index.html#sec-getthisenvironment( ). Return thisER.BindThisValue www.ecma-international.org/ecma-262/6.0/index.html#sec-bindthisvalue(result). Since Error can be called as a function without new operator, the result of step 7 is an error instance rather than undefined, so this error instance becomes this value of thisER and then returned as the result of new MyError constructor.

Huh?? Step 7 isn’t a [[Call]] it is a [[Construct]]. Syntactic rules such as “must use new to invoke a constructor” can’t apply at the level of pseudo-code.

The problem is, the spec also said "The Error constructor is designed to be subclassable." so I think instanceof should work correctly on subclasses, however it fails now.

It is specified to work. Your implementation must be wrong or imp complete.

This also comes to Map, Set, Array and Object constructors.

Also should work