ES6 클래스로 기능을 확장하는 방법은 무엇입니까?


105

ES6는 특수 개체를 확장 할 수 있습니다. 따라서 함수에서 상속 할 수 있습니다. 이러한 객체는 함수로 호출 할 수 있지만 이러한 호출에 대한 논리를 어떻게 구현할 수 있습니까?

class Smth extends Function {
  constructor (x) {
    // What should be done here
    super();
  }
}

(new Smth(256))() // to get 256 at this call?

클래스의 모든 메서드는를 통해 클래스 인스턴스에 대한 참조를 가져옵니다 this. 이 함수로 호출 될 때, this을 의미한다 window. 함수로 호출 될 때 클래스 인스턴스에 대한 참조를 어떻게 얻을 수 있습니까?

추신 : 러시아어로도 같은 질문입니다.


17
아, 마지막으로 사람이 queston :-) 질문
BERGI

1
그냥 하시겠습니까 super(x)(예 :에 전달 Function)? Function하지만 실제로 확장 할 수 있는지 확실하지 않습니다 .
Felix Kling

내장 클래스를 확장하는 데 여전히 문제가 있음을 명심하십시오. 사양은 그것이 가능해야한다고 제안하지만 Error다른 것들 중에서 확장하는 문제에 부딪 혔습니다.
ssube

1
이는 Function단순히 함수 생성자 임을 명심하십시오 . 함수의 구현은 생성자에게 전달되어야합니다. Smth구현을 수락 하지 않으려면 생성자에서 제공해야합니다 super('function implementation here').
펠릭스 클링

1
@Qwertiy : 이것이 일반적인 경우 가 아니라 예외 라고 주장합니다 . 이것은 또한 함수 표현식 에만 적용 되지만 Function함수 표현식 (구문)과는 매우 다른 생성자 (런타임)를 사용하고 있습니다.
Felix Kling

답변:


49

super호출은 Function코드 문자열을 예상하는 생성자 를 호출 합니다. 인스턴스 데이터에 액세스하려면 하드 코딩하면됩니다.

class Smth extends Function {
  constructor(x) {
    super("return "+JSON.stringify(x)+";");
  }
}

그러나 그것은 정말로 만족스럽지 않습니다. 클로저를 사용하고 싶습니다.

반환 된 함수가 인스턴스 변수에 액세스 할 수 있는 클로저가되는 것은 가능하지만 쉽지는 않습니다. 좋은 점은 super원하지 않는 경우 호출 할 필요가 없다는 것 return입니다. ES6 클래스 생성자에서 임의의 객체를 사용할 수 있습니다 . 이 경우 우리는

class Smth extends Function {
  constructor(x) {
    // refer to `smth` instead of `this`
    function smth() { return x; };
    Object.setPrototypeOf(smth, Smth.prototype);
    return smth;
  }
}

그러나 우리는 더 잘할 수 있으며 다음에서 이것을 추상화 할 수 있습니다 Smth.

class ExtensibleFunction extends Function {
  constructor(f) {
    return Object.setPrototypeOf(f, new.target.prototype);
  }
}

class Smth extends ExtensibleFunction {
  constructor(x) {
    super(function() { return x; }); // closure
    // console.log(this); // function() { return x; }
    // console.log(this.prototype); // {constructor: …}
  }
}
class Anth extends ExtensibleFunction {
  constructor(x) {
    super(() => { return this.x; }); // arrow function, no prototype object created
    this.x = x;
  }
}
class Evth extends ExtensibleFunction {
  constructor(x) {
    super(function f() { return f.x; }); // named function
    this.x = x;
  }
}

물론 이것은 상속 체인에 추가적인 수준의 간접을 생성하지만 반드시 나쁜 것은 아닙니다 (native 대신 확장 할 수 있음 Function). 그것을 피하려면

function ExtensibleFunction(f) {
  return Object.setPrototypeOf(f, new.target.prototype);
}
ExtensibleFunction.prototype = Function.prototype;

그러나 Smth동적으로 정적 Function속성을 상속하지는 않습니다 .


함수에서 클래스 상태에 액세스하고 싶습니다.
Qwertiy

2
@Qwertiy : 그런 다음 Bergi의 두 번째 제안을 사용합니다.
Felix Kling 16.

@ AlexanderO'Mara : Smth인스턴스를 원하면 함수의 프로토 타입을 변경하지 않습니다 instanceof Smth(모든 사람이 예상하는대로). Object.setPrototypeOf이것이 필요하지 않거나 클래스에서 선언 된 프로토 타입 메서드가 필요하지 않은 경우 호출을 생략 할 수 있습니다 .
Bergi

@ AlexanderO'Mara : 또한 Object.setPrototypeOf객체를 생성 한 직후에 수행되는 한 최적화 위험은 그다지 많지 않습니다. 개체의 [[prototype]]을 수명 동안 앞뒤로 변경하면 좋지 않습니다.
Bergi

1
@amn 아니, 당신은 사용하지 않을 때 thisreturn개체.
Bergi

32

이것은 프로토 타입을 엉망으로 만들지 않고 개체 멤버를 올바르게 참조하고 올바른 상속을 유지하는 호출 가능한 개체를 만드는 방법입니다.

간단히:

class ExFunc extends Function {
  constructor() {
    super('...args', 'return this.__self__.__call__(...args)')
    var self = this.bind(this)
    this.__self__ = self
    return self
  }

  // Example `__call__` method.
  __call__(a, b, c) {
    return [a, b, c];
  }
}

이 클래스를 확장하고 __call__메서드를 추가하십시오 .

코드 및 주석 설명 :

// This is an approach to creating callable objects
// that correctly reference their own object and object members,
// without messing with prototypes.

// A Class that extends Function so we can create
// objects that also behave like functions, i.e. callable objects.
class ExFunc extends Function {
  constructor() {
    super('...args', 'return this.__self__.__call__(...args)');
    // Here we create a function dynamically using `super`, which calls
    // the `Function` constructor which we are inheriting from. Our aim is to create
    // a `Function` object that, when called, will pass the call along to an internal
    // method `__call__`, to appear as though the object is callable. Our problem is
    // that the code inside our function can't find the `__call__` method, because it
    // has no reference to itself, the `this` object we just created.
    // The `this` reference inside a function is called its context. We need to give
    // our new `Function` object a `this` context of itself, so that it can access
    // the `__call__` method and any other properties/methods attached to it.
    // We can do this with `bind`:
    var self = this.bind(this);
    // We've wrapped our function object `this` in a bound function object, that
    // provides a fixed context to the function, in this case itself.
    this.__self__ = self;
    // Now we have a new wrinkle, our function has a context of our `this` object but
    // we are going to return the bound function from our constructor instead of the
    // original `this`, so that it is callable. But the bound function is a wrapper
    // around our original `this`, so anything we add to it won't be seen by the
    // code running inside our function. An easy fix is to add a reference to the
    // new `this` stored in `self` to the old `this` as `__self__`. Now our functions
    // context can find the bound version of itself by following `this.__self__`.
    self.person = 'Hank'
    return self;
  }
  
  // An example property to demonstrate member access.
  get venture() {
    return this.person;
  }
  
  // Override this method in subclasses of ExFunc to take whatever arguments
  // you want and perform whatever logic you like. It will be called whenever
  // you use the obj as a function.
  __call__(a, b, c) {
    return [this.venture, a, b, c];
  }
}

// A subclass of ExFunc with an overridden __call__ method.
class DaFunc extends ExFunc {
  constructor() {
    super()
    this.a = 'a1'
    this.b = 'b2'
    this.person = 'Dean'
  }

  ab() {
    return this.a + this.b
  }
  
  __call__(ans) {
    return [this.ab(), this.venture, ans];
  }
}

// Create objects from ExFunc and its subclass.
var callable1 = new ExFunc();
var callable2 = new DaFunc();

// Inheritance is correctly maintained.
console.log('\nInheritance maintained:');
console.log(callable2 instanceof Function);  // true
console.log(callable2 instanceof ExFunc);  // true
console.log(callable2 instanceof DaFunc);  // true

// Test ExFunc and its subclass objects by calling them like functions.
console.log('\nCallable objects:');
console.log( callable1(1, 2, 3) );  // [ 'Hank', 1, 2, 3 ]
console.log( callable2(42) );  // [ 'a1b2', Dean', 42 ]

// Test property and method access
console.log(callable2.a, callable2.b, callable2.ab())

repl.it에서보기

추가 설명 bind:

function.bind()과 유사하게 작동 function.call()하며 유사한 메서드 서명을 공유합니다.

fn.call(this, arg1, arg2, arg3, ...);MDN에 대해 더 알아보기

fn.bind(this, arg1, arg2, arg3, ...);MDN에 대해 더 알아보기

첫 번째 인수 모두에서 this함수 내부의 컨텍스트를 재정의합니다 . 추가 인수를 값에 바인딩 할 수도 있습니다. 그러나 call바인딩 된 값을 사용하여 즉시 함수를 호출하는 곳 bind에서는 원본을 투명하게 감싸는 "이국적인"함수 객체를 반환합니다 this.

따라서 함수를 정의 할 때 bind인수 중 일부는 다음 과 같습니다.

var foo = function(a, b) {
  console.log(this);
  return a * b;
}

foo = foo.bind(['hello'], 2);

나머지 인수 만 사용하여 바인딩 된 함수를 호출합니다. 컨텍스트는이 경우로 미리 설정됩니다 ['hello'].

// We pass in arg `b` only because arg `a` is already set.
foo(2);  // returns 4, logs `['hello']`

bind작동 하는 이유 (즉, 인스턴스를 반환하는 이유)에 대한 설명을 추가해 주 ExFunc시겠습니까?
Bergi

@Bergi bind는 호출 된 함수 객체를 감싸는 투명한 함수 객체를 반환합니다.이 객체는 this컨텍스트 리바운드 와 함께 호출 가능한 객체 입니다. 따라서 실제로 투명하게 래핑 된 ExFunc. 에 대한 추가 정보로 게시물이 업데이트되었습니다 bind.
Adrien 2016

1
@Bergi 모든 게터 / 세터 및 방법은 속성 / 속성은 할당해야 액세스 할 수 있습니다 constructorbind에서 ExFunc. ExFunc의 하위 클래스에서는 모든 멤버에 액세스 할 수 있습니다. 에 관해서는 instanceof; es6에서 바인딩 된 함수는 이국적이라고 불리기 때문에 내부 작동은 분명하지 않지만을 통해 래핑 된 대상에 호출을 전달한다고 생각합니다 Symbol.hasInstance. 프록시와 매우 비슷하지만 원하는 효과를 얻을 수있는 간단한 방법입니다. 그들의 서명은 비슷 하지 않습니다.
Adrien 2011

1
@Adrien하지만 내부에서 __call__나는 액세스 할 수 없습니다 this.athis.ab(). 예 : repl.it/repls/FelineFinishedDesktopenvironment
rob

1
@rob 잘 발견되었습니다. 참조 오류가 있습니다. 답변과 코드를 수정 사항과 새로운 설명으로 업데이트했습니다.
아드

20

당신은에 떨어지게 인스턴스를 래핑 할 수 프록시apply(아마도 construct) 트랩 :

class Smth extends Function {
  constructor (x) {
    super();
    return new Proxy(this, {
      apply: function(target, thisArg, argumentsList) {
        return x;
      }
    });
  }
}
new Smth(256)(); // 256

멋진 아이디어. 이렇게. apply 내부에 배치하는 대신 더 많은 로직을 구현해야합니까?
Qwertiy

4
프록시는 상당한 오버 헤드를 발생시키지 않습니까? 또한 this여전히 빈 함수입니다 (확인 new Smth().toString()).
Bergi

2
@Bergi 성능에 대해 전혀 모릅니다. MDN은 큰 빨간색 굵은 경고를 가지고 setPrototypeOf있으며 프록시에 대해서는 아무 말도하지 않습니다. 하지만 프록시는 setPrototypeOf. 그리고 toString.NET에서 사용자 지정 메서드를 사용하여 섀도 잉 할 수 있습니다 Smth.prototype. 네이티브는 어쨌든 구현에 따라 다릅니다.
Oriol

@Qwertiy construct트랩을 추가하여 의 동작을 지정할 수 있습니다 new new Smth(256)(). 그리고 toStringBergi가 언급했듯이 함수의 코드에 액세스하는 네이티브 메서드를 숨기는 사용자 지정 메서드를 추가 합니다.
Oriol

I, 표준은되는 apply방법이 사용하는 가정되는 방식으로 구현, 아니면 그냥 데모 내가에 대한 자세한 정보를 볼 필요 Proxy하고 Reflect적절한 방법을 사용할 수 있나요?
Qwertiy

3

나는 Bergi의 답변에서 조언을 받아 NPM 모듈 로 포장했습니다 .

var CallableInstance = require('callable-instance');

class ExampleClass extends CallableInstance {
  constructor() {
    // CallableInstance accepts the name of the property to use as the callable
    // method.
    super('instanceMethod');
  }

  instanceMethod() {
    console.log("instanceMethod called!");
  }
}

var test = new ExampleClass();
// Invoke the method normally
test.instanceMethod();
// Call the instance itself, redirects to instanceMethod
test();
// The instance is actually a closure bound to itself and can be used like a
// normal function.
test.apply(null, [ 1, 2, 3 ]);

3

최신 정보:

불행히도 이것은 현재 클래스 대신 함수 객체를 반환하기 때문에 제대로 작동하지 않습니다. 따라서 프로토 타입을 수정하지 않고는 실제로 수행 할 수없는 것 같습니다. 절름발이.


기본적으로 문제는 생성자에 대한 this값을 설정할 방법이 없다는 것입니다 Function. 이 작업을 수행하는 유일한 방법은 .bind나중에 메서드 를 사용하는 것이지만 클래스 친화적이지 않습니다.

헬퍼베이스 클래스에서이 작업을 수행 할 수 있지만 this초기 super호출 이후까지 사용할 수 없으므로 약간 까다 롭습니다.

작업 예 :

'use strict';

class ClassFunction extends function() {
    const func = Function.apply(null, arguments);
    let bound;
    return function() {
        if (!bound) {
            bound = arguments[0];
            return;
        }
        return func.apply(bound, arguments);
    }
} {
    constructor(...args) {
        (super(...args))(this);
    }
}

class Smth extends ClassFunction {
    constructor(x) {
        super('return this.x');
        this.x = x;
    }
}

console.log((new Smth(90))());

(예제에는 최신 브라우저 또는 node --harmony.)

기본적으로 ClassFunctionextends 기본 함수 는와 Function유사한 사용자 정의 함수로 생성자 호출을 래핑 .bind하지만 나중에 첫 번째 호출에서 바인딩을 허용합니다. 그런 다음 ClassFunction생성자 자체에서 super현재 바인딩 된 함수 인 반환 된 함수를 호출 this하여 사용자 지정 바인딩 함수 설정을 완료하기 위해 전달 합니다.

(super(...))(this);

이것은 모두 상당히 복잡하지만 최적화상의 이유로 잘못된 형식으로 간주되고 브라우저 콘솔에서 경고를 생성 할 수있는 프로토 타입을 변경하는 것을 방지합니다.


1
당신은 일을 지나치게 복잡하게 만들고 있습니다. 해당 익명 클래스 bound의 함수를 참조합니다 return. 이름을 지정하고 직접 참조하십시오. 또한 코드 문자열을 전달하지 않는 것이 좋습니다 . 개발 프로세스의 모든 단계 에서 작업하기가 엉망 입니다.
Bergi

그것은 extends실제로 예상대로 작동하지 않는 것 같 Function.isPrototypeOf(Smth)으며 또한 new Smth instanceof Function거짓입니다.
Bergi

@Bergi 어떤 JS 엔진을 사용하고 있습니까? console.log((new Smth) instanceof Function);true노드 v5.11.0 최신 파이어 폭스에서 나를 위해.
Alexander O'Mara

죄송합니다. 잘못된 예입니다. 그것은 new Smth instanceof Smth당신의 솔루션과 작동하지 않습니다. 또한 Smth인스턴스에서의 메서드를 사용할 수 Function없습니다 Smth..
Bergi

1
@Bergi 젠장, 당신이 옳은 것 같습니다. 그러나 기본 유형을 확장하면 동일한 문제가있는 것 같습니다. extend Function또한 new Smth instanceof Smth거짓 을 만듭니다 .
Alexander O'Mara

1

처음에는으로 해결책을 arguments.callee찾았지만 끔찍했습니다.
전역 엄격 모드에서 중단 될 것으로 예상했지만 거기에서도 작동하는 것 같습니다.

class Smth extends Function {
  constructor (x) {
    super('return arguments.callee.x');
    this.x = x;
  }
}

(new Smth(90))()

를 사용 arguments.callee하고 코드를 문자열로 전달하고 엄격하지 않은 모드에서 강제로 실행 하기 때문에 나쁜 방법이었습니다 . 그러나 무시하는 생각보다 apply나타났습니다.

var global = (1,eval)("this");

class Smth extends Function {
  constructor(x) {
    super('return arguments.callee.apply(this, arguments)');
    this.x = x;
  }
  apply(me, [y]) {
    me = me !== global && me || this;
    return me.x + y;
  }
}

그리고 테스트는 다른 방법으로 이것을 함수로 실행할 수 있음을 보여줍니다.

var f = new Smth(100);

[
f instanceof Smth,
f(1),
f.call(f, 2),
f.apply(f, [3]),
f.call(null, 4),
f.apply(null, [5]),
Function.prototype.apply.call(f, f, [6]),
Function.prototype.apply.call(f, null, [7]),
f.bind(f)(8),
f.bind(null)(9),
(new Smth(200)).call(new Smth(300), 1),
(new Smth(200)).apply(new Smth(300), [2]),
isNaN(f.apply(window, [1])) === isNaN(f.call(window, 1)),
isNaN(f.apply(window, [1])) === isNaN(Function.prototype.apply.call(f, window, [1])),
] == "true,101,102,103,104,105,106,107,108,109,301,302,true,true"

버전

super('return arguments.callee.apply(arguments.callee, arguments)');

실제로 다음과 같은 bind기능이 있습니다.

(new Smth(200)).call(new Smth(300), 1) === 201

버전

super('return arguments.callee.apply(this===(1,eval)("this") ? null : this, arguments)');
...
me = me || this;

callapplywindow일관성 :

isNaN(f.apply(window, [1])) === isNaN(f.call(window, 1)),
isNaN(f.apply(window, [1])) === isNaN(Function.prototype.apply.call(f, window, [1])),

따라서 수표는 다음으로 이동해야합니다 apply.

super('return arguments.callee.apply(this, arguments)');
...
me = me !== global && me || this;

1
실제로 무엇을하려고합니까?
감사합니다

2
나는 클래스가 엄격 모드에서 항상 생각 : stackoverflow.com/questions/29283935/...
알렉산더 O'Mara

this그건 그렇고, @ AlexanderO'Mara 는 창이며 정의되지 않았으므로 생성 된 함수는 엄격 모드가 아닙니다 (적어도 크롬에서는).
Qwertiy

이 대답을 무시하지 마십시오. 나는 그것이 나쁜 방법이라고 이미 썼습니다. 그러나 실제로는 답입니다. FF와 Chrome 모두에서 작동합니다 (확인할 Edge가 없음).
Qwertiy

Function엄격 모드가 아니기 때문에 이것이 작동한다고 생각 합니다. 끔찍하지만 재미있는 +1입니다. 당신은 아마 더 이상 사슬을 걸을 수 없을 것입니다.
Alexander O'Mara

1

이것은 기능 확장에 대한 모든 요구 사항을 충족하고 저에게 아주 잘 봉사 한 솔루션입니다. 이 기술의 이점은 다음과 같습니다.

  • 를 확장 할 때 ExtensibleFunction코드는 ES6 클래스를 확장하는 관용적입니다 (아니요, 가장 생성자 또는 프록시로 비웃음).
  • 프로토 타입 체인은 모든 하위 클래스를 통해 유지되며 instanceof/ .constructor예상 값을 반환합니다.
  • .bind() .apply()그리고 .call()모든 기능이 예상대로. 이는 ExtensibleFunction(또는 하위 클래스의) 인스턴스 와는 반대로 "내부"함수의 컨텍스트를 변경하기 위해 이러한 메서드를 재정의함으로써 수행됩니다 .
  • .bind()함수 생성자의 새 인스턴스를 반환합니다 (자체 ExtensibleFunction또는 하위 클래스). Object.assign()바인딩 된 함수에 저장된 속성이 원래 함수의 속성과 일치하는지 확인하는 데 사용 됩니다.
  • 폐쇄가 존중되고 화살표 기능은 적절한 컨텍스트를 계속 유지합니다.
  • "내부"함수 Symbol는를 통해 저장 되며 모듈이나 IIFE (또는 참조를 사유화하는 다른 일반적인 기술)에 의해 난독 화 될 수 있습니다.

그리고 더 이상 고민하지 않고 코드 :

// The Symbol that becomes the key to the "inner" function 
const EFN_KEY = Symbol('ExtensibleFunctionKey');

// Here it is, the `ExtensibleFunction`!!!
class ExtensibleFunction extends Function {
  // Just pass in your function. 
  constructor (fn) {
    // This essentially calls Function() making this function look like:
    // `function (EFN_KEY, ...args) { return this[EFN_KEY](...args); }`
    // `EFN_KEY` is passed in because this function will escape the closure
    super('EFN_KEY, ...args','return this[EFN_KEY](...args)');
    // Create a new function from `this` that binds to `this` as the context
    // and `EFN_KEY` as the first argument.
    let ret = Function.prototype.bind.apply(this, [this, EFN_KEY]);
    // For both the original and bound funcitons, we need to set the `[EFN_KEY]`
    // property to the "inner" function. This is done with a getter to avoid
    // potential overwrites/enumeration
    Object.defineProperty(this, EFN_KEY, {get: ()=>fn});
    Object.defineProperty(ret, EFN_KEY, {get: ()=>fn});
    // Return the bound function
    return ret;
  }

  // We'll make `bind()` work just like it does normally
  bind (...args) {
    // We don't want to bind `this` because `this` doesn't have the execution context
    // It's the "inner" function that has the execution context.
    let fn = this[EFN_KEY].bind(...args);
    // Now we want to return a new instance of `this.constructor` with the newly bound
    // "inner" function. We also use `Object.assign` so the instance properties of `this`
    // are copied to the bound function.
    return Object.assign(new this.constructor(fn), this);
  }

  // Pretty much the same as `bind()`
  apply (...args) {
    // Self explanatory
    return this[EFN_KEY].apply(...args);
  }

  // Definitely the same as `apply()`
  call (...args) {
    return this[EFN_KEY].call(...args);
  }
}

/**
 * Below is just a bunch of code that tests many scenarios.
 * If you run this snippet and check your console (provided all ES6 features
 * and console.table are available in your browser [Chrome, Firefox?, Edge?])
 * you should get a fancy printout of the test results.
 */

// Just a couple constants so I don't have to type my strings out twice (or thrice).
const CONSTRUCTED_PROPERTY_VALUE = `Hi, I'm a property set during construction`;
const ADDITIONAL_PROPERTY_VALUE = `Hi, I'm a property added after construction`;

// Lets extend our `ExtensibleFunction` into an `ExtendedFunction`
class ExtendedFunction extends ExtensibleFunction {
  constructor (fn, ...args) {
    // Just use `super()` like any other class
    // You don't need to pass ...args here, but if you used them
    // in the super class, you might want to.
    super(fn, ...args);
    // Just use `this` like any other class. No more messing with fake return values!
    let [constructedPropertyValue, ...rest] = args;
    this.constructedProperty = constructedPropertyValue;
  }
}

// An instance of the extended function that can test both context and arguments
// It would work with arrow functions as well, but that would make testing `this` impossible.
// We pass in CONSTRUCTED_PROPERTY_VALUE just to prove that arguments can be passed
// into the constructor and used as normal
let fn = new ExtendedFunction(function (x) {
  // Add `this.y` to `x`
  // If either value isn't a number, coax it to one, else it's `0`
  return (this.y>>0) + (x>>0)
}, CONSTRUCTED_PROPERTY_VALUE);

// Add an additional property outside of the constructor
// to see if it works as expected
fn.additionalProperty = ADDITIONAL_PROPERTY_VALUE;

// Queue up my tests in a handy array of functions
// All of these should return true if it works
let tests = [
  ()=> fn instanceof Function, // true
  ()=> fn instanceof ExtensibleFunction, // true
  ()=> fn instanceof ExtendedFunction, // true
  ()=> fn.bind() instanceof Function, // true
  ()=> fn.bind() instanceof ExtensibleFunction, // true
  ()=> fn.bind() instanceof ExtendedFunction, // true
  ()=> fn.constructedProperty == CONSTRUCTED_PROPERTY_VALUE, // true
  ()=> fn.additionalProperty == ADDITIONAL_PROPERTY_VALUE, // true
  ()=> fn.constructor == ExtendedFunction, // true
  ()=> fn.constructedProperty == fn.bind().constructedProperty, // true
  ()=> fn.additionalProperty == fn.bind().additionalProperty, // true
  ()=> fn() == 0, // true
  ()=> fn(10) == 10, // true
  ()=> fn.apply({y:10}, [10]) == 20, // true
  ()=> fn.call({y:10}, 20) == 30, // true
  ()=> fn.bind({y:30})(10) == 40, // true
];

// Turn the tests / results into a printable object
let table = tests.map((test)=>(
  {test: test+'', result: test()}
));

// Print the test and result in a fancy table in the console.
// F12 much?
console.table(table);

편집하다

기분 이 좋아서 npm에 이것에 대한 패키지게시 할 것이라고 생각했습니다 .


1

JavaScript의 기능적 기능을 활용하는 간단한 솔루션이 있습니다. "로직"을 클래스 생성자에 함수 인수로 전달하고 해당 클래스의 메서드를 해당 함수에 할당 한 다음 생성자에서 해당 함수 를 결과로 반환합니다. :

class Funk
{
    constructor (f)
    { let proto       = Funk.prototype;
      let methodNames = Object.getOwnPropertyNames (proto);
      methodNames.map (k => f[k] = this[k]);
      return f;
    }

    methodX () {return 3}
}

let myFunk  = new Funk (x => x + 1);
let two     = myFunk(1);         // == 2
let three   = myFunk.methodX();  // == 3

위는 Node.js 8에서 테스트되었습니다.

위 예제의 단점은 슈퍼 클래스 체인에서 상속 된 메서드를 지원하지 않는다는 것입니다. 이를 지원하려면 "Object. getOwnPropertyNames (...)"를 상속 된 메서드의 이름도 반환하는 것으로 바꾸면됩니다. 내가 믿는 방법은 Stack Overflow :-)의 다른 질문 답변에 설명되어 있습니다. BTW. ES7이 상속 된 메서드의 이름도 생성하는 메서드를 추가하면 좋을 것입니다 ;-).

상속 된 메서드를 지원해야하는 경우 한 가지 가능성은 모든 상속 및 로컬 메서드 이름을 반환하는 위의 클래스에 정적 메서드를 추가하는 것입니다. 그런 다음 생성자에서 호출하십시오. 그런 다음 해당 클래스 Funk를 확장하면 해당 정적 메서드도 상속됩니다.


이 예제는 원래 질문 "... 이러한 호출에 대한 논리를 어떻게 구현할 수 있습니까?"에 대한 간단한 대답을 제공한다고 생각합니다. 함수 값 인수로 생성자에 전달하기 만하면됩니다. 위의 코드에서 Funk 클래스는 가능하더라도 명시 적으로 Function을 확장하지 않습니다. 실제로 그럴 필요는 없습니다. 보시다시피 일반적인 함수를 호출하는 것처럼 "인스턴스"juts를 호출 할 수 있습니다.
Panu Logic
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.