3

以下代码取自 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.construct和 和有什么不一样MyConstructor.prototype.construct

4

4 回答 4

11

TL;博士:

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作为ThisBindingthis传递,是为了将go 的属性分配给被调用函数的ThisBinding,在这种情况下,它将是由“new”运算符创建的新对象。

    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

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

于 2013-06-14T10:50:09.957 回答
2

您的两个问题的答案是thisinside ofconstruct()是根据您如何称呼它而设置的。

如果你写Function.prototype.construct(), thiswill be Function.prototype,它就会坏掉。

当您编写MyConstructor.construct(), thisisMyConstructor时,代码将起作用。


附带说明一下,此代码在支持浏览器方面可以大大简化:

Function.prototype.construct = function (args) {
    return new (this.bind.apply(this, [null].concat(args)))();
};
于 2013-06-14T02:27:25.853 回答
2

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

于 2013-06-14T02:56:11.203 回答
1

关于你的第一个问题:

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

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

关于你的第二个问题:

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

于 2013-06-14T02:55:07.660 回答