以下代码摘自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"
我对此代码有两个问题:
我知道如果我使用var myInstance = new MyConstructor();
,它会调用MyConstructor()
,但var myInstance = MyConstructor.construct(myArray);
如何调用MyConstructor()
?
MyConstructor.construct(myArray);
被称为MyConstructor
的方法,但该方法被声明为Function.prototype.construct
,而不是MyConstructor.prototype.construct
。 Function.prototype.construct
和MyConstructor.prototype.construct
之间有什么区别?
答案 0 :(得分:9)
<强> TL; DR:强>
Q1:它通过嵌套函数调用,该函数本身由
new fNewConstr()
调用调用。它只是允许一个参数列表作为数组传递,而不修改函数实际如何与其参数一起工作 Q2:原型的任何内容都可以被该构造函数的所有实例访问(Function
是JavaScript中所有本机函数的构造函数),但MyConstructor不是它自身的对象实例,这就是为什么它需要在Function.prototype
中定义。
我将答案分成两部分,问题1和问题2:
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
这是两者之间的唯一区别 - 一个用参数列表简单地调用构造函数,另一个用参数列表表示为数组(构造函数本身仍然将参数作为列表而不是数组)。
所有函数实例都从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()
时,this
为MyConstructor
,因此代码可以正常运行。
作为旁注,在支持浏览器时可以大大简化此代码:
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
时,您会扩展所有函数对象。