提问人:fadedbee 提问时间:7/29/2010 最后编辑:ROMANIA_engineerfadedbee 更新时间:7/23/2017 访问量:54460
如何使用 call 或 apply 调用 javascript 构造函数?[复制]
How can I call a javascript constructor using call or apply? [duplicate]
问:
我怎样才能推广下面的函数来接受 N 个参数?(使用调用或应用?
有没有一种编程方法可以将参数应用于“新”?我不希望构造函数被视为普通函数。
/**
* This higher level function takes a constructor and arguments
* and returns a function, which when called will return the
* lazily constructed value.
*
* All the arguments, except the first are pased to the constructor.
*
* @param {Function} constructor
*/
function conthunktor(Constructor) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
console.log(args);
if (args.length === 0) {
return new Constructor();
}
if (args.length === 1) {
return new Constructor(args[0]);
}
if (args.length === 2) {
return new Constructor(args[0], args[1]);
}
if (args.length === 3) {
return new Constructor(args[0], args[1], args[2]);
}
throw("too many arguments");
}
}
qUnit 测试:
test("conthunktorTest", function() {
function MyConstructor(arg0, arg1) {
this.arg0 = arg0;
this.arg1 = arg1;
}
MyConstructor.prototype.toString = function() {
return this.arg0 + " " + this.arg1;
}
var thunk = conthunktor(MyConstructor, "hello", "world");
var my_object = thunk();
deepEqual(my_object.toString(), "hello world");
});
答:
试试这个:
function conthunktor(Constructor) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var Temp = function(){}, // temporary constructor
inst, ret; // other vars
// Give the Temp constructor the Constructor's prototype
Temp.prototype = Constructor.prototype;
// Create a new instance
inst = new Temp;
// Call the original Constructor with the temp
// instance as its context (i.e. its 'this' value)
ret = Constructor.apply(inst, args);
// If an object has been returned then return it otherwise
// return the original instance.
// (consistent with behaviour of the new operator)
return Object(ret) === ret ? ret : inst;
}
}
评论
Date
XMLHttpRequest
TypeError: Failed to construct 'XMLHttpRequest': Please use the 'new' operator, this DOM object constructor cannot be called as a function.
XMLHttpRequest
Temp.name = Constructor.name
name
Temp
__proto__
此函数与所有情况下相同。不过,它可能会比 999 的答案慢得多,所以只有在你真的需要它时才使用它。new
function applyConstructor(ctor, args) {
var a = [];
for (var i = 0; i < args.length; i++)
a[i] = 'args[' + i + ']';
return eval('new ctor(' + a.join() + ')');
}
更新:一旦 ES6 得到广泛支持,你就可以这样写了:
function applyConstructor(ctor, args) {
return new ctor(...args);
}
...但您不需要这样做,因为标准库函数完全符合您的要求!Reflect.construct()
评论
这是你如何做到的:
function applyToConstructor(constructor, argArray) {
var args = [null].concat(argArray);
var factoryFunction = constructor.bind.apply(constructor, args);
return new factoryFunction();
}
var d = applyToConstructor(Date, [2008, 10, 8, 00, 16, 34, 254]);
通话稍微容易一些
function callConstructor(constructor) {
var factoryFunction = constructor.bind.apply(constructor, arguments);
return new factoryFunction();
}
var d = callConstructor(Date, 2008, 10, 8, 00, 16, 34, 254);
您可以使用其中任一来创建工厂函数:
var dateFactory = applyToConstructor.bind(null, Date)
var d = dateFactory([2008, 10, 8, 00, 16, 34, 254]);
或
var dateFactory = callConstructor.bind(null, Date)
var d = dateFactory(2008, 10, 8, 00, 16, 34, 254);
它可以与任何构造函数一起使用,而不仅仅是内置函数或可以兼作函数的构造函数(如 Date)。
但是,它确实需要 Ecmascript 5 .bind 函数。填充码可能无法正常工作。
一种不同的方法,更像是其他一些答案的风格,是创建一个内置的函数版本。这不适用于所有内置软件(如 Date)。new
function neu(constructor) {
// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.2
var instance = Object.create(constructor.prototype);
var result = constructor.apply(instance, Array.prototype.slice.call(arguments, 1));
// The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.
return (result !== null && typeof result === 'object') ? result : instance;
}
function Person(first, last) {this.first = first;this.last = last};
Person.prototype.hi = function(){console.log(this.first, this.last);};
var p = neu(Person, "Neo", "Anderson");
现在,你当然可以像往常一样做或继续。.apply
.call
.bind
neu
例如:
var personFactory = neu.bind(null, Person);
var d = personFactory("Harry", "Potter");
我觉得我给出的第一个解决方案更好,因为它不依赖于您正确复制内置的语义,并且它可以与内置一起正常工作。
评论
constructor
bind
apply
new
new
this
另一种方法,需要修改被调用的实际构造函数,但对我来说似乎比使用 eval() 或在构造链中引入新的虚拟函数更干净......保持你的 conthunktor 函数,如
function conthunktor(Constructor) {
// Call the constructor
return Constructor.apply(null, Array.prototype.slice.call(arguments, 1));
}
并修改被调用的构造函数...
function MyConstructor(a, b, c) {
if(!(this instanceof MyConstructor)) {
return new MyConstructor(a, b, c);
}
this.a = a;
this.b = b;
this.c = c;
// The rest of your constructor...
}
因此,您可以尝试:
var myInstance = conthunktor(MyConstructor, 1, 2, 3);
var sum = myInstance.a + myInstance.b + myInstance.c; // sum is 6
评论
this instanceof Constructor
function Foo(){}; function Bar(){ Foo.call(this); }
Bar.prototype
Foo
instanceof
对于这种情况,有一个可回收的解决方案。对于要使用 apply 或 call 方法调用的每个类,必须在 convertToAllowApply('classNameInString') 之前调用;类必须在同一个 Scoope o 全局 scoope 中(例如,我不尝试发送 ns.className......
代码如下:
function convertToAllowApply(kName){
var n = '\n', t = '\t';
var scrit =
'var oldKlass = ' + kName + ';' + n +
kName + '.prototype.__Creates__ = oldKlass;' + n +
kName + ' = function(){' + n +
t + 'if(!(this instanceof ' + kName + ')){'+ n +
t + t + 'obj = new ' + kName + ';'+ n +
t + t + kName + '.prototype.__Creates__.apply(obj, arguments);'+ n +
t + t + 'return obj;' + n +
t + '}' + n +
'}' + n +
kName + '.prototype = oldKlass.prototype;';
var convert = new Function(scrit);
convert();
}
// USE CASE:
myKlass = function(){
this.data = Array.prototype.slice.call(arguments,0);
console.log('this: ', this);
}
myKlass.prototype.prop = 'myName is myKlass';
myKlass.prototype.method = function(){
console.log(this);
}
convertToAllowApply('myKlass');
var t1 = myKlass.apply(null, [1,2,3]);
console.log('t1 is: ', t1);
如果没有,使用临时构造函数似乎是最好的解决方案。Object.create
如果可用,那么使用它是一个更好的选择。Node.js,使用 Object.create
可以生成更快的代码。下面是如何使用的示例:Object.create
Object.create
function applyToConstructor(ctor, args) {
var new_obj = Object.create(ctor.prototype);
var ctor_ret = ctor.apply(new_obj, args);
// Some constructors return a value; make sure to use it!
return ctor_ret !== undefined ? ctor_ret: new_obj;
}
(显然,该参数是要应用的参数列表。args
我有一段代码,最初用于读取由另一个工具创建的数据。(是的,是邪恶的。这将实例化一个由数百到数千个元素组成的树。基本上,JavaScript 引擎负责解析和执行一堆表达式。我将我的系统转换为解析 JSON 结构,这意味着我必须让我的代码确定为树中的每种类型的对象调用哪个构造函数。当我在测试套件中运行新代码时,我惊讶地发现相对于版本,速度急剧下降。eval
eval
new ...(...)
eval
- 版本:1 秒的测试套件。
eval
- 使用 JSON 版本的测试套件,使用临时构造函数:5 秒。
- 具有 JSON 版本的测试套件,使用:1 秒。
Object.create
测试套件创建多个树。我计算出,当测试套件运行时,我的函数被调用了大约 125,000 次。applytoConstructor
在 ECMAScript 6 中,您可以使用 spread 运算符将带有 new 关键字的构造函数应用于参数数组:
var dateFields = [2014, 09, 20, 19, 31, 59, 999];
var date = new Date(...dateFields);
console.log(date); // Date 2014-10-20T15:01:59.999Z
评论