我注意到并非所有的 Javascript 函数都是构造函数。
var obj = Function.prototype;
console.log(typeof obj === 'function'); //true
obj(); //OK
new obj(); //TypeError: obj is not a constructor
问题 1:如何检查函数是否是构造函数,以便可以使用 new 调用它?
问题 2:当我创建一个函数时,是否可以使它不是构造函数?
我注意到并非所有的 Javascript 函数都是构造函数。
var obj = Function.prototype;
console.log(typeof obj === 'function'); //true
obj(); //OK
new obj(); //TypeError: obj is not a constructor
问题 1:如何检查函数是否是构造函数,以便可以使用 new 调用它?
问题 2:当我创建一个函数时,是否可以使它不是构造函数?
一点背景:
ECMAScript 6+ 区分了可调用(可以不用 调用new)和可构造(可以用 调用new)函数:
class语法创建的函数不可调用。Function构造函数)创建的函数都是可调用和可构造的。关于 Function.prototype
Function.prototype是一个所谓的内置函数 ,它是不可构造的。从规范:
[[Construct]]除非在特定函数的描述中另有说明,否则未被标识为构造函数的内置函数对象不会实现内部方法。
的值Function.prototype是在运行时初始化开始时创建的。它基本上是一个空函数,并没有明确说明它是可构造的。
如何检查函数是否是构造函数,以便可以使用 new 调用它?
没有内置的方法可以做到这一点。您可以try使用 调用该函数new,然后检查错误或返回true:
function isConstructor(f) {
try {
new f();
} catch (err) {
// verify err is the expected error and then
return false;
}
return true;
}
但是,这种方法不是故障安全的,因为函数可能会产生副作用,因此在调用 之后f,您不知道环境处于哪种状态。
此外,这只会告诉你一个函数是否可以作为构造函数调用,而不是它是否打算作为构造函数调用。为此,您必须查看文档或函数的实现。
注意:永远没有理由在生产环境中使用这样的测试。一个函数是否应该被调用new应该可以从它的文档中辨别出来。
当我创建一个函数时,如何使它不是构造函数?
要创建一个真正不可构造的函数,您可以使用箭头函数:
var f = () => console.log('no constructable');
箭头函数根据定义是不可构造的。或者,您可以将函数定义为对象或类的方法。
否则,您可以通过检查函数new的this值来检查函数是否被调用(或类似的东西),如果是,则抛出错误:
function foo() {
if (this instanceof foo) {
throw new Error("Don't call 'foo' with new");
}
}
当然,由于还有其他方法可以设置 的值this,因此可能会出现误报。
例子
function isConstructor(f) {
try {
new f();
} catch (err) {
if (err.message.indexOf('is not a constructor') >= 0) {
return false;
}
}
return true;
}
function test(f, name) {
console.log(`${name} is constructable: ${isConstructor(f)}`);
}
function foo(){}
test(foo, 'function declaration');
test(function(){}, 'function expression');
test(()=>{}, 'arrow function');
class Foo {}
test(Foo, 'class declaration');
test(class {}, 'class expression');
test({foo(){}}.foo, 'object method');
class Foo2 {
static bar() {}
bar() {}
}
test(Foo2.bar, 'static class method');
test(new Foo2().bar, 'class method');
test(new Function(), 'new Function()');
您正在寻找函数是否具有[[Construct]]内部方法。内部方法IsConstructor详述步骤:
IsConstructor(argument)ReturnIfAbrupt(argument). // (Check if an exception has been thrown; Not important.) If Type(argument) is not Object, return false. // argument === Object(argument), or (typeof argument === 'Object' || typeof argument === 'function') If argument has a [[Construct]] internal method, return true. Return false.
现在我们需要找到IsConstructor被使用但[[Construct]]没有被调用的地方(通常是通过Construct内部方法)。
我发现它用于String函数的newTarget(new.target在js中),可以与Reflect.construct:
function is_constructor(f) {
try {
Reflect.construct(String, [], f);
} catch (e) {
return false;
}
return true;
}
(我真的可以使用任何东西,比如Reflect.construct(Array, [], f);,但它String是第一个)
这产生以下结果:
// true
is_constructor(function(){});
is_constructor(class A {});
is_constructor(Array);
is_constructor(Function);
is_constructor(new Function);
// false
is_constructor();
is_constructor(undefined);
is_constructor(null);
is_constructor(1);
is_constructor(new Number(1));
is_constructor(Array.prototype);
is_constructor(Function.prototype);
is_constructor(() => {})
is_constructor({method() {}}.method)
<注意>
我发现它唯一不起作用的值是Symbol,尽管在 Firefox 中new Symbol抛出了 a ,但. 这在技术上是正确的答案,因为确实有一个内部方法(这意味着它也可以被子类化),但是使用or是特例用于抛出错误(所以,是一个构造函数,错误消息是错误的,它只是可以不能用作一个。)不过,您可以添加到顶部。TypeError: Symbol is not a constructoris_constructor(Symbol) === trueSymbol [[Construct]]newsuperSymbolSymbolif (f === Symbol) return false;
对于这样的事情也是如此:
function not_a_constructor() {
if (new.target) throw new TypeError('not_a_constructor is not a constructor.');
return stuff(arguments);
}
is_constructor(not_a_constructor); // true
new not_a_constructor; // TypeError: not_a_constructor is not a constructor.
因此,作为构造函数的功能的意图不能像这样(直到Symbol.is_constructor添加了类似的东西或其他标志)。
</note>
有一种快速简便的方法可以确定函数是否可以实例化,而不必求助于 try-catch 语句(v8 无法对其进行优化)
function isConstructor(obj) {
return !!obj.prototype && !!obj.prototype.constructor.name;
}
有一个警告,即:在定义中命名的函数仍然会产生 name 属性并因此通过此检查,因此在依赖函数构造函数的测试时需要谨慎。
在以下示例中,该函数不是匿名的,但实际上称为“myFunc”。它的原型可以扩展为任何 JS 类。
let myFunc = function () {};
:)
使用 ES6+ 代理,可以在[[Construct]]不实际调用构造函数的情况下进行测试。这是一个片段:
const handler={construct(){return handler}} //Must return ANY object, so reuse one
const isConstructor=x=>{
try{
return !!(new (new Proxy(x,handler))())
}catch(e){
return false
}
}
如果传递的项不是对象,则Proxy构造函数会抛出错误。如果它不是可构造的对象,则new抛出错误。但是如果它是一个可构造的对象,那么它会在handler不调用其构造函数的情况下返回该对象,然后不调用它的构造函数true。
如您所料,Symbol仍被视为构造函数。那是因为它是,并且实现仅在[[Construct]]调用时抛出错误。这可能是任何用户定义的函数在new.target存在时抛出错误的情况,因此将其作为额外检查专门清除似乎是不正确的,但如果您发现这有帮助,请随时这样做。
如果函数是构造函数,那么它将有一个“原型”成员,而该成员又具有一个等于函数本身的“构造函数”成员。
function isConstructor(func) {
return (func && typeof func === "function" && func.prototype && func.prototype.constructor) === func;
}