如何使用ES6类扩展函数?

ES6允许扩展特殊对象。因此,可以从函数继承。这样的对象可以作为函数调用,但是我该如何实现这种调用的逻辑呢?

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

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

类的任何方法都通过 获取对类实例的引用。但是当它被调用为函数时,指的是 。当类实例作为函数调用时,如何获取对类实例的引用?thisthiswindow

PS:俄语中也有同样的问题。


答案 1

调用将调用构造函数,该构造函数需要一个代码字符串。如果您想访问实例数据,只需对其进行硬编码:superFunction

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

但这并不令人满意。我们想要使用闭包。

让返回的函数成为可以访问您的实例变量的闭包是可能的,但并不容易。好消息是,如果您不想调用,则不必调用 - 您仍然可以从ES6类构造函数中任意调用对象。在这种情况下,我们会做superreturn

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

诚然,这会在继承链中创建额外的间接寻址级别,但这并不一定是一件坏事(您可以扩展它而不是本机)。如果您想避免它,请使用Function

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

但请注意,它不会动态继承静态属性。SmthFunction


答案 2

这是一种创建可调用对象的方法,这些对象可以正确引用其对象成员,并保持正确的继承,而不会弄乱原型。

只是:

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 的更多信息

在这两种情况下,第一个参数都重新定义了函数内的上下文。其他参数也可以绑定到一个值。但是,如果立即调用具有绑定值的函数,则返回一个“exotic”函数对象,该对象透明地包装原始对象,并预设任何参数。thiscallbindthis

因此,当您定义一个函数时,它的一些参数: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']`