最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

constructor - Passing a constructer as a parameter as if it were a regular function in JavaScript? - Stack Overflow

programmeradmin0浏览0评论

I am using ANTLR 4, and it is common to instantiate a class and immediately pass it as the argument of the next class' constructor, as per the docs for the ANTLR 4 JavaScript target:

import antlr4 from 'antlr4';
import MyGrammarLexer from './QueryLexer.js';
import MyGrammarParser from './QueryParser.js';
import MyGrammarListener from './QueryListener.js';

const input = "field = 123 AND items in (1,2,3)"
const chars = new antlr4.InputStream(input);
const lexer = new MyGrammarLexer(chars);
const tokens = new antlr4.CommonTokenStream(lexer);
const parser = new MyGrammarParser(tokens);
const tree = parser.MyQuery();

Clearly this is a situation suited for function composition, and I am using Lodash (FP).

At first I tried the following:

// imports...
import _ from "lodash/fp.js";

const parse = _pose([
  _.invoke("MyQuery"),
  MyGrammarParser,
  antlr4.CommonTokenStream,
  MyGrammarLexer,
  antlr4.InputStream,
]);

But this fails with TypeError: Class constructor ke cannot be invoked without 'new'.

Inserting new on each line inside _pose (other than _.invoke) fails with TypeError: Cannot read properties of undefined (reading 'length') at new antlr4.InputStream.

Instead, I can use a helper function:

// imports...
import _ from "lodash/fp.js";

_.construct = Constructor => param => new Constructor(param);

const parse = _pose([
  _.invoke("MyQuery"),
  _.construct(MyGrammarParser),
  _.construct(antlr4.CommonTokenStream),
  _.construct(MyGrammarLexer),
  _.construct(antlr4.InputStream),
]);

const tree = parse("field = 123 AND items in (1,2,3)");

This works, and IMO looks way better than the original, but I want to know if there is a way to write this using built-in Javascript or Lodash functions. As far as I know, it is not possible to pass a constructor as a parameter as if it were a regular function, hence _.construct.

In summary, I want to pass a constructor as a parameter as if it were a regular function without having to first wrap it in a helper function.

For those unfamiliar with Lodash, see the Lodash Documentation and the Lodash FP Guide, however the solution to this question should be Lodash/ANTLR 4 independent.

I am using ANTLR 4, and it is common to instantiate a class and immediately pass it as the argument of the next class' constructor, as per the docs for the ANTLR 4 JavaScript target:

import antlr4 from 'antlr4';
import MyGrammarLexer from './QueryLexer.js';
import MyGrammarParser from './QueryParser.js';
import MyGrammarListener from './QueryListener.js';

const input = "field = 123 AND items in (1,2,3)"
const chars = new antlr4.InputStream(input);
const lexer = new MyGrammarLexer(chars);
const tokens = new antlr4.CommonTokenStream(lexer);
const parser = new MyGrammarParser(tokens);
const tree = parser.MyQuery();

Clearly this is a situation suited for function composition, and I am using Lodash (FP).

At first I tried the following:

// imports...
import _ from "lodash/fp.js";

const parse = _.compose([
  _.invoke("MyQuery"),
  MyGrammarParser,
  antlr4.CommonTokenStream,
  MyGrammarLexer,
  antlr4.InputStream,
]);

But this fails with TypeError: Class constructor ke cannot be invoked without 'new'.

Inserting new on each line inside _.compose (other than _.invoke) fails with TypeError: Cannot read properties of undefined (reading 'length') at new antlr4.InputStream.

Instead, I can use a helper function:

// imports...
import _ from "lodash/fp.js";

_.construct = Constructor => param => new Constructor(param);

const parse = _.compose([
  _.invoke("MyQuery"),
  _.construct(MyGrammarParser),
  _.construct(antlr4.CommonTokenStream),
  _.construct(MyGrammarLexer),
  _.construct(antlr4.InputStream),
]);

const tree = parse("field = 123 AND items in (1,2,3)");

This works, and IMO looks way better than the original, but I want to know if there is a way to write this using built-in Javascript or Lodash functions. As far as I know, it is not possible to pass a constructor as a parameter as if it were a regular function, hence _.construct.

In summary, I want to pass a constructor as a parameter as if it were a regular function without having to first wrap it in a helper function.

For those unfamiliar with Lodash, see the Lodash Documentation and the Lodash FP Guide, however the solution to this question should be Lodash/ANTLR 4 independent.

Share Improve this question asked 2 days ago James BawJames Baw 433 bronze badges 5
  • You cannot. Constructors for classes declared with class syntax can only be called as constructors, so Lodash would need separate APIs for dealing with them, or else something like what you've done here. – Pointy Commented 2 days ago
  • 2 Re: "As far as I know, it is not possible to pass a constructor as a parameter as if it were a regular function, hence _.construct": That's clearly not true, since you're successfully passing a constructor as a parameter to _.construct. What you really mean is "As far as I know, if a function expects its argument to be a regular function rather than a constructor, it is not possible to pass a constructor and have the function create the desired object." Which is true. But there's really nothing wrong with your current approach, namely, wrapping the new expression in a regular function. – ruakh Commented 2 days ago
  • @Pointy — more exactly: the question has two unrelated parts. 1) There is nothing wrong with passing constructor objects (actually, function objects) as parameters, it can make a good polymorphic technique. 2) What is wrong is just trying to call a constructor function without new. – Sergey A Kryukov Commented yesterday
  • 1 @SergeyAKryukov yes I know that; my comment "you cannot" was directly in response to the precise question asked. Of course passing a constructor reference is fine; it's just a value. But just as how a number parameter value should be used as a number, a constructor should be used as a constructor. – Pointy Commented yesterday
  • @Pointy — sure. – Sergey A Kryukov Commented yesterday
Add a comment  | 

1 Answer 1

Reset to default 0

It is possible to invoke a constructor without new using Reflect.construct:

class Example {
  hello = "world"
}

//pass an empty array for the constructor arguments
const obj = Reflect.construct(Example, []);

console.log( obj instanceof Example );
console.log( obj.hello );

To make this point-free using vanilla JS only, there is Function#bind:

class Example {
  hello = "world"
}

const myConstruct = Reflect.construct.bind(null, Example, []);

const obj = myConstruct();

console.log( obj instanceof Example );
console.log( obj.hello );

However, Lodash provides partial() which can be used with placeholders and can be more convenient:

class Example1 {
  hello = "world"
}

class Example2 {
  hello = "Fred"
}

const myConstruct = _.partial(Reflect.construct, _, []);
// placeholder for a constructor ----------------^

const obj1 = myConstruct(Example1);
const obj2 = myConstruct(Example2);

console.log( obj1 instanceof Example1 );
console.log( obj1.hello );

console.log( obj2 instanceof Example2 );
console.log( obj2.hello );
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash-fp/0.10.4/lodash-fp.min.js"></script>


However, I personally feel it is not really worth doing that. In the end, it ends up throwing more library code at a task that where a simple function is enough:

const construct = (constructor) => (...args) =>
    new constructor(...args); // or Reflect.construct(constructor, args)

which is essentially the solution you already came up with.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论