调用不带括号的函数

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

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

但是这些需要在applycall上加上括号,让我们回到原点。我还考虑了将函数传递给某种事件处理程序的想法,例如setTimeout:

setTimeout(f, 500);

但接下来的问题变成了“如何在不带括号的情况下调用setTimeout ?”

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

95806 次浏览

下面是用于特定情况的例子:

window.onload = funcRef;

虽然该语句实际上不是调用,但会导致未来的调用

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

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

让我们假设你已经定义了这个函数:

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

下面是不带括号调用greet的方法:

1. 作为构造函数

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

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

new操作符上的MDN:

语法

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开始),所以你也可以实现方法:

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

2.b在函数原型中重写valueOf

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

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函数以及,但它会触发一个异常的进程, greet已被执行(在FF和Chrome上测试)。你可以使用try...catch块来管理异常。

4. 作为Getter

@jehna1对此有完整的答案,所以给他信任吧。这里有一种在全局作用域中调用parentheses-less函数的方法,避免使用弃用__defineGetter__方法。它使用Object.defineProperty代替。

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

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

然后:

greet_get;

window替换为全局对象。

你可以调用原来的greet函数,而不会在全局对象上留下跟踪,如下所示:

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

但是有人可能会说这里确实有括号(尽管在实际调用中没有涉及到它们)。

5. As标签功能

从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

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

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


new f;

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

如果没有使用参数,new操作符不需要括号。

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

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

1. locationjavascript:协议:

其中一种技术是在location赋值时滥用javascript:协议。

工作的例子:

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

Although technically \x28 and \x29 are still parenthesis once the code is evaluated, the actual ( and ) character does not appear. The parentheses are escaped in a string of JavaScript which gets evaluated on assignment.


2. onerror and eval:

Similarly, depending on the browser we can abuse the global onerror, by setting it to eval, and throwing something that will stringify to valid JavaScript. This one is trickier, because browsers are inconsistent in this behavior, but here's an example for Chrome.

Working example for Chrome (not Firefox, others untested):

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

在ES6中,你有所谓的带标签的模板文字

例如:

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


foo`Tagged Template Literals`;

这是另一个例子,我传入函数1然后传入无参数的函数,它被称为。

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

可以使用匿名函数。 它只在使用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中的一个bug。

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

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