调用不带括号的函数

IT技术 javascript
2021-02-01 15:40:42

今天有人告诉我,可以调用不带括号的函数。我能想到的唯一方法是使用像applyor 之类的函数call

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

但是这些需要括号applycall让我们保持在第一个位置。我还考虑了将函数传递给某种事件处理程序的想法,例如setTimeout

setTimeout(f, 500);

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

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

6个回答

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

假设你定义了这个函数:

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

那么这里遵循一些greet不带括号的调用方式

1. 作为构造函数

有了new你可以调用一个函数不使用括号:

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

MDN的newoprator

句法

new constructor[([arguments])]

2. 作为toStringvalueOf实施

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

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.bvalueOf函数原型中的覆盖

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

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

完成后,您可以编写:

+greet;

尽管后面涉及括号,但实际的触发调用没有括号。在博客“在 JavaScript 中调用方法,而不是真正调用它们”中查看更多相关信息

3. 作为发电机

您可以定义一个生成器函数(with *),它返回一个iterator您可以使用扩展语法语法调用它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函数执行上述操作,但它会在执行 在过程中触发异常greet(在 FF 和 Chrome 上测试)。您可以使用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
在这种情况下,函数不会被执行,而是传递给调用者。
2021-03-18 15:40:42
@trincot 另一种方法将很快与管道运营商成为可能'1' |> alert
2021-03-24 15:40:42
您通过调用 eval 使用了方括号 :)
2021-03-25 15:40:42
你忘记了 ES6: func``;
2021-04-05 15:40:42
这是一个非常有趣的方法,使用valueOf.
2021-04-09 15:40:42

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

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

new f;

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

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

更正,最简单的方法是在前面添加一个强制计算函数表达式的操作数。!f结果相同,无需实例化构造函数的实例(这需要创建新的 this 上下文)。它的性能要好得多,并在许多流行的库(如 Bootstrap)中使用。
2021-03-31 15:40:42
在此类库中添加感叹号或任何其他单字符一元运算符 ( +, -, ~) 的目的是在不需要返回值的库的最小化版本中保存一个字节。(即!function(){}())通常的自调用语法是(function(){})()(不幸的是语法function(){}()不是跨浏览器的)由于最顶层的自调用函数通常没有返回值,因此它通常是这种字节保存技术的目标
2021-04-04 15:40:42
@THEtheChad - 评估表达式与执行函数不同,但如果您有不同的(更好?更令人惊讶?)调用(导致执行)函数的方法 - 发布答案!
2021-04-11 15:40:42
@THEtheChad 这是真的吗?我只是在 Chrome 中尝试过,但没有执行函数。function foo() { alert("Foo!") };例如:!foo退货false
2021-04-11 15:40:42

您可以使用 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 所说:无论您的意图多么好,您都永远不要在生产中使用这段代码。

@MonkeyZeus 哈哈,是的!为可以从 Google 找到的未来编码人员添加了免责声明
2021-03-14 15:40:42
严格来说,“我是一个挥舞着斧头的疯子,有幸接管你的代码,因为你已经转向更大更好的东西;但我知道你住在哪里”。我真的希望这不是正常的哈哈。在您维护的插件内部使用它,可以接受。编写业务逻辑代码,请稍等,我在磨砺我的斧头:)
2021-03-23 15:40:42
其实这个免责声明太苛刻了。“getter 作为函数调用”有合法的用例。事实上,它在一个非常成功的图书馆中被广泛使用。(你想要一个提示吗??:-)
2021-03-23 15:40:42
请注意,__defineGetter__已弃用。使用Object.defineProperty来代替。
2021-03-26 15:40:42
@MonkeyZeus - 正如我在评论中明确写的那样 - 这种函数调用风格正在一个非常成功的公共库中广泛和有意地用作 API 本身,而不是在内部使用。
2021-04-09 15:40:42

以下是针对特定情况示例

window.onload = funcRef;

尽管该语句实际上并未调用,但会导致将来的调用

但是,我认为灰色区域可能适合这样的谜语:)

@zzzzBov,“虽然经常使用 JavaScript 访问 DOM,但它不是 JavaScript 语言的一部分。其他语言也可以访问它。” . 例如,FireFox 使用 XPIDL 和 XPCOM 来实现 DOM。你指定的回调函数的调用是在 JavaScript 中实现的,这并不是不言而喻的。DOM 不像其他 JavaScript 库那样是一个 API。
2021-03-17 15:40:42
@Amit,DOM 是浏览器 JS 环境的一部分,它仍然是 JavaScript。
2021-03-18 15:40:42
@zzzzBov 并非所有 ECMAScript 都在 Web 浏览器中运行。或者您是使用“JavaScript”来特指将 ES 与 HTML DOM 相结合而不是 Node 的人吗?
2021-03-18 15:40:42
@DamianYerrick,我认为 DOM 是在某些 JS 环境中可用的库,这并不会使它比在某些环境中可用的下划线更少的 JavaScript。它仍然是 JavaScript,只是不是 ECMAScript 规范中指定的部分。
2021-03-19 15:40:42
这很好,但那不是 JavaScript,而是 DOM。
2021-03-25 15:40:42

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

1.locationjavascript:协议:

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

工作示例:

location='javascript:alert\x281\x29'

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


2.onerroreval

同样,根据浏览器,我们可以滥用 global onerror,将其设置为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,并用有效载荷替换 test 。


综上所述:

这样的代码真的很糟糕永远不应该使用,但有时会用于 XSS 攻击,所以这个故事的寓意是不要依赖过滤括号来防止 XSS。使用CSP来防止此类代码也是一个好主意。

可以说\x28\x29仍然是括号。'\x28' === '('.
2021-03-18 15:40:42
@ZenSpitz 是的,但eval通常需要括号,因此这个过滤器逃避黑客。
2021-03-22 15:40:42
@trincot 这是我在答案中涵盖的一点,这是一种横向思维方法,展示了可以这样做的原因。我已经在答案中说得更清楚了。
2021-03-25 15:40:42
此外,无论谁投票删除此内容,请查看删除答案的指南
2021-03-26 15:40:42
eval与在不实际使用括号字符的情况下使用和编写字符串是否相同
2021-04-10 15:40:42