How to detect if a function is called as constructor?
NOTE: This is now possible in ES2015 and later. See Daniel Weiner's answer.
I don't think what you want is possible [prior to ES2015]. There simply isn't enough information available within the function to make a reliable inference.
Looking at the ECMAScript 3rd edition spec, the steps taken when new x()
is called are essentially:
- Create a new object
- Assign its internal [[Prototype]] property to the prototype property of
x
- Call
x
as normal, passing it the new object asthis
- If the call to
x
returned an object, return it, otherwise return the new object
Nothing useful about how the function was called is made available to the executing code, so the only thing it's possible to test inside x
is the this
value, which is what all the answers here are doing. As you've observed, a new instance of* x
when calling x
as a constructor is indistinguishable from a pre-existing instance of x
passed as this
when calling x
as a function, unless you assign a property to every new object created by x
as it is constructed:
function x(y) {
var isConstructor = false;
if (this instanceof x // <- You could use arguments.callee instead of x here,
// except in in EcmaScript 5 strict mode.
&& !this.__previouslyConstructedByX) {
isConstructor = true;
this.__previouslyConstructedByX = true;
}
alert(isConstructor);
}
Obviously this is not ideal, since you now have an extra useless property on every object constructed by x
that could be overwritten, but I think it's the best you can do.
(*) "instance of" is an inaccurate term but is close enough, and more concise than "object that has been created by calling x
as a constructor"
How to check if a Javascript function is a constructor
A little bit of background:
ECMAScript 6+ distinguishes between callable (can be called without new
) and constructable (can be called with new
) functions:
- Functions created via the arrow functions syntax or via a method definition in classes or object literals are not constructable.
- Functions created via the
class
syntax are not callable. - Functions created in any other way (function expression/declaration,
Function
constructor) are callable and constructable. - Built-in functions are not constructrable unless explicitly stated otherwise.
About Function.prototype
Function.prototype
is a so called built-in function that is not constructable. From the spec:
Built-in function objects that are not identified as constructors do not implement the
[[Construct]]
internal method unless otherwise specified in the description of a particular function.
The value of Function.prototype
is create at the very beginning of the runtime initialization. It is basically an empty function and it is not explicitly stated that it is constructable.
How do I check if an function is a constructor so that it can be called with a new?
There isn't a built-in way to do that. You can try
to call the function with new
, and either inspect the error or return true
:
function isConstructor(f) {
try {
new f();
} catch (err) {
// verify err is the expected error and then
return false;
}
return true;
}
However, that approach is not failsafe since functions can have side effects, so after calling f
, you don't know which state the environment is in.
Also, this will only tell you whether a function can be called as a constructor, not if it is intended to be called as constructor. For that you have to look at the documentation or the implementation of the function.
Note: There should never be a reason to use a test like this one in a production environment. Whether or not a function is supposed to be called with new
should be discernable from its documentation.
When I create a function, how do I make it NOT a constructor?
To create a function is truly not constructable, you can use an arrow function:
var f = () => console.log('no constructable');
Arrow functions are by definition not constructable. Alternatively you could define a function as a method of an object or a class.
Otherwise you could check whether a function is called with new
(or something similar) by checking it's this
value and throw an error if it is:
function foo() {
if (this instanceof foo) {
throw new Error("Don't call 'foo' with new");
}
}
Of course, since there are other ways to set the value of this
, there can be false positives.
Examples
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()');
How does JavaScript know if I am writing a function or a constructor?
It doesn't. Any function declared using the function
keyword or constructor
within a class
body can be called as a constructor by preceding the call with new
. Unless the function explicitly returns an object, the value returned by the call is a new object created by the call. The new object can be referred to inside the constructor function as this
.
Object instances created by a function (by calling it as a constructor using the new
keyword) have their prototype chain initialized to the value of the function object's prototype
property.
A function object's prototype
property is created when the function or class is declared or a function or a class expression is evaluated. The value of the prototype
property can be altered if the keyword used was function
but can't be changed for class declarations/expressions. However properties of the prototype object can be modified in both cases and are subsequently inherited by object instances.
Arrow functions do not support being called as constructors - they have no prototype
property.
Happy learning about "how does JavaScript prototypal inheritance work"!
P.S.
There is a convention of starting class and constructor function names with an upper-case letter to distinguish them from "regular" functions, but there is no syntactical requirement to do so.
Regular functions (called without new
) can have a this
value that depends on how the function was defined and called. "How is the value of this
set within JavaScript functions" is a topic you may wish to look up separately.
Advanced Javascript: Detecting whether current function is being called by a constructor
One possible solution is passing the constructor context as parameter. There is no need to pass in the arguments object as it can be accessed through this.arguments
, as you are doing in adoptArguments
on your linked answer.
This solution makes sense for me as I expect
Function.prototype.someMethod
to be
called withing the context of aFunction
instance rather than other context
(i.e., the newly created instance).
Function.prototype.doSomethingWith = function doSomethingWith(instance) {
if( instance instanceof this ) // proceed
};
// constructor
function SomeType() {
SomeType.doSomethingWith(this);
}
WARN: your adoptArguments
function has a serious bug, see below
var comments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var parser = /^function[^\(]*\(([^)]*)\)/mi;
var splitter = /\s*,\s*/mi;
Function.prototype.adoptArguments = function() {
var args;
// remove code comments
args = this.toString().replace(comments, "");
// parse function string for arguments
args = parser.exec(args)[1];
if (!args) return; // empty => no arguments
// get individual argument names
args = args.split(splitter);
// adopt all as own prototype members
for(var i = 0, len = args.length; i < len; ++i)
{
this.prototype[args[i]] = this.arguments[i];
}
};
console.log('the problem with your implementation:');
console.log('> adopting arguments as prototype members');
console.log('> implies you override values for every instance of YourType');
function YourType(a, b, c) {
YourType.adoptArguments();
}
var foo = new YourType( 1, 2, 3 );
console.log( 'foo', foo.a, foo.b, foo.c ); // foo 1 2 3
var bar = new YourType( 4, 5, 6 );
console.log( 'foo', foo.a, foo.b, foo.c ); // foo 4 5 6
console.log( 'bar', bar.a, bar.b, bar.c ); // bar 4 5 6
console.log();
console.log('also, a trim is need because:');
function OtherType( a, b, c ) { // see where whitespaces are
OtherType.adoptArguments();
}
var baz = new OtherType( 1, 2, 3 );
console.log( 'baz', baz.a, baz.b, baz.c );
// baz undefined 2 undefined
//
// My solution
//
console.log();
console.log('results');
// slighly modified from your adoptArguments function
Function.prototype.injectParamsOn = function injectParamsOn( instance ) {
// you may check `instance` to be instanceof this
if( ! (instance instanceof this) ) return;
// proceed with injection
var args;
// remove code comments
args = this.toString().replace(comments, "");
// parse function string for arguments
args = parser.exec(args)[1];
if (!args) return; // empty => no arguments
// get individual argument names (note the trim)
args = args.trim().split(splitter);
// adopt all as instance members
var n = 0;
while( args.length ) instance[ args.shift() ] = this.arguments[ n++ ];
};
function MyType( a, b, c ){
MyType.injectParamsOn( this );
}
var one = new MyType( 1, 2, 3 );
console.log( 'one', one.a, one.b, one.c ); // one 1 2 3
var two = new MyType( 4, 5, 6 );
console.log( 'one', one.a, one.b, one.c ); // one 1 2 3
console.log( 'two', two.a, two.b, two.c ); // two 4 5 6
var bad = MyType( 7, 8, 8 );
// this will throw as `bad` is undefined
// console.log( 'bad', bad.a, bad.b, bad.c );
console.log( global.a, global.b, global.c );
// all undefined, as expected (the reason for instanceof check)
Check if object is a constructor - IsConstructor
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();
Check if the constructor call is coming from extended class
Use instanceof
like this to detect whether TheOtherLibrary
is anywhere in the prototype chain:
function TheOtherLibrary (foo) {
if (!(this instanceof TheOtherLibrary)) {
return new TheOtherLibrary(foo);
}
...
}
This will work for a direct instance of TheOtherLibrary
or for an instance of any derived class. If you want to support calling without new
for the derived classes too, then you have to put this structure in the constructor for the derived classes too so you catch whatever constructor function is being called without foo
and can then create the right type of object.
Detecting if the new keyword was used inside the constructor?
It is fairly common to force a new object when the keyword is omitted-
function Group(O){
if(!(this instanceof Group)) return new Group(O);
if(!O || typeof O!= 'object') O= {};
for(var p in O){
if(O.hasOwnProperty(p)) this[p]= O[p];
}
}
How do you check the difference between an ECMAScript 6 class and function?
I think the simplest way to check if the function is ES6 class is to check the result of .toString()
method. According to the es2015 spec:
The string representation must have the syntax of a FunctionDeclaration FunctionExpression, GeneratorDeclaration, GeneratorExpression, ClassDeclaration, ClassExpression, ArrowFunction, MethodDefinition, or GeneratorMethod depending upon the actual characteristics of the object
So the check function looks pretty simple:
function isClass(func) {
return typeof func === 'function'
&& /^class\s/.test(Function.prototype.toString.call(func));
}
Related Topics
JavaScript Date.Utc() Function Is Off by a Month
How to Listen for Keyboard Open/Close in JavaScript/Sencha
Non-Blocking Settimeout in JavaScript VS Sleep in Ruby
Why Does "True" == True Show False in JavaScript
How to Use Source: Function()... and Ajax in Jquery UI Autocomplete
Safari on iOS 9 Does Not Trigger Click Event on Hidden Input File
JavaScript Call to Swift from Uiwebview
How to Convert Special Utf-8 Chars to Their Iso-8859-1 Equivalent Using JavaScript
Time Conversion Between Ruby on Rails and JavaScript Vice Versa
Ignore Errors for Self-Signed Ssl Certs Using the Fetch API in a Reactnative App
Slow Assets Compilation in Development Mode
Pdf.Js: Rendering a PDF File Using a Base64 File Source Instead of Url
Programming Language Independent Model Validation
Ios: Authentication Using Xmlhttprequest - Handling 401 Response
How to Create Query Parameters in JavaScript
Finding All Indexes of a Specified Character Within a String