了解Function.prototype.apply

时间:2013-06-14 02:24:59

标签: javascript prototype

以下代码摘自MDN页面Function.prototype.apply

Function.prototype.construct = function (aArgs) {
    var fConstructor = this,
        fNewConstr = function () { fConstructor.apply(this, aArgs); };
    fNewConstr.prototype = fConstructor.prototype;
    return new fNewConstr();
};

function MyConstructor() {
    for (var nProp = 0; nProp < arguments.length; nProp++) {
        this["property" + nProp] = arguments[nProp];
    }
}

var myArray = [4, "Hello world!", false];
var myInstance = MyConstructor.construct(myArray);
alert(myInstance.property1); // alerts "Hello world!"
alert(myInstance instanceof MyConstructor); // alerts "true"
alert(myInstance.constructor); // alerts "MyConstructor"

我对此代码有两个问题:

  1. 我知道如果我使用var myInstance = new MyConstructor();,它会调用MyConstructor(),但var myInstance = MyConstructor.construct(myArray);如何调用MyConstructor()

  2. MyConstructor.construct(myArray);被称为MyConstructor的方法,但该方法被声明为Function.prototype.construct,而不是MyConstructor.prototype.constructFunction.prototype.constructMyConstructor.prototype.construct之间有什么区别?

4 个答案:

答案 0 :(得分:9)

  

<强> TL; DR:

     

Q1:它通过嵌套函数调用,该函数本身由new fNewConstr()调用调用。它只是允许一个参数列表作为数组传递,而不修改函数实际如何与其参数一起工作    Q2:原型的任何内容都可以被该构造函数的所有实例访问(Function是JavaScript中所有本机函数的构造函数),但MyConstructor不是它自身的对象实例,这就是为什么它需要在Function.prototype中定义。

我将答案分成两部分,问题1和问题2:

问题1

Function.prototype.construct旨在允许传递数组作为参数列表,而不使用Function.prototype.bind。它在嵌套函数中调用原始函数,并将传递的参数作为数组调用,并将原型设置为原始函数的原型。

在给出的代码中,Function.prototype.construct方法的作用如下

Function.prototype.construct = function (aArgs) {
    var fConstructor = this, 

第一行允许我们访问被调用的函数 - this(规范中的 ThisBinding )的值,该范围是我们要调用的函数

        fNewConstr = function () { fConstructor.apply(this, aArgs); };

下一行将使用Function.prototype.apply调用原始函数,该函数允许将该函数的参数作为数组传递。 this作为 ThisBinding 传递的原因是为this的属性赋值转到被调用函数的 ThisBinding ,其中这种情况将是“新”运算符创建的新对象。

    fNewConstr.prototype = fConstructor.prototype;

这简单地使得“new”运算符创建的返回对象的原型与调用函数相同,因为在新构造函数上调用“new”运算符,而不是原始运算符。

    return new fNewConstr();

这几乎是不言自明的 - 它创建了一个新对象,其中包含构造函数在this属性上创建的关联值,或者返回函数返回的对象(如果有)。

返回的内容与直接调用new MyConstructor()的内容相同,除了参数的给定方式(就像Function.prototype.call()Function.prototype.apply()都存在一样)。例如,这两个代码示例是等效的:

new MyConstructor(4, "Hello world!", false); // { property0: 4, property1: 'Hello world!', property2: false }
MyConstructor.construct([4, "Hello world!", false]); // { property0: 4, property1: 'Hello world!', property2: false }

......就像这些是等价的:

function foo(a, b, c, d, e) { return a + b + c + d + e; }
foo.call(null, 1, 2, 3, 4, 5); // 15
foo.apply(null, [1, 2, 3, 4, 5]); // 15

这是两者之间的唯一区别 - 一个用参数列表简单地调用构造函数,另一个用参数列表表示为数组(构造函数本身仍然将参数作为列表而不是数组)。

问题2

所有函数实例都从Function原型借用,因此Function.prototype上定义的任何方法(或者就此而言,属性)都可用于所有函数。举个例子:

function foo() { return 1 + 1; }   
Function.prototype.bar = function ()
{   var result = this();
    if (typeof result === 'number' && isFinite(result)) return result + 0.5;
    return NaN;
};

foo.bar(); // 2.5

但是,当您在MyConstructor.prototype上声明方法时,它仅适用于MyConstructor实例,而不是MyConstructor本身,如下所示:

function MyConstructor(num) { this.x = 1 + num; }
MyConstructor.prototype.bar = function () { return 2 + 2; };

MyConstructor.bar(); // TypeError: MyConstructor.bar is not a function
MyConstructor.x; // undefined

看看这不起作用?您需要在MyConstructor

的实例上使用它
var foo = new MyConstructor(4);
foo.x; // 5
foo.bar(); // 4

如果它在原型上,对原型的任何编辑都将影响对象上的所有原型方法/值:

function MyConstructor(num) { this.x = 1 + num; }
MyConstructor.prototype.bar = function () { return 2 + 2; };

var foo1 = new MyConstructor(6);
foo1.bar(); // 4

MyConstructor.prototype.bar = function () { return 3 + 3; };
var foo2 = new MyConstructor(9);

foo2.bar(); // 6
foo1.bar(); // 6

请记住,如果您完全创建一个新的原型对象,那么在更改之前创建的实例仍将引用旧对象:

var foo1 = new MyConstructor(6);
foo1.bar(); // 4

MyConstructor.prototype = { bar: function () { return 3 + 3; } };

var foo2 = new MyConstructor(9);
foo2.bar(); // 6
foo1.bar(); // 4?!

请记住,直接在MyConstructor上声明的属性不会传递给实例:

MyConstructor.bar2 = function () { return 42; }; // the answer to everything?
MyConstructor.y = 3.141592653589793238462643383279502881197169399; // I only can remember that much

MyConstructor.bar2(); // 42, as you'd expect
var foo = new MyConstructor(99);

foo.bar2(); // TypeError: foo.bar2 is not defined
foo.y; // undefined

基于原型的编程可能非常强大(实际上我更喜欢它的类,但这是一个不同的故事),但你需要了解它的作用。如果使用得当,你可以用它做很棒的事情 - 继续学习! :)

答案 1 :(得分:2)

您的两个问题的答案是this内的construct()是根据您的调用方式设置的。

如果你写Function.prototype.construct()this将是Function.prototype,它会中断。

当您撰写MyConstructor.construct()时,thisMyConstructor,因此代码可以正常运行。


作为旁注,在支持浏览器时可以大大简化此代码:

Function.prototype.construct = function (args) {
    return new (this.bind.apply(this, [null].concat(args)))();
};

答案 2 :(得分:2)

您现在已经为每个construct创建了Function方法,因为您使用Function.prototype访问了该方法。 fConstructor是一个误导性变量,因为this已分配给它,实际上是指每个Function。在作为Object this的属性的方法内部实际上是指该方法所属的Object。 fNewConstr必须this.fNewConstr来引用每个Function。在这种情况下,它实际上是一个变量,它包含对Function的引用,Function后来成为fNewConstr.prototype = fConstructor.prototype原型的一部分new fNewConstr(),后来用new fNewConstr()执行。调用apply()时,它使用fConstructor传递Function或每个MyConstrutor一个参数数组。所以你将数组传递给{{1}}。

答案 3 :(得分:1)

关于你的第一个问题:

MyConstructor通过一系列间接调用,并传递一个数组作为其参数列表。调用MyConstructor.construct(myArray)后,this内的construct将为MyConstructor。然后将对它的引用存储为fConstructor,并创建子构造函数fNewConstr。该构造函数使用MyConstructor调用apply,并将myArray中的元素作为参数传递。

作为旁注,这似乎是一种解释Function.prototype.apply的过于复杂的方式(MDN说这是为了模仿Java,所以这可以解释它。)

关于你的第二个问题:

construct添加到Function.prototype后,它将在所有功能对象(Function的实例)上可用,包括MyConstructor。如果您添加到MyConstructor.prototype,则会影响MyConstructor的所有实例,并且当您添加到Function.prototype时,您会扩展所有函数对象。