Вы можете использовать звонок или подать заявку с новым?
Связано с Как я могу вызвать конструктор javascript, используя call или apply?
но не то же самое, я пытаюсь применить SO-ответы к форсированию конструктора Джона Резига, когда он не вызывается должным образом.
function User(first, last){
if ( !(this instanceof User) )
// the line I want to replace, or remove the redundancy from:
return new User(first, last);
this.name = first + " " + last;
}
var name = "Resig";
var user = User("John", name);
assert( user, "This was defined correctly, even if it was by mistake." );
assert( name == "Resig", "The right name was maintained." );
Целевая строка кода означает, что каждый раз, когда конструктор изменяется, кто-то должен помнить об изменении внутренних аргументов самовызова. У меня уже была поездка по проекту 3 раза за последние 3 дня.
Все примеры в связанном вопросе говорят о прохождении constructor
, но что это constructor
в этом случае? Это даже не закончено, будучи определенным еще.
но до сих пор все попытки не проходят тест или не генерируют переполнение стека.
Как сделать так, чтобы вызываемый конструктор приводил к тому, что правильно реагирует на instanceof User
даже когда вызывается без new
ключевое слово, исключая при этом повторение параметров аргумента?
2 ответа
Некоторые варианты для вас, все с использованием Object.create
:
Опция 1:
function User(first, last){
var rv;
if ( !(this instanceof User) ) {
// They called us without `new`: Create an object backed by `User.prototype`:
rv = Object.create(User.prototype);
// Now, call this function applying the arguments
User.apply(rv, arguments);
// Return the object
return rv;
}
// Normal constructor stuff
this.name = first + " " + last;
}
Конечно, всю эту логику не нужно повторять для каждой создаваемой вами функции конструктора, вы можете использовать вспомогательную функцию:
function constructWith(obj, ctor, args) {
if (obj instanceof ctor) {
return null;
}
obj = Object.create(ctor.prototype);
ctor.apply(obj, args);
return obj;
}
затем
function User(first, last){
var rv;
if ((rv = constructWith(this, User, arguments)) != null) {
return rv;
}
// Normal constructor stuff
this.name = first + " " + last;
}
Вариант 2: не использовать this
много:
function User(first, last){
var rv;
if (this instanceof User) {
// They (probably) used `new`, all is good, use `this`
rv = this;
} else {
// They didn't use `new`, create an object backed by `User.prototype`
rv = Object.create(User.prototype);
}
// ...use `rv`, not `this`, from here on
rv.name = first + " " + last;
// This is important for the case where they didn't use `new`, and harmless
// in the case where they did.
return rv;
}
Как видите, это намного проще, но если вам действительно нравится выделение синтаксиса (серьезно, у меня есть клиент, для которого действительно важно, чтобы this
выпрыгивает) и т.д...
И, конечно, вы можете обернуть это в помощник:
function useOrConstruct(obj, ctor) {
return obj instanceof ctor ? obj : Object.create(ctor.prototype);
}
затем
function User(first, last){
var rv = useOrConstruct(this, User);
// ...use `rv`, not `this`, from here on
rv.name = first + " " + last;
// This is important for the case where they didn't use `new`, and harmless
// in the case where they did.
return rv;
}
Вариант 3: constructOMatic
Конечно, если мы собираемся определить помощников, может быть, мы должны идти целиком:
function User() {
return constructOMatic(this, User, arguments, function(first, last) {
this.name = first + " " + last;
});
}
...где constructOMatic
является:
function constructOMatic(obj, ctor, args, callback) {
var rv;
if (!(obj instanceof ctor)) {
obj = Object.create(ctor.prototype);
}
rv = callback.apply(obj, args);
return rv !== null && typeof rv === "object" ? rv : obj;
}
Теперь вы можете использовать this
к вашему сердцу содержание в обратном вызове. Это возиться с rv
против obj
в return
в конце концов, подражать поведению new
(результат new
выражение является объектом, созданным new
оператор, если функция конструктора не возвращаетnull
ссылка на объект, в этом случае это имеет приоритет).
Object.create
это функция ES5, доступная во всех современных браузерах, но ее версия с одним аргументом, использованная выше, может быть отключена для устаревших браузеров:
if (!Object.create) {
Object.create = function(proto, props) {
if (typeof props !== "undefined") {
throw "The two-argument version of Object.create cannot be shimmed.";
}
function ctor() { }
ctor.prototype = proto;
return new ctor; // Yes, you really don't need () (but put them on if you prefer)
};
}
Копировать и вставлять очень легко, а код чистый. Вам не нужно менять это.
Если вы принимаете eval
, вы можете сделать это так:
function User(first, last){
if ( !(this instanceof arguments.callee) ) {
var name = arguments.callee.name;
var param = [].map.call(arguments,function(e,i){return 'arguments['+i+']';});
return eval('new '+name+'('+ param +')');
}
this.name = first + " " + last;
}
//test
var user1 = User("John", "Resig");
var user2 = new User("John", "Resig");
Без eval
, вы можете сделать это так:
function instantiate(C,a){
switch(a.length){
case 0: return new C();
case 1: return new C(a[0]);
case 2: return new C(a[0],a[1]);
case 3: return new C(a[0],a[1],a[2]);
case 4: return new C(a[0],a[1],a[2],a[3]);
default : throw("too many arguments");
}
}
function User(first, last){
if ( !(this instanceof arguments.callee) ) {
return instantiate(arguments.callee, arguments);
}
this.name = first + " " + last;
}
//test
var user1 = User("John", "Resig");
var user2 = new User("John", "Resig");
В ECMAScript 6 вы можете использовать оператор распространения, чтобы применить конструктор с ключевым словом new к массиву аргументов:
"use strict";
function User(first, last){
if ( !(this instanceof User) ) {
return new User(...arguments);
}
this.name = first + " " + last;
}