Конструктор для вызываемого объекта в 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);
}