2016-12-01 196 views
16

我注意到並非所有的Javascript函數都是構造函數。如何檢查一個Javascript函數是否是構造函數

var obj = Function.prototype; 
console.log(typeof obj === 'function'); //true 
obj(); //OK 
new obj(); //TypeError: obj is not a constructor 

問題1:如何檢查一個函數是否是一個構造函數,以便可以使用新函數調用?

問題2:當我創建一個函數時,是否有可能使它成爲而不是的構造函數?

+0

有趣的是'Function'和'Function.prototype'是唯一不是構造函數的函數嗎? – Adam

+2

功能是一個construtor。新函數();作品。 –

+0

只需檢查該類型是否爲函數。 – rlemon

回答

17

背景的一點點:創建

  • 功能:

    的ECMAScript 6+之間可調用(可以不new被稱爲)和施工的(可以與new調用)功能區分通過箭頭函數語法或通過類或對象文字中的方法定義是不可構造的。

  • 通過class語法創建的函數是不可調用
  • 以任何其他方式創建的函數(函數表達式/聲明,構造函數Function)都可調用並且可構造。
  • 除非另有明確說明,否則內置函數不可構造。

關於Function.prototype

Function.prototype是所謂的built-in functionthat is not constructable。從規格:

未標識爲構造函數的內置函數對象不實現[[Construct]]內部方法,除非在特定函數的說明中另有說明。

Function.prototype的值是在運行時初始化時創建的。它基本上是一個空的函數,並沒有明確說明它是可以構造的。


我如何檢查如果一個函數是一個構造函數,以便它可以用一個新的被稱爲?

有沒有一種內置的方式來做到這一點。您可以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()');

5

有確定的功能可以被實例化,而無需一個快速簡便的方法訴諸的try-catch語句(可不會被v8優化)

function isConstructor(obj) { 
    return !!obj.prototype && !!obj.prototype.constructor.name; 
} 
  1. 首先我們檢查對象是否是原型鏈的一部分。
  2. 然後我們排除匿名函數

有一個警告,那就是:functions named inside a definition仍會招致name屬性,因此通過此檢查,所以對功能構造測試依靠時,需要謹慎。

在下面的例子中,函數不是匿名的,但實際上被稱爲'myFunc'。它的原型可以擴展爲任何JS類。

let myFunc = function() {}; 

:)

+0

爲什麼要排除匿名函數?他們是可修復的。 – Taurus

+0

對於這個函數,我更喜歡Object.hasOwnProperty(「prototype」)。順便說一下,雖然大多數可構建物確實具有'。原型',但有些像綁定函數那樣,但它們仍然是可構造的。這是因爲可構造性與'.prototype'沒有直接關係,它全部與內部['[[construct]]']有關(https://stackoverflow.com/questions/21874128/construct-internal-方法)方法。因此,對許多案例來說,這是一個體面的解決方案,但這並不完全是無懈可擊的(正如你所指出的)._ – Taurus

2

您正在尋找如果一個函數有一個[[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(js中的new.target)中使用,其中ca n,其中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); 

<備註>

我發現它的唯一值不起作用的是Symbol,其中雖然new Symbol在Firefox中拋出了TypeError: Symbol is not a constructoris_constructor(Symbol) === true。這是技術上正確的答案,因爲Symbol[[Construct]]內部方法(這意味着它也可以被繼承),但使用newsuper是特殊的套管爲Symbol拋出一個錯誤(所以,Symbol是一個構造,錯誤信息是錯誤的,它不能作爲一個使用。)您可以將if (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. 

所以,作爲一個構造函數的意圖不能gotton這樣的(直到財產以後像Symbol.is_constructor,或者加入了一些其他的標誌)。

< /注>

+1

這是天才! –

2

隨着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存在時會引發錯誤,因此,將其作爲附加檢查明確區分並不合適,但如果您發現該函數有幫助,則可以隨意這樣做。

相關問題