2016-12-01 23 views
16

Tôi nhận thấy không phải tất cả hàm Javascript đều là hàm tạo.Cách kiểm tra xem hàm Javascript có phải là hàm tạo hay không

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

Câu hỏi 1: Làm cách nào để kiểm tra xem hàm có phải là hàm tạo để có thể gọi hàm mới không?

Câu hỏi 2: Khi tôi tạo một hàm, có thể biến nó thành NOT một hàm tạo không?

+0

Thú vị là 'Function' và' Function.prototype' là 'hàm' duy nhất không phải là hàm tạo? – Adam

+2

Chức năng là một construtor. new Function(); công trinh. –

+0

Chỉ cần kiểm tra xem loại có phải là một hàm hay không. – rlemon

Trả lời

17

Một chút nền:

ECMAScript 6 + phân biệt giữa callable (có thể được gọi mà không new) và constructable (có thể được gọi với new) chức năng:

  • Chức năng tạo thông qua cú pháp hàm mũi tên hoặc thông qua định nghĩa phương thức trong các lớp hoặc đối tượng literals là không thể cấu hình được.
  • Các hàm được tạo thông qua cú pháp classkhông gọi được.
  • Chức năng được tạo theo bất kỳ cách nào khác (biểu thức/khai báo hàm, Function hàm tạo) có thể gọi và có thể cấu hình được.
  • Các chức năng tích hợp không được xây dựng trừ khi có quy định rõ ràng khác.

Về Function.prototype

Function.prototype là một cái gọi là built-in functionthat is not constructable. Từ thông số:

Đối tượng chức năng tích hợp không được xác định là nhà thầu không thực hiện phương thức nội bộ [[Construct]] trừ khi được chỉ định khác trong mô tả của một chức năng cụ thể.

Giá trị Function.prototype được tạo ngay từ đầu khởi tạo thời gian chạy. Về cơ bản nó là một hàm rỗng và nó không được tuyên bố rõ ràng rằng nó có thể xây dựng được.


Làm thế nào để kiểm tra xem một chức năng là một nhà xây dựng để nó có thể được gọi với một mới?

Không có cách tích hợp để thực hiện điều đó. Bạn có thể try để gọi hàm với new, và một trong hai kiểm tra các lỗi hoặc trả lại true:

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

Tuy nhiên, cách tiếp cận đó không phải là chạy failsafe từ chức năng có thể có tác dụng phụ, vì vậy sau khi gọi f, bạn không biết trong đó nêu rõ môi trường.

Ngoài ra, điều này sẽ chỉ cho bạn biết chức năng có thể được gọi là hàm tạo hay không, không phải là được dự định để được gọi là hàm tạo. Cho rằng bạn phải xem tài liệu hoặc việc thực hiện chức năng.

Lưu ý: Không bao giờ có lý do để sử dụng thử nghiệm như thế này trong môi trường sản xuất. Có hay không một chức năng được cho là được gọi với new nên được nhận thấy rõ ràng từ tài liệu của nó.

Khi tôi tạo hàm, làm cách nào để làm cho hàm KHÔNG phải là hàm tạo?

Để tạo một chức năng thực sự là không constructable, bạn có thể sử dụng một chức năng mũi tên:

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

chức năng mũi tên là theo định nghĩa không constructable. Hoặc bạn có thể định nghĩa một hàm như là một phương thức của một đối tượng hoặc một lớp.

Nếu không, bạn có thể kiểm tra xem một hàm được gọi với new (hoặc một cái gì đó tương tự) bằng cách kiểm tra nó this giá trị và ném ra một lỗi nếu nó là:

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

Tất nhiên, kể từ khi có những cách khác để thiết lập giá trị của this, có thể có dương tính giả.


Ví dụ

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

Có một cách nhanh chóng và dễ dàng xác định nếu chức năng có thể được khởi tạo, mà không cần phải dùng đến try-catch báo cáo (mà có thể không được tối ưu hóa bởi v8)

function isConstructor(obj) { 
    return !!obj.prototype && !!obj.prototype.constructor.name; 
} 
  1. Trước tiên, chúng tôi kiểm tra xem đối tượng có phải là một phần của chuỗi nguyên mẫu hay không.
  2. Sau đó chúng tôi loại trừ chức năng ẩn danh

Có một caveat, đó là: functions named inside a definition vẫn sẽ phải chịu một property name và do đó vượt qua kiểm tra này, vì vậy cần thận trọng khi khi dựa vào các xét nghiệm cho nhà xây dựng chức năng.

Trong ví dụ sau, hàm không được ẩn danh nhưng trên thực tế được gọi là 'myFunc'. Nguyên mẫu của nó có thể được mở rộng như bất kỳ lớp JS nào.

let myFunc = function() {}; 

:)

+0

Tại sao bạn muốn loại trừ các chức năng ẩn danh? Họ là những người xây dựng được. – Taurus

+0

Tôi thích 'Object.hasOwnProperty (" prototype ")' cho hàm này. BTW, trong khi hầu hết các structables có '.prototype', một số hàm như ràng buộc thì không, nhưng chúng vẫn có thể xây dựng được. Điều này là do khả năng xây dựng không có mối quan hệ trực tiếp với '.prototype', tất cả đều phải làm với [' [[construct]] '] nội bộ (https://stackoverflow.com/questions/21874128/construct-internal- phương thức). _Vì vậy, trong khi đây là một giải pháp tốt cho nhiều trường hợp, nó không phải là hoàn toàn chống đạn (như bạn đã lưu ý) ._ – Taurus

2

Bạn đang tìm kiếm nếu một hàm có một phương pháp nội [[Construct]].Phương pháp nội IsConstructor chi tiết các bước:

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. 

Bây giờ chúng ta cần phải tìm nơi IsConstructor được sử dụng, nhưng [[Construct]] không được gọi (thường là do Construct phương pháp nội bộ.)

Tôi thấy rằng nó được sử dụng trong các chức năng newTarget (new.target trong js), mà ca n được sử dụng với Reflect.construct:

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

(tôi có thể sử dụng bất cứ điều gì thực sự, như Reflect.construct(Array, [], f);, nhưng String là đầu tiên)

nào mang lại kết quả như sau:

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

< lưu ý >

Giá trị duy nhất mà tôi tìm thấy nó không hoạt động là Symbol, trong đó, mặc dù new Symbol ném một số TypeError: Symbol is not a constructor trong Firefox, is_constructor(Symbol) === true. Đây là kỹ thuật câu trả lời đúng, như Symbolkhông có một phương pháp nội [[Construct]] (Có nghĩa là nó cũng có thể được subclassed), nhưng sử dụng new hoặc super là đặc biệt cased cho Symbol để ném một lỗi (Vì vậy, Symbol là một constructor , các thông báo lỗi là sai, nó chỉ có thể không được sử dụng như một.) Bạn chỉ có thể thêm if (f === Symbol) return false; đến đầu mặc dù.

Cùng cho một cái gì đó như thế này:

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. 

Vì vậy, các ý định của hàm trở thành một nhà xây dựng không thể được gotton như thế này (Cho đến somthing như Symbol.is_constructor hoặc một số lá cờ khác được thêm vào).

</note >

+1

Đây là thiên tài! –

2

Với ES6 + Proxy, người ta có thể kiểm tra cho [[Construct]] mà không thực sự gọi các nhà xây dựng. Đây là một đoạn mã:

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

Nếu mục được chuyển không phải là đối tượng, thì hàm tạo Proxy sẽ phát sinh lỗi. Nếu nó không phải là một đối tượng có thể xây dựng, thì new sẽ phát ra lỗi. Nhưng nếu đó là một đối tượng có thể xây dựng, thì nó trả về đối tượng handler mà không cần gọi hàm tạo của nó, mà sau đó không được chú ý vào true.

Như bạn có thể mong đợi, Symbol vẫn được coi là một hàm tạo. Đó là bởi vì nó là, và việc thực hiện chỉ đơn thuần là ném một lỗi khi [[Construct]] được gọi. Đây có thể là trường hợp trên BẤT CỨ chức năng do người dùng xác định gây ra lỗi khi tồn tại new.target, do đó, có vẻ như không phải đặc biệt loại bỏ nó như một kiểm tra bổ sung, nhưng vui lòng làm như vậy nếu bạn thấy điều đó hữu ích.

Các vấn đề liên quan