Конструктор для вызываемого объекта в 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, Можно ли сделать рабочий конструктор для вызываемого объекта?

4 ответа

Решение

Оказывается, это действительно возможно. Когда функция создана, либо с помощью 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с, я установил прототип CallablePoint в Function, так что я CallablePoint что наследует от Function,

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

Я напишу свой ответ, предполагая, что вы были после __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). Т.е. у вас может быть объект, который формально наследует от функции, но он не вызывается, и я не смог найти способ убедить среду выполнения, что он может быть вызван. У меня были похожие результаты в реализации JavaScrip в Mozilla, поэтому я думаю, что она должна быть универсальной.

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

Я не уверен, если вы знаете, что ваш объект будет только экземпляром 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

Если вы хотите 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);
}
Другие вопросы по тегам