JavaScript .apply 方法如何工作?

How does the JavaScript .apply method work?

我不是问如何使用它。我知道怎么用了。

但是,当 apply 调用调用它的函数时,它究竟是如何将参数数组传递到一个函数中的,而这个函数并不是为了将数组作为参数而编写的?它是否将其给定的参数数组与调用的函数组合 "arguments"?

我查看了 .apply 和 .call 的最新 ECMAscript 规范,但我并没有真正看到任何关于底层逻辑的内容。

欢迎任何解释。我是 JavaScript 的新手,想更好地了解引擎盖下发生的事情。我目前正在尝试自己重新创建一些基本功能,而这个给我带来了很多麻烦。

Function.prototype.apply 是 implementation-specific:它是在 JavaScript 引擎本身中实现的。真的没有办法复制它,因为当你调用它时它使用 C* API 告诉引擎调用你想调用的函数。用户代码无法操作内部 API,因此没有本机代码就无法实现 Function.prototype.apply


"Source code" of Function.prototype.apply 根据 V8 中的 Function.prototype.toString

* 假设一个用 C 编写的 JS 引擎。替换为任何语言 是用什么语言编写的。

来自spec.

我们必须获取 argArray 并创建参数对象伪数组。

基本上

Function.prototype.apply = function apply (thisArg, argArray) {
    var len = argArray.length;
    var n = ToUint32(len);
    var argList = []; // where this is a List not an array.
    var index = 0;
    var indexName;
    var nextArg;
    while (index < len) {
        indexName = ToString(index);
        nextArg = argArray[indexName];
        argList.push(nextArg);
        index++;
    }
    return this['[[Call]]'](func, thisArg, argList); // ignore the syntax however
                                                     // This is the line where the function
                                                     // will be called and provide 
                                                     // the thisArg and thisArray
}

我省略了一些发生的类型检查,但这基本上非常接近规范规定的 Function.prototype.apply 是如何实现的。我们制作自己的对象并在调用函数之前构建 argList

重要的是要注意。名为 [[Call]] 的内部方法不同于 Function.prototype.call

how exactly does it pass an array of arguments into a function that was not written to take an array for an argument

您误解了它的工作原理。 apply 不会 将参数数组传递给对象。它 采用 一个数组,然后使用它来动态构建函数调用,类似于您可以使用 eval 语句执行的操作(但它是本机执行的)。

例如,eval 语句可以这样工作:

function buildFromArray(funcName, arrayOfArgs)
{
    var functionCall = funcName + "(";

    for ( var i = 0; i < arrayOfArgs.length; i++ )
    {
        //Very simplified, only allows for string args
        functionCall += "\"" + arrayOfArgs + "\"";

        if ( i < arrayOfArgs.length - 1 ) functionCall += ",";
    }

    functionCall += ")";

    //Now we run the compiled call which will be something like:
    //myFunction("one","two")
    eval( functionCall );
}

buildFromArray( "myFunction", [ "one", "two" ] );

这非常简单,但是您可以看到数组永远不会传递给函数 myFunction