Bagaimana cara memperluas Fungsi dengan kelas ES6?

105

ES6 memungkinkan untuk memperluas objek khusus. Jadi dimungkinkan untuk mewarisi dari fungsinya. Objek seperti itu dapat disebut sebagai fungsi, tetapi bagaimana cara mengimplementasikan logika untuk panggilan tersebut?

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

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

Setiap metode kelas mendapat referensi ke instance kelas melalui this. Tapi ketika itu disebut sebagai fungsi, thismengacu pada window. Bagaimana saya bisa mendapatkan referensi ke instance kelas ketika itu disebut sebagai fungsi?

PS: Pertanyaan yang sama dalam bahasa Rusia.

Qwertiy
sumber
17
Ah, akhirnya seseorang menanyakan pertanyaan ini pada :-)
Bergi
1
Lakukan saja super(x)(yaitu menyebarkannya ke Function)? Tidak yakin apakah Functionbenar-benar dapat diperpanjang.
Felix Kling
Ingatlah bahwa masih ada masalah dengan memperluas kelas bawaan. Spesifikasi menunjukkan bahwa itu harus dimungkinkan, tetapi saya mengalami masalah yang meluas Error, antara lain.
ssube
1
Perlu diingat bahwa Functionini hanyalah konstruktor fungsi. Implementasi fungsi harus diteruskan ke konstruktor. Jika Anda tidak ingin Smthmenerima implementasi, Anda harus menyediakannya di konstruktor, yaitu super('function implementation here').
Felix Kling
1
@ Qwertiy: Saya berpendapat bahwa ini adalah pengecualian , bukan kasus umum. Ini juga sangat spesifik untuk ekspresi fungsi , tetapi Anda menggunakan Functionkonstruktor (runtime) yang sangat berbeda dari ekspresi fungsi (sintaks).
Felix Kling

Jawaban:

49

The superpanggilan akan memanggil Functionkonstruktor, yang mengharapkan string kode. Jika Anda ingin mengakses data instance Anda, Anda dapat membuat hardcode:

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

tapi itu tidak terlalu memuaskan. Kami ingin menggunakan penutupan.

Memiliki fungsi yang dikembalikan menjadi closure yang dapat mengakses variabel instan Anda adalah mungkin, tetapi tidak mudah. Hal baiknya adalah Anda tidak perlu memanggil superjika Anda tidak mau - Anda masih dapat returnmengubah objek dari konstruktor kelas ES6 Anda. Dalam hal ini, kami akan melakukannya

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

Tetapi kita dapat melakukan lebih baik lagi, dan mengabstraksikan hal ini dari 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;
  }
}

Memang, ini menciptakan tingkat tipuan tambahan dalam rantai pewarisan, tetapi itu belum tentu merupakan hal yang buruk (Anda dapat memperpanjangnya alih-alih yang asli Function). Jika Anda ingin menghindarinya, gunakan

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

tetapi perhatikan bahwa Smthtidak akan secara dinamis mewarisi Functionproperti statis .

Bergi
sumber
Saya ingin mendapatkan akses ke status kelas dari fungsi tersebut.
Qwertiy
2
@ Qwertiy: Kalau begitu gunakan saran kedua Bergi.
Felix Kling
@ AlexanderO'Mara: Anda tidak boleh mengubah prototipe fungsi jika Anda ingin Smthinstance Anda menjadi instanceof Smth(seperti yang diharapkan semua orang). Anda dapat menghilangkan Object.setPrototypeOfpanggilan jika Anda tidak memerlukan metode ini atau metode prototipe apa pun yang dideklarasikan di kelas Anda.
Bergi
@ AlexanderO'Mara: Juga Object.setPrototypeOftidak banyak bahaya pengoptimalan selama itu dilakukan tepat setelah membuat objek. Hanya jika Anda memutasikan [[prototipe]] suatu objek bolak-balik selama masa pakainya, hal itu akan berdampak buruk.
Bergi
1
@ amn Tidak, tidak, jika Anda tidak menggunakan thisdan returnobjek.
Bergi
32

Ini adalah pendekatan untuk membuat objek yang dapat dipanggil yang mereferensikan anggota objeknya dengan benar, dan mempertahankan pewarisan yang benar, tanpa mengotak-atik prototipe.

Secara sederhana:

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

Perluas kelas ini dan tambahkan __call__metode, selengkapnya di bawah ...

Penjelasan dalam kode dan komentar:

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

Lihat di repl.it

Penjelasan lebih lanjut tentang bind:

function.bind()berfungsi seperti function.call(), dan mereka berbagi tanda tangan metode serupa:

fn.call(this, arg1, arg2, arg3, ...);lebih lanjut tentang mdn

fn.bind(this, arg1, arg2, arg3, ...);lebih lanjut tentang mdn

Di kedua argumen pertama mendefinisikan ulang thiskonteks di dalam fungsi. Argumen tambahan juga bisa diikat ke nilai. Tapi di mana callsegera memanggil fungsi dengan nilai terikat, bindmengembalikan objek fungsi "eksotis" yang secara transparan membungkus aslinya, dengan thisdan semua argumen preset.

Jadi ketika Anda mendefinisikan suatu fungsi maka bindbeberapa argumennya:

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

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

Anda memanggil fungsi terikat dengan hanya argumen yang tersisa, konteksnya telah diatur sebelumnya, dalam hal ini ke ['hello'].

// We pass in arg `b` only because arg `a` is already set.
foo(2);  // returns 4, logs `['hello']`
Adrien
sumber
Bisakah Anda menambahkan penjelasan mengapa bindberhasil (yaitu mengapa mengembalikan contoh ExFunc)?
Bergi
@Bergi bindmengembalikan objek fungsi transparan yang membungkus objek fungsi yang dipanggilnya, yang merupakan objek yang dapat dipanggil, hanya dengan thiskonteks rebound. Jadi itu benar-benar mengembalikan contoh yang dibungkus secara transparan ExFunc. Posting diperbarui dengan info lebih lanjut tentang bind.
Adrien
1
@Bergi Semua getter / setter dan metode dapat diakses, properti / atribut harus ditetapkan constructorsetelah binddalam ExFunc. Di subclass ExFunc, semua anggota dapat diakses. Adapun instanceof; di es6 fungsi terikat disebut sebagai eksotis, jadi cara kerja dalamnya tidak terlihat, tapi saya pikir itu meneruskan panggilan ke target yang dibungkus, via Symbol.hasInstance. Ini seperti Proxy, tetapi ini adalah metode sederhana untuk mencapai efek yang diinginkan. Tanda tangan mereka mirip tidak sama.
Adrien
1
@Adrien tetapi dari dalam __call__saya tidak dapat mengakses this.aatau this.ab(). mis. repl.it/repls/FelineFinishedDesktopenvironment
rampok
1
@rob well spotted, ada kesalahan referensi, saya telah memperbarui jawaban dan kode dengan perbaikan dan penjelasan baru.
Adrien
20

Anda dapat menggabungkan instance Smth dalam Proxy dengan perangkap apply(dan mungkin construct):

class Smth extends Function {
  constructor (x) {
    super();
    return new Proxy(this, {
      apply: function(target, thisArg, argumentsList) {
        return x;
      }
    });
  }
}
new Smth(256)(); // 256
Oriol
sumber
Ide keren. Seperti ini. Haruskah saya menerapkan lebih banyak logika daripada menempatkan di dalam menerapkan?
Qwertiy
4
Proksi akan menimbulkan biaya tambahan, bukan? Juga, thismasih merupakan fungsi kosong (centang new Smth().toString()).
Bergi
2
@Bergi Tidak tahu tentang kinerja. MDN memiliki peringatan tebal berwarna merah setPrototypeOfdan tidak mengatakan apa pun tentang proxy. Tapi saya kira proxy bisa sama bermasalahnya dengan setPrototypeOf. Dan tentang toString, itu bisa dibayangi dengan metode kustom di Smth.prototype. Yang asli tergantung pada implementasi.
Oriol
@Qwertiy Anda dapat menambahkan constructjebakan untuk menentukan perilakunya new new Smth(256)(). Dan tambahkan metode khusus yang membayangi metode asli yang mengakses kode suatu fungsi, seperti yang toStringdicatat Bergi.
Oriol
Saya ment adalah applymetode Anda yang diterapkan dengan cara yang seharusnya digunakan, atau ini hanya demonstrasi dan saya perlu melihat lebih banyak informasi tentang ProxydanReflect menggunakannya dengan cara yang benar?
Qwertiy
3

Saya mengambil saran dari jawaban Bergi dan membungkusnya menjadi modul 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 ]);
Ryan Patterson
sumber
3

Memperbarui:

Sayangnya ini tidak cukup berfungsi karena sekarang mengembalikan objek fungsi alih-alih kelas, jadi tampaknya ini sebenarnya tidak dapat dilakukan tanpa memodifikasi prototipe. Kuno.


Pada dasarnya masalahnya adalah tidak ada cara untuk mengatur thisnilai Functionkonstruktor. Satu-satunya cara untuk benar-benar melakukan ini adalah dengan menggunakan .bindmetode setelahnya, namun ini tidak terlalu ramah Kelas.

Kita bisa melakukan ini di kelas dasar helper, namun thistidak tersedia sampai setelah superpanggilan awal , jadi ini agak rumit.

Contoh Kerja:

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

(Contoh membutuhkan browser modern atau node --harmony.)

Pada dasarnya fungsi dasar ClassFunctionextends akan membungkus Functionpanggilan konstruktor dengan fungsi kustom yang mirip dengan .bind, tetapi memungkinkan pengikatan nanti, pada panggilan pertama. Kemudian di ClassFunctionkonstruktor itu sendiri, ia memanggil fungsi yang dikembalikan superyang sekarang menjadi fungsi terikat, meneruskan thisuntuk menyelesaikan penyiapan fungsi bind kustom.

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

Ini semua agak rumit, tetapi itu menghindari mutasi prototipe, yang dianggap bentuk buruk untuk alasan pengoptimalan dan dapat menghasilkan peringatan di konsol browser.

Alexander O'Mara
sumber
1
Anda terlalu rumit. boundakan merujuk ke fungsi yang Anda returndari kelas anonim itu. Sebut saja, dan lihat langsung. Saya juga akan merekomendasikan untuk menghindari melewatkan string kode, mereka hanya berantakan untuk dikerjakan (dalam setiap langkah proses pengembangan).
Bergi
Itu extendstidak benar-benar berfungsi seperti yang diharapkan, karena Function.isPrototypeOf(Smth)dan juga new Smth instanceof Functionsalah.
Bergi
@Bergi Mesin JS apa yang Anda gunakan? console.log((new Smth) instanceof Function);adalah truebagi saya dalam Node v5.11.0 dan Firefox terbaru.
Alexander O'Mara
Ups, salah contoh. Itu new Smth instanceof Smthtidak berhasil dengan solusi Anda. Juga tidak ada metode yang Smthakan tersedia pada instans Anda - karena Anda hanya mengembalikan standar Function, bukan file Smth.
Bergi
1
@Bergi Darn itu, sepertinya Anda benar. Namun memperluas jenis asli apa pun tampaknya memiliki masalah yang sama. extend Functionjuga membuat new Smth instanceof Smthpalsu.
Alexander O'Mara
1

Pertama saya datang ke solusi dengan arguments.callee, tapi itu mengerikan.
Saya mengharapkannya untuk istirahat dalam mode ketat global, tetapi sepertinya itu berfungsi bahkan di sana.

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

(new Smth(90))()

Itu cara yang buruk karena menggunakan arguments.callee, meneruskan kode sebagai string dan memaksa eksekusinya dalam mode non-ketat. Tapi ide untuk mengesampingkan applymuncul.

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

Dan pengujiannya, menunjukkan saya dapat menjalankan ini sebagai fungsi dengan cara yang berbeda:

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"

Versi dengan

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

sebenarnya mengandung bindfungsionalitas:

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

Versi dengan

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

membuat calldan applypada windowtidak konsisten:

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

jadi cek harus dipindahkan ke apply:

super('return arguments.callee.apply(this, arguments)');
...
me = me !== global && me || this;
Qwertiy
sumber
1
Apa yang sebenarnya Anda coba lakukan?
Terima kasih
2
Saya pikir Kelas selalu dalam mode ketat: stackoverflow.com/questions/29283935/…
Alexander O'Mara
@ AlexanderO'Mara, omong-omong, thisadalah jendela, tidak ditentukan, jadi fungsi yang dibuat tidak dalam mode ketat (setidaknya di chrome).
Qwertiy
Tolong, berhentilah meremehkan jawaban ini. Saya sudah menulis bahwa ini adalah cara yang buruk. Tetapi ini benar-benar sebuah jawaban - ini berfungsi baik di FF dan Chrome (tidak memiliki Edge untuk diperiksa).
Qwertiy
Saya menduga ini berhasil karena Functiontidak dalam mode ketat. Meski mengerikan, ini menarik +1. Anda mungkin tidak akan bisa berjalan lebih jauh.
Alexander O'Mara
1

Ini adalah solusi yang telah saya kerjakan yang melayani semua kebutuhan saya akan fungsi perluasan dan telah melayani saya dengan cukup baik. Manfaat dari teknik ini adalah:

  • Saat memperluas ExtensibleFunction, kode ini idiomatis untuk memperluas setiap kelas ES6 (tidak, menyia-nyiakan dengan konstruktor atau proxy yang berpura-pura).
  • Rantai prototipe dipertahankan melalui semua subclass, dan instanceof/ .constructormengembalikan nilai yang diharapkan.
  • .bind() .apply()dan .call()semua berfungsi seperti yang diharapkan. Ini dilakukan dengan menimpa metode ini untuk mengubah konteks fungsi "dalam" sebagai lawan dariExtensibleFunction instance (atau subkelasnya).
  • .bind()mengembalikan instance baru dari konstruktor fungsi (baik itu ExtensibleFunctionatau subclass). Ini digunakan Object.assign()untuk memastikan properti yang disimpan di fungsi terikat konsisten dengan yang ada di fungsi asal.
  • Penutupan dihormati, dan fungsi panah terus mempertahankan konteks yang tepat.
  • Fungsi "dalam" disimpan melalui a Symbol, yang dapat dikaburkan oleh modul atau IIFE (atau teknik umum lainnya untuk referensi privatisasi).

Dan tanpa basa-basi lagi, kodenya:

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

Edit

Karena saya sedang mood, saya pikir saya akan menerbitkan paket untuk ini di npm.

Aaron Levine
sumber
1

Ada solusi sederhana yang memanfaatkan kemampuan fungsional JavaScript: Teruskan "logika" sebagai argumen fungsi ke konstruktor kelas Anda, tetapkan metode kelas tersebut ke fungsi itu, lalu kembalikan fungsi itu dari konstruktor sebagai hasilnya :

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

Di atas telah diuji pada Node.js 8.

Kelemahan dari contoh di atas adalah tidak mendukung metode yang diwarisi dari rantai superkelas. Untuk mendukung itu, cukup ganti "Object. GetOwnPropertyNames (...)" dengan sesuatu yang juga mengembalikan nama metode yang diturunkan. Bagaimana melakukan itu saya yakin dijelaskan dalam beberapa pertanyaan-jawaban lain di Stack Overflow :-). BTW. Akan lebih baik jika ES7 menambahkan metode untuk menghasilkan nama metode yang diturunkan juga ;-).

Jika Anda perlu mendukung metode yang diwariskan, satu kemungkinan adalah menambahkan metode statis ke kelas di atas yang mengembalikan semua nama metode yang diwariskan dan lokal. Kemudian panggil itu dari konstruktor. Jika Anda kemudian memperluas kelas Funk itu, Anda mendapatkan metode statis yang diwarisi juga.

Panu Logic
sumber
Saya rasa contoh ini memberikan jawaban sederhana untuk pertanyaan awal "... bagaimana saya dapat menerapkan logika untuk panggilan seperti itu". Berikan saja sebagai argumen nilai fungsi ke konstruktor. Dalam kode di atas kelas Funk tidak secara eksplisit memperluas Fungsi meskipun bisa, itu tidak benar-benar perlu. Seperti yang Anda lihat, Anda dapat memanggil "instance" -nya seperti Anda memanggil fungsi biasa.
Panu Logic