检查对象是否是构造函数 - IsConstructor

Check if object is a constructor - IsConstructor

我想检查一个 JavaScript 值是否是一个构造函数,也就是说,它是否有一个 [[Construct]] 内部方法。

ECMAScript 定义了 IsConstructor,正是这样做的,但它是一个内部操作。

所以我想效仿一下。我考虑过在 try 语句中尝试实例化或子类化,但在所有情况下都不能可靠地工作。

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();

有什么方法可以可靠地测试它吗?如果不在 ES6 或 ES7 中,也许在某些草案或提议的功能中?

这基于 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();

如果代理对象的初始目标是构造函数,则代理对象只是构造函数。来自 ProxyCreate

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

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

因此,代码创建了一个代理对象,其目标是我们要检查的值,并且其处理程序具有不抛出的构造陷阱。

这样,如果代理是一个构造函数(即被测试的值是一个构造函数),当实例化时它将运行陷阱中的代码而不是将操作重定向到目标,并且它赢了'扔。如果代理不是构造函数(即测试值都不是),则在实例化时会抛出错误。

不过有点小问题。创建代理时,目标必须是非代理对象或未撤销的代理对象。否则,它抛出,因此上面的代码认为它是一个非构造函数。

这对于基元来说没问题,因为它们不能是构造函数。但是,被撤销的代理可以是构造函数,也可以不是构造函数,我们无法正确测试它们。

您可能想要 以便以不同的方式处理这种情况。