Javascript 如何使用 ES6 类扩展函数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/36871299/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to extend Function with ES6 classes?
提问by Qwertiy
ES6 allows to extend special objects. So it's possible to inherit from the function. Such object can be called as a function, but how can I implement the logic for such call?
ES6 允许扩展特殊对象。所以可以从函数继承。这样的对象可以作为函数调用,但是如何实现这种调用的逻辑呢?
class Smth extends Function {
constructor (x) {
// What should be done here
super();
}
}
(new Smth(256))() // to get 256 at this call?
Any method of class gets reference to the class instance via this
. But when it is called as a function, this
refers to window
. How can I get the reference to the class instance when it is called as a function?
类的任何方法都通过this
. 但是当它作为函数调用时,this
指的是window
. 当类实例作为函数被调用时,如何获取对类实例的引用?
PS:同样的问题俄语。
回答by Bergi
The super
call will invoke the Function
constructor, which expects a code string. If you want to access your instance data, you could just hardcode it:
该super
调用将调用Function
构造函数,该构造函数需要一个代码字符串。如果你想访问你的实例数据,你可以硬编码它:
class Smth extends Function {
constructor(x) {
super("return "+JSON.stringify(x)+";");
}
}
but that's not really satisfying. We want to use a closure.
但这并不令人满意。我们想使用闭包。
Having the returned function be a closure that can access your instancevariables is possible, but not easy. The good thing is that you don't have to call super
if you don't want to - you still can return
arbitrary objects from your ES6 class constructors. In this case, we'd do
让返回的函数成为可以访问您的实例变量的闭包是可能的,但并不容易。好消息是,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;
}
}
But we can do even better, and abstract this thing out of 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;
}
}
Admittedly, this creates an additional level of indirection in the inheritance chain, but that's not necessarily a bad thing (you can extend it instead of the native Function
). If you want to avoid it, use
诚然,这在继承链中创建了一个额外的间接级别,但这不一定是一件坏事(您可以扩展它而不是 native Function
)。如果你想避免它,请使用
function ExtensibleFunction(f) {
return Object.setPrototypeOf(f, new.target.prototype);
}
ExtensibleFunction.prototype = Function.prototype;
but notice that Smth
will not dynamically inherit static Function
properties.
但请注意,Smth
不会动态继承静态Function
属性。
回答by Adrien
This is an approach to creating callable objects that correctly reference their object members, and maintain correct inheritance, without messing with prototypes.
这是一种创建可调用对象的方法,这些对象正确引用其对象成员,并保持正确的继承,而不会弄乱原型。
Simply:
简单地:
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];
}
}
Extend this class and add a __call__
method, more below...
扩展这个类并添加一个__call__
方法,更多如下...
An explanation in code and comments:
代码和注释中的解释:
// 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())
Further explanation of bind
:
进一步解释bind
:
function.bind()
works much like function.call()
, and they share a similar method signature:
function.bind()
工作起来很像function.call()
,它们共享一个类似的方法签名:
fn.call(this, arg1, arg2, arg3, ...);
more on mdn
fn.call(this, arg1, arg2, arg3, ...);
更多关于mdn
fn.bind(this, arg1, arg2, arg3, ...);
more on mdn
fn.bind(this, arg1, arg2, arg3, ...);
更多关于mdn
In both the first argument redefines the this
context inside the function. Additional arguments can also be bound to a value.
But where call
immediately calls the function with the bound values, bind
returns an "exotic" function object that transparently wraps the original, with this
and any arguments preset.
在这两个参数中,第一个参数重新定义this
了函数内部的上下文。其他参数也可以绑定到一个值。但是,在call
立即使用绑定值调用函数的地方,bind
返回一个“异国情调”函数对象,该对象透明地包装了原始函数this
和任何预设的参数。
So when you define a function then bind
some of its arguments:
所以当你定义一个函数时bind
,它的一些参数:
var foo = function(a, b) {
console.log(this);
return a * b;
}
foo = foo.bind(['hello'], 2);
You call the bound function with only the remaining arguments, its context is preset, in this case to ['hello']
.
您仅使用其余参数调用绑定函数,其上下文已预设,在本例中为['hello']
。
// We pass in arg `b` only because arg `a` is already set.
foo(2); // returns 4, logs `['hello']`
回答by Oriol
You can wrap the Smth instance in a Proxywith an apply
(and maybe construct
) trap:
您可以将 Smth 实例包装在带有(可能还有)陷阱的代理中:apply
construct
class Smth extends Function {
constructor (x) {
super();
return new Proxy(this, {
apply: function(target, thisArg, argumentsList) {
return x;
}
});
}
}
new Smth(256)(); // 256
回答by Alexander O'Mara
Update:
更新:
Unfortunately this doesn't quite work because it's now returning a function object instead of a class, so it seems this actually can't be done without modifying the prototype. Lame.
不幸的是,这并不完全有效,因为它现在返回的是一个函数对象而不是一个类,所以如果不修改原型,这似乎实际上无法完成。瘸。
Basically the problem is there is no way of setting the this
value for the Function
constructor. The only way to really do this would be to use the .bind
method afterwards, however this is not very Class-friendly.
基本上问题是没有办法this
为Function
构造函数设置值。真正做到这一点的唯一方法是.bind
事后使用该方法,但这对类不太友好。
We could do this in a helper base class, however this
does does not become available until after the initial super
call, so it's a bit tricky.
我们可以在 helper 基类中执行此操作,但是this
直到初始super
调用之后才可用,因此有点棘手。
Working Example:
工作示例:
'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))());
(Example requires modern browser or node --harmony
.)
(示例需要现代浏览器或node --harmony
.)
Basically the base function ClassFunction
extends will wrap the Function
constructor call with a custom function which is similar to .bind
, but allows binding later, on the first call. Then in the ClassFunction
constructor itself, it calls the returned function from super
which is now the bound function, passing this
to finish setting up the custom bind function.
基本上,基函数ClassFunction
extends 将Function
使用自定义函数包装构造函数调用,该函数类似于.bind
,但允许稍后在第一次调用时进行绑定。然后在ClassFunction
构造函数本身中,它调用super
现在是绑定函数的返回函数,传递this
以完成自定义绑定函数的设置。
(super(...))(this);
This is all quite a bit complicated, but it does avoid mutating the prototype, which is considered bad-form for optimization reasons and can generate warnings in browser consoles.
这一切都相当复杂,但它确实避免了原型的变异,出于优化原因,原型被认为是不良形式,并且会在浏览器控制台中生成警告。
回答by Ryan Patterson
I took the advice from Bergi's answer and wrapped it into an NPM module.
我从 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 ]);
回答by Aaron Levine
This is the solution I've worked out that serves all my needs of extending functions and has served me quite well. The benefits of this technique are:
这是我制定的解决方案,它满足了我扩展功能的所有需求,并且非常适合我。这种技术的好处是:
- When extending
ExtensibleFunction
, the code is idiomatic of extending any ES6 class (no, mucking about with pretend constructors or proxies). - The prototype chain is retained through all subclasses, and
instanceof
/.constructor
return the expected values. .bind()
.apply()
and.call()
all function as expected. This is done by overriding these methods to alter the context of the "inner" function as opposed to theExtensibleFunction
(or it's subclass') instance..bind()
returns a new instance of the functions constructor (be itExtensibleFunction
or a subclass). It usesObject.assign()
to ensure the properties stored on the bound function are consistent with those of the originating function.- Closures are honored, and arrow functions continue to maintain the proper context.
- The "inner" function is stored via a
Symbol
, which can be obfuscated by modules or an IIFE (or any other common technique of privatizing references).
- 扩展时
ExtensibleFunction
,代码是扩展任何 ES6 类的惯用语(不,假装构造函数或代理)。 - 原型链通过所有子类保留,并
instanceof
/.constructor
返回预期值。 .bind()
.apply()
并且.call()
所有功能都按预期运行。这是通过覆盖这些方法来改变“内部”函数的上下文而不是ExtensibleFunction
(或其子类)实例来完成的。.bind()
返回函数构造函数的新实例(无论是它ExtensibleFunction
还是子类)。它用于Object.assign()
确保存储在绑定函数上的属性与原始函数的属性一致。- 闭包受到尊重,箭头函数继续保持正确的上下文。
- “内部”函数通过 存储
Symbol
,它可以被模块或 IIFE(或任何其他私有化引用的常用技术)混淆。
And without further ado, the code:
不用多说,代码:
// 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
编辑
Since I was in the mood, I figured I'd publish a packagefor this on npm.
由于心情好,我想我会在 npm 上为此发布一个包。
回答by Qwertiy
Firstly I came to solution with arguments.callee
, but it was awful.
I expected it to break in global strict mode, but seems like it works even there.
首先我用 来解决arguments.callee
,但这很糟糕。
我预计它会在全局严格模式下中断,但似乎它甚至在那里也能工作。
class Smth extends Function {
constructor (x) {
super('return arguments.callee.x');
this.x = x;
}
}
(new Smth(90))()
It was a bad way because of using arguments.callee
, passing the code as a string and forcing its execution in non-strict mode. But than idea to override apply
appeared.
这是一种糟糕的方式,因为使用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;
}
}
And the test, showing I'm able to run this as function in different ways:
测试表明我能够以不同的方式将其作为函数运行:
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"
Version with
版本与
super('return arguments.callee.apply(arguments.callee, arguments)');
in fact contains bind
functionality:
实际上包含bind
功能:
(new Smth(200)).call(new Smth(300), 1) === 201
Version with
版本与
super('return arguments.callee.apply(this===(1,eval)("this") ? null : this, arguments)');
...
me = me || this;
makes call
and apply
on window
inconsistent:
使call
和apply
上window
不一致:
isNaN(f.apply(window, [1])) === isNaN(f.call(window, 1)),
isNaN(f.apply(window, [1])) === isNaN(Function.prototype.apply.call(f, window, [1])),
so the check should be moved into apply
:
所以支票应该移到apply
:
super('return arguments.callee.apply(this, arguments)');
...
me = me !== global && me || this;
回答by Panu Logic
There is a simple solution which takes advantage of JavaScript's functional capabilities: Pass the "logic" as a function-argument to the constructor of your class, assign the methods of that class to that function, then return that functionfrom the constructor as the result:
有一个简单的解决方案可以利用 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
The above was tested on Node.js 8.
以上是在 Node.js 8 上测试的。
A shortcoming of the example above is it does not support methods inherited from the superclass-chain. To support that, simply replace "Object . getOwnPropertyNames(...)" with something that returns also the names of inherited methods. How to do that I believe is explained in some other question-answer on Stack Overflow :-). BTW. It would be nice if ES7 added a method to produce inherited methods' names as well ;-).
上面例子的一个缺点是它不支持从超类链继承的方法。为了支持这一点,只需将“Object .getOwnPropertyNames(...)”替换为还返回继承方法名称的内容。我相信在 Stack Overflow 上的其他一些问答中解释了如何做到这一点:-)。顺便提一句。如果 ES7 添加一个方法来生成继承方法的名称,那就太好了;-)。
If you need to support inherited methods one possibility is adding a static method to the above class which returns all inherited and local method names. Then call that from the constructor. If you then extend that class Funk, you get that static method inherited along as well.
如果您需要支持继承的方法,一种可能性是向上述类添加一个静态方法,该方法返回所有继承的和本地的方法名称。然后从构造函数调用它。如果您随后扩展该类 Funk,您也会同时继承该静态方法。