2016-12-01 9 views
16

Ich habe festgestellt, nicht alle Javascript-Funktionen sind Konstruktoren.Wie überprüft man, ob eine Javascript-Funktion ein Konstruktor ist

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

Frage 1: Wie kann ich überprüfen, ob eine Funktion ein Konstruktor ist, so dass es mit einem neuen bezeichnet werden kann?

Frage 2: Wenn ich eine Funktion erstelle, ist es möglich, es NICHT ein Konstruktor zu machen?

+0

Interessant sind 'Function' und' Function.prototype' die einzigen 'Funktionen', die keine Konstruktoren sind? – Adam

+2

Funktion ist ein Konstrukteur. neue Funktion(); funktioniert. –

+0

Überprüfen Sie einfach, ob der Typ eine Funktion ist. – rlemon

Antwort

17

Ein wenig Hintergrund:

ECMAScript 6+ unterscheidet zwischen aufrufbar (ohne new genannt werden) und konstruierbar Funktionen (mit new genannt werden):

  • Funktionen erstellt über die Pfeilfunktionssyntax oder über eine Methodendefinition in Klassen oder Objektliteralen sind nicht konstruierbar.
  • Funktionen, die über die class-Syntax erstellt wurden, sind nicht aufrufbar.
  • Funktionen, die auf andere Weise erstellt wurden (Funktionsausdruck/Deklaration, Function Konstruktor), sind aufrufbar und konstruierbar.
  • Eingebaute Funktionen sind nicht bearbeitbar, sofern nicht ausdrücklich anders angegeben.

Über Function.prototype

Function.prototype ist eine so genannte built-in functionthat is not constructable. Von der Spezifikation:

Integrierte Funktionsobjekte, die nicht als Konstrukteurs identifiziert sind, die sind [[Construct]] internen Verfahren nicht umsetzen, wenn nicht anders in der Beschreibung einer bestimmten Funktion angegeben.

Der Wert Function.prototype wird am Anfang der Laufzeitinitialisierung erstellt. Es ist im Grunde eine leere Funktion und es wird nicht explizit gesagt, dass es konstruierbar ist.


Wie kann ich überprüfen, ob eine Funktion ein Konstruktor ist, so dass es mit einem neuen bezeichnet werden kann?

Es gibt keine integrierte Möglichkeit, dies zu tun. Sie können try die Funktion mit new zu nennen, und entweder den Fehler untersuchen oder true zurück:

function isConstructor(f) { 
    try { 
    new f(); 
    } catch (err) { 
    // verify err is the expected error and then 
    return false; 
    } 
    return true; 
} 

jedoch dieser Ansatz ist nicht störungssicher, da Funktionen Nebenwirkungen haben können, so dass nach f Aufruf, Sie wissen nicht, in welchem ​​Zustand sich die Umwelt befindet.

Auch dies wird Ihnen nur sagen, ob eine Funktion kann als Konstruktor aufgerufen werden, nicht wenn es soll als Konstruktor aufgerufen werden. Dafür müssen Sie sich die Dokumentation oder die Implementierung der Funktion ansehen.

Hinweis: Es sollte niemals einen Grund geben, einen Test wie diesen in einer Produktionsumgebung zu verwenden. Ob eine Funktion mit new aufgerufen werden soll, sollte aus ihrer Dokumentation ersichtlich sein.

Wenn ich eine Funktion erstelle, wie mache ich es NICHT zu einem Konstruktor?

var f =() => console.log('no constructable'); 

Pfeil Funktionen sind per Definition nicht konstruierbar:

eine Funktion erstellen wirklich nicht konstruierbar ist, können Sie einen Pfeil-Funktion verwenden. Alternativ könnten Sie eine Funktion als Methode eines Objekts oder einer Klasse definieren.

Sie könnten sonst prüfen, ob eine Funktion mit new (oder so ähnlich) genannt wird, durch seine Überprüfung this Wert und einen Fehler aus, wenn es sich um:

function foo() { 
    if (this instanceof foo) { 
    throw new Error("Don't call 'foo' with new"); 
    } 
} 

Natürlich, da es andere Wege gibt zu setzen der Wert von this, kann es falsche Positive geben.


Beispiele

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

Es ist eine schnelle und einfache Art und Weise zu bestimmen, ob Funktion instanziiert werden kann, ohne dass Aussagen zurückgreifen, um zu versuchen Beifang (die nicht durch v8 optimiert werden)

function isConstructor(obj) { 
    return !!obj.prototype && !!obj.prototype.constructor.name; 
} 
  1. Zuerst prüfen wir, ob das Objekt Teil einer Prototypkette ist.
  2. Dann schließen wir anonyme Funktionen

Es gibt eine Einschränkung, die lautet: functions named inside a definition noch einen Namen Eigenschaft entstehen und damit diese Prüfung passieren, so ist Vorsicht geboten, wenn sie auf Tests zur Funktionsbauer angewiesen zu sein.

Im folgenden Beispiel ist die Funktion nicht anonym, sondern heißt tatsächlich 'myFunc'. Sein Prototyp kann wie jede JS-Klasse erweitert werden.

let myFunc = function() {}; 

:)

+0

Warum möchten Sie anonyme Funktionen ausschließen? Sie sind Konstrukte. – Taurus

+0

Ich würde 'Object.hasOwnProperty (" Prototyp ")' für diese Funktion bevorzugen. BTW, während die meisten Constructables '.prototype's haben, manche wie gebundene Funktionen nicht, aber sie sind immer Konstruktables. Dies liegt daran, dass die Konstruierbarkeit keine direkte Beziehung zu "Prototyp" hat, sondern alles mit dem internen ['[[Konstrukt]]'] (https://stackoverflow.com/questions/21874128/construct-internal- Methode). _So, während dies eine anständige Lösung für viele Fälle ist, ist es nicht ganz kugelsicher (wie Sie bemerkt haben) ._ – Taurus

2

Sie suchen, wenn eine Funktion ein [[Construct]] interne Methode hat.Das interne Verfahren IsConstructor beschreibt die Schritte:

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. 

Jetzt brauchen wir Orte zu finden, wo IsConstructor verwendet wird, aber [[Construct]] wird nicht genannt (in der Regel durch die Construct internen Verfahren.)

Ich fand, dass es in der newTarget (new.target in js) String Funktion verwendet wird, die ca n mit Reflect.construct verwendet werden:

function is_constructor(f) { 
    try { 
    Reflect.construct(String, [], f); 
    } catch (e) { 
    return false; 
    } 
    return true; 
} 

(ich etwas wirklich genutzt haben könnte, wie Reflect.construct(Array, [], f);, aber String war zuerst)

, die die folgenden Ergebnisse liefert:

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

<Hinweis>

Der einzige Wert, den ich fand, dass es nicht funktionierte, ist Symbol, die, obwohl new Symbol eine TypeError: Symbol is not a constructor in Firefox, is_constructor(Symbol) === true wirft. Dies ist technisch die richtige Antwort, wie Symbolhat eine [[Construct]] interne Methode (was bedeutet, es kann auch subclassed werden), aber unter Verwendung von new oder super speziell für Symbol verrohrt ist, einen Fehler zu werfen (So ist Symbol ein Konstruktor , die Fehlermeldung ist falsch, sie kann nur nicht als eine verwendet werden.) Sie können einfach if (f === Symbol) return false; an die Spitze hinzufügen.

Das gleiche gilt für so etwas wie dieses:

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. 

So sind die Absichten der Funktion einen Konstruktor des Seins nicht so (ist wie Symbol.is_constructor oder eine andere Flagge Bis Somthing hinzugefügt) gotton werden kann.

</note >

+1

Das ist Genie! –

2

Mit ES6 + Proxies kann man für [[Construct]] testen, ohne den Konstruktor tatsächlich aufgerufen wird. Hier ist ein Ausschnitt:

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 
    } 
} 

Wenn das übergebene Element nicht ein Objekt ist, das Proxy Konstruktor einen Fehler wirft. Wenn es kein konstruierbares Objekt ist, gibt new einen Fehler aus. Wenn es sich jedoch um ein konstruierbares Objekt handelt, gibt es das Objekt handler zurück, ohne seinen Konstruktor aufzurufen, der dann in true nicht angezeigt wird.

Wie Sie vielleicht erwarten, wird Symbol immer noch als Konstruktor betrachtet. Das liegt daran, dass dies der Fall ist, und die Implementierung gibt nur einen Fehler aus, wenn [[Construct]] aufgerufen wird. Dies könnte der Fall sein bei jeder benutzerdefinierten Funktion, die einen Fehler ausgibt, wenn new.target vorhanden ist, also scheint es nicht richtig zu sein, es als zusätzliche Überprüfung auszusondern, aber fühlen Sie sich frei, dies zu tun, wenn Sie das als hilfreich empfinden.

Verwandte Themen