programing

prototype.constructor.apply를 호출하여 JavaScript 객체 인스턴스화

copyandpastes 2021. 1. 14. 23:38
반응형

prototype.constructor.apply를 호출하여 JavaScript 객체 인스턴스화


제가하려는 작업의 구체적인 예부터 시작하겠습니다.

나는 년, 월, 일,시, 분, 초 및 밀리 초 구성 요소의 배열을 형식으로 가지고 [ 2008, 10, 8, 00, 16, 34, 254 ]있습니다. 다음 표준 생성자를 사용하여 Date 객체를 인스턴스화하고 싶습니다.

new Date(year, month, date [, hour, minute, second, millisecond ])

이 생성자에 배열을 전달하여 새 Date 인스턴스를 얻으려면 어떻게해야합니까? [ 업데이트 : 내 질문은 실제로이 특정 예를 넘어서 확장됩니다. 생성자가 내 도달 범위를 벗어난 Date, Array, RegExp 등과 같은 기본 제공 JavaScript 클래스에 대한 일반적인 솔루션을 원합니다. ]

다음과 같은 작업을 시도하고 있습니다.

var comps = [ 2008, 10, 8, 00, 16, 34, 254 ];
var d = Date.prototype.constructor.apply(this, comps);

new어딘가에 " " 가 필요할 것입니다 . 위는 " (new Date()).toString()"를 호출 한 것처럼 현재 시간을 반환합니다 . 또한 위의 내용이 완전히 잘못된 방향 일 수 있음을 인정합니다. :)

참고 : 아니요 eval()및 배열 항목에 하나씩 액세스하지 마십시오. 배열을 그대로 사용할 수 있어야한다고 확신합니다.


업데이트 : 추가 실험

아무도 아직 제대로 된 답을 찾지 못했기 때문에 더 많이 놀았습니다. 여기에 새로운 발견이 있습니다.

내 수업으로 이것을 할 수 있습니다.

function Foo(a, b) {
    this.a = a;
    this.b = b;

    this.toString = function () {
        return this.a + this.b;
    };
}

var foo = new Foo(1, 2);
Foo.prototype.constructor.apply(foo, [4, 8]);
document.write(foo); // Returns 12 -- yay!

그러나 내장 Date 클래스에서는 작동하지 않습니다.

var d = new Date();
Date.prototype.constructor.call(d, 1000);
document.write(d); // Still returns current time :(

Number에서도 작동하지 않습니다.

var n = new Number(42);
Number.prototype.constructor.call(n, 666);
document.write(n); // Returns 42

아마도 이것은 본질적인 객체로는 불가능할까요? Firefox BTW로 테스트 중입니다.


나는 내 자신에 대해 더 많은 조사를 했고 Date 클래스가 구현되는 방식으로 인해 이것이 불가능한 업적 이라는 결론을 내렸다 .

Date가 어떻게 구현되었는지 확인하기 위해 SpiderMonkey 소스 코드를 조사했습니다 . 나는 모든 것이 다음과 같은 몇 줄로 요약된다고 생각합니다.

static JSBool
Date(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    jsdouble *date;
    JSString *str;
    jsdouble d;

    /* Date called as function. */
    if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) {
        int64 us, ms, us2ms;
        jsdouble msec_time;

        /* NSPR 2.0 docs say 'We do not support PRMJ_NowMS and PRMJ_NowS',
         * so compute ms from PRMJ_Now.
         */
        us = PRMJ_Now();
        JSLL_UI2L(us2ms, PRMJ_USEC_PER_MSEC);
        JSLL_DIV(ms, us, us2ms);
        JSLL_L2D(msec_time, ms);

        return date_format(cx, msec_time, FORMATSPEC_FULL, rval);
    }

    /* Date called as constructor. */
    // ... (from here on it checks the arg count to decide how to create the date)

Date가 함수로 사용되는 경우 ( 정확히 동일한 항목 인 Date()또는 Date.prototype.constructor()) 현재 시간을 로케일 형식의 문자열로 반환하도록 기본 설정됩니다. 이것은 전달 된 인수와 관계가 없습니다.

alert(Date()); // Returns "Thu Oct 09 2008 23:15:54 ..."
alert(typeof Date()); // Returns "string"

alert(Date(42)); // Same thing, "Thu Oct 09 2008 23:15:54 ..."
alert(Date(2008, 10, 10)); // Ditto
alert(Date(null)); // Just doesn't care

나는 이것을 회피하기 위해 JS 수준에서 할 수있는 일이 없다고 생각한다. 그리고 이것은 아마도이 주제에 대한 나의 추구의 끝일 것입니다.

또한 흥미로운 점을 발견했습니다.

    /* Set the value of the Date.prototype date to NaN */
    proto_date = date_constructor(cx, proto);
    if (!proto_date)
        return NULL;
    *proto_date = *cx->runtime->jsNaN;

Date.prototype내부 값이있는 Date 인스턴스 NaN이므로

alert(Date.prototype); // Always returns "Invalid Date"
                       // on Firefox, Opera, Safari, Chrome
                       // but not Internet Explorer

IE는 우리를 실망시키지 않습니다. 그것은 약간 다른 일을하고 아마도 -1Date.prototype이 항상 epoch보다 약간 앞선 날짜를 반환하도록 내부 값을로 설정합니다 .


최신 정보

나는 마침내 ECMA-262 자체를 파헤쳐 보았고, (Date 객체로) 달성하려는 것은 정의상 불가능하다는 것이 밝혀졌습니다.

15.9.2 함수로 호출되는 날짜 생성자

Date가 생성자가 아닌 함수로 호출되면 현재 시간 (UTC)을 나타내는 문자열을 반환합니다.

참고 함수 호출 Date(…)new Date(…)인수가 동일한 객체 생성 표현식 과 동일 하지 않습니다 .

15.9.2.1 날짜 ([년 [, 월 [, 날짜 [,시 [, 분 [, 초 [, ms]]]]]]))

모든 인수는 선택 사항입니다. 제공된 모든 인수가 허용되지만 완전히 무시됩니다. 표현식에 의해 마치 문자열이 생성되고 반환됩니다 (new Date()).toString().


나는 이것을 우아하다고 거의 부르지 않지만 내 테스트 (FF3, Saf4, IE8)에서는 작동합니다.

var arr = [ 2009, 6, 22, 10, 30, 9 ];

대신 :

var d = new Date( arr[0], arr[1], arr[2], arr[3], arr[4], arr[5] );

이 시도:

var d = new Date( Date.UTC.apply( window, arr ) + ( (new Date()).getTimezoneOffset() * 60000 ) );


다음은 특정 사례를 해결하는 방법입니다.

function writeLn(s)
{
    //your code to write a line to stdout
    WScript.Echo(s)
}

var a =  [ 2008, 10, 8, 00, 16, 34, 254 ]

var d = NewDate.apply(null, a)

function NewDate(year, month, date, hour, minute, second, millisecond)
{
    return new Date(year, month, date, hour, minute, second, millisecond);
}

writeLn(d)

그러나 더 일반적인 솔루션을 찾고 있습니다. 생성자 메서드를 만드는 데 권장되는 코드는이를 사용하는 것 return this입니다.

그 후:-

function Target(x , y) { this.x = x, this.y = y; return this; }

건설 될 수 있습니다 :-

var x = Target.apply({}, [1, 2]);

그러나 프로토 타입 체인이 잘못 되었기 때문에 모든 구현이 이러한 방식으로 작동하는 것은 아닙니다.

var n = {};
Target.prototype = n;
var x = Target.apply({}, [1, 2]);
var b = n.isPrototypeOf(x); // returns false
var y = new Target(3, 4);
b = n.isPrototypeOf(y); // returns true

우아하지는 않지만 여기에 해결책이 있습니다.

function GeneratedConstructor (methodName, argumentCount) {
    var params = []

    for (var i = 0; i < argumentCount; i++) {
        params.push("arguments[" + i + "]")
    }

    var code = "return new " + methodName + "(" + params.join(",") +  ")"

    var ctor = new Function(code)

    this.createObject = function (params) {
        return ctor.apply(this, params)
    }
}

The way this works should be pretty obvious. It creates a function through code generation. This example has a fixed number of parameters for each constructor you create, but that's useful anyway. Most of the time you have atleast a maximum number of arguments in mind. This also is better than some of the other examples here because it allows you to generate the code once and then re-use it. The code that's generated takes advantage of the variable-argument feature of javascript, this way you can avoid having to name each parameter (or spell them out in a list and pass the arguments in to the function you generate). Here's a working example:

var dateConstructor = new GeneratedConstructor("Date", 3)
dateConstructor.createObject( [ 1982, 03, 23 ] )

This will return the following:

Fri Apr 23 1982 00:00:00 GMT-0800 (PST)

It is indeed still...a bit ugly. But it atleast conveniently hides the mess and doesn't assume that compiled code itself can get garbage collected (since that may depend on the implementation and is a likely area for bugs).

Cheers, Scott S. McCoy


This is how you do it:

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]);

It will work with any constructor, not just built-ins or constructors that can double as functions (like Date).

However it does require the Ecmascript 5 .bind function. Shims will probably not work correctly.

By the way, one of the other answers suggests returning this out of a constructor. That can make it very difficult to extend the object using classical inheritance, so I would consider it an antipattern.


With ES6 syntax, there's at least 2 methods to achieve this:

var comps = [ 2008, 10, 8, 00, 16, 34, 254 ];

// with the spread operator
var d1 = new Date(...comps);

// with Reflect.construct
var d2 = Reflect.construct(Date, comps);

console.log('d1:', d1, '\nd2:', d2);
// or more readable:
console.log(`d1: ${d1}\nd2: ${d2}`);


It will work with ES6 spread operator. You simply:

const arr = [2018, 6, 15, 12, 30, 30, 500];
const date = new Date(...arr);

console.log(date);

You can do it with flagrant, flagrant abuse of eval:

var newwrapper = function (constr, args) {
  var argHolder = {"c": constr};
  for (var i=0; i < args.length; i++) {
    argHolder["$" + i] = args[i];
  }

  var newStr = "new (argHolder['c'])(";
  for (var i=0; i < args.length; i++) {
    newStr += "argHolder['$" + i + "']";
    if (i != args.length - 1) newStr += ", ";
  }
  newStr += ");";

  return eval(newStr);
}

sample usage:

function Point(x,y) {
    this.x = x;
    this.y = y;
}
var p = __new(Point, [10, 20]);
alert(p.x); //10
alert(p instanceof Point); //true

enjoy =).


function gettime()
{
    var q = new Date;
    arguments.length && q.setTime( ( arguments.length === 1
        ? typeof arguments[0] === 'number' ? arguments[0] : Date.parse( arguments[0] )
        : Date.UTC.apply( null, arguments ) ) + q.getTimezoneOffset() * 60000 );
    return q;
};

gettime(2003,8,16)

gettime.apply(null,[2003,8,16])

I know it's been a long time, but I have the real answer to this question. This is far from impossible. See https://gist.github.com/747650 for a generic solution.

var F = function(){};
F.prototype = Date.prototype;
var d = new F();
Date.apply(d, comps);

Here is another solution:

function createInstance(Constructor, args){
    var TempConstructor = function(){};
    TempConstructor.prototype = Constructor.prototype;
    var instance = new TempConstructor;
    var ret = Constructor.apply(instance, args);
    return ret instanceof Object ? ret : instance;
}

console.log( createInstance(Date, [2008, 10, 8, 00, 16, 34, 254]) )

Edited

Sorry, I was sure I made it that way years ago, right now I'll stick to:

var d = new Date(comps[0],comps[1],comps[2],comps[3],comps[4],comps[5],comps[6]);

Edit:

But do remember that a javascript Date-object uses indexes for months, so the above array means

November 8 2008 00:16:34:254


var comps = [ 2008, 10, 8, 00, 16, 34, 254 ];
var d = eval("new Date(" + comps.join(",") + ");");

ReferenceURL : https://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply

반응형