【问题标题】:Can javascript's `new` operator ever return a callable object?javascript 的`new` 操作符可以返回一个可调用对象吗?
【发布时间】:2019-07-13 10:38:47
【问题描述】:

你好!如果类的实例是可调用的,有时 API 可以写得特别干净。当一个类具有比任何其他操作更常见的操作时,这似乎特别有用。

例如,考虑一个用于定义树的库,其中树中的每个 Node 都有一个值和一个子节点的索引列表:

let Node = function(value, children) { /* ... */ };
Node.prototype = { /* ... */ };

let root = new Node('root', [
  new Node('child1', [
    new Node('grandchild11', []),
    new Node('grandchild12', [])
  ]),
  new Node('child2', [
    new Node('grandchild21', []),
    new Node('grandchild22', [])
  ])
]);

我会说Node 有一个比任何其他操作都更常见的操作:在特定索引处获取一个孩子:

root.getChild(1); // Returns the "child2" node (0-based indexing)

我会说这个操作很常见,通过以下方式达到相同的结果会非常可读和干净:

root(1);

但是要启用这样的语法,root 必须是一个可调用对象(因此Node 构造函数需要返回一个可调用对象)。这样的功能在链接时会非常酷!:

root(0)(1); // Returns `grandchild12`

可以想象这样的语法可以传递其他类型,例如传递一个函数可以返回匹配搜索的节点:

root(node => node.value === 'child1')(node => node.value === 'grandchild11');

是否有一些聪明的(元编程?)技术可以让 javascript 的 new 关键字返回一个可调用对象,并简化这样的语法?

请注意,对于更复杂的 API,多态性成为一项重要功能!如果可能,我想保留对象的原型链。

注意:

Jsperf 比较可调用实例 (root(0)) 和实例方法 (root.getChild(0)) 似乎告诉我 (Chrome 72.0.3626) 可调用实例是 a tiny bit slower

【问题讨论】:

  • 可以在构造函数中显式返回函数...
  • 您的构造函数可以返回一个带有节点属性的function 实例。如果构造函数被作为构造函数调用(使用new)并且它有一个显式的return 返回any 类型的对象,那么该对象将用作new 的结果自动构造的普通对象。
  • 啊,这可能吗,不会丢失原型链?这可能正是我想要的!
  • Object.setPrototypeOf(function someCallableNodeObj(){}, nodeProto)(并确保nodeProto.__proto__Function,或者在链中的某个位置。你可以使用nodeProto = Object.create(Function) 或类似的方法)
  • @apsillers Function.prototype 不是 Function 确切地说

标签: javascript metaprogramming


【解决方案1】:

当然,通过new 调用可以返回任何类型的对象,包括函数。确实,使用new 调用函数会自动用新创建的对象填充this,但您不必将this 作为构造函数的返回值:只需return 任何其他对象即可。

你真正想要的是有一个Node 是一种功能。只需让您的Node 构造函数返回一个具有适当属性和原型链的函数对象。您需要确保

  1. 构造函数的返回值是一个实际的函数
  2. 该函数值的原型已手动更改为您的 Node 原型对象
  3. 您的Node 原型对象继承自Function.prototype,以便您的Node 实例获得像callbind 这样的函数方法

例如:

function Node(value) {
    // build the function to return
    var returnFunc = function getChild() { /*...*/ };

    // set instance properties like `value`, `children`, whatever
    returnFunc.value = value;

    // inherit Node methods and make `this instanceof Node` be true
    Object.setPrototypeOf(returnFunc, Node.prototype);

    // or non/barely-standard, but with older support:
    //    returnFunc.__proto__ = Node.prototype

    // you must explicitly `return` a value, or else `this` is used
    return returnFunc;
}

// Node instances should inherit Function methods like `call` and `bind`
// so Node must be a prototypal child of Function
Node.prototype = Object.create(Function.prototype);

// set Node inherited prototype methods
Node.prototype.getChild = function() { /*...*/ }

// optional: change the `toString` representation to reflect Node `value` instead of function code
Node.prototype.toString = function() { return "I'm a Node: " + this.value; }

【讨论】:

    猜你喜欢
    • 2016-09-23
    • 1970-01-01
    • 1970-01-01
    • 2021-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-09
    相关资源
    最近更新 更多