Подтвердить что ты не робот

Что делает `new` в JavaScript, так или иначе?

Я очень смущен тем, как работают конструкторы в Javascrpt; несмотря на использование языка в течение нескольких лет (в основном, как будто это полуприватная версия LISP), я хотел бы узнать больше о том, как объекты должны работать в нем.

С учетом этого кода:

function Foo(x) {
    return {
        bar: function() { return x; }
    };
}

В чем разница между вызовами myFoo = Foo(5) и myFoo = new Foo(5)? Или, другими словами, что именно делает конструктор в Javascript?

4b9b3361

Ответ 1

В чем разница между вызовами myFoo = Foo(5) и myFoo = new Foo(5)?

Нет разницы для этого кода, потому что он возвращает объект, а spec говорит:

  • Пусть результат будет вызван внутренним свойством [[Call]] F, предоставив obj как значение this и предоставив список аргументов, переданных в [[Construct]] в качестве аргументов.
  • Если Type(result) - Object, то верните результат.

Так как эта функция возвращает результат, являющийся объектом, его результат используется. Вы заметили бы разницу, если бы он не возвращал объект, или если он был отмечен this, например, если вы переписали его как:

function Foo(x) {
  if (!(this instanceof Foo)) { return new Foo(x); }
  this.bar = function() { return x; };
}
// Now instanceof works.
alert((new Foo) instanceof Foo);

Что вообще делает new в JavaScript?

Оператор new вызывает вызов функции с помощью this, связанного с вновь созданным Object, прототипом которого является свойство функции prototype.

Для пользовательских функций

new f(a, b, c)

эквивалентно

// Create a new instance using f prototype.
var newInstance = Object.create(f.prototype), result;

// Call the function
result = f.call(newInstance, a, b, c),

// If the result is a non-null object, use it, otherwise use the new instance.
result && typeof result === 'object' ? result : newInstance

Обратите внимание, что спецификация языка фактически определяет функции с двумя операциями, [[Call]] и [[Construct]], поэтому есть некоторые угловые случаи, когда new ведет себя странно.

Например, связанные и встроенные функции:

var g = f.call.bind(f);

должен определять функцию, которая при вызове просто вызывает f, поэтому g должен быть таким же, как f во всех отношениях, но

new g()

производит

TypeError: function call() { [native code] } is not a constructor

потому что встроенная функция Function.prototype.call поддерживает [[Call]], но не [[Construct]].

Function.prototype.bind также ведет себя по-разному вокруг new и регулярных вызовов. Значение this всегда является обязательным значением thisValue при вызове, но является вновь созданным экземпляром, когда вы используете new.

Ответ 2

В этом конкретном примере нет никакой разницы в конечном результате.

Это потому, что ваша функция Foo возвращает экземпляр объекта.

Оператор new возвращает вновь созданный объект, который наследуется от прототипа конструктора только тогда, когда функция возвращает примитивное значение (или оно ничего не возвращает, что является технически значением undefined).

Например:

function Foo () {
  return 5; // or "", or null, or no return statement at all (undefined)
}

var foo = new Foo();
typeof foo; // "object"
foo instanceof Foo; // true
Foo.prototype.isPrototypeOf(foo); // true

Когда вы возвращаете объект, вновь созданный объект, который наследуется от прототипа конструктора, просто отбрасывается:

function Foo () {
  return {};
}

var foo = new Foo();
typeof foo; // "object"
foo instanceof Foo; // false
Foo.prototype.isPrototypeOf(foo); // false

См. также:

Ответ 3

В этом случае не будет никакой разницы, поскольку вы возвращаете новый объект. Его можно было бы переписать как:

function Foo(x){
   this._x = x;
}

Foo.prototype.bar = function() { 
   return this._x;
}

С помощью этого синтаксиса каждый раз, когда вы вызываете new Foo, он создаст новый объект с свойством _x. Преимущество состоит в том, что функция bar будет храниться один раз и повторно использоваться для нескольких экземпляров Foo. С кодом в вопросе вызова Foo() несколько раз создаст функцию бара для каждого экземпляра. Поэтому привязка функций к прототипу, а не их непосредственное использование в объекте, будет легче в памяти.

Полный прорыв работы прототипа можно найти в MDC.