Nice programing

ES6 : 새 키워드없이 클래스 생성자 호출

nicepro 2020. 11. 14. 11:06
반응형

ES6 : 새 키워드없이 클래스 생성자 호출


간단한 수업이 주어짐

class Foo {
  constructor(x) {
    if (!(this instanceof Foo)) return new Foo(x);
    this.x = x;
  }
  hello() {
    return `hello ${this.x}`;
  }
}

new키워드 없이 클래스 생성자를 호출 할 수 있습니까?

사용은 허용해야합니다

(new Foo("world")).hello(); // "hello world"

또는

Foo("world").hello();       // "hello world"

그러나 후자는 실패합니다

Cannot call a class as a function

클래스에는 생성자 인 "클래스 본문"이 있습니다.
내부 constructor()함수 를 사용하면 해당 함수도 동일한 클래스 본문이되며 클래스가 호출 될 때 호출되므로 클래스는 항상 생성자입니다.

생성자는 new연산자를 사용하여 새 인스턴스를 만들어야합니다. new연산자 없이 클래스를 호출 하면 오류가 발생 합니다. 클래스 생성자가 새 인스턴스를 만드는 데 필요 하기 때문입니다.

오류 메시지도 매우 구체적이며 정확합니다.

TypeError : 클래스 생성자는 'new'없이 호출 할 수 없습니다.

당신은 할 수 있습니다;

  • 클래스 1 대신 일반 함수를 사용하십시오 .
  • 항상 new.
  • 클래스 new의 이점을 얻을 수있는 방식으로 항상를 사용하여 래핑 일반 함수 내에서 클래스를 호출 하지만 래핑 함수는 new연산자 2 사용 여부에 관계없이 호출 할 수 있습니다 .

1)

function Foo(x) {
    if (!(this instanceof Foo)) return new Foo(x);
    this.x = x;
    this.hello = function() {
        return this.x;
    }
}

2)

class Foo {
    constructor(x) {
        this.x = x;
    }
    hello() {
        return `hello ${this.x}`;
    }
}

var _old = Foo;
Foo = function(...args) { return new _old(...args) };

다른 사람들이 지적했듯이 ES2015 사양은 이러한 호출이 TypeError를 발생시켜야한다고 엄격하게 명시하지만 동시에 원하는 결과를 정확히 달성하는 데 사용할 수있는 기능, 즉 Proxies를 제공 합니다.

프록시를 사용하면 객체 개념을 가상화 할 수 있습니다. 예를 들어, 다른 것에 영향을주지 않고 특정 개체의 일부 동작을 변경하는 데 사용할 수 있습니다.

특정 사용의 경우 class Foo이다 Function object라고 할 수있는 -이 일반적으로이 기능의 몸이 실행된다는 것을 의미합니다. 그러나 이것은 다음과 Proxy같이 변경할 수 있습니다 .

const _Foo = new Proxy(Foo, {
  // target = Foo
  apply (target, thisArg, argumentsList) {
    return new target(...argumentsList);
  }
});

_Foo("world").hello(); 
const f = _Foo("world");
f instanceof Foo; // true
f instanceof _Foo; // true

( _Foo이제 노출하려는 클래스이므로 식별자는 아마도 반대 방향이어야합니다)

프록시를 지원하는 브라우저에서 실행하는 경우 호출 _Foo(...)은 이제 apply원래 생성자 대신 트랩 함수를 실행 합니다.

동시에이 "새로운" _Foo클래스는 원본과 구별 할 수 없습니다 Foo(일반 함수로 호출 할 수 있다는 점을 제외하고). 마찬가지로 Foo및로 만든 객체를 구분할 수있는 차이가 없습니다 _Foo.

이것의 가장 큰 단점은 transpilled 또는 pollyfilled 할 수 없지만 앞으로도 Scala와 같은 클래스를 JS에 적용 할 수있는 실행 가능한 솔루션입니다.


여기 저에게 정말로 도움이되는 패턴이 있습니다. 를 사용 class하지 않지만 new둘 다 사용할 필요는 없습니다 . 승리 / 승리.

const Foo = x => ({
  x,
  hello: () => `hello ${x}`,
  increment: () => Foo(x + 1),
  add: ({x: y}) => Foo(x + y)
})

console.log(Foo(1).x)                   // 1
console.log(Foo(1).hello())             // hello 1
console.log(Foo(1).increment().hello()) // hello 2
console.log(Foo(1).add(Foo(2)).hello()) // hello 3


아니요, 불가능합니다. 사용하여 만든 생성자 class키워드에서만 구성 할 수 new가있는 경우, [[전화]] 에드 그들은 항상하지 않고 1 (심지어 외부에서이를 감지 할 수있는 방법이 아니다). 1 : 트랜스 파일러가이게 옳은지 잘 모르겠습니다throwTypeError

하지만 일반 함수를 해결 방법으로 사용할 수 있습니다.

class Foo {
  constructor(x) {
    this.x = x;
  }
  hello() {
    return `hello ${this.x}`;
  }
}
{
  const _Foo = Foo;
  Foo = function(...args) {
    return new _Foo(...args);
  };
  Foo.prototype = _Foo.prototype;
}

면책 조항 : instanceof연장 Foo.prototype정상적으로 작업은 Foo.length하지 않습니다, .constructor그리고 정적 메서드는하지 않지만 추가하여 고정 할 수 Foo.prototype.constructor = Foo;Object.setPrototypeOf(Foo, _Foo)필요한 경우.

사용 하여 Foo(아님 _Foo) 서브 클래 싱 하려면 호출 대신을 class Bar extends Foo …사용해야 return Reflect.construct(_Foo, args, new.target)합니다 new _Foo. ES5 스타일 (사용 Foo.call(this, …))의 서브 클래 싱 은 불가능합니다.


class MyClass {

  constructor(param) {
     // ...
  }

  static create(param) {
    return new MyClass(param);
  }

  doSomething() {
    // ...
  }

}

MyClass.create('Hello World').doSomething();

너가 원하는게 그거야?

의 새 인스턴스를 만들 때 몇 가지 논리가 필요한 경우 논리 MyClass를 능가하기 위해 "CreationStrategy"를 구현하는 것이 도움이 될 수 있습니다.

class MyClassCreationStrategy {

  static create(param) {
    let instance = new MyClass();
    if (!param) {
      // eg. handle empty param
    }

    instance.setParam(param);
    return instance;
  }

}

class DefaultCreationStrategy {

  static create(classConstruct) { 
    return new classConstruct(); 
  }

}

MyClassCreationStrategy.create(param).doSomething();
DefaultCreationStrategy.create(MyClass).doSomething();

나는 당신을 위해이 npm 모듈을 만들었습니다.)

https://www.npmjs.com/package/classy-decorator

import classy from "classy-decorator";

@classy()
class IamClassy {
    constructor() {
        console.log("IamClassy Instance!");
    }
}

console.log(new IamClassy() instanceof IamClassy);  // true 

console.log(IamClassy() instanceof IamClassy);  // true 

초안 에서 이걸 파내

클래스 정의 구문을 사용하여 정의 된 생성자는 함수로 호출 될 때 발생합니다.

그래서 나는 그것이 수업에서는 불가능하다고 생각합니다.


수동으로 클래스 생성자를 호출하면 코드를 리팩토링 할 때 유용 할 수 있습니다 (ES6에서 코드의 일부, 함수 및 프로토 타입 정의에 다른 부분 포함).

나는 작지만 유용한 상용구를 사용하여 생성자를 다른 함수로 분할했습니다. 기간.

 class Foo {
  constructor() {
    //as i will not be able to call the constructor, just move everything to initialize
    this.initialize.apply(this, arguments)
  }

  initialize() {
    this.stuff = {};
    //whatever you want
  }
 }

  function Bar () {
    Foo.prototype.initialize.call(this); 
  } 
  Bar.prototype.stuff = function() {}

다음은 '범위 안전 생성자'를 사용할 수있는 곳입니다.이 코드를 관찰하십시오.

function Student(name) {
  if(this instanceof Student) {
    this.name = name;
  } else {
    return new Student(name);
  }
}

이제 다음과 같이 new를 사용하지 않고 Student 개체를 만들 수 있습니다.

var stud1 = Student('Kia');

다른 답변에서 언급 한 변환 함수로 변환 된 클래스를 확장하는 데 문제가 있습니다. 문제는 노드 (v9.4.0 기준)가 인수 확산 연산자 ( (...args) =>)를 제대로 지원하지 않는 것 같습니다 .

classy-decorator ( 다른 답변 에서 언급)의 변환 된 출력을 기반으로 한이 함수 는 나를 위해 작동하며 데코레이터 또는 인수 확산 연산자에 대한 지원이 필요하지 않습니다.

// function that calls `new` for you on class constructors, simply call
// YourClass = bindNew(YourClass)
function bindNew(Class) {
  function _Class() {
    for (
      var len = arguments.length, rest = Array(len), key = 0;
      key < len;
      key++
    ) {
      rest[key] = arguments[key];
    }

    return new (Function.prototype.bind.apply(Class, [null].concat(rest)))();
  }
  _Class.prototype = Class.prototype;
  return _Class;
}

용법:

class X {}
X = bindNew(X);

// or

const Y = bindNew(class Y {});

const x = new X();
const x2 = X(); // woohoo

x instanceof X; // true
x2 instanceof X; // true

class Z extends X {} // works too

보너스로 TypeScript ( "es5"출력 포함)는 이전 instanceof트릭 으로 괜찮아 보입니다 (음,없이 사용하면 typecheck는 아니지만 new어쨌든 작동합니다).

class X {
  constructor() {
    if (!(this instanceof X)) {
      return new X();
    }
  }
}

다음과 같이 컴파일되기 때문입니다.

var X = /** @class */ (function () {
    function X() {
        if (!(this instanceof X)) {
            return new X();
        }
    }
    return X;
}());

여기에 또 다른 답이 있습니다.이 질문은 매우 혁신적이라고 생각합니다.

기본적으로 Naomik의 대답과 비슷한 일을 할 때의 문제는 메서드를 함께 연결할 때마다 함수를 생성한다는 것입니다.

편집 :이 솔루션은 동일한 문제를 공유하지만이 답변은 교육 목적으로 남겨졌습니다.

그래서 여기에서는 기본적으로 독립적 인 함수 인 메서드에 새 값을 바인딩하는 방법을 제공합니다. 이것은 다른 모듈의 함수를 새로 생성 된 객체로 가져올 수있는 추가적인 이점을 제공합니다.

좋아, 여기 간다.

const assoc = (prop, value, obj) => 
  Object.assign({},obj,{[prop]: value})

const reducer = ( $values, accumulate, [key,val] ) => assoc( key, val.bind( undefined,...$values ), accumulate )

const bindValuesToMethods = ( $methods, ...$values ) => 
  Object.entries( $methods ).reduce( reducer.bind( undefined, ...$values), {} )

const prepareInstance = (instanceMethods, staticMethods = ({}) ) => Object.assign(
  bindValuesToMethods.bind( undefined, instanceMethods ),
  staticMethods
)

// Let's make our class-like function

const RightInstanceMethods = ({
  chain: (x,f) => f(x),
  map: (x,f) => Right(f(x)),
  fold: (x,l,r) => r(x),
  inspect: (x) => `Right(${x})`
})

const RightStaticMethods = ({
  of: x => Right(x)
})

const Right = prepareInstance(RightInstanceMethods,RightStaticMethods)

이제 할 수 있습니다

Right(4)
  .map(x=>x+1)
  .map(x=>x*2)
  .inspect()

당신은 또한 할 수 있습니다

Right.of(4)
  .map(x=>x+1)
  .map(x=>x*2)
  .inspect()

또한 모듈에서 내보낼 수 있다는 추가 이점도 있습니다.

export const Right = prepareInstance(RightInstanceMethods,RightStaticMethods)

ClassInstance.constructor당신이 가지고 있지는 않지만 FunctorInstance.name(참고, Function.name브라우저 호환성을 Function.name위해 내보내기를 위해 polyfill 및 / 또는 화살표 기능을 사용하지 않아야 할 수 있습니다 )

export function Right(...args){
  return prepareInstance(RightInstanceMethods,RightStaticMethods)(...args)
}

추신-prepareInstance에 대한 새로운 이름 제안이 환영되었습니다.

https://gist.github.com/babakness/56da19ba85e0eaa43ae5577bc0064456


new키워드 없이 클래스 생성자를 호출 할 수 없습니다.

오류 메시지는 매우 구체적입니다.

2ality사양 에 대한 블로그 게시물을 참조하십시오 .

However, you can only invoke a class via new, not via a function call (Sect. 9.2.2 in the spec):

    > Point()
    TypeError: Classes can’t be function-called

나는 이것을 naomik의 코멘트에 대한 후속 조치로 추가하고 Tim과 Bergi가 설명한 방법을 활용하고 있습니다. of일반적인 경우로 사용할 기능 도 제안 할 예정 입니다.

기능적인 방식으로이를 수행하고 프로토 타입의 효율성을 활용하려면 (새 인스턴스가 생성 될 때마다 모든 메소드를 다시 생성하지 않음)이 패턴을 사용할 수 있습니다.

const Foo = function(x){ this._value = x ... }
Foo.of = function(x){ return new Foo(x) }
Foo.prototype = {
  increment(){ return Foo.of(this._value + 1) },
  ...
}

이것은 fantasy-landJS 사양 과 일치합니다.

https://github.com/fantasyland/fantasy-land#of-method

개인적으로 ES6 클래스 구문을 사용하는 것이 더 깨끗하다고 ​​느낍니다.

class Foo {
  static of(x) { new Foo(x)}
  constructor(x) { this._value = x }
  increment() { Foo.of(this._value+1) }
}

이제 이것을 클로저로 감쌀 수 있습니다.

class Foo {
  static of(x) { new _Foo(x)}
  constructor(x) { this._value = x }
  increment() { Foo.of(this._value+1) }
}


function FooOf (x) {

    return Foo.of(x)

}

또는 이름을 변경 FooOf하고 Foo원하는대로, 즉 클래스가 될 수 FooClass와 기능 단지 Foo

새 인스턴스를 만드는 것은 새 클래스를 만드는 데 부담을주지 않기 때문에 함수에 클래스를 배치하는 것보다 낫습니다.

또 다른 방법은 of함수 를 만드는 것입니다.

const of = (classObj,...args) => (
  classObj.of 
    ? classObj.of(value) 
    : new classObj(args)
)

그리고 다음과 같이하십시오. of(Foo,5).increment()


이것은 약간의 인위적 일 수 있지만 작동합니다.

function Foo(x){
"use strict"

    class Bar {
        constructor(x) {
            if (!(this instanceof Bar)) return new Bar(x);
            this.x = x;
        }

        hello() {
            return `hello ${this.x}`;
        }
    }

    return new Bar(x)
}

Foo("world").hello()

새 생성자 없이는 클래스를 사용할 수 없습니다. 제 경우에는 클래스를 사용하고 싶을 때 new생성자 를 사용하고 싶지 않았기 때문에 다음과 같이 클래스를 래핑하면됩니다 (제 경우에는 날짜 유틸리티 라이브러리) :

enter image description here

참고URL : https://stackoverflow.com/questions/30689817/es6-call-class-constructor-without-new-keyword

반응형