调用不带括号的函数


275

今天有人告诉我,可以不带括号地调用一个函数。我能想到的唯一方法就是使用类似apply或的函数call

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

但是,这些都需要用括号括起来,apply然后call放在第一个平方上。我还考虑了将函数传递给某种事件处理程序的想法,例如setTimeout

setTimeout(f, 500);

但是,问题就变成了“如何在setTimeout没有括号的情况下调用?”

那么这个谜题的解决方案是什么?如何在不使用括号的情况下调用Javascript中的函数?


59
不想粗鲁,但我必须问:为什么?
jehna1'3

36
@ jehna1因为我回答的问题是如何调用函数,并在我说最后需要括号时得到了纠正。
Mike Cluck

3
惊人!我没想到这个问题会有那么大的吸引力。。也许我应该
阿米特

5
这种问题让我很伤心。这种虐待和剥削可能带来什么好处?我想知道一个有效的用例,<insert any solution>从客观上讲,它比更好或更有用f()
谢谢您

5
@Aaron:您说没有正当理由,但是,正如Alexander O'Mara的答案所指出的那样,人们可能正在考虑以下问题:删除方括号是否足以阻止代码执行-或混淆的Java竞争如何?当然,在尝试编写可维护的代码时这是荒谬的目标,但这是一个有趣的问题。
PJTraill '16

Answers:


429

有几种不带括号的函数调用方法。

假设您已定义此函数:

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

然后,请按照以下方法在greet不带括号的情况下进行调用:

1.作为建设者

使用new可以调用不带括号的函数:

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

MDN的newoprator

句法

new constructor[([arguments])]

2.作为toStringvalueOf实施

toString并且valueOf是特殊的方法:在需要进行转换时会隐式调用它们:

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

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

您可以(ab)使用此模式调用greet而不带括号:

'' + { toString: greet };

或搭配valueOf

+{ valueOf: greet };

valueOf并且toString实际上是从@@ toPrimitive方法调用的(自ES6起),因此您也可以实现方法:

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

2.b valueOf函数原型中的覆盖

您可以采用先前的想法来覆盖原型valueOf上的方法:Function

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

完成此操作后,您可以编写:

+greet;

尽管行中包含括号,但实际的触发调用没有括号。在博客“没有真正调用它们的情况下用JavaScript调用方法”中了解更多信息。

3.作为发电机

您可以定义一个生成器函数(使用*),该函数返回一个迭代器。您可以使用传播语法语法来调用它for...of

首先,我们需要原始greet函数的生成器变体:

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

然后,通过定义@@ iterator方法,将其命名为无括号:

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

通常,生成器在yield某个地方会有一个关键字,但是不需要调用该函数。

最后一条语句调用该函数,但是也可以通过解构来完成:

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

for ... of结构,但具有自己的括号:

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

请注意,您也可以使用原始greet功能执行上述操作,但是在执行(在FF和Chrome上测试) ,它会在进程中触发异常greet。您可以使用try...catch块来管理异常。

4.作为吸气剂

@ jehna1对此有完整的答案,请给他功劳。这是在全局范围调用无括号的函数的一种方法,从而避免了不建议使用的__defineGetter__方法。它Object.defineProperty改为使用。

我们需要为此创建原始greet函数的变体:

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

然后:

greet_get;

替换window为您的全局对象。

您可以调用原始greet函数而无需在全局对象上留下这样的痕迹:

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

但是有人可能会说我们在这里确实有括号(尽管它们没有参与实际的调用)。

5.作为标签功能

从ES6开始,您可以使用以下语法调用将其传递为模板文字的函数:

greet``;

请参阅“带标签的模板文字”

6.作为代理处理程序

从ES6开始,您可以定义代理

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

然后读取任何属性值将调用greet

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

这有很多变化。再举一个例子:

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

1 in proxy; // triggers greet

7.作为实例检查器

定义后,instanceof运算符将@@hasInstance在第二个操作数上执行该方法:

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

1
使用,这是一种非常有趣的方法valueOf
Mike Cluck

4
您忘记了ES6:func``;
Ismael Miguel

1
您通过调用eval :)使用了方括号
Trincot

1
在那种情况下,该函数不执行,而是传递给调用者。
Trincot '17

1
@trincot 管道运算符很快就会有另一种方法:'1' |> alert
deniro

224

最简单的方法是与new运算符:

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

new f;

尽管这是不合常规和不自然的,但它确实有效并且完全合法。

new如果不使用参数,运营商不需要括号。


6
校正,最简单的方法是在操作数前添加强制对函数表达式求值的操作数。!f结果相同,而没有实例化构造函数的实例(这需要创建一个新的this上下文)。它的性能要好得多,并在许多流行的库(例如Bootstrap)中使用。
THETHE乍得

6
@THETHEChad-计算表达式与执行函数并不相同,但是如果您使用不同的(更复杂?更令人惊讶?)调用函数(导致执行)的方法,请发布答案!
阿米特

前面加上一个感叹号,或任何其他单个字符元运算符(点+-~在这样的库),是在其中不需要的返回值的库的最小化版本保存一个字节。(即!function(){}())通常的自调用语法是(function(){})()(不幸的function(){}()是,该语法不是跨浏览器的)由于最顶层的自调用函数通常没有任何返回值,因此通常是此字节保存技术的目标
Ultimater

1
@THEtheChad这是否成立?我只是在Chrome中尝试过,但没有执行功能。function foo() { alert("Foo!") };例如:!foo返回false
Glenn'devalias'17

95

您可以使用getter和setter。

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

仅使用以下命令运行此脚本:

h.ello  // Fires up alert "world"

编辑:

我们甚至可以争论!

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

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

编辑2:

您还可以定义可以在不带括号的情况下运行的全局函数:

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

并带有参数:

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

免责声明:

正如@MonkeyZeus所说:无论您的意图多么好,您都绝不能在生产中使用这段代码。


9
从POV严格地说,“我是挥舞着斧头的疯子,很荣幸能够接管您的代码,因为您已经着手开发更大更好的东西;但我知道您的住所”。我真的希望这不是正常的笑。在您维护的插件内部使用它是可以接受的。编写业务逻辑代码,请在我削尖斧头时按住:)
MonkeyZeus

3
@MonkeyZeus哈哈,是的!为将来的编码人员添加了免责声明,可以从Google找到该免责声明
jehna1,2016年

实际上,此免责声明过于苛刻。有合法的用例“将getter作为函数调用”。实际上,它已经在非常成功的库中广泛使用。(您想要一个提示吗?:-)
Amit

1
请注意,__defineGetter__已弃用。使用Object.defineProperty代替。
Felix Kling

2
@MonkeyZeus-正如我在评论中明确指出的那样- 在非常成功的公共库中,这种风格的函数调用广泛且有意地用作API本身,而不是内部使用。
阿米特

23

这是针对特定情况的示例

window.onload = funcRef;

尽管该语句实际上不是在调用,但是会导致将来的调用

但是,我认为灰色区域对于这样的谜语可能还可以:)


6
很好,但这不是JavaScript,而是DOM。
阿米特

6
@ Amit,DOM是浏览器JS环境(仍是JavaScript)的一部分。
zzzzBov

3
@zzzzBov并非所有ECMAScript都在Web浏览器中运行。还是在使用“ JavaScript”来专门指代将ES与HTML DOM(而不是Node)结合在一起的人之中?
Damian Yerrick '16

9
@DamianYerrick,我认为DOM是在某些JS环境中可用的库,与在某些环境中可用的下划线一样,JavaScript的使用也没有减少。它仍然是JavaScript,并不是ECMAScript规范中指定的一部分。
zzzzBov

3
@zzzzBov,“尽管经常使用JavaScript访问DOM,但它不是JavaScript语言的一部分。其他语言也可以访问它。” 。例如,FireFox使用XPIDL和XPCOM进行DOM实现。用JavaScript实现对指定回调函数的调用并不是很明显。DOM不是像其他JavaScript库一样的API。
Trincot

17

如果我们接受横向思考方法,则在浏览器中有几个API,我们可以滥用它们来执行任意JavaScript,包括调用函数,而没有任何实际的括号字符。

1. locationjavascript:协议:

一种这样的技术是滥用javascript:协议location分配。

工作示例:

location='javascript:alert\x281\x29'

尽管从技术上讲 \x28\x29在对代码进行评估后仍然是括号,但实际()字符都不会出现。括号在JavaScript字符串中转义,该字符串在赋值时进行评估。


2. onerroreval

同样,根据浏览器的不同,我们可以onerror通过将global 设置为eval,然后抛出将被字符串化为有效JavaScript的内容。这很棘手,因为浏览器的行为与此不一致,但这是Chrome的一个示例。

Chrome的工作示例(不是Firefox,其他未经测试):

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

这在Chrome中有效,因为throw'test'它将'Uncaught test'作为第一个参数传递给onerror,这几乎是有效的JavaScript。如果我们改为这样做throw';test',它将通过'Uncaught ;test'。现在我们有了有效的JavaScript!只需定义Uncaught,然后用有效载荷替换测试即可。


结论:

这样的代码确实糟糕透顶绝不应该使用,但有时会在XSS攻击中使用,因此,故事的寓意不是依靠过滤括号来防止XSS。使用CSP防止此类代码也是一个好主意。


eval与在不实际使用括号字符的情况下使用和写入字符串不同吗?
Zev Spitz

@ZenSpitz是的,但eval通常需要使用括号,因此此逃避过滤器的行为是合理的。
亚历山大·奥玛拉

5
可以说\x28\x29仍然是括号。'\x28' === '('
Trincot

@trincot,这是我在答案中提到的要点,这是一种横向思考的方法,它展示了可以这样做的原因。我已经在答案中更清楚了。
亚历山大·奥玛拉

另外,投票赞成删除此答案的人,请查看删除答案的准则
亚历山大·奥玛拉

1

在ES6中,您具有所谓的标记模板文字

例如:

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

foo`Tagged Template Literals`;


3
确实,这就是我在您的1.5年前发布的答案中……不确定为什么它应该得到一个新的答案?
特里科特

2
因为其他想要立即实现相同功能的人可以使用新答案。
mehta-rohan

3
@ mehta-rohan,我不明白那条评论。如果这有意义,那么为什么不一遍又一遍地重复相同的答案呢?我看不出这有什么用。
特里科特,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.