javascript 调用不带括号的函数

cbeh67ev  于 2023-05-16  发布在  Java
关注(0)|答案(9)|浏览(98)

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

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

但是这些需要在applycall上加上括号,这让我们陷入了困境。我还考虑了将函数传递给某种事件处理程序(如setTimeout)的想法:

setTimeout(f, 500);

但是问题就变成了“如何在没有括号的情况下调用setTimeout?”“
那么这个谜语的答案是什么呢?如何在不使用括号的情况下调用JavaScript中的函数?

628mspwn

628mspwn1#

在ES6中,你有所谓的标记模板文字
例如:

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

foo`Tagged Template Literals`;
ufj5ltwl

ufj5ltwl2#

Array.constructor`alert\x28"invoke with whatever u want"\x29```;

因为Array.constructor是一个Function对象。当函数对象被调用时,它们返回一个函数,它的主体是它得到的参数。

szqfcxe2

szqfcxe23#

这是另一个例子,我把函数1传入then函数,没有parantheses,它被调用了。

function one() {
  console.log("one called");
}
function two() {
  return new Promise((resolve, reject) => {
    resolve();
  });
}
two().then(one);
m528fe3b

m528fe3b4#

可以使用匿名函数。它只能使用ES6之前的语法,但它仍然可以工作。
验证码:

//ES6+ (using const, still not using arrow functions)
const myFunc = (function(args){console.log("no parenthesis")})(args);

并像这样调用它:

myFunc;
// ^^^ no parenthesis in invocation, but you do have parenthesis in definition
// also note that the semicolon is optional if it is the only thing on the line

ES6之前:

var myNonES6Func = (function(args){console.log("Use var before ES6.")})(args);

然后像这样调用它

myNonES6Func;
// ^^^ same as the ES6 invocation.
const es6_func = (function() {
    alert("ES6!")
})();
var before_es6_func = (function() {
    alert("Before ES6.")
})();
const es6 = () => {
    es6_func
}

function before_es6() {
    before_es6_func
}
<button onclick="es6">ES6+</button>
<button onclick="before_es6">Before ES6</button>

注:现代浏览器似乎不再具有此功能(它在页面加载时调用自己,但当您正常调用它时,它也会工作,但您必须添加一些计数代码以防止它在页面加载时运行)我在Internet Explorer 11上测试了它,它似乎仍然可以工作,但Chrome,Firefox和Edge不工作,这可能只是IE中的一个错误。

ny6fqffe

ny6fqffe5#

有几种不同的方法可以调用不带括号的函数。
假设你已经定义了这个函数:

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

然后这里遵循一些不带括号调用greet的方法:

1.构造函数

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

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

MDN on the new oprator

语法

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

valueOftoString实际上是从@@toPrimitive方法(从ES6开始)调用的,所以你也可以实现 that 方法:

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

2.b函数原型中覆盖valueOf

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

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

一旦你做到了这一点,你可以写:

+greet;

尽管在代码的后面有括号,但实际的触发调用没有括号。有关此问题的更多信息,请参阅博客"Calling methods in JavaScript, without really calling them"

3.作为Generator

您可以定义一个generator function(使用*),它返回一个迭代器。您可以使用spread syntaxfor...of语法调用它。
首先,我们需要一个原始greet函数的生成器变体:

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

然后我们通过定义@@iterator方法来调用它,而不使用括号:

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

通常生成器在某处会有一个yield关键字,但调用函数时并不需要它。
最后一条语句调用函数,但也可以用destructuring来完成:

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

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

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

请注意,您也可以 * 使用原始的greet函数执行上述操作,但它会在执行 * greet后 * 触发异常(在FF和Chrome上测试)。您可以使用try...catch块管理异常。

4.作为Getter

@jehna1对此有一个完整的答案,所以给予他信用。下面是一种在全局范围内调用函数 * parentheseses-less * 的方法,避免了deprecated __defineGetter__方法。它使用Object.defineProperty代替。
我们需要创建一个原始greet函数的变体:

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

然后:

greet_get;

window替换为您的全局对象。
你可以调用原始的greet函数,而不像下面这样在全局对象上留下痕迹:

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

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

5.标签函数

从ES6开始,你可以用下面的语法调用一个传递template literal的函数:

greet``;

请参见“标记模板文字”。

6.代理处理器

从ES6开始,你可以定义一个proxy

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方法,定义如下:

q5iwbnjs

q5iwbnjs6#

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

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

new f;

虽然这是非正统和不自然的,它的工作,是完全法律的的。
如果不使用参数,则new运算符不需要括号。

uqxowvwt

uqxowvwt7#

你可以使用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"

编辑二:

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

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所说:永远不要在生产环境中使用这段代码,不管你的意图有多好。

1tuwyuhd

1tuwyuhd8#

下面是针对特定情况的example

window.onload = funcRef;

虽然该语句实际上不是调用,但会导致未来的调用
但是,我认为灰色区域可能适合这样的谜语:)

0lvr5msh

0lvr5msh9#

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

1. locationjavascript:协议:

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

示例:

location='javascript:alert\x281\x29'

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

2. onerroreval

类似地,根据浏览器的不同,我们可以滥用全局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替换为payload。

总结:

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

相关问题