javascript的"new"运算符可以返回可调用对象吗? [英] Can javascript's `new` operator ever return a callable object?

查看:50
本文介绍了javascript的"new"运算符可以返回可调用对象吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Нello!如果可以调用类的实例,则有时可以特别干净地编写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)可调用实例是解决方案

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

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

  1. 构造函数的返回值是一个实际函数
  2. 该函数值的原型已手动更改为您的Node原型对象
  3. 您的Node原型对象继承自Function.prototype,因此您的Node实例获得诸如callbind
  4. 之类的Function方法.

例如:

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

Нello! Sometimes an API could be written especially cleanly if instances of classes are callable. This seems to be especially useful when a class has an operation which is much more common than any other operation.

For example, consider a library for defining Trees, where each Node in the tree has a value and an indexed list of child nodes:

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', [])
  ])
]);

I would say that a Node has an operation which is much more common than any other operation: to get a child at a particular index:

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

I would say that this operation is so common, it would be very readable and clean to achieve the same result via the following:

root(1);

However to enable syntax like this, root would have to be a callable object (and therefore the Node constructor would need to return a callable object). Such functionality would be really cool when chained!:

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

It's imaginable that with such syntax other types could be passed, for example passing a function could return the node which matches the search:

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

Is there some clever (metaprogramming?) technique which could allow javascript's new keyword to return a callable object, and facilitate syntax such as this?

Note that for more complex APIs, polymorphism becomes an important feature! I'd like to preserve the object's prototype chain, if possible.

NOTE:

Jsperf comparing callable instances (root(0)) and instance methods (root.getChild(0)) seems to shows me (Chrome 72.0.3626) that callable instances are a tiny bit slower.

解决方案

Sure, a call via new can return any kind of object, including functions. It is true that calling a function with new will automatically fill this with a newly-created object, but you need not have this be the return value of your constructor: simply return any other object.

Really what you want here is have a Node be a kind of function. Simply have your Node constructor return a function object with the appropriate properties and prototype chain. You'll need to ensure

  1. The return value of the constructor is an actual function
  2. The prototype of that function value has been manually changed to your Node prototype object
  3. Your Node prototype object inherits from Function.prototype so that your Node instances get Function methods like call and bind

For example:

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

这篇关于javascript的"new"运算符可以返回可调用对象吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆