Вы можете использовать шаблон метаконструктора *, чтобы обойти это.
function defineCtor(metaCtor) {
var proto = new metaCtor();
var ctor = proto.hasOwnProperty('constructor') ?
proto.constructor : new Function();
return (ctor.prototype = proto).constructor = ctor;
}
Теперь у вас есть функция, которая строит конструктор (или более точно строит прототипы и возвращает конструктор).
var Widget = defineCtor(function() {
function doInternalStuff() {
// ...cant see me
}
// this function ends up on the prototype
this.getFoo = function() { return doInternalStuff(); };
});
// ...
var myWidget = new Widget();
Объяснение
defineCtor
принимает один анонимную функцию как свойство. Он вызывает функцию с new
, создавая объект. Он присваивает объект в качестве свойства прототипа новой функции конструктора (либо пустой функции, либо собственного свойства объекта constructor
собственного объекта прототипа), и возвращает эту функцию.
Это обеспечивает замыкание для внутренних функций, адресации вопроса 1 и настраивает пару конструктор/прототип для вас, обращаясь к Question 2.
Сравнение
Сравнить defineCtor
техники к следующим двум примерам.
В этом примере используется прототип и имеет проблему 1: внутренняя информация не инкапсулирована.
function Widget(options) {
this.options = options;
}
Widget.prototype = {
getFoo: function() {
return doInternalStuff();
}
};
// How to encapsulate this?
function doInternalStuff() { /* ... */ }
Этот пример устанавливает все в конструкторе, и имеет проблемы 2: каждый раз, когда он создает объект, он конкретизирует новые функциональные объекты для каждого свойства.
function Widget(options) {
this.options = options;
function doInternalStuff() { /* ... */ }
this.getFoo = function() {
return doInternalStuff();
};
}
В этом примере используется метод, описанный выше, чтобы обеспечить герметизацию при этом используя прототип:
var Widget = defineCtor(function() {
// ^
// This function runs once, constructing the prototype.
// In here, `this` refers to the prototype.
// The real constructor.
this.constructor = function(options) {
// In function properties, `this` is an object instance
// with the outer `this` in its prototype chain.
this.options = options;
};
function doInternalStuff() { /* ... */ }
this.getFoo = function() { return doInternalStuff(); };
});
// ...
var myWidget = new Widget();
Этот подход имеет несколько преимуществ, некоторые более очевидными, чем другие.
Он обеспечивает инкапсуляцию. Вы можете сделать это, обернув первый пример сравнения в сразу вызываемой функции, но этот подход может быть более чистым и более «принудительным» в настройках команды.
Это расширяемый. Вы можете дать своим «метаконструкторам» свои собственные прототипы, с такими функциональными свойствами, как «extends», «mixin» и т. Д. Затем внутри тела metaCtor
вы можете писать такие вещи, как this.extends(BaseWidget)
. API defineCtor
никогда не нуждается в изменении, чтобы это произошло.
Это «трюки» компилятора Google Closure, Eclipse, jsdoc и т. Д., Чтобы думать, что вы определяете фактическую функцию-конструктор, а не «мета-функцию». Это может быть полезно в определенных ситуациях (код «самодокументирован», как это понимают эти инструменты).
* Насколько я знаю, слово "metaconstructor" полностью составлен.
15 объектов вряд ли вызовут проблему. Я, однако, предпочитаю оставить методы на прототипе –
Зачем беспокоиться об инкапсуляции? Это javascript, а не .NET или Java. –
@CesarCanassa encapsulation может быть просто организационным инструментом. Кто-то, смотрящий на код, может легко сказать, что инкапсулированные вещи используются только вокруг вещей. Конечно, это не проблема, если вы используете несколько исходных файлов и каким-то образом склеиваете их, вы можете просто минимизировать все и обернуть их в IIFE. –