2

I'm a little bit confused with the class feature in es2016, though it is assumed to be just a syntax sugar for creating classes, in comparison to function and prototype, but the behaviour in some cases is different, in particular - classes can't be called same as functions and it seems, there is no way to find out if a function is the class constructor or a simple function, without using toString and the /^class/ RegExp.

Assume the example:

class Foo {
    constructor () {
        this.name = 'foo';
    }
}
function Bar () {
    this.name = 'bar';
}

function doSmth (anyArg) {
    if (typeof anyArg === 'function') {
        var obj = { someProp: 'qux' };
        anyArg.call(obj);
        return obj;
    }
    // ...
}


doSmth(Bar);
doSmth(Foo); // Class constructor Foo cannot be invoked without 'new'

Is typeof 'function', but can't call it as a function! Nice.

And here are my 2 questions:

  1. Is there some way I can call the Foo constructor function same as Bar with the overriden this context?
  2. Is there some way I can detect the anyArg is the constructor of a class, sothat I can handle it differently in my doSmth function. Without toString and the RegExp (as the performance penalty would be huge in this case). I could then use Reflect.construct to initialize the new instance, and Object.assign to extend my obj variable with the values from the instance.

Thank you, Alex

tenbits
  • 7,568
  • 5
  • 34
  • 53
  • 2
    FWIW, it sounds rather insane to me to a) not know what exactly one receives and b) wanting to coerce object construction like that. – deceze Nov 17 '16 at 13:49
  • possible duplicate of [How can I differentiate between an arrow function, class and a normal function?](http://stackoverflow.com/a/31947622/1048572) – Bergi Nov 17 '16 at 14:18

3 Answers3

1

No to both questions.

Here's how Angular 1.x detects classes:

function isClass(func) {
  // IE 9-11 do not support classes and IE9 leaks with the code below.
  if (msie <= 11 || typeof func !== 'function') {
    return false;
  }
  var result = func.$$ngIsClass;
  if (!isBoolean(result)) {
    // Support: Edge 12-13 only
    // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6156135/
    result = func.$$ngIsClass = /^(?:class\b|constructor\()/.test(stringifyFn(func));
  }
  return result;
}

Unfortunately, it's the best possible solution. And it even doesn't work in Firefox at that.

thorn0
  • 9,362
  • 3
  • 68
  • 96
  • Thank you for providing sample from angular. Yeap, seems it is really the only one way to detect classes. If no better solution somebody provides will accept this as the answer. – tenbits Nov 17 '16 at 14:30
0

I'm not aware of a way to do what you ask in your first question.

For the second question, you actually identified a method to tell the difference yourself. One of the differences between a class constructor and a function used as a constructor is that the former errors when using it without the new keyword, and the latter doesn't.

So, if we use a try/catch, we can do roughly what you want:

class Foo {
    constructor () {
        this.name = 'foo';
    }
}
function Bar () {
    this.name = 'bar';
}

function doSmth (anyArg) {
    if (typeof anyArg === 'function') {
        var obj = { someProp: 'qux' };
        try {
          anyArg.call(obj);
        } catch(e) {
          var x = Reflect.construct(anyArg, []);
          Object.assign(obj, x);
        }
        return obj;
    }
}


doSmth(Bar);
doSmth(Foo);

To be clear, I'm not suggesting this is a good idea, good code, or that it has the performance you're looking for, but I thought I'd point out that the possibility does exist.

Hecksa
  • 2,762
  • 22
  • 34
  • `try..catch` would be a solution, but here we can't be sure the exception is caused by calling a class as a function, and not of some code inside the function. From your example we should somehow parse the message of the exception, but is the message unified across browsers? – tenbits Nov 17 '16 at 14:23
  • Absolutely. You'd either have to do that, or cover all code inside the function called with a more local try/catch, which has its own issues. Like I say, I'm not claiming this approach is perfect (I doubt you'll find a perfect fix for this to be honest), just wanted to raise the possibility for completeness' sake, and in case it helps anyone else who visits the page in the future. – Hecksa Nov 17 '16 at 14:30
0

If you're concerned about the performance penalty of RegEx then use substring(). If you're still concerned about the performance of string operations, then consider another language that actually differentiates function types. In JavaScript they're all function.

class Foo {
  constructor() {
    this.name = 'foo';
  }
}

function Bar() {
  this.name = 'bar';
}

function doSmth(anyArg) {
  if (typeof anyArg === 'function') {
    var obj = {
      someProp: 'qux'
    };
    if (anyArg.toString().substring(0, 5) === 'class') {
      Object.assign(obj, new anyArg());
    } else {
      anyArg.call(obj);
    }
    return obj;
  }
  // ...
}

var bar = doSmth(Bar);
var foo = doSmth(Foo);

console.log(bar);
console.log(foo);
Dan Wilson
  • 3,937
  • 2
  • 17
  • 27
  • 1
    `anyArg.constructor === Function` so `anyArg.constructor()` always returns a no-op function. – thorn0 Nov 17 '16 at 14:28