call 또는 apply를 사용하여 자바 스크립트 생성자를 어떻게 호출 할 수 있습니까? [복제]
이 질문에 이미 답변이 있습니다.
N 개의 인수를 사용하도록 아래 함수를 어떻게 일반화 할 수 있습니까? (전화를 사용하거나 신청 하시겠습니까?)
'new'에 인수를 적용하는 프로그래밍 방식이 있습니까? 생성자가 일반 함수처럼 취급되는 것을 원하지 않습니다.
/**
* 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");
}
}
q 단위 테스트 :
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;
}
}
방법은 다음과 같습니다.
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 함수가 필요합니다. 심은 아마도 제대로 작동하지 않을 것입니다.
A different approach, more in the style of some of the other answers is to create a function version of the built in new
. This will not work on all builtins (like Date).
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");
And now, of course you can do .apply
or .call
or .bind
on neu
as normal.
For example:
var personFactory = neu.bind(null, Person);
var d = personFactory("Harry", "Potter");
I feel that the first solution I give is better though, as it doesn't depend on you correctly replicating the semantics of a builtin and it works correctly with builtins.
This function is identical to new
in all cases. It will probably be significantly slower than 999’s answer, though, so use it only if you really need it.
function applyConstructor(ctor, args) {
var a = [];
for (var i = 0; i < args.length; i++)
a[i] = 'args[' + i + ']';
return eval('new ctor(' + a.join() + ')');
}
UPDATE: Once ES6 support is widespread, you'll be able to write this:
function applyConstructor(ctor, args) {
return new ctor(...args);
}
...but you won't need to, because the standard library function Reflect.construct()
does exactly what you're looking for!
Another approach, which requires to modify the actual constructor being called, but seems cleaner to me than using eval() or introducing a new dummy function in the construction chain... Keep your conthunktor function like
function conthunktor(Constructor) {
// Call the constructor
return Constructor.apply(null, Array.prototype.slice.call(arguments, 1));
}
And modify the constructors being called...
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...
}
So you can try:
var myInstance = conthunktor(MyConstructor, 1, 2, 3);
var sum = myInstance.a + myInstance.b + myInstance.c; // sum is 6
Using a temporary constructor seems to be the best solution if Object.create
is not available.
If Object.create
is available, then using it is a much better option. On Node.js, using Object.create
results in much faster code. Here's an example of how Object.create
can be used:
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;
}
(Obviously, the args
argument is a list of arguments to apply.)
I had a piece of code that originally used eval
to read a piece of data created by another tool. (Yes, eval
is evil.) This would instantiate a tree of hundreds to thousands of elements. Basically, the JavaScript engine was responsible for parsing and executing a bunch of new ...(...)
expressions. I converted my system to parse a JSON structure, which means I have to have my code determine which constructor to call for each type of object in the tree. When I ran the new code in my test suite, I was surprised to see a dramatic slow down relative to the eval
version.
- Test suite with
eval
version: 1 second. - Test suite with JSON version, using temporary constructor: 5 seconds.
- Test suite with JSON version, using
Object.create
: 1 second.
The test suite creates multiple trees. I calculated my applytoConstructor
function was called about 125,000 times when the test suite is run.
In ECMAScript 6, you can use the spread operator to apply a constructor with the new keyword to an array of arguments:
var dateFields = [2014, 09, 20, 19, 31, 59, 999];
var date = new Date(...dateFields);
console.log(date); // Date 2014-10-20T15:01:59.999Z
There is a rehusable solution for this case. For every Class to you wish to call with apply or call method, you must call before to convertToAllowApply('classNameInString'); the Class must be in the same Scoope o global scoope (I don't try sending ns.className for example...)
There is the code:
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);
'code' 카테고리의 다른 글
"선택기 배열"을 만드는 방법 (0) | 2020.09.24 |
---|---|
git 저장소에서 bin 및 obj 폴더를 어떻게 무시할 수 있습니까? (0) | 2020.09.24 |
PropertiesConfiguration을 사용하여 여러 줄 속성 값을 작성하는 방법은 무엇입니까? (0) | 2020.09.24 |
부동 소수점 값의 정밀도를 유지하기위한 Printf 너비 지정자 (0) | 2020.09.24 |
Mac OS X Yosemite / El Capitan에서 MySQL 서버 자동 시작 (0) | 2020.09.24 |