IT

함수가 생성자로 호출 감지하는 방법은 무엇입니까?

lottoking 2020. 8. 14. 08:02
반응형

함수가 생성자로 호출 감지하는 방법은 무엇입니까?


주어진 함수 :

function x(arg) { return 30; }

두 가지 방법으로 호출 할 수 있습니다.

result = x(4);
result = new x(4);

첫 번째는 30을 반환하고 반환합니다.

함수 내부에서 함수 가 호출 된 방식을 어떻게 감지 할 수 있습니까?

해결책이 무엇이든 다음 호출을 작동해야합니다.

var Z = new x(); 
Z.lolol = x; 
Z.lolol();

모든 솔루션은 현재 Z.lolol()에서 생성자로 호출 할 생각합니다 .


참고 : 이제 ES2015 이상에서 가능합니다. Daniel Weiner의 답변을 참조하십시오 .

나는 당신이 원하는 것이 가능하다고 생각하지 않는다 [ES2015 이전]. 함수 내에서 수행 할 수있는 추론을 수행 할 수있는 정보가 충분하지 않습니다.

ECMAScript 제 3 판 사양을 수행하는 단계를 new x()호출 할 때 기본적으로 다음과 같습니다.

  • 새 개체 만들기
  • 내부 [[Prototype]] 속성을 전용 타입 속성에 할당합니다. x
  • x정상적으로 호출 하여 새 개체를 다음과 같이 전달합니다.this
  • , 대한 호출이에 xobject- 반환하면 반환하고 오는가 않으면 새 object-를 반환합니다.

이 테스트 내부 가능성이 유일한 있도록 호출 된 함수가 실행 코드로 사용할 수 있습니다 방법에 대한 유용한 아무것도 , x없는 this값이 여기에 모든 해답이하고있는을 구석으로입니다. 이미 본 것처럼 생성자 x호출 할 때 의 새 인스턴스 * x생성 된 모든 새 인스턴스에 속성을 할당 하지 않는 한 함수로 호출 할 때 x전달 된 기존 인스턴스와 구별 할 수 없습니다 .thisxx

function x(y) {
    var isConstructor = false;
    if (this instanceof x // <- You could use arguments.callee instead of x here,
                          // except in in EcmaScript 5 strict mode.
            && !this.__previouslyConstructedByX) {
        isConstructor = true;
        this.__previouslyConstructedByX = true;
    }
    alert(isConstructor);
}

이제는 x덮어 쓸 수있는 모든 object-에 쓸모없는 추가 속성이 있기 때문에 이것은 이상적이지 않습니다 .하지만 이것이 최선이라고 생각합니다.

(*) 는 부정확 한 용어이지만 ","인스턴스 " x생성자 로 호출 하여 생성 된 object-"보다 충분히 가깝고 간결합니다.


ECMAScript 6부터는 . 함수가로 ( 또는와 같이 작동하는 ) 호출하면 설정되고 , 이름 .new.targetnew.targetnewReflect.constructnewundefined

function Foo() {
    if (new.target) {
       console.log('called with new');
    } else {
       console.log('not called with new');
    }
}

new Foo(); // "called with new"
Foo(); // "not called with new"
Foo.call({}); // "not called with new"

1) 다음을 확인할 수 있습니다 this.constructor.

function x(y)
{
    if (this.constructor == x)
        alert('called with new');
    else
         alert('called as function');
}

2) 예, new컨텍스트 에서 사용할 때 반환 값이 삭제됩니다.


참고 :이 답변은 javascript가 1999 년 부터 ES3에 입찰 2008 년 에 작성합니다 . 그 이후로 많은 새로운 기능이 추가되어 더 나은 솔루션이 존재합니다. 이 답변은 동영상 유지됩니다.

아래 코드의 장점은 함수 이름을 두 번 생산 필요가 있다고 익명 함수라고하는 것입니다.

function x() {
    if ( (this instanceof arguments.callee) ) {
      alert("called as constructor");
    } else {
      alert("called as function");
    }
}

업데이트claudiu는 아래 댓글에서 지적 당신이 만든 같은 객체 생성에 할당하는 경우, 위의 코드가 작동하지 않습니다. 나는 그렇게하는 코드를 새로 본 적이있다.

Claudius 예 :

var Z = new x();
Z.lolol = x;
Z.lolol();

객체에 속성을 추가하면 객체가 생성 여부를 감지 할 수 있습니다.

function x() {
    if ( (this instanceof arguments.callee && !this.hasOwnProperty("__ClaudiusCornerCase")) ) {
        this.__ClaudiusCornerCase=1;
        alert("called as constructor");
    } else {
        alert("called as function");
    }
}

추가 된 속성을 삭제하면 위의 코드도 깨집니다. 그러나 undefined계속 작동합니다. 그러나 삭제하면 깨집니다.

현재는 함수가 생성자로 호출하여 감지하기위한 ecmascript의 기본 지원이 없습니다. 이것은 내가 지금까지 생각 해낸 가장 가까운 속성을 삭제하지 않는 작동합니다.


두 가지 방법, 본질적으로 동일합니다. 범위 this가 무엇인지 테스트하거나 무엇인지 테스트 할 수 있습니다 this.constructor.

this메소드를 생성자 호출하면 클래스의 새 인스턴스가되고, 메소드를 호출하면 메소드 this의 생성가됩니다. 객체 생성자가 생성자가되는 것입니다. 진흙처럼 분명하지만 도움이 될 것입니다.

var a = {};

a.foo = function () 
{
  if(this==a) //'a' because the context of foo is the parent 'a'
  {
    //method call
  }
  else
  {
    //constructor call
  }
}

var bar = function () 
{
  if(this==window) //and 'window' is the default context here
  {
    //method call
  }
  else
  {
    //constructor call
  }
}

a.baz = function ()
{
  if(this.constructor==a.baz); //or whatever chain you need to reference this method
  {
    //constructor call
  }
  else
  {
    //method call
  }
}

생성자 내에서 [this]의 인스턴스 유형을 확인하는 것이 방법입니다. 문제는 더 이상 고민하지 않는 방법이 발생하는 것입니다. 그러나 해결책이 있습니다.

ClassA () 함수를 다루고 검증 가정 해봅시다. 기본적인 접근 방식은 다음과 가변적입니다.

    function ClassA() {
        if (this instanceof arguments.callee) {
            console.log("called as a constructor");
        } else {
            console.log("called as a function");
        }
    }

작동하지 않는 솔루션이 예상대로 작동하지 않는 몇 가지 방법이 있습니다. 다음 두 가지만 고려하십시오.

    var instance = new ClassA;
    instance.classAFunction = ClassA;
    instance.classAFunction(); // <-- this will appear as constructor call

    ClassA.apply(instance); //<-- this too

a) 인스턴스의 필드에 "ConstructorFinished"와 같은 일부 정보를 배치하고 다시 확인하거나 b) 등록을 목록으로 추적 할 것을 제안합니다. ClassA의 모든 인스턴스를 변경하는 것이 유형 관련 기능이 작동하기에는 너무 침략 비용이 많이 들기 때문에 둘 다 불편합니다. ClassA에 많은 인스턴스가있는 경우 목록의 모든 개체를 수집하면 가비지 수집 및 리소스 문제가 보관 수 있습니다.

가는 방법은 ClassA 함수의 실행을 제어 할 수있는 것입니다. 간단한 접근 방식은 다음과 가변합니다.

    function createConstructor(typeFunction) {
        return typeFunction.bind({});
    }

    var ClassA = createConstructor(
        function ClassA() {
            if (this instanceof arguments.callee) {
                console.log("called as a function");
                return;
            }
            console.log("called as a constructor");
        });

    var instance = new ClassA();

이렇게하면 [this] 값으로 속이려는 모든 시도를 방지 할 수 있습니다. 바인딩 된 함수는 새로운 연산자로 호출하지 않는 한 항상 원래의 [this]가 계속 유지 됩니다.

고급 버전은 임의의 객체에 생성 적용되는 기능을 제공합니다. 일부 용도는 생성 튼 형식 변환기로 사용하거나 상속 시나리오에서 기본 클래스 생성자의 호출 가능한 체인을 제공하는 것입니다.

    function createConstructor(typeFunction) {
        var result = typeFunction.bind({});
        result.apply = function (ths, args) {
            try {
                typeFunction.inApplyMode = true;
                typeFunction.apply(ths, args);
            } finally {
                delete typeFunction.inApplyMode;
            }
        };
        return result;
    }

    var ClassA = createConstructor(
        function ClassA() {
            if (this instanceof arguments.callee && !arguments.callee.inApplyMode) {
                console.log("called as a constructor");
            } else {
                console.log("called as a function");
            }
        });

그렇게 많은 단어가 실제로 가능하고 간단합니다 ... 왜 그렇게 작은 것을 이해하지 못합니다

업데이트 :TwilightSun 덕분에 Claudiu가 제안한 테스트에도 솔루션이 완료되었습니다 ! 감사합니다 !!!

function Something()
{
    this.constructed;

    if (Something.prototype.isPrototypeOf(this) && !this.constructed)
    {
        console.log("called as a c'tor"); this.constructed = true;
    }
    else
    {
        console.log("called as a function");
    }
}

Something(); //"called as a function"
new Something(); //"called as a c'tor"

여기에서 설명 : https://jsfiddle.net/9cqtppuf/


Gregs 솔루션을 확장하면 한 테스트 케이스와 완벽하게 작동합니다.

function x(y) {
    if( this.constructor == arguments.callee && !this._constructed ) {
        this._constructed = true;
        alert('called with new');
    } else {
        alert('called as function');
    }
}

편집 : 일부 테스트 케이스 추가

x(4);             // OK, function
var X = new x(4); // OK, new

var Z = new x();  // OK, new
Z.lolol = x; 
Z.lolol();        // OK, function

var Y = x;
Y();              // OK, function
var y = new Y();  // OK, new
y.lolol = Y;
y.lolol();        // OK, function

이 사례를보기는 생성자가 인스턴스의 속성 일 수 있다고 생각한 것이 다음 코드는 드문 시나리오를 다루고 있습니다.

// Store instances in a variable to compare against the current this
// Based on Tim Down's solution where instances are tracked
var Klass = (function () {
    // Store references to each instance in a "class"-level closure
    var instances = [];

    // The actual constructor function
    return function () {
        if (this instanceof Klass && instances.indexOf(this) === -1) {
            instances.push(this);
            console.log("constructor");
        } else {
            console.log("not constructor");
        }
    };
}());

var instance = new Klass();  // "constructor"
instance.klass = Klass;
instance.klass();            // "not constructor"

대부분의 경우 나는 아마도 instanceof를 확인할 것입니다.


JavaScript 코드에서 함수가 호출되는 방식이 없습니다. 1

그러나 함수 호출은 this전역 개체에 this할당되고 생성자 새 개체에 할당됩니다. 이 새 객체는 객체가 될 수 없습니다. 구현에서 전역을 접근 할 수 없습니다.

함수 (heh) 가를 반환하는 함수로 호출하여 전역을 호출 this합니다.

내 직감은 ECMAScript 1.3 사양에서 함수로 호출 될 때 정의 된 동작을 가진 동작을 가진 사람이 비교를 사용하여 호출 된 방법을 구별해야한다는 것입니다.

function MyClass () {
    if ( this === (function () { return this; })() ) {
        // called as a function
    }
    else {
        // called as a constructor
    }
}

어쨌든 누구나 함수 나 생성자의 call또는 apply를 사용 this하고 무엇이든 설정할 수 있습니다. 그러나 이렇게하면 전역 개체를 "초기화"하는 것을 방지 할 수 있습니다.

function MyClass () {
    if ( this === (function () { return this; })() ) {
        // Maybe the caller forgot the "new" keyword
        return new MyClass();
    }
    else {
        // initialize
    }
}

1. 호스트 (일명 구현)는 내부 속성 [[Call]][[Construct]]. 전자는 함수 또는 메서드 식에 대해 호출하고 후자는 new식에 대해 호출 합니다.


내 테스트에서 http://packagesinjavascript.wordpress.com/ 그게 내가 사용하여 마감 한 그래서 나는 모든 경우에 크로스 브라우저를 작동하는 (이 == 창) 경우 테스트를 발견했다.

-Stijn


John Resig에서 :

function makecls() {

   return function(args) {

        if( this instanceof arguments.callee) {
            if ( typeof this.init == "function")
                this.init.apply(this, args.callee ? args : arguments)
        }else{
            return new arguments.callee(args);
        }
    };
}

var User = makecls();

User.prototype.init = function(first, last){

    this.name = first + last;
};

var user = User("John", "Resig");

user.name

hackish instanceof가면 new.target다른 답변과 답변 최소 솔루션 입니다. 그러나 instanceof솔루션을 사용하면 다음 예제에서 실패합니다.

let inst = new x;
x.call(inst);

@TimDown 솔루션과 결합하면 WeakSet인스턴스 내부에 속성을 넣는 것을 방지하기 위해 이전 ECMAScript 버전과의 사용하려는 경우 ES6를 사용할 수 있습니다 . 글쎄, WeakSet사용하지 않는 개체를 가비지 수집하는 데 사용합니다. new.targetES6의 구문 기능은 동일한 소스 코드에서 호환됩니다. ECMAScript는 식별자가 예약어 중 하나가 될 수 new있습니다.

(function factory()
{
    'use strict';
    var log = console.log;

    function x()
    {
        log(isConstructing(this) ?
            'Constructing' :
            'Not constructing'
        );
    }

    var isConstructing, tracks;
    var hasOwnProperty = {}.hasOwnProperty;

    if (typeof WeakMap === 'function')
    {
        tracks = new WeakSet;
        isConstructing = function(inst)
        {
            if (inst instanceof x)
            {
                return tracks.has(inst) ?
                    false : !!tracks.add(inst);
            }
            return false;
        }
    } else {
        isConstructing = function(inst)
        {
            return inst._constructed ?
                false : inst._constructed = true;
        };
    }
    var z = new x; // Constructing
    x.call(z)      // Not constructing
})();

ECMAScript 3의 instanceof연산자는 다음 과 같이 지정 합니다.

11.8.6 instanceof 연산자
--- Production RelationalExpression : RelationalExpression instanceof ShiftExpression은 다음과 같이 평가됩니다.
--- 1. RelationalExpression을 평가합니다.
--- 2. GetValue (결과 (1))를 호출합니다.
--- 3. ShiftExpression을 평가합니다.
--- 4. GetValue (결과 (3))를 호출합니다.
--- 5. Result (4)가 아니면 아니면 TypeError 예외를 발생시키는 것입니다.
--- 6. 결과 (4)에 [[HasInstance]] 메서드가 분류 오류 예외를 throw합니다 .
--- 7. 결과 (2) 매개 변수를 사용하여 Result (4)의 [[HasInstance]] 메서드를 호출합니다.
--- 8. 결과 (7)을 반환합니다.
15.3.5.3 [[HasInstance]] (V)
--- F가 함수라고 가정합니다.
--- F의 [[HasInstance]] 메서드가 V 값으로 호출되면 다음 단계가 수행됩니다.
--- 1. V가 객체가 아니면 false를 반환 합니다.
--- 2. 속성 이름이 "prototype" 인 F의 [[Get]] 메서드를 호출합니다 .
--- 3. O를 결과 (2)로.
--- 4. O가 객체가 아니면 TypeError 예외를 발생시킵니다.
--- 5. V를 V의 [[Prototype]] 속성 값이라고합니다.
--- 6. V가 ** null **이면 false를 반환 합니다.
--- 7. O와 V가 객체를 참조하거나 서로 결합 된 참조 (13.1.2) 반환 합니다. .
--- 8. 5 단계로 이동합니다.

즉, 일련의 타입으로 이동 한 후 사용할 때 아닐 때까지 또는 지정된 메서드를 사용하는 것이 아닐 때까지 또는 지정된 [[HasInstance]]메서드를 사용하는 것 입니다. 왼편이 오른편의 인스턴스인지 확인하고 왼편이 거의 모든 의미입니다.

function x() {
    if (this instanceof x) {
        /* Probably invoked as constructor */
    } else return 30;
}

어쩌면 내가 틀렸을 수도 있다고 (기생충을 신호 서) 다음 코드가 해결책처럼.

function x(arg) {
    //console.debug('_' in this ? 'function' : 'constructor'); //WRONG!!!
    //
    // RIGHT(as accepted)
    console.debug((this instanceof x && !('_' in this)) ? 'function' : 'constructor');
    this._ = 1;
    return 30;
}
var result1 = x(4),     // function
    result2 = new x(4), // constructor
    Z = new x();        // constructor
Z.lolol = x; 
Z.lolol();              // function

사용 this instanceof arguments.callee(선택적으로 대체 arguments.callee뭔가가 생성자로 호출되어 있는지 확인하는 기능을 향상에서 기능과). 쉽게 사용할 수 있으므로 사용 하지 않습니다.this.constructor .


Tim Down 내가 맞다고 생각합니다. 두 통화 모드를 구분할 수 있습니다.” this라고 생각하는 지점에 도달하면 "키워드를 사용하지 않습니다 . this많은 수의 전역 개체가 있습니다. 실제로, 일부는 작동하는 기능이 있고 작동을 수행하고 다른 일부는 작동을 수행하는 것입니다. 그 때문에 내려고하는 것입니다.

호출 방법에 관계없이 동일하게 작동하는 생성자 함수를 만드는 관용적 방법이 있습니다. Thing (), new Thing () 또는 foo.Thing ()과 같은 것입니다. 다음과 같이 진행됩니다.

function Thing () {
   var that = Object.create(Thing.prototype);
   that.foo="bar";
   that.bar="baz";
   return that;
}

여기서 Object.create는 다음과 같이 일반 자바 펼쳐보기로 구현할 수있는 새로운 ecmascript 5 표준 메소드입니다.

if(!Object.create) {
    Object.create = function(Function){
        // WebReflection Revision
       return function(Object){
           Function.prototype = Object;
           return new Function;
    }}(function(){});
}

Object.create는 개체를 매개 변수로 사용하고 개체에 전달 된 새 개체를 제공합니다.

그러나 실제로는 행동이 호출되는 방식에 따라 다르게 동작하도록 제안하는 것이 제안 코드를 작성합니다.


__previouslyConstructedByX속성에 object- 을 넣지 않으려면 - 객체의 공용 인터페이스를 오염시키고 쉽게 덮어 쓸 수 있기 때문에 - x다음 인스턴스를 반환하지 마십시오 .

function x() {

    if(this instanceof x) {
        console.log("You invoked the new keyword!");
        return that;
    }
    else {
        console.log("No new keyword");
        return undefined;
    }

}

x();
var Z = new x(); 
Z.lolol = x; 
Z.lolol();
new Z.lolol();

이제 x함수는 유형의 객체를 반환하지 x않을 때 키워드로 this instanceof x함수가 호출 될 때만 true로 평가 new됩니다.

단점은 이것이 효과적으로 동작을 망칠 수 있다는 것입니다 instanceof. 그러나 얼마나 많이 사용 하느냐에 따라 문제가 없을 수도 없습니다.


두 경우 모두 반환을 목표로하는 경우 30다음 인스턴스 Number대신 인스턴스를 반환 할 수 있습니다 x.

function x() {

    if(this instanceof x) {
        console.log("You invoked the new keyword!");
        var that = {};
        return new Number(30);
    }
    else {
        console.log("No new");
        return 30;
    }

}

console.log(x());
var Z = new x();
console.log(Z);
Z.lolol = x;
console.log(Z.lolol());
console.log(new Z.lolol());

이와 같은 문제가 발생했습니다.

함수 시작 부분에 "this"가 있는지 확인합니다.

function RGB(red, green, blue) {
    if (this) {
        throw new Error("RGB can't be instantiated");
    }

    var result = "#";
    result += toHex(red);
    result += toHex(green);
    result += toHex(blue);

    function toHex(dec) {
        var result = dec.toString(16);

        if (result.length < 2) {
            result = "0" + result;
        }

        return result;
    }

    return result;
}

어쨌든 결국에는 RGB () 의사 클래스를 rgb () 함수로 결정 인스턴스화하지 않기 때문에 안전 검사가 전혀하지 않습니다. 그러나 그것은 당신이하려는 일에 달려 있습니다.


function createConstructor(func) {
    return func.bind(Object.create(null));
}

var myClass = createConstructor(function myClass() {
    if (this instanceof myClass) {
        console.log('You used the "new" keyword');
    } else {
        console.log('You did NOT use the "new" keyword');
        return;
    }
    // constructor logic here
    // ...
});

이 표기되지 않은 오래 사용 엄격 모드 ( )에서 함수의 언급 이 이전과 같이 global / window로 설정없이 정의되지 않는다고 한 사람이 많지에 놀랐습니다 . 따라서 새로운가 발생하지 않았는지 여부 확인 결과 값을 테스트하십시오. -예 :'use strict'thisfalsey!this

function ctor() { 'use strict';
  if (typeof this === 'undefined') 
    console.log('Function called under strict mode (this == undefined)');
  else if (this == (window || global))
    console.log('Function called normally (this == window)');
  else if (this instanceof ctor)
    console.log('Function called with new (this == instance)');
  return this; 
}

해당 함수를 그대로 테스트하면 함수 시작 부분의 지시문으로 인해 this값으로 정의되지 않습니다 'use strict'. 물론 이미 모드가 켜져 있으면 엄격한 'use strict'지시문 을 제거해도 계명 변경되지 않지만 오는가 않으면 제거하면 this값이 window또는 로 설정됩니다 global. new를 호출하는 함수 데 사용 하는 경우 this값은 instanceof를와 일치합니다 확인 (다른 사항을 확인했다면 인스턴스를가 마지막 옵션 이므로이 검사는 필요하지 않으며 어쨌든 인스턴스를 상속하려면 피해야합니다 )

function ctor() { 'use strict';
  if (!this) return ctor.apply(Object.create(ctor.prototype), arguments);
  console.log([this].concat([].slice.call(arguments)));
  return this;
}

그러면 this콘솔에 함수에 전달한 값과 인수 가 기록되고 반환 this됩니다. 상기 중간 this값이 falsey그것을 이용한 새로운 인스턴스를 생성 후 Object.create(ctor.prototype)및 용법으로 Function.apply()동일하지만 PARAMS와 같은 올바른 인스턴스 생성 복원 재 호출 this. 는 IF this값이 이외의 다른 것입니다 falsey.


질문의 상단에, 아래 코드는 함수가 새로운없이 호출되는 경우 문제를 자동 수정합니다.

function Car() {

    if (!(this instanceof Car)) return new Car();

    this.a = 1;
    console.log("Called as Constructor");

}
let c1 = new Car();
console.log(c1);

참고 URL : https://stackoverflow.com/questions/367768/how-to-detect-if-a-function-is-called-as-constructor

반응형