Нет простого и простого способа сделать это с помощью функции-конструктора. Это связано с тем, что при использовании ключевого слова new
возникают особые вещи, чтобы вызвать функцию-конструктор, и поэтому, если вы этого не сделаете, вы должны подражать всем этим особым вещам. Это:
- Создание экземпляра нового объекта (вы делаете это).
- Установка внутреннего прототипа объекта для функции конструктора
prototype
.
- Установка этого объекта
constructor
.
- Вызов функции-конструктора с экземпляром этого объекта как значение
this
(вы это делаете).
- Обработка специального возвращаемого значения из функции конструктора.
Я думаю именно об этом, но стоит перепроверять в the spec.
Так что, если вы можете избежать этого и просто использовать функцию конструктора напрямую, я бы это сделал. :-) Если вы не можете, вы все равно можете это сделать, это просто неудобно и требует обходных решений. (См. Также this related answer здесь, в StackOverflow, хотя я здесь покрываю всю землю [и затем некоторые].)
Ваша самая большая проблема - № 2 выше: настройка внутреннего прототипа объекта. Долгое время не было стандартного способа сделать это. Некоторые браузеры поддерживали свойство __proto__
, которое это делало, поэтому вы можете использовать это, если оно есть. Хорошей новостью является то, что ECMAScript 5 вводит способ сделать это явно: Object.create
.Таким образом, у таких современных браузеров, как Chrome, это будет. Но если вы имеете дело с браузером, который не имеет ни Object.create
, ни __proto__
, он становится немного уродливым:
1) Определите пользовательскую конструкторскую функцию.
2) Установите его prototype
свойства к prototype
свойству функции реального конструктора
3) Используйте его, чтобы создать экземпляр пустого объекта.
Для этого используется прототип. Затем вы продолжаете:
4) Замените свойство constructor
на этот экземпляр функцией реального конструктора.
5) Позвоните в реальную конструкторскую функцию через apply
.
6) Если возвращаемое значение функции реального конструктора является объектом, используйте его вместо того, который вы создали; в противном случае используйте тот, который вы создали.
Что-то вроде этого (live example):
function applyConstruct(ctor, params) {
var obj, newobj;
// Use a fake constructor function with the target constructor's
// `prototype` property to create the object with the right prototype
function fakeCtor() {
}
fakeCtor.prototype = ctor.prototype;
obj = new fakeCtor();
// Set the object's `constructor`
obj.constructor = ctor;
// Call the constructor function
newobj = ctor.apply(obj, params);
// Use the returned object if there is one.
// Note that we handle the funky edge case of the `Function` constructor,
// thanks to Mike's comment below. Double-checked the spec, that should be
// the lot.
if (newobj !== null
&& (typeof newobj === "object" || typeof newobj === "function")
) {
obj = newobj;
}
// Done
return obj;
}
Вы можете взять его на шаг дальше и использовать только поддельной конструктор, если это необходимо, глядя, чтобы увидеть, если Object.create
или __proto__
поддерживаются первой, как это (live example) :
function applyConstruct(ctor, params) {
var obj, newobj;
// Create the object with the desired prototype
if (typeof Object.create === "function") {
// ECMAScript 5
obj = Object.create(ctor.prototype);
}
else if ({}.__proto__) {
// Non-standard __proto__, supported by some browsers
obj = {};
obj.__proto__ = ctor.prototype;
if (obj.__proto__ !== ctor.prototype) {
// Setting it didn't work
obj = makeObjectWithFakeCtor();
}
}
else {
// Fallback
obj = makeObjectWithFakeCtor();
}
// Set the object's constructor
obj.constructor = ctor;
// Apply the constructor function
newobj = ctor.apply(obj, params);
// If a constructor function returns an object, that
// becomes the return value of `new`, so we handle
// that here.
if (typeof newobj === "object") {
obj = newobj;
}
// Done!
return obj;
// Subroutine for building objects with specific prototypes
function makeObjectWithFakeCtor() {
function fakeCtor() {
}
fakeCtor.prototype = ctor.prototype;
return new fakeCtor();
}
}
В Chrome 6, выше использует Object.create
; на Firefox 3.6 и Opera, он использует __proto__
. В IE8 он использует функцию поддельного конструктора.
Вышеприведенный вариант является довольно-таки-манжетами, но в основном это касается проблем, о которых я знаю в этой области.
Это было немного более активное участие, чем я ожидал. Спасибо, хотя, за отличное объяснение! – einarmagnus
@ormuriauga: :-) Рад, что помогло. Объяснение заставляет его казаться немного более сложным, чем есть (как объясняют объяснения). Как вы можете видеть из первого фрагмента кода, это может быть довольно короткий код. –
даже однострочники часто очень привлекательны;) более тонкие детали объектной модели javascript по-прежнему уклоняются от меня, но сейчас я купил «хорошие детали» и скоро прочитаю.Есть ли веская причина использовать последний фрагмент кода, когда работает более короткая и менее сложная? Смогу ли я получить какую-либо скорость или совместимость или что-то еще, используя новые функции? – einarmagnus