Вы можете использовать звонок или подать заявку с новым?

Связано с Как я могу вызвать конструктор 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;
}
Другие вопросы по тегам