Конструктор для вызываемого объекта в JavaScript

Как я могу создать конструктор для вызываемого объекта в JavaScript?

Я пытался использовать различные способы, например, следующие. В примере есть только сокращенный пример фактического объекта.

function CallablePoint(x, y) {
    function point() {
        // Complex calculations at this point
        return point
    }
    point.x = x
    point.y = y
    return point
}

Это работает сначала, но создаваемый объект не является экземпляром CallablePoint, поэтому он не копирует свойства из CallablePoint.prototype и говорит false на instanceof CallablePoint. Можно ли создать рабочий конструктор для вызываемого объекта?

Ответ 1

Оказывается, это действительно возможно. Когда функция создается, используя синтаксис function или конструктор function, он получает внутреннее свойство [[Call]]. Это не свойство самой функции, а свойство, которое всякая функция получает при построении.

Хотя это означает только то, что все, что было в [[Call]], могло быть только function при его построении (ну, есть одно исключение – Function.prototype, которое не наследуется от function), что не означает, что он не может стать чем-то другим позже, сохраняя свойство [[Call]]. Ну, если ваш браузер не IE, 11.

Вещь, которая позволяет изменить магию, будет __proto__ из ES6, уже реализованная во многих браузерах. __proto__ - магическое свойство, содержащее прототип. Изменяя его, я могу сделать функцию, которая наследуется от чего-то, что не является function.

function CallablePoint(x, y) {
    function point() {
        // Complex calculations at this point
        return point
    }
    point.__proto__ = CallablePoint.prototype
    point.x = x
    point.y = y
    return point
}
// CallablePoint should inherit from Function, just so you could use
// various function methods. This is not a requirement, but it's
// useful.
CallablePoint.prototype = Object.create(Function.prototype)

Сначала конструктор для CallablePoint делает function (только function разрешено начинать с свойства [[Call]]. Затем я меняю свой прототип, чтобы он наследовал CallablePoint. В этот момент я имеют функцию, которая не наследует от function (вроде путают).

После того, как я определил конструктор для CallablePoint s, я установил прототип CallablePoint в function, поэтому у меня есть CallablePoint, который наследуется от function.

Таким образом, экземпляры CallablePoint имеют цепочку прототипов: CallablePoint -> Function -> Object, но все еще подлежащая вызову. Кроме того, поскольку объект является вызываемым, он имеет в соответствии со спецификацией typeof, равным 'function'.

Ответ 2

Я напишу свой ответ, предположив, что вы были после __call__ функциональности, доступной в Python и часто называемой "вызываемым объектом". "Вызываемый объект" звучит чуждо в контексте JavaScript.

Я пробовал несколько движков JavaScript, но ни один из тех, которые я пробовал, позволяет вам называть объекты, даже если вы наследуете от Function. Например:

function Callable(x) {
...     "use strict";
...     this.__proto__ = Function.prototype;
...     this.toString = function() { return x; };
... }
undefined
> var c = new Callable(42);
var c = new Callable(42);
undefined
> c;
c;
{ toString: [function] }
> c(42);
c(42);
TypeError: Property 'c' of object #<Object> is not a function
    at repl:1:1
    at REPLServer.eval (repl.js:80:21)
    at repl.js:190:20
    at REPLServer.eval (repl.js:87:5)
    at Interface.<anonymous> (repl.js:182:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:162:10)
    at Interface._line (readline.js:426:8)
    at Interface._ttyWrite (readline.js:603:14)
    at ReadStream.<anonymous> (readline.js:82:12)
> c instanceof Function;
c instanceof Function;
true
c.apply(null, [43]);
TypeError: Function.prototype.apply was called on 43, which is a object and not a function
    at Function.APPLY_PREPARE (native)
    at repl:1:3
    at REPLServer.eval (repl.js:80:21)
    at repl.js:190:20
    at REPLServer.eval (repl.js:87:5)
    at Interface.<anonymous> (repl.js:182:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:162:10)
    at Interface._line (readline.js:426:8)
    at Interface._ttyWrite (readline.js:603:14)
> 

Это V8 (Node.js). То есть у вас может быть объект, который формально наследуется от функции, но он не может быть вызван, и я не мог найти способ убедить, что он может быть вызван. У меня были аналогичные результаты в реализации Mozilla JavaScrip, поэтому я считаю, что он должен быть универсальным.

Тем не менее, роль пользовательских типов в JavaScript исчезающе мала, поэтому я не думаю, что вы все равно будете ее пропускать. Но, как вы уже обнаружили, вы можете создавать свойства функций, как и на объектах. Таким образом, вы можете сделать это, менее удобным способом.

Ответ 3

Я не уверен, знаете ли вы, что ваш объект будет только экземпляром CallablePoint, если вы используете ключевое слово new. Называя его "вызываемым", вы заставляете меня думать, что вы не хотите использовать new. Во всяком случае, есть способ принудительно вернуть экземпляр (спасибо за подсказку, Resig):

function CallablePoint(x, y) {
    if (this instanceof CallablePoint) {
        // Your "constructor" code goes here.
        // And don't return from here.
    } else {
        return new CallablePoint(x, y);
    }
}

Это вернет экземпляр CallablePoint независимо от того, как он был вызван:

var obj1 = CallablePoint(1,2);
console.log(obj1 instanceof CallablePoint); // true

var obj2 = new CallablePoint(1,2);
console.log(obj2 instanceof CallablePoint); // true

Ответ 4

Если вы хотите, чтобы конструктор CallablePoint() возвращал объект типа CallablePoint, вы можете сделать что-то вроде этого, когда объект CallablePoint содержит точку как свойство объекта, но остается объектом CallablePoint:

function CallablePoint(x, y) {
    this.point = {};
    this.point.x = x
    this.point.y = y
}

или, если то, что вы действительно пытаетесь сделать, - это сделать функцию, которая возвращает вам объект CallablePoint, тогда вы можете создать факторную функцию:

function CallablePoint(x, y) {
    this.point = {};
    this.point.x = x
    this.point.y = y
}

function makeCallablePoint(x, y) {
   return new CallablePoint(x,y);
}