3

I have always been told “Classes are just syntactic sugar for prototypes.”

But in this example this shows this isn't true.

function SubArray() {}
SubArray.prototype = new Array( );
console.log(Array.isArray(new SubArray()))  // false

and the same example with classes.

SubArray = class extends Array{}
console.log(Array.isArray(new SubArray()))  // true

Funnily instanceof works fine on both new SubArray instanceof Array. Why isn’t Array.isArray returning true with prototype here?

5
  • classes are syntactic sugar for the prototypal pattern in javascript Commented Jul 10, 2018 at 15:03
  • 2
    class are mostly syntax sugar for prototypes, the main difference is that classes can extend built-ins while regular prototypes can't. 2ality.com/2015/02/… Commented Jul 10, 2018 at 15:04
  • 4
    Because inheriting from builtins is one of the few bits where classes are more than sugar. Commented Jul 10, 2018 at 15:04
  • stackoverflow.com/questions/36419713/… Commented Jul 10, 2018 at 15:05
  • 2
    Possible duplicate of Are the es6 classes really semantic sugar? Commented Jul 10, 2018 at 15:06

5 Answers 5

6

That's because you did not write the code that class is actually syntactic sugar of:

function SubArray () {
  if (!(new.target)) {
    throw new TypeError("Class constructor SubArray cannot be invoked without 'new'")
  }

  return Reflect.construct(Array, arguments, new.target)
}

Object.setPrototypeOf(SubArray.prototype, Array.prototype)
Object.setPrototypeOf(SubArray, Array)

console.log(Array.isArray(new SubArray())) // true

The above should behave identically to the example you provided using class syntax. Unfortunately not all of this behavior can be accurately reproduced without other ES6 constructs like new.target and Reflect.construct(), but at least those aren't necessarily required in order to produce your desired behavior:

function SubArray () {
  if (!(this instanceof SubArray)) {
    throw new TypeError("Class constructor SubArray cannot be invoked without 'new'")
  }

  return Array.apply(this, arguments)
}

SubArray.prototype = Object.create(Array.prototype)
// the line below is not necessary for Array.isArray()
// but accurately reproduces behavior of `class SubArray extends Array {}`
SubArray.__proto__ = Array // implementation hack if Object.setPrototypeOf() is not available

console.log(Array.isArray(new SubArray())) // true

The key here is that you delegate construction of the instantiated object to the Array constructor in order to initialize the object as an Array exotic object. So hypothetically, all that's strictly necessary is the following:

function SubArray () {
  return Array.call(this)
}

console.log(Array.isArray(new SubArray())) // true

But of course, you won't have access to Array.prototype methods in this case, so you should stick to class syntax or the second example if you have to support ES5.

Edit

I did some tinkering and I personally think this is a horrible idea, but if you want to emulate class as closely as possible in ES5, you can opt out of strict mode in order to have access to arguments.caller:

// DON'T ACTUALLY DO THIS
// just for demonstration purposes

function SubArray () {
  // this is about as close as you can get to new.target in ES5
  if (!(this instanceof SubArray) && !(arguments.caller && this instanceof arguments.caller)) {
    throw new TypeError("Class constructor SubArray cannot be invoked without 'new'")
  }

  return Array.apply(this, arguments)
}

SubArray.prototype.__proto__ = Array.prototype
SubArray.__proto__ = Array

// we want FooBar to extend SubArray sloppily
function FooBar () {
  if (!(this instanceof SubArray) && !(arguments.caller && this instanceof arguments.caller)) {
    throw new TypeError("Class constructor FooBar cannot be invoked without 'new'")
  }

  return SubArray.apply(this, arguments)
}

FooBar.prototype.__proto__ = SubArray.prototype
FooBar.__proto__ = SubArray

try {
  SubArray()
} catch (e) {
  console.log(e.toString())
}

console.log(new SubArray(1, 2, 3))

try {
  FooBar()
} catch (e) {
  console.log(e.toString())
}

console.log(new FooBar(1, 2, 3))

Sign up to request clarification or add additional context in comments.

Comments

0

Consider the steps to perform Array.isArray():

IsArray ( argument )

The abstract operation IsArray takes one argument argument, and performs the following steps:

If Type(argument) is not Object, return false.
If argument is an Array exotic object, return true.
If argument is a Proxy exotic object, then
    If the value of the [[ProxyHandler]] internal slot of argument is null, throw a TypeError exception.
    Let target be the value of the [[ProxyTarget]] internal slot of argument.
    Return IsArray(target).
Return false.

Your example fails at the step "If argument is an Array exotic object, return true." because it doesn't have it's own length property.

function SubArray() {}
SubArray.prototype = new Array( );
console.log(Array.isArray(new SubArray())) // false


const sa = new SubArray();
console.log(typeof sa); // object
console.log(Object.getOwnPropertyDescriptor(sa, "length")); // undefined

3 Comments

In practice setting .length doesn't seem to make Array.isArray return true though
An object doesn't become an Array exotic object just because it has an own length property.
@William @bergi I'm not trying to imply that setting a length causes an Object to become an Array. I'm saying that's where the Object fails the isArray function. Besides, setting an explicit length would create a configurable property. Array exotics have non-configurable length.
0

Thanks to rlemon in chat you can do something similar to what Patrick did with out Reflect and should theoretically have IE11 support.

function Foo() {
    const arr = new Array(...arguments);
    arr.__proto__ = Foo.prototype;
    return arr;
}

Foo.prototype.constructor = Foo;

Foo.prototype.__proto__ = Array.prototype;

Array.isArray( new Foo(123) ) 

5 Comments

I don't see what the point of avoiding Reflect is here. You're still using several ES6 constructs in order to implement this, and this does not emulate what class extends does.
@PatrickRoberts probably makes sense to use __proto__. Usuing caniuse.com it appears IE11(some still support this saddly) supports __proto__ but not Reflect.
Yeah, but Foo is not a constructor in this implementation. It completely discards the instantiated object created with new Foo and returns a different one constructed with new Array.
@PatrickRoberts in practice what does this mean? instanceof and the constructor are correct.
class Bar extends Foo {} console.log(new Bar() instanceof Bar) // false!!
0

This method makes use of iframes and allows you to extend the class if you choose using class extend fuctionality.

var iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);

frames[frames.length - 1].document.write(
  "<script>parent.SubArray = Array;<\/script>"
);

SubArray.prototype.__proto__ = Array.prototype;
SubArray.__proto__ = Array;
console.log(Array.isArray(new SubArray()));
console.log(new SubArray() instanceof Array);
console.log(new SubArray() instanceof SubArray);

SubArray2 = class extends SubArray {} 

console.log(new SubArray2() instanceof SubArray)

Comments

-2

ES5 way of doing things do not allow you to properly to inherit from Array. ES6 makes it work properly.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.