关于javascript:调用没有括号的函数

Invoking a function without parentheses

今天有人告诉我可以调用一个没有括号的函数。我能想到的唯一方法就是使用像applycall这样的函数。

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

但这些都需要在applycall上加括号,这样我们就可以在1号方格处了。我还考虑了将函数传递给某种类型的事件处理程序(如setTimeout的想法:

1
setTimeout(f, 500);

但是问题变成了"你如何在没有括号的情况下调用setTimeout"?

那么这个谜语的答案是什么呢?如何在不使用括号的情况下调用javascript中的函数?


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

假设您定义了此函数:

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

然后,下面用一些方法调用greet,不带括号:

1。作为建造师

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

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

来自new操作器上的MDN:

Syntax

1
new constructor[([arguments])]

号2。作为toStringvalueOf的实施

toStringvalueOf是特殊的方法:当需要转换时,会隐式调用它们:

1
2
3
4
5
6
7
var obj = {
    toString: function() {
         return 'hello';
    }
}

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

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

1
'' + { toString: greet };

或与valueOf一起:

1
+{ valueOf: greet };

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

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

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

一旦你做到了,你就可以写:

1
+greet;

尽管行下面有括号,但实际触发调用没有括号。在博客"在javascript中调用方法,而不真正调用它们"中了解更多有关这方面的信息。

三。作为发电机

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

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

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

然后我们称之为无括号:

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

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

最后一条语句调用函数,但也可以使用析构函数来完成:

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

或者是for ... of结构,但它有自己的括号:

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

请注意,您也可以使用原始的greet功能执行上述操作,但在执行greet之后(在ff和chrome上测试),它将在过程中触发异常。您可以使用try...catch块来管理异常。

4。作为吸气剂

@Jehna1对此有一个完整的答案,所以请相信他。这里有一种方法可以在全局范围内少调用函数括号,避免使用不推荐使用的__defineGetter__方法。它使用Object.defineProperty代替。

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

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

然后:

1
greet_get;

用您的全局对象替换window

您可以调用原始的greet函数,而不必在全局对象上留下这样的跟踪:

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

但有人可能会说我们这里确实有括号(尽管它们不涉及实际调用)。

5。作为标记函数

使用ES6,可以使用以下语法调用传递模板文本的函数:

1
greet``;

请参见"标记模板文本"。

6。作为代理处理程序

在ES6中,可以定义代理:

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

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

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

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

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

1 in proxy; // triggers greet


最简单的方法是使用new操作符:

1
2
3
4
5
function f() {
  alert('hello');
}

new f;

虽然这是非正统和非自然的,但它是有效的,是完全合法的。

如果不使用参数,new运算符不需要括号。


你可以使用getter和setter。

1
2
3
4
5
var h = {
  get ello () {
    alert("World");
  }
}

运行此脚本时只使用:

1
h.ello  // Fires up alert"world"

编辑:

我们甚至可以争论!

1
2
3
4
5
6
7
var h = {
  set ello (what) {
    alert("Hello" + what);
  }
}

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

编辑2:

还可以定义不带括号的全局函数:

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

有了论据:

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

免责声明:

正如@monkeyzeus所说:在生产中永远不要使用这段代码,无论您的意图有多好。


下面是一个特定情况的例子:

1
window.onload = funcRef;

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

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


如果我们接受横向思维的方法,那么在浏览器中有几个API可以用来执行任意的javascript,包括调用函数,而不使用任何实际的括号字符。

1。locationjavascript:协议:

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

工作示例:

1
location='javascript:alert\x281\x29'

虽然从技术上讲,一旦代码被评估,\x28\x29仍然是圆括号,但实际的()字符不会出现。括号在一个javascript字符串中转义,该字符串在赋值时进行计算。

2。onerroreval

同样,根据浏览器的不同,我们可以通过将全局onerror设置为eval,并抛出将字符串化为有效javascript的东西来滥用它。这一点比较棘手,因为浏览器在这种行为中不一致,但下面是Chrome的一个例子。

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

1
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来防止这种代码也是一个好主意。


在ES6中,有所谓的标记模板文本。

例如:

1
2
3
4
5
function foo(val) {
    console.log(val);
}

foo`Tagged Template Literals`;