Gọi một hàm không có dấu ngoặc đơn


275

Hôm nay tôi đã nói rằng có thể gọi một hàm mà không có dấu ngoặc đơn. Cách duy nhất tôi có thể nghĩ là sử dụng các hàm như applyhoặc call.

f.apply(this);
f.call(this);

Nhưng những điều này đòi hỏi dấu ngoặc đơn trên applycallđể chúng ta ở một hình vuông. Tôi cũng đã xem xét ý tưởng chuyển chức năng cho một số loại xử lý sự kiện như setTimeout:

setTimeout(f, 500);

Nhưng sau đó, câu hỏi trở thành "làm thế nào để bạn gọi setTimeoutmà không có dấu ngoặc đơn?"

Vậy giải pháp cho câu đố này là gì? Làm thế nào bạn có thể gọi một hàm trong Javascript mà không cần sử dụng dấu ngoặc đơn?


59
Không cố tỏ ra thô lỗ, nhưng tôi phải hỏi: Tại sao?
jehna1

36
@ jehna1 Bởi vì tôi đã trả lời một câu hỏi về cách các hàm được gọi và đã được sửa khi tôi nói rằng chúng yêu cầu dấu ngoặc đơn ở cuối.
Mike Cluck

3
Kinh ngạc! Tôi không tưởng tượng được câu hỏi này sẽ có sức kéo đến thế .. Có lẽ tôi nên tự hỏi nó :-)
Amit

5
Đây là loại câu hỏi làm tan vỡ trái tim tôi. Điều gì tốt có thể có thể đến thông qua lạm dụng và khai thác như vậy? Tôi muốn biết một trường hợp sử dụng hợp lệ trong đó <insert any solution>khách quan tốt hơn hoặc hữu ích hơn f().
Cảm ơn bạn

5
@Aaron: bạn nói không có lý do chính đáng, nhưng, như câu trả lời Alexander O'Mara của chỉ ra, người ta có thể được xem xét câu hỏi liệu loại bỏ các dấu ngoặc là đủ để ngăn chặn thực thi mã - hoặc những gì về một cuộc thi Javascript obfuscated? Tất nhiên đó là một mục tiêu vô lý khi cố gắng viết mã có thể duy trì, nhưng đó là một câu hỏi thú vị.
PJTraill

Câu trả lời:


428

Có một số cách khác nhau để gọi một hàm mà không có dấu ngoặc đơn.

Giả sử bạn có chức năng này được xác định:

function greet() {
    console.log('hello');
}

Sau đó, ở đây làm theo một số cách để gọi greetmà không có dấu ngoặc đơn:

1. Là người xây dựng

Với newbạn có thể gọi một hàm mà không có dấu ngoặc đơn:

new greet; // parentheses are optional in this construct.

Từ MDN trên newoprator :

Cú pháp

new constructor[([arguments])]

2. Như toStringhoặc valueOfthực hiện

toStringvalueOflà các phương thức đặc biệt: chúng được gọi ngầm khi cần chuyển đổi:

var obj = {
    toString: function() {
         return 'hello';
    }
}

'' + obj; // concatenation forces cast to string and call to toString.

Bạn có thể (ab) sử dụng mẫu này để gọi greetmà không có dấu ngoặc đơn:

'' + { toString: greet };

Hoặc với valueOf:

+{ valueOf: greet };

valueOftoStringtrên thực tế được gọi từ phương thức @@ toPrimitive (kể từ ES6), và do đó bạn cũng có thể thực hiện phương thức đó :

+{ [Symbol.toPrimitive]: greet }
"" + { [Symbol.toPrimitive]: greet }

2.b Ghi đè valueOftrong Nguyên mẫu Chức năng

Bạn có thể lấy ý tưởng trước đó để ghi đè valueOfphương thức trên Functionnguyên mẫu :

Function.prototype.valueOf = function() {
    this.call(this);
    // Optional improvement: avoid `NaN` issues when used in expressions.
    return 0; 
};

Một khi bạn đã làm điều đó, bạn có thể viết:

+greet;

Và mặc dù có các dấu ngoặc đơn liên quan đến dòng xuống, lời gọi kích hoạt thực tế không có dấu ngoặc đơn. Xem thêm về điều này trong blog "Phương thức gọi bằng JavaScript, mà không thực sự gọi chúng"

3. Là máy phát điện

Bạn có thể định nghĩa một hàm tạo (với *), trả về một trình vòng lặp . Bạn có thể gọi nó bằng cú pháp lây lan hoặc với for...ofcú pháp.

Đầu tiên chúng ta cần một biến thể của greethàm ban đầu :

function* greet_gen() {
    console.log('hello');
}

Và sau đó chúng ta gọi nó mà không có dấu ngoặc đơn bằng cách định nghĩa phương thức @@ iterator :

[...{ [Symbol.iterator]: greet_gen }];

Thông thường các trình tạo sẽ có một yieldtừ khóa ở đâu đó, nhưng hàm này không cần thiết để được gọi.

Câu lệnh cuối cùng gọi hàm, nhưng điều đó cũng có thể được thực hiện với hàm hủy :

[,] = { [Symbol.iterator]: greet_gen };

hoặc một for ... ofcấu trúc, nhưng nó có dấu ngoặc đơn của chính nó:

for ({} of { [Symbol.iterator]: greet_gen });

Lưu ý rằng bạn cũng có thể thực hiện các thao tác trên với greetchức năng ban đầu , nhưng nó sẽ kích hoạt một ngoại lệ trong quy trình, sau khi greet đã được thực thi (đã thử nghiệm trên FF và Chrome). Bạn có thể quản lý ngoại lệ với một try...catchkhối.

4. Là Getter

@ jehna1 có câu trả lời đầy đủ về vấn đề này, vì vậy hãy cho anh ta tín dụng. Đây là một cách để gọi một hàm ngoặc đơn trên phạm vi toàn cục, tránh phương thức không dùng nữa__defineGetter__ . Nó sử dụng Object.definePropertythay thế.

Chúng ta cần tạo một biến thể của greetchức năng ban đầu cho việc này:

Object.defineProperty(window, 'greet_get', { get: greet });

Và sau đó:

greet_get;

Thay thế windowbằng bất cứ đối tượng toàn cầu của bạn là gì.

Bạn có thể gọi greethàm ban đầu mà không để lại dấu vết trên đối tượng toàn cầu như thế này:

Object.defineProperty({}, 'greet', { get: greet }).greet;

Nhưng người ta có thể tranh luận rằng chúng ta có dấu ngoặc đơn ở đây (mặc dù chúng không liên quan đến việc gọi thực tế).

5. Chức năng thẻ

Vì ES6, bạn có thể gọi một hàm truyền cho nó một mẫu bằng chữ với cú pháp này:

greet``;

Xem "Tagged Template Literals" .

6. Là Trình xử lý Proxy

Kể từ ES6, bạn có thể xác định proxy :

var proxy = new Proxy({}, { get: greet } );

Và sau đó đọc bất kỳ giá trị tài sản sẽ gọi greet:

proxy._; // even if property not defined, it still triggers greet

Có nhiều biến thể của điều này. Một ví dụ nữa:

var proxy = new Proxy({}, { has: greet } );

1 in proxy; // triggers greet

7. Trình kiểm tra ví dụ

Các instanceofnhà điều hành thực hiện các @@hasInstancephương pháp trên các toán hạng thứ hai, khi đã xác định:

1 instanceof { [Symbol.hasInstance]: greet } // triggers greet

1
Đó là một cách tiếp cận rất thú vị bằng cách sử dụng valueOf.
Mike Cluck

4
Bạn đã quên ES6:func``;
Ismael Miguel

1
Bạn đã sử dụng dấu ngoặc bằng cách gọi eval :)
trincot

1
Trong trường hợp đó, hàm không được thực thi mà được truyền cho người gọi.
trincot

1
@trincot Một phương pháp khác sẽ sớm được thực hiện với nhà điều hành đường ống :'1' |> alert
deniro

224

Cách dễ nhất để làm điều đó là với newtoán tử:

function f() {
  alert('hello');
}

new f;

Mặc dù điều đó không chính thống và không tự nhiên, nhưng nó hoạt động và hoàn toàn hợp pháp.

Các newnhà điều hành không đòi hỏi ngoặc nếu không có thông số được sử dụng.


6
Sửa lỗi, cách dễ nhất để làm điều này là thêm vào một toán hạng buộc biểu thức hàm được ước tính. !fdẫn đến kết quả tương tự, mà không khởi tạo một thể hiện của hàm tạo (yêu cầu tạo bối cảnh mới này). Nó hiệu quả hơn nhiều và được sử dụng trong nhiều thư viện phổ biến (như Bootstrap).
THEtheChad 13/03/2016

6
@THEtheChad - đánh giá một biểu thức không giống như thực thi một hàm, nhưng nếu bạn có một phương thức khác (đẹp hơn? Đáng ngạc nhiên hơn?) Để gọi (gây ra sự thực thi) của hàm - hãy đăng câu trả lời!
Amit

Mục đích của prepending một dấu chấm than, hay bất cứ ký tự đơn điều hành unary khác ( +, -, ~) trong các thư viện như vậy, là để tiết kiệm một byte trong phiên bản thu nhỏ của thư viện nơi giá trị trả về là không cần thiết. (ví dụ !function(){}()) Cú pháp tự gọi thông thường là (function(){})()(không may là cú pháp function(){}()không phải là trình duyệt chéo) Vì chức năng tự gọi hàng đầu thường không có gì để trả về, nên nó thường là mục tiêu của kỹ thuật tiết kiệm byte này
Ultimater

1
@THEtheChad Điều này có đúng không? Tôi mới thử nó trong Chrome và không được thực thi chức năng. function foo() { alert("Foo!") };Ví dụ: !footrả vềfalse
Glenn 'devalias'

95

Bạn có thể sử dụng getters và setters.

var h = {
  get ello () {
    alert("World");
  }
}

Chạy kịch bản này chỉ với:

h.ello  // Fires up alert "world"

Biên tập:

Chúng tôi thậm chí có thể làm tranh luận!

var h = {
  set ello (what) {
    alert("Hello " + what);
  }
}

h.ello = "world" // Fires up alert "Hello world"

Chỉnh sửa 2:

Bạn cũng có thể xác định các hàm toàn cầu có thể chạy mà không cần dấu ngoặc đơn:

window.__defineGetter__("hello", function() { alert("world"); });
hello;  // Fires up alert "world"

Và với các đối số:

window.__defineSetter__("hello", function(what) { alert("Hello " + what); });
hello = "world";  // Fires up alert "Hello world"

Tuyên bố từ chối trách nhiệm:

Như @MonkeyZeus đã tuyên bố: Bạn sẽ không bao giờ sử dụng đoạn mã này trong sản xuất, bất kể ý định của bạn tốt đến đâu.


9
Nói một cách chính xác từ POV của "Tôi là một cái rìu cầm người điên có vinh dự chiếm lấy mã của bạn bởi vì bạn đã chuyển sang những thứ lớn hơn và tốt hơn; tôi biết bạn sống ở đâu". Tôi thực sự hy vọng điều này không bình thường lol. Sử dụng nó bên trong một plugin mà bạn duy trì, chấp nhận được. Viết mã số kinh doanh-logic, xin hãy giữ lấy trong khi tôi làm sắc nét rìu của tôi :)
MonkeyZeus

3
@MonkeyZeus haha, vâng! Thêm một từ chối trách nhiệm cho lập trình viên của tương lai có thể tìm thấy điều này từ Google
jehna1

Thật ra sự từ chối này quá khắc nghiệt. Có những trường hợp sử dụng hợp pháp cho "getter as a function call". Trong thực tế, nó được sử dụng rộng rãi trong một thư viện rất thành công. (bạn có muốn một gợi ý không ?? :-)
Amit

1
Lưu ý rằng __defineGetter__không được chấp nhận. Sử dụng Object.definePropertythay thế.
Felix Kling

2
@MonkeyZeus - như tôi đã viết rõ ràng trong bình luận - kiểu gọi chức năng này đang được sử dụng , rộng rãi và có chủ ý, trong một thư viện công cộng rất thành công như chính API, chứ không phải bên trong.
Amit

23

Đây là một ví dụ cho một tình huống cụ thể:

window.onload = funcRef;

Mặc dù tuyên bố đó không thực sự được viện dẫn nhưng sẽ dẫn đến một lời mời trong tương lai .

Nhưng, tôi cho rằng các khu vực màu xám có thể ổn đối với các câu đố như thế này :)


6
Điều đó thật tuyệt nhưng đó không phải là JavaScript, đó là DOM.
Amit

6
@Amit, DOM là một phần của môi trường JS của trình duyệt, vẫn là JavaScript.
zzzzBov

3
@zzzzBov Không phải tất cả ECMAScript đều chạy trong trình duyệt web. Hay bạn nằm trong số những người sử dụng "JavaScript" để đề cập cụ thể đến việc kết hợp ES với HTML DOM, trái ngược với Node?
Damian Yerrick

9
@DamianYerrick, tôi coi DOM là một thư viện có sẵn trong một số môi trường JS, điều này không làm cho nó có ít JavaScript hơn so với gạch dưới có sẵn trong một số môi trường. Nó vẫn là JavaScript, nó không phải là một phần được mô tả trong đặc tả ECMAScript.
zzzzBov

3
@zzzzBov, "Mặc dù DOM thường được truy cập bằng JavaScript, nhưng nó không phải là một phần của ngôn ngữ JavaScript. Nó cũng có thể được truy cập bởi các ngôn ngữ khác." . Ví dụ: FireFox sử dụng XPIDL và XPCOM để triển khai DOM. Không phải là quá rõ ràng rằng cuộc gọi của chức năng gọi lại được chỉ định của bạn được thực hiện trong JavaScript. DOM không phải là API như các thư viện JavaScript khác.
trincot

17

Nếu chúng ta chấp nhận cách tiếp cận tư duy bên, trong một trình duyệt, có một số API chúng ta có thể lạm dụng để thực thi JavaScript tùy ý, bao gồm gọi một hàm, mà không có bất kỳ ký tự dấu ngoặc đơn thực tế nào.

1. locationjavascript:giao thức:

Một kỹ thuật như vậy là lạm dụng javascript: giao thức trên locationbài tập.

Ví dụ làm việc:

location='javascript:alert\x281\x29'

Mặc dù về mặt kỹ thuật \x28\x29vẫn là dấu ngoặc đơn khi mã được đánh giá, thực tế () ký tự không xuất hiện. Các dấu ngoặc đơn được thoát trong một chuỗi JavaScript được đánh giá khi gán.


2. onerroreval:

Tương tự, tùy thuộc vào trình duyệt, chúng ta có thể lạm dụng toàn cầu onerror, bằng cách đặt nó evalvà ném thứ gì đó sẽ xâu chuỗi thành JavaScript hợp lệ. Điều này là khó khăn hơn, bởi vì các trình duyệt không nhất quán trong hành vi này, nhưng đây là một ví dụ cho Chrome.

Ví dụ hoạt động cho Chrome (không phải Firefox, những người khác chưa được kiểm tra):

window.onerror=eval;Uncaught=0;throw';alert\x281\x29';

Điều này hoạt động trong Chrome vì throw'test'sẽ chuyển 'Uncaught test'làm đối số đầu tiên onerror, gần như là JavaScript hợp lệ. Nếu chúng ta thay vào throw';test'đó sẽ vượt qua'Uncaught ;test' . Bây giờ chúng tôi có JavaScript hợp lệ! Chỉ cần xác định Uncaughtvà thay thế thử nghiệm với tải trọng.


Tóm lại là:

Mã như vậy thực sự khủng khiếpkhông bao giờ nên được sử dụng , nhưng đôi khi được sử dụng trong các cuộc tấn công XSS, vì vậy đạo đức của câu chuyện không dựa vào việc lọc dấu ngoặc đơn để ngăn XSS. Sử dụng CSP để ngăn chặn mã như vậy cũng sẽ là một ý tưởng tốt.


Đây không giống như sử dụng evalvà viết chuỗi mà không thực sự sử dụng các ký tự dấu ngoặc đơn.
Zev Spitz

@ZenSpitz Yep, nhưng evalthường yêu cầu dấu ngoặc đơn, do đó bộ lọc này trốn tránh hack.
Alexander O'Mara

5
Có thể cho rằng \x28\x29vẫn là dấu ngoặc đơn. '\x28' === '('.
trincot

@trincot Đây là một trong những điểm tôi đã nêu trong câu trả lời, đây là một cách tiếp cận tư duy bên trong đó thể hiện một lý do mà trong đó điều này có thể được thực hiện. Tôi đã làm cho nó rõ ràng hơn trong câu trả lời.
Alexander O'Mara

Ngoài ra, bất cứ ai bỏ phiếu để xóa điều này, xin vui lòng xem lại hướng dẫn để xóa câu trả lời .
Alexander O'Mara

1

Trong ES6, bạn có cái được gọi là Văn bản mẫu được gắn thẻ .

Ví dụ:

function foo(val) {
    console.log(val);
}

foo`Tagged Template Literals`;


3
Thật vậy, đây là trong câu trả lời tôi đã đăng 1,5 năm trước bạn ... không biết tại sao nó xứng đáng với câu trả lời mới?
trincot

2
bởi vì một số người khác, những người muốn thực hiện điều tương tự ngay bây giờ có thể sử dụng câu trả lời mới.
mehta-rohan

3
@ mehta-rohan, tôi không hiểu bình luận đó. Nếu điều này có ý nghĩa thì tại sao không lặp lại cùng một câu trả lời hơn và hơn? Tôi không thể thấy nó sẽ hữu ích như thế nào.
trincot
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.