ES6 sınıflarıyla İşlev nasıl genişletilir?


106

ES6, özel nesnelerin genişletilmesine izin verir. Yani işlevden miras almak mümkündür. Böyle bir nesne bir işlev olarak çağrılabilir, ancak böyle bir çağrı için mantığı nasıl uygulayabilirim?

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

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

Herhangi bir sınıf yöntemi, aracılığıyla sınıf örneğine başvuru alır this. Ancak bir işlev olarak adlandırıldığında, thisifade eder window. İşlev olarak çağrıldığında sınıf örneğinin başvurusunu nasıl alabilirim?

Not: Rusça da aynı soru.


18
Ah, sonunda birisi bu soruyu sordu :-)
Bergi

1
Sadece yap super(x)(yani iletmek Function)? FunctionYine de uzatılabileceğinden emin değilim .
Felix Kling

Yerleşik sınıfların genişletilmesiyle ilgili hala sorunlar olduğunu unutmayın. Spesifikasyonlar bunun mümkün olabileceğini öne sürüyor, ancak Errordiğerlerinin yanı sıra genişleyen sorunlarla karşılaştım .
ssube

1
Bunun Functionbasitçe bir işlev yapıcısı olduğunu unutmayın . İşlevin uygulanması kurucuya aktarılmalıdır. Bir Smthuygulamayı kabul etmek istemiyorsanız , kurucuda sağlamalısınız, yani super('function implementation here').
Felix Kling

1
@Qwertiy: Bunun genel durum değil istisna olduğunu iddia ediyorum . Bu aynı zamanda işlev ifadelerine çok özeldir , ancak Functionbir işlev ifadesinden (sözdizimi) çok farklı olan yapıcıyı (çalışma zamanı) kullanıyorsunuz .
Felix Kling

Yanıtlar:


49

superÇağrı çağıracağı Functionbir şifre dizisi beklediği yapıcı. Örnek verilerinize erişmek istiyorsanız, sadece sabit bir şekilde kodlayabilirsiniz:

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

ama bu gerçekten tatmin edici değil. Bir kapatma kullanmak istiyoruz.

Döndürülen işlevin örnek değişkenlerinize erişebilen bir kapanış olması mümkündür, ancak kolay değildir. İşin iyi yanı, istemiyorsanız aramanıza supergerek olmamasıdır - yine de returnES6 sınıf kurucularınızdan rastgele nesneler yapabilirsiniz . Bu durumda yapardık

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

Ancak daha da iyisini yapabilir ve bu şeyi şunlardan soyutlayabiliriz 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;
  }
}

Kuşkusuz bu, kalıtım zincirinde ek bir dolaylılık seviyesi yaratır, ancak bu mutlaka kötü bir şey değildir (yerel yerine genişletebilirsiniz Function). Bundan kaçınmak istiyorsanız, kullanın

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

ancak bunun Smthstatik Functionözellikleri dinamik olarak devralmayacağına dikkat edin .


İşlevden sınıf durumuna erişmek istiyorum.
Qwertiy

2
@Qwertiy: O zaman Bergi'nin ikinci önerisini kullanın.
Felix Kling

@ AlexanderO'Mara: SmthÖrneklerinizin olmasını istiyorsanız instanceof Smth(herkesin beklediği gibi) işlevin prototipini değiştiremezsiniz . Object.setPrototypeOfBuna veya sınıfınızda bildirilen prototip yöntemlerinden herhangi birine ihtiyacınız yoksa aramayı atlayabilirsiniz .
Bergi

@ AlexanderO'Mara: Object.setPrototypeOfNesneyi oluşturduktan hemen sonra yapıldığı sürece optimizasyon tehlikesi o kadar da fazla değildir. Sadece, bir nesnenin [[prototip]] 'ini ömrü boyunca ileri geri mutasyona uğratırsanız kötü olur.
Bergi

1
@amn Hayır, kullanmadığınızda thisve returnbir nesne kullanmazsınız .
Bergi

32

Bu, prototiplerle uğraşmadan nesne üyelerine doğru şekilde referans veren ve doğru mirası koruyan çağrılabilir nesneler oluşturmaya yönelik bir yaklaşımdır.

Basitçe:

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

Bu sınıfı genişletin ve bir __call__yöntem ekleyin , daha fazlası aşağıda ...

Kod ve yorumlarda bir açıklama:

// 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 üzerinde görüntüle

Daha fazla açıklama bind:

function.bind()çok benzer şekilde çalışır function.call()ve benzer bir yöntem imzasını paylaşırlar:

fn.call(this, arg1, arg2, arg3, ...);mdn hakkında daha fazlası

fn.bind(this, arg1, arg2, arg3, ...);mdn hakkında daha fazlası

Her ikisinde de ilk bağımsız değişken this, işlevin içindeki bağlamı yeniden tanımlar . Ek bağımsız değişkenler de bir değere bağlanabilir. Ancak callişlevi hemen bağlı değerlerle çağırdığında, bindorijinali thisve herhangi bir argüman ön ayarıyla şeffaf bir şekilde saran "egzotik" bir işlev nesnesi döndürür .

Yani bir işlevi tanımladığınızda, bindbazı argümanları:

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

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

Bağlı işlevi yalnızca kalan bağımsız değişkenlerle çağırırsınız, bağlamı önceden ayarlanmıştır, bu durumda ['hello'].

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

Lütfen neden bindişe yaradığına dair bir açıklama ekleyebilir misiniz (yani neden bir örneğini döndürüyor ExFunc)?
Bergi

@Bergi bind, çağrıldığı işlev nesnesini, yani çağrılabilir nesnemiz olan, yalnızca thisbağlam geri tepmesi ile saran şeffaf bir işlev nesnesi döndürür . Yani gerçekten şeffaf bir şekilde sarılmış bir örneğini döndürür ExFunc. Yayın, hakkında daha fazla bilgi ile güncellendi bind.
Adrien

1
@Bergi Tüm alıcılar / ayarlayıcılar ve yöntemlere erişilebilir, özellikler / öznitelikler constructorsonra bindiçinde atanmalıdır ExFunc. ExFunc alt sınıflarında tüm üyelere erişilebilir. Gelince instanceof; es6'da bağımlı işlevler egzotik olarak adlandırılır, bu nedenle iç işleyişleri belirgin değildir, ancak çağrıyı sarmalanmış hedefine ilettiğini düşünüyorum Symbol.hasInstance. Proxy'ye çok benzer, ancak istenen efekti elde etmenin basit bir yöntemidir. Onların imzadır benzer değil aynı.
Adrien

1
@Adrien ama içeriden __call__erişemiyorum this.aveya this.ab(). örneğin repl.it/repls/FelineFinishedDesktopenvironment
soymak

1
@rob iyi tespit edildi, bir referans hatası var, cevabı ve kodu bir düzeltme ve yeni bir açıklama ile güncelledim.
Adrien

20

Bir de Smth örneğini sarabilirsiniz proxy bir ile apply(ve belki construct) tuzak:

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

İyi fikir. Bunun gibi. Başvurunun içine yerleştirmek yerine daha fazla mantık uygulamalı mıyım?
Qwertiy

4
Bir vekil oldukça ek yük getirir, değil mi? Ayrıca, thishala boş bir işlevdir (kontrol edin new Smth().toString()).
Bergi

2
@Bergi Performans hakkında hiçbir fikrim yok. MDN'nin büyük kırmızı ve kalın bir uyarısı var setPrototypeOfve proxy'ler hakkında hiçbir şey söylemiyor. Ancak vekiller kadar sorunlu olabilir setPrototypeOf. Ve hakkında toString, içinde özel bir yöntemle gölgelenebilir Smth.prototype. Yerel olan zaten uygulamaya bağlıdır.
Oriol

@Qwertiy constructDavranışını belirtmek için bir tuzak ekleyebilirsiniz new new Smth(256)(). Ve toStringBergi'nin belirttiği gibi, bir işlevin koduna erişen yerel olanları gölgeleyen özel yöntemler ekleyin .
Oriol

Ben ment senin olan applyyöntem kullanılacak gerekiyordu şekilde uygulandığında, ya da sadece bir gösteri olduğunu ve hakkında daha fazla bilgi incelemem gerekiyor Proxyve Reflectdüzgün bir şekilde kullanılır?
Qwertiy

3

Bergi'nin cevabındaki tavsiyeyi aldım ve bir UÖM modülüne sardım .

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

Güncelleme:

Ne yazık ki bu pek işe yaramıyor çünkü artık bir sınıf yerine bir işlev nesnesi döndürüyor, bu yüzden prototipi değiştirmeden bu gerçekten yapılamaz gibi görünüyor. Topal.


Temelde sorun, kurucu için thisdeğer belirlemenin bir yolu olmamasıdır Function. Bunu gerçekten yapmanın tek yolu, .bindyöntemi daha sonra kullanmaktır , ancak bu çok Sınıf dostu değildir.

Bunu yardımcı bir temel sınıfta yapabilirdik, ancak thisilk superçağrının sonrasına kadar kullanılamaz , bu yüzden biraz zor.

Çalışma Örneği:

'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))());

(Örnek, modern tarayıcı veya gerektirir node --harmony.)

Temel olarak, temel işlev ClassFunctionextends, Functionyapıcı çağrısını benzer .bindancak daha sonra ilk çağrıda bağlanmaya izin veren özel bir işlevle sarmalar. Daha sonra ClassFunctionyapıcının kendisinde, superartık bağlı işlev olan döndürülen işlevi çağırır thisve özel bağlama işlevini ayarlamayı bitirmek için geçer .

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

Bunların hepsi oldukça karmaşık, ancak optimizasyon nedenleriyle kötü form olarak kabul edilen ve tarayıcı konsollarında uyarılar oluşturabilen prototipin değiştirilmesini engelliyor.


1
İşleri çok karmaşık hale getiriyorsun. o anonim sınıftan boundaldığınız işleve atıfta bulunacaktır return. Sadece adlandırın ve doğrudan bakın. Ayrıca, kod dizelerini etrafta geçirmekten kaçınmanızı tavsiye ederim, bunlar sadece çalışmak için bir karmaşa ( geliştirme sürecinin her adımında).
Bergi

Bu extends, beklendiği gibi çalışmıyor Function.isPrototypeOf(Smth)ve aynı zamanda new Smth instanceof Functionyanlış.
Bergi

@Bergi Hangi JS motorunu kullanıyorsunuz? console.log((new Smth) instanceof Function);olan trueDüğüm v5.11.0 ve en son Firefox'ta benim için.
Alexander O'Mara

Oops, yanlış örnek. Bu new Smth instanceof Smthsizin çözümünüzle çalışmıyor. Ayrıca Smthörneklerinizde hiçbir yöntem kullanılamayacaktır - yalnızca bir standart döndürdüğünüz için Function, bir Smth.
Bergi

1
@Bergi Lanet olsun, haklısın gibi görünüyor. Bununla birlikte, herhangi bir yerel türü genişletmek de aynı soruna sahip görünüyor. extend Functionayrıca new Smth instanceof Smthyanlış yapar .
Alexander O'Mara

1

Önce çözüme ulaştım arguments.calleeama çok kötüydü.
Küresel katı moda geçmesini bekliyordum ama orada bile işe yarıyor gibi görünüyor.

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

(new Smth(90))()

arguments.calleeKodu kullanmak , bir dizge olarak geçirmek ve katı olmayan modda çalıştırmaya zorlamak nedeniyle kötü bir yoldu . Ancak, geçersiz kılma fikri applyortaya çıktı.

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;
  }
}

Ve test, bunu farklı şekillerde işlev olarak çalıştırabileceğimi gösteriyor:

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"

Sürüm

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

aslında bindişlevsellik içerir :

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

Sürüm

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

yapar callve applyüzerine windowtutarsız:

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

bu nedenle çek şuraya taşınmalıdır apply:

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

1
Aslında ne yapmaya çalışıyorsun?
Teşekkürler

2
Sınıfların her zaman katı modda olduğunu düşünüyorum: stackoverflow.com/questions/29283935/…
Alexander O'Mara

@ AlexanderO'Mara, bu arada, thispenceredir, tanımsız değildir, bu nedenle oluşturulan işlev katı modda değildir (en azından kromda).
Qwertiy

Lütfen bu cevabı hafife almayı bırakın. Bunun kötü bir yol olduğunu zaten yazdım. Ama bu gerçekten bir cevap - hem FF'de hem de Chrome'da çalışıyor (kontrol edilecek Edge yok).
Qwertiy

Sanırım bu işe yarıyor çünkü Functionkatı modda değil. Korkunç olsa da, ilginç +1. Muhtemelen bir zinciri daha fazla yürüyemezsin.
Alexander O'Mara

1

Bu, geliştirdiğim, fonksiyon genişletme konusundaki tüm ihtiyaçlarıma hizmet eden ve bana oldukça iyi hizmet eden çözüm. Bu tekniğin faydaları şunlardır:

  • Uzatırken ExtensibleFunction , kodu (hiç taklit kurucular veya dolaylı ile mucking) herhangi bir ES6 sınıfı uzanan deyimseldir.
  • Prototip zinciri tüm alt sınıflarda tutulur ve instanceof/ .constructorbeklenen değerleri döndürür.
  • .bind() .apply()ve .call()tümü beklendiği gibi çalışır. Bu, "iç" işlevin bağlamını, işlevin tersine değiştirmek için bu yöntemleri geçersiz kılarak yapılır.ExtensibleFunction (veya alt sınıfı) örneğinin .
  • .bind()işlev yapıcısının yeni bir örneğini döndürür (o ExtensibleFunctionveya bir alt sınıf). Object.assign()Bağlı işlevde depolanan özelliklerin, başlatan işlevin özellikleriyle tutarlı olmasını sağlamak için kullanır .
  • Kapanışlara saygı duyulur ve ok işlevleri uygun bağlamı korumaya devam eder.
  • "İç" işlev, Symbolmodüller veya bir IIFE (veya referansları özelleştirmeye yönelik diğer yaygın bir teknik) tarafından gizlenebilen a aracılığıyla saklanır .

Ve daha fazla uzatmadan, kod:

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

Düzenle

Havamda olduğumdan, bunun için npm'de bir paket yayınlayacağımı düşündüm .


1

JavaScript'in işlevsel yeteneklerinden yararlanan basit bir çözüm var: "mantığı" sınıfınızın yapıcısına bir işlev bağımsız değişkeni olarak iletin, bu sınıfın yöntemlerini bu işleve atayın, ardından sonuç olarak yapıcıdan bu işlevi geri döndürün :

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

Yukarıdakiler Node.js 8 üzerinde test edilmiştir.

Yukarıdaki örneğin bir dezavantajı, süper sınıf zincirinden miras alınan yöntemleri desteklememesidir. Bunu desteklemek için "Object. GetOwnPropertyNames (...)" yerine miras alınan yöntemlerin adlarını da döndüren bir şeyle değiştirin. Bunun nasıl yapılacağına inanıyorum ki Stack Overflow'daki başka bir soru-cevapta açıklanmıştır :-). BTW. ES7'nin miras alınan yöntemlerin adlarını üretmek için bir yöntem eklemesi güzel olurdu ;-).

Miras alınan yöntemleri desteklemeniz gerekiyorsa, yukarıdaki sınıfa tüm miras alınan ve yerel yöntem adlarını döndüren statik bir yöntem eklemeniz bir olasılıktır. Sonra bunu kurucudan arayın. Daha sonra bu Funk sınıfını genişletirseniz, bu statik yöntemi de miras alırsınız.


Sanırım bu örnek, "... böyle bir çağrı için mantığı nasıl uygulayabilirim" sorusuna basit bir cevap veriyor. Yapıcıya işlev değerli bir argüman olarak iletin. Yukarıdaki kodda, Funk sınıfı, yapabilse de, Fonksiyonu açıkça genişletmez, gerçekten buna gerek yoktur. Gördüğünüz gibi, herhangi bir sıradan işlevi çağırdığınız gibi, onun "örneklerini" çağırabilirsiniz.
Panu Logic
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.