如何检查函数是否使用call/apply进行调用

8
function foo(){
    console.log('foo', this);
}

foo();
foo.call({ bar: 1 });
foo.apply([{ bar: 1 }]);

有没有办法知道是否使用正常调用或 call/apply 的方式调用了 foo()http://jsfiddle.net/H4Awm/1/

1
我觉得你是指的 foo.call(this, { bar: 1 }));foo.apply(this, [{ bar: 1 }])); 对吧? - Matteo Tassinari
6个回答

3
不,你无法检测到函数是从call/apply调用还是正常调用。它们并不是神奇的存在,它们所做的只是设置参数和this值。当涉及到未定义/未声明的值时,有一个微妙的差异,但仅此而已。在ES5中,所有.apply.call所做的都是:返回调用func的[[Call]]内部方法的结果,将thisArg作为this值提供,并将argList作为参数列表提供。它正确地设置了内部的caller属性,因此像this这样的简单方法将不起作用。

2
除非重新定义 Function.prototype.call Function.prototype.apply (并将另一个参数传递给函数),否则没有办法这样做 - 实际的内部机制( [[Call]] 内部函数)不提供任何方法来表示使用()调用它。

比较一般函数调用 Function.prototype.apply 的规范 - 每个调用函数的内部代码方式完全相同,并且没有设置任何外部属性,能够告诉您是否使用了该函数。

请参见内部函数 [[Call]] 的规范:

13.2.1 [[Call]]

当使用函数对象F的值的[[FormalParameters]]内部属性、传递的参数列表args和this值在10.4.3中描述的方式为函数代码建立新的执行上下文时,将调用函数对象F的 [[Call]] 内部方法,执行以下步骤:

  1. 让funcCtx成为使用F的[[FormalParameters]]内部属性的值、传递的参数列表args和this值为函数代码建立新的执行上下文的结果。
  2. 让result成为计算F的[[Code]]内部属性的值的FunctionBody的结果。如果F没有[[Code]]内部属性或其值为空的FunctionBody,则result为(normal,undefined,empty)。
  3. 退出执行上下文funcCtx,恢复先前的执行上下文。
  4. 如果result.type是throw,则抛出result.value。
  5. 如果result.type是return,则返回result.value。
  6. 否则,result.type必须是正常的。返回未定义。

没有规定可以从是否使用call/apply调用函数来更改其运行 - 唯一更改其所做事情的是函数本身的参数以及函数内部的this的意义。


除非你劫持Function.prototype.call - 我很想看到这种实现方式 - 我相信它会比你预期的更加困难,因为.call也被内部使用了。 - Benjamin Gruenbaum
@BenjaminGruenbaum:我指的只是将函数重新定义为一个包装器,我应该让这更加清晰明了。 - Qantas 94 Heavy

1
我希望这能解决你的问题:

function foo(){
    console.log('foo', this);

    if (typeof this.length === "number") {
        //function has been apply
    } else {
        //function has been call
    }
}

foo();
foo.call({ bar: 1 });
foo.apply([{ bar: 1 }]);

问题在于你为 apply 传递了一个数组,而为 call 传递了一个对象。如果你同时为两者都传递一个对象会发生什么? - Qantas 94 Heavy

1
尝试像这样做一些事情:

function foo(){

    console.log('foo', this);
    console.log( arguments );
}
Function.prototype._apply =  Function.prototype.apply;
Function.prototype.apply = function(ths,args){
    args.unshift('apply');
    this._apply(ths,args);
};

foo();
foo.call(this, { bar: 1 });
foo.apply(this, [{ bar: 1 }]);

1
脏、肮脏的黑客技巧:
function foo() {
    var isNatural = !/foo\.(call|apply)/.test("" + foo.caller)
    console.log(isNatural ? "foo()" : "foo.{call,apply}()")
}

function caller1() {
    foo()
}
function caller2() {
    foo.call(null, { bar: 1 })
}
function caller3() {
    foo.apply(null, [{ bar: 1 }])
}

caller1()
caller2()
caller3()

这只是一些思考的食物,不要在生产中使用它。


-1

我想不出你需要检查这个的原因,但是你可以通过比较this === window来进行检查,因为这是默认作用域(假设基于浏览器的Javascript),但是这可以通过简单地像这样调用foo来伪造:foo.call(window),这基本上就是通常调用foo()所做的。

对于其他对象或原型的函数属性,使用window也可能不起作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接