![](/img/trans.png)
[英]How different are the JavaScript Constructor function and Scala constructor?
[英]How to check if a Javascript function is a constructor
我注意到並非所有的 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');
箭頭函數根據定義是不可構造的。 或者,您可以將函數定義為對象或類的方法。
否則,您可以通過檢查this
值來檢查一個函數是否被new
(或類似的東西)調用,如果是,則拋出錯誤:
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
(js中的new.target
)中使用的,可以和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
,盡管new Symbol
拋出TypeError: Symbol is not a constructor
in Firefox,但is_constructor(Symbol) === true
。 這在技術上是正確的答案,因為Symbol
確實有一個[[Construct]]
內部方法(這意味着它也可以被子類化),但是使用new
或super
是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.
所以作為構造函數的意圖不能像這樣(直到添加了像Symbol.is_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;
}
對於問題 1,這個助手呢?
Function.isConstructor = ({ prototype }) => Boolean(prototype) && Boolean(prototype.constructor)
Function.isConstructor(class {}); // true
Function.isConstructor(function() {}); // true
Function.isConstructor(() => {}); // false
Function.isConstructor("a string"); // false
對於問題 2,箭頭函數是解決方案。 它不能用作構造函數,因為它不依賴與常規函數相同的作用域並且沒有原型(實例的定義,類似於真正的 OOP 的類定義)
const constructable = function() { console.log(this); };
const callable = () => { console.log(this); };
constructable(); // Window {}
callable(); // Window {}
new constructable(); // aConstructableFunction {}
new callable(); // Uncaught TypeError: callable is not a constructor
我嘗試了許多解決方法,但都不能滿足我的需求,所以我通過使用反射元數據制作了自己的解決方法。
目標:檢查當前函數是否具有它自己的 __類__ 元數據,這些元數據表示此函數是否為構造函數。
注意:此解決方法中區分構造函數和普通函數或類的唯一方法是使用類裝飾器@Class()
import 'reflect-metadata';
type Constructor<T = any> = new (...args: any[]) => T;
function Class() {
return function (target: Constructor) {
if (!!Reflect.getOwnMetadata('__class__', target)) {
throw new Error(`Cannot apply @Class decorator on ${target.name} multiple times.`);
}
Reflect.defineMetadata('__class__', target, target);
};
}
function isConstructor<T>(type: Constructor<T>): boolean {
if (typeof type !== 'function') return false;
return !!Reflect.getOwnMetadata('__class__', type);
}
/*
* ------------------
* Example
* ------------------
*/
@Class()
class ServiceClass1 {}
class ServiceClass2 {}
function Test() {}
console.log(isConstructor(ServiceClass1)) // true
console.log(isConstructor(ServiceClass2)) // false
console.log(isConstructor(Test)) // false
有一種快速簡便的方法可以確定函數是否可以被實例化,而不必求助於 try-catch 語句(v8 無法優化)
function isConstructor(value) {
return !!value && !!value.prototype && !!value.prototype.constructor;
}
value
是否真實。value
是否是原型鏈的一部分。constructor
;-)請注意,上面被命名為
isConstructor
不是isConstructable
,我的意思是,這將為Bound-constructors
返回false
,如注釋中所述,因為“bound”意味着重定向到某些東西,而不是直接成為真正的構造函數。所以這回答了標題的“
check ... is constructor
”問題,但不是以后“check ... can be called with new
。例子:
const myClazz = {method() {}}; myClazz.method = myClazz.method.bind(myClazz); // We can call above with new keyword. new (myClazz.method); // But it's just a callback. if (isConstructor(myClass)) throw new Error('expected to return false for arrow-functions and similar.');
下面是基於Jasmine
的。
// Change import to wherever your common functions are
// (for me they're in src directory, outside of tests directory).
import * as common from '../common-tools';
let isMyClassCalled = false;
class MyClass {
constructor() {
isMyClassCalled = true;
}
}
describe('App isConstructor tool', () => {
it('should detect constructor', function () {
detect(class A {});
detect(Array);
detect(Function);
detect(new Function);
detect({method() {}}.method);
detect((() => {}));
});
it('should NOT detect as constructor', function () {
noDetect();
noDetect(undefined);
noDetect(null);
noDetect(1);
noDetect(new Number(1));
noDetect(new (function(){}));
noDetect(Array.prototype);
noDetect(Function.prototype);
});
it('should NOT detect bound constructors', function () {
const clazz = {method() {}};
clazz.method = clazz.method.bind(clazz);
noDetect(clazz.method);
});
it('should never call constructor', function () {
common.isConstructor(MyClass);
expect(isMyClassCalled).toBe(false);
});
function detect(value, expecting = true) {
expect(common.isConstructor(value))
.withContext('For "' + value + '" value')
.toBe(expecting);
}
function noDetect(value) {
detect(value, false);
}
});
以上所有測試也通過以下測試。
function isConstructor(value) {
return typeof value === 'function' && !!value.prototype && value.prototype.constructor === value;
}
作為Felix Kling 回答的補充,即使函數不可構造,如果它具有prototype
屬性,我們仍然可以像構造函數一樣使用它。 我們可以在Object.create()
的幫助下做到這一點。 示例:
// The built-in object Symbol is not constructable, even though it has a "prototype" property:
new Symbol
// TypeError: Symbol is not a constructor.
Object.create(Symbol.prototype);
// Symbol {}
// description: (...)
// __proto__: Symbol
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.