最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Check if object is a constructor - IsConstructor - Stack Overflow

programmeradmin0浏览0评论

I want to check if a JavaScript value is a constructor, that is, if it has a [[Construct]] internal method.

ECMAScript defines IsConstructor, which does exactly this, but it's an internal operation.

So I want to emulate it. I thought about attempting instantiation or subclassing inside a try statement, but neither does work reliably for all cases.

function isConstructor(value) {
  try {
    return new value(), true;
  } catch(err) {
    return false;
  }
}

function isConstructor(value) {
  try {
    return new value(), true;
  } catch(err) {
    return false;
  }
}
var tests = 0,
    failed = 0;  
function test(value, expected, msg) {
  ++tests;
  try {
    var result = isConstructor(window.eval(value));
  } catch(err) {
    result = err;
  }
  if(result !== expected) {
    ++failed;
    console.log('Testing: ' + value + '\nMessage: ' + msg + '\nResult: ' + result + '\nExpected: ' + expected);
  }
}
function testEnd() {
  console.log(failed + ' out of ' + tests + ' tests failed.');
}
test('undefined', false, 'undefined is not a constructor');
test('null', false, 'null is not a constructor');
test('true', false, 'booleans are not constructors');
test('0', false, 'numbers are not constructors');
test('"abc"', false, 'strings are not constructors');
test('Symbol()', false, 'symbols are not constructors');
test('({})', false, '{} is not a constructor');
test('[]', false, 'arrays are not constructors');
test('(function(){})', true, 'normal functions are constructors');
test('(function(){throw TypeError()})', true, 'normal functions are constructors');
test('(function(){}.bind())', true, 'bounded normal functions are constructors');
test('() => {}', false, 'arrow functions are not constructors');
test('((() => {}).bind())', false, 'bounded arrow functions are not constructors');
test('(function*(){})', false, 'generator functions are not constructors');
test('(function*(){}.bind())', false, 'bounded generator functions are not constructors');
test('(class{})', true, 'classes are constructors');
test('(class extends function(){}{})', true, 'classes are constructors');
test('new Proxy([],{})', false, 'proxies whose target is not constructor are not constructors');
test('new Proxy(function(){},{})', true, 'proxies whose target is a constructor are constructors');
test('new Proxy(function(){},{get:()=>{throw TypeError()}})', true, 'proxies whose target is a constructor are constructors');
test('new Proxy(function(){},{construct:()=>{throw TypeError()}})', true, 'proxies whose target is a constructor are constructors');
test('var r1 = Proxy.revocable([],{}); r1.proxy', false, 'revocable proxies whose target is not a constructor are notconstructors');
test('r1.revoke(); r1.proxy', false, 'revoked proxies whose target was not a constructor are not constructors');
test('var r2 = Proxy.revocable(function(){},{}); r2.proxy', true, 'revocable proxies whose target is a constructor are constructors');
test('r2.revoke(); r2.proxy', true, 'revoked proxies whose target was a constructor are constructors');
testEnd();

I want to check if a JavaScript value is a constructor, that is, if it has a [[Construct]] internal method.

ECMAScript defines IsConstructor, which does exactly this, but it's an internal operation.

So I want to emulate it. I thought about attempting instantiation or subclassing inside a try statement, but neither does work reliably for all cases.

function isConstructor(value) {
  try {
    return new value(), true;
  } catch(err) {
    return false;
  }
}

function isConstructor(value) {
  try {
    return new value(), true;
  } catch(err) {
    return false;
  }
}
var tests = 0,
    failed = 0;  
function test(value, expected, msg) {
  ++tests;
  try {
    var result = isConstructor(window.eval(value));
  } catch(err) {
    result = err;
  }
  if(result !== expected) {
    ++failed;
    console.log('Testing: ' + value + '\nMessage: ' + msg + '\nResult: ' + result + '\nExpected: ' + expected);
  }
}
function testEnd() {
  console.log(failed + ' out of ' + tests + ' tests failed.');
}
test('undefined', false, 'undefined is not a constructor');
test('null', false, 'null is not a constructor');
test('true', false, 'booleans are not constructors');
test('0', false, 'numbers are not constructors');
test('"abc"', false, 'strings are not constructors');
test('Symbol()', false, 'symbols are not constructors');
test('({})', false, '{} is not a constructor');
test('[]', false, 'arrays are not constructors');
test('(function(){})', true, 'normal functions are constructors');
test('(function(){throw TypeError()})', true, 'normal functions are constructors');
test('(function(){}.bind())', true, 'bounded normal functions are constructors');
test('() => {}', false, 'arrow functions are not constructors');
test('((() => {}).bind())', false, 'bounded arrow functions are not constructors');
test('(function*(){})', false, 'generator functions are not constructors');
test('(function*(){}.bind())', false, 'bounded generator functions are not constructors');
test('(class{})', true, 'classes are constructors');
test('(class extends function(){}{})', true, 'classes are constructors');
test('new Proxy([],{})', false, 'proxies whose target is not constructor are not constructors');
test('new Proxy(function(){},{})', true, 'proxies whose target is a constructor are constructors');
test('new Proxy(function(){},{get:()=>{throw TypeError()}})', true, 'proxies whose target is a constructor are constructors');
test('new Proxy(function(){},{construct:()=>{throw TypeError()}})', true, 'proxies whose target is a constructor are constructors');
test('var r1 = Proxy.revocable([],{}); r1.proxy', false, 'revocable proxies whose target is not a constructor are notconstructors');
test('r1.revoke(); r1.proxy', false, 'revoked proxies whose target was not a constructor are not constructors');
test('var r2 = Proxy.revocable(function(){},{}); r2.proxy', true, 'revocable proxies whose target is a constructor are constructors');
test('r2.revoke(); r2.proxy', true, 'revoked proxies whose target was a constructor are constructors');
testEnd();

function isConstructor(value) {
  if(value === null) return false;
  try {
    return class extends value {}, true;
  } catch(err) {
    return false;
  }
}

function isConstructor(value) {
  if(value === null) return false;
  try {
    return class extends value {}, true;
  } catch(err) {
    return false;
  }
}
var tests = 0,
    failed = 0;  
function test(value, expected, msg) {
  ++tests;
  try {
    var result = isConstructor(window.eval(value));
  } catch(err) {
    result = err;
  }
  if(result !== expected) {
    ++failed;
    console.log('Testing: ' + value + '\nMessage: ' + msg + '\nResult: ' + result + '\nExpected: ' + expected);
  }
}
function testEnd() {
  console.log(failed + ' out of ' + tests + ' tests failed.');
}
test('undefined', false, 'undefined is not a constructor');
test('null', false, 'null is not a constructor');
test('true', false, 'booleans are not constructors');
test('0', false, 'numbers are not constructors');
test('"abc"', false, 'strings are not constructors');
test('Symbol()', false, 'symbols are not constructors');
test('({})', false, '{} is not a constructor');
test('[]', false, 'arrays are not constructors');
test('(function(){})', true, 'normal functions are constructors');
test('(function(){throw TypeError()})', true, 'normal functions are constructors');
test('(function(){}.bind())', true, 'bounded normal functions are constructors');
test('() => {}', false, 'arrow functions are not constructors');
test('((() => {}).bind())', false, 'bounded arrow functions are not constructors');
test('(function*(){})', false, 'generator functions are not constructors');
test('(function*(){}.bind())', false, 'bounded generator functions are not constructors');
test('(class{})', true, 'classes are constructors');
test('(class extends function(){}{})', true, 'classes are constructors');
test('new Proxy([],{})', false, 'proxies whose target is not constructor are not constructors');
test('new Proxy(function(){},{})', true, 'proxies whose target is a constructor are constructors');
test('new Proxy(function(){},{get:()=>{throw TypeError()}})', true, 'proxies whose target is a constructor are constructors');
test('new Proxy(function(){},{construct:()=>{throw TypeError()}})', true, 'proxies whose target is a constructor are constructors');
test('var r1 = Proxy.revocable([],{}); r1.proxy', false, 'revocable proxies whose target is not a constructor are notconstructors');
test('r1.revoke(); r1.proxy', false, 'revoked proxies whose target was not a constructor are not constructors');
test('var r2 = Proxy.revocable(function(){},{}); r2.proxy', true, 'revocable proxies whose target is a constructor are constructors');
test('r2.revoke(); r2.proxy', true, 'revoked proxies whose target was a constructor are constructors');
testEnd();

Is there some way to test it reliably? If not in ES6 or ES7, maybe in some draft or proposed feature?

Share Improve this question asked Sep 5, 2016 at 16:09 OriolOriol 289k71 gold badges457 silver badges530 bronze badges 5
  • You could potentially just check if there is a prototype? Or something like try { (new constructor()) instanceof constructor }? – somethinghere Commented Sep 5, 2016 at 16:19
  • @somethinghere The problem is that constructors may throw, so it's not a reliable test. – Oriol Commented Sep 5, 2016 at 16:21
  • 1 There is nothing, but it pops up in esdicuss regularly: esdiscuss/topic/… – Bergi Commented Sep 5, 2016 at 18:00
  • @Bergi Thanks for the link, I wasn't subscribed to esdicuss back then. – Oriol Commented Sep 5, 2016 at 18:07
  • Would you mind closing as a duplicate of (and merging your answer into) How to check if a Javascript function is a constructor? – Bergi Commented Jul 30, 2021 at 22:12
Add a ment  | 

1 Answer 1

Reset to default 8

This is based on the code posted by Jason Orendorff on esdicuss.

function isConstructor(value) {
  try {
    new new Proxy(value, {construct() { return {}; }});
    return true;
  } catch (err) {
    return false;
  }
}

function isConstructor(value) {
  try {
    new new Proxy(value, {construct() { return {}; }});
    return true;
  } catch (err) {
    return false;
  }
}
var tests = 0,
    failed = 0;
function test(value, expected, msg) {
  ++tests;
  try {
    var result = isConstructor(window.eval(value));
  } catch(err) {
    result = err;
  }
  if(result !== expected) {
    ++failed;
    console.log('Testing: ' + value + '\nMessage: ' + msg + '\nResult: ' + result + '\nExpected: ' + expected);
  }
}
function testEnd() {
  console.log(failed + ' out of ' + tests + ' tests failed.');
}
test('undefined', false, 'undefined is not a constructor');
test('null', false, 'null is not a constructor');
test('true', false, 'booleans are not constructors');
test('0', false, 'numbers are not constructors');
test('"abc"', false, 'strings are not constructors');
test('Symbol()', false, 'symbols are not constructors');
test('({})', false, '{} is not a constructor');
test('[]', false, 'arrays are not constructors');
test('(function(){})', true, 'normal functions are constructors');
test('(function(){throw TypeError()})', true, 'normal functions are constructors');
test('(function(){}.bind())', true, 'bounded normal functions are constructors');
test('() => {}', false, 'arrow functions are not constructors');
test('((() => {}).bind())', false, 'bounded arrow functions are not constructors');
test('(function*(){})', false, 'generator functions are not constructors');
test('(function*(){}.bind())', false, 'bounded generator functions are not constructors');
test('(class{})', true, 'classes are constructors');
test('(class extends function(){}{})', true, 'classes are constructors');
test('new Proxy([],{})', false, 'proxies whose target is not constructor are not constructors');
test('new Proxy(function(){},{})', true, 'proxies whose target is a constructor are constructors');
test('new Proxy(function(){},{get:()=>{throw TypeError()}})', true, 'proxies whose target is a constructor are constructors');
test('new Proxy(function(){},{construct:()=>{throw TypeError()}})', true, 'proxies whose target is a constructor are constructors');
test('var r1 = Proxy.revocable([],{}); r1.proxy', false, 'revocable proxies whose target is not a constructor are notconstructors');
test('r1.revoke(); r1.proxy', false, 'revoked proxies whose target was not a constructor are not constructors');
test('var r2 = Proxy.revocable(function(){},{}); r2.proxy', true, 'revocable proxies whose target is a constructor are constructors');
test('r2.revoke(); r2.proxy', true, 'revoked proxies whose target was a constructor are constructors');
testEnd();

Proxy objects are only constructors if their initial target was a constructor. From ProxyCreate,

If target has a [[Construct]] internal method, then

  • Set the [[Construct]] internal method of P as specified in 9.5.14.

Therefore, the code creates a proxy object whose target is the value we want to check, and whose handler has a construct trap which doesn't throw.

This way, if the proxy is a constructor (i.e. the tested value is a constructor), when instantiated it will run the code in the trap instead of redirecting the operation to the target, and it won't throw. If the proxy is not a constructor (i.e. the tested value is neither), when instantiated it will throw an error.

There is a little problem, though. When creating a proxy, the target must be either a non-proxy object, or a non-revoked proxy object. Otherwise, it throws, and thus the code above considers it a non-constructor.

This is OK in case of primitives, because they can't be constructors. However, revoked proxies can be constructors or not, and we can't test them properly.

You might want to detect if the value is a revoked proxy in order to handle that case differently.

发布评论

评论列表(0)

  1. 暂无评论