如何使用ES6类扩展函数?

Qwe*_*tiy 93 javascript inheritance function ecmascript-6 javascript-inheritance

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

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

(new Smth(256))() // to get 256 at this call?
Run Code Online (Sandbox Code Playgroud)

类的任何方法都可以通过引用类实例this.但是当它被称为函数时,this指的是window.当作为函数调用时,如何获取对类实例的引用?

PS:俄语同样的问题.

Ber*_*rgi 44

super调用将调用Function构造函数,该构造函数需要代码字符串.如果要访问实例数据,可以对其进行硬编码:

class Smth extends Function {
  constructor(x) {
    super("return "+JSON.stringify(x)+";");
  }
}
Run Code Online (Sandbox Code Playgroud)

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

让返回的函数成为可以访问实例变量的闭包是可能的,但并不容易.好处是super如果你不想要你就不必调用- 你仍然可以使用returnES6类构造函数中的任意对象.在这种情况下,我们会这样做

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

但是我们可以做得更好,并从中抽象出来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;
  }
}
Run Code Online (Sandbox Code Playgroud)

不可否认,这在继承链中创建了一个额外的间接层,但这不一定是坏事(你可以扩展它而不是本机Function).如果你想避免它,请使用

function ExtensibleFunction(f) {
  return Object.setPrototypeOf(f, new.target.prototype);
}
ExtensibleFunction.prototype = Function.prototype;
Run Code Online (Sandbox Code Playgroud)

但请注意,它Smth不会动态继承静态Function属性.

  • @amn不,当你不使用“this”和“return”对象时,你不会。 (4认同)
  • @Qwertiy:然后使用Bergi的第二个建议. (2认同)

Adr*_*ien 27

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

只是:

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];
  }
}
Run Code Online (Sandbox Code Playgroud)

扩展此类并添加一个__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())
Run Code Online (Sandbox Code Playgroud)

在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);
Run Code Online (Sandbox Code Playgroud)

只使用其余参数调用绑定函数,其上下文是预设的,在本例中为['hello'].

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


Ori*_*iol 20

您可以使用(也许)陷阱将Smth实例包装在Proxy中:applyconstruct

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

  • 代理会产生相当大的开销,不是吗?另外,`this`仍然是一个空函数(检查`new Smth().toString()`). (4认同)
  • @Bergi不知道性能.MDN有一个关于[`setPrototypeOf`]的大红色粗体警告(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf),并没有说任何有关代理的内容.但我猜代理可能与`setPrototypeOf`一样有问题.关于`toString`,它可以用`Smth.prototype`中的自定义方法进行阴影处理.无论如何,原生的是依赖于实现的. (2认同)

Ale*_*ara 5

更新:

不幸的是,这不太有效,因为它现在返回一个函数对象而不是一个类,所以看起来这实际上无法在不修改原型的情况下完成。瘸。


基本上问题是无法设置构造函数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))());
Run Code Online (Sandbox Code Playgroud)

(示例需要现代浏览器或node --harmony。)

基本上,基本函数ClassFunctionextends 将Function使用自定义函数包装构造函数调用,该自定义函数类似于.bind,但允许稍后在第一次调用时进行绑定。然后在ClassFunction构造函数本身中,它调用返回的函数,super该函数现在是绑定函数,传递this以完成设置自定义绑定函数。

(super(...))(this);
Run Code Online (Sandbox Code Playgroud)

这一切都相当复杂,但它确实避免了原型的变异,出于优化原因,原型被认为是不好的形式,并且可以在浏览器控制台中生成警告。