Что такое «новое» ключевое слово в JavaScript?

1745

newКлючевое слово в JavaScript может быть довольно запутанным , когда он впервые встречается, так как люди склонны думать , что JavaScript не является объектно-ориентированный язык программирования.

  • Что это?
  • Какие проблемы это решает?
  • Когда это уместно, а когда нет?
Алон Губкин
источник
10
Кроме того, связанный поток - stackoverflow.com/questions/383402/…
Chetan Sastry
Сначала прочтите эти примеры, developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Martian2049

Ответы:

2145

Это делает 5 вещей:

  1. Это создает новый объект. Тип этого объекта просто объект .
  2. Он устанавливает внутреннее недоступное свойство [[prototype]] (т.е. __proto__ ) этого нового объекта как внешний, доступный, объект- прототип функции конструктора (каждый объект функции автоматически имеет свойство prototype ).
  3. Это делает thisточку переменной для вновь созданного объекта.
  4. Он выполняет функцию конструктора, используя вновь созданный объект всякий раз, когда thisупоминается.
  5. Он возвращает вновь созданный объект, если только функция конструктора не возвращает nullссылку на не объект. В этом случае вместо этого возвращается ссылка на объект.

Примечание: функция конструктора ссылается на функцию после newключевого слова, как в

new ConstructorFunction(arg1, arg2)

Как только это будет сделано, если запрошено неопределенное свойство нового объекта, сценарий проверит объект объекта [[prototype]] вместо этого свойства. Вот как вы можете получить нечто похожее на традиционное наследование классов в JavaScript.

Самая сложная часть этого - точка № 2. Каждый объект (включая функции) имеет это внутреннее свойство, называемое [[prototype]] . Это может быть установлено только во время создания объекта, либо с новым , с Object.create , либо на основе литерала (функции по умолчанию для Function.prototype, числа для Number.prototype и т. Д.). Его можно прочитать только с помощью Object.getPrototypeOf (someObject) . Там есть нет другого пути , чтобы установить или прочитать это значение.

Функции, в дополнение к скрытому свойству [[prototype]] , также имеют свойство с именем prototype , и именно этим вы можете обращаться и изменять, чтобы предоставлять унаследованные свойства и методы для создаваемых вами объектов.


Вот пример:

ObjMaker = function() {this.a = 'first';};
// ObjMaker is just a function, there's nothing special about it that makes 
// it a constructor.

ObjMaker.prototype.b = 'second';
// like all functions, ObjMaker has an accessible prototype property that 
// we can alter. I just added a property called 'b' to it. Like 
// all objects, ObjMaker also has an inaccessible [[prototype]] property
// that we can't do anything with

obj1 = new ObjMaker();
// 3 things just happened.
// A new, empty object was created called obj1.  At first obj1 was the same
// as {}. The [[prototype]] property of obj1 was then set to the current
// object value of the ObjMaker.prototype (if ObjMaker.prototype is later
// assigned a new object value, obj1's [[prototype]] will not change, but you
// can alter the properties of ObjMaker.prototype to add to both the
// prototype and [[prototype]]). The ObjMaker function was executed, with
// obj1 in place of this... so obj1.a was set to 'first'.

obj1.a;
// returns 'first'
obj1.b;
// obj1 doesn't have a property called 'b', so JavaScript checks 
// its [[prototype]]. Its [[prototype]] is the same as ObjMaker.prototype
// ObjMaker.prototype has a property called 'b' with value 'second'
// returns 'second'

Это похоже на наследование классов, потому что теперь все объекты, которые вы используете new ObjMaker(), также, по-видимому, унаследовали свойство 'b'.

Если вы хотите что-то вроде подкласса, то вы делаете это:

SubObjMaker = function () {};
SubObjMaker.prototype = new ObjMaker(); // note: this pattern is deprecated!
// Because we used 'new', the [[prototype]] property of SubObjMaker.prototype
// is now set to the object value of ObjMaker.prototype.
// The modern way to do this is with Object.create(), which was added in ECMAScript 5:
// SubObjMaker.prototype = Object.create(ObjMaker.prototype);

SubObjMaker.prototype.c = 'third';  
obj2 = new SubObjMaker();
// [[prototype]] property of obj2 is now set to SubObjMaker.prototype
// Remember that the [[prototype]] property of SubObjMaker.prototype
// is ObjMaker.prototype. So now obj2 has a prototype chain!
// obj2 ---> SubObjMaker.prototype ---> ObjMaker.prototype

obj2.c;
// returns 'third', from SubObjMaker.prototype

obj2.b;
// returns 'second', from ObjMaker.prototype

obj2.a;
// returns 'first', from SubObjMaker.prototype, because SubObjMaker.prototype 
// was created with the ObjMaker function, which assigned a for us

Я прочитал тонну мусора на эту тему, прежде чем наконец нашел эту страницу , где это очень хорошо объясняется с помощью хороших диаграмм.

Daniel Howard
источник
47
Просто хотел добавить: на самом деле есть способ получить доступ к внутреннему [[prototype]] с помощью __proto__. Однако это нестандартно и поддерживается только относительно новыми браузерами (и не всеми). Существует стандартизированный способ, а именно Object.getPrototypeOf (obj), но это Ecmascript3.1, и он сам по себе поддерживается только в новых браузерах - опять же. Как правило, рекомендуется не использовать это свойство, но там все очень быстро усложняется.
Blub
9
Вопрос: что происходит по-другому, если ObjMakerопределяется как функция, которая возвращает значение?
Джим Блэклер
13
@LonelyPixel newсуществует, так что вам не нужно писать фабричные методы для конструирования / копирования функций / объектов. Это означает: «Скопируйте это, сделав его так же, как его родительский« класс »; делайте это эффективно и правильно; и храните информацию о наследовании, которая доступна только мне, JS, для внутреннего использования». Для этого он изменяет недоступный в противном случае внутренний prototypeобъект нового объекта, чтобы непрозрачно инкапсулировать унаследованные члены, имитируя классические цепочки наследования ОО (которые нельзя изменить во время выполнения). Вы можете смоделировать это без new, но наследование будет изменяемым во время выполнения. Хорошо? Плохой? Вам решать.
инженер
11
небольшая точка для добавления: вызов конструктора, которому предшествует ключевое слово new, автоматически возвращает созданный объект; нет необходимости явно возвращать его из конструктора.
Чарли Робертс
7
Есть записка с надписью Notice that this pattern is deprecated!. Каков правильный современный шаблон для установки прототипа класса?
Том Пажурек
400

Предположим, у вас есть эта функция:

var Foo = function(){
  this.A = 1;
  this.B = 2;
};

Если вы вызываете это как отдельную функцию, вот так:

Foo();

Выполнение этой функции добавит два свойства к windowобъекту ( Aи B). Он добавляет его в windowпотому что windowэто объект, который вызвал функцию, когда вы выполняете ее таким образом, иthis в функции это объект, который вызвал функцию. По крайней мере в Javascript.

Теперь назовите это так new:

var bar = new Foo();

Что происходит, когда вы добавляете newк вызову функции то, что новый объект создается (просто var bar = new Object()) и что thisвнутри функции указывает на Objectтолько что созданный вами объект, а не на объект, который вызвал функцию. Так barчто теперь объект со свойствами Aи B. Любая функция может быть конструктором, просто она не всегда имеет смысл.

JulianR
источник
7
Зависит от контекста исполнения. В моем случае (Qt scripting) это просто глобальный объект.
Максим
2
это вызовет больше использования памяти?
Юрген Пауль
2
потому что окно является объектом, который вызвал функцию - должно быть: потому что окно является объектом, который содержит функцию.
Давид Хорват
1
@Taurus В веб-браузере windowнеявная функция будет методом неявно. Даже в закрытии, даже если анонимус. Однако в примере это простой вызов метода для окна: Foo();=> [default context].Foo();=> window.Foo();. В этом выражении windowесть контекст (не только вызывающий , что не имеет значения).
Давид Хорват
1
@ Телец В основном да. Однако в ECMA 6 и 7 все более сложно (см. Лямбды, классы и т. Д.).
Давид Хорват
164

В дополнение к ответу Дэниела Ховарда, вот что newделает (или, по крайней мере, кажется, делает):

function New(func) {
    var res = {};
    if (func.prototype !== null) {
        res.__proto__ = func.prototype;
    }
    var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
        return ret;
    }
    return res;
}

Пока

var obj = New(A, 1, 2);

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

var obj = new A(1, 2);
Basilikum
источник
73
Я обнаружил, что JavaScript легче понять, чем английский: V
Dumphat
Отличный ответ. У меня есть один крошечный вопрос: Как это может быть возможным , func.prototypeчтобы быть null? Не могли бы вы рассказать подробнее об этом?
Том Пажурек
6
@tomp вы можете переопределить свойство prototype, просто написав. A.prototype = null;В этом случае вы new A()получите объект on, то есть внутренний прототип указывает на Objectобъект: jsfiddle.net/Mk42Z
basilikum
2
Проверка typeof может быть неправильной, потому что хост-объект может производить что-то отличное от «object» или «function». Чтобы проверить, если что-то является объектом, я предпочитаю Object(ret) === ret.
Oriol
2
@Oriol спасибо за комментарий. Это правда, что вы говорите, и любой реальный тест должен быть сделан более надежным способом. Тем не менее, я думаю, что для этого концептуального ответа, typeofтест просто облегчает понимание того, что происходит за кулисами.
Базиликум
109

Для начинающих, чтобы понять это лучше

Попробуйте следующий код в консоли браузера.

function Foo() { 
    return this; 
}

var a = Foo();       //returns window object
var b = new Foo();   //returns empty object of foo

a instanceof Window;  // true
a instanceof Foo;     // false

b instanceof Window;  // false
b instanceof Foo;     // true

Теперь вы можете прочитать ответ вики сообщества :)

Anulal S
источник
4
Хороший ответ. Кроме того, return this;опускание дает тот же результат.
Нелу
37

так что это, вероятно, не для создания экземпляров объекта

Это используется именно для этого. Вы определяете конструктор функции так:

function Person(name) {
    this.name = name;
}

var john = new Person('John');

Однако дополнительное преимущество, которое имеет ECMAScript, заключается в том, что вы можете расширить его с помощью .prototypeсвойства, чтобы мы могли сделать что-то вроде ...

Person.prototype.getName = function() { return this.name; }

Все объекты, созданные из этого конструктора, теперь будут иметь getNameцепочку прототипов, к которой они имеют доступ.

медер омуралиев
источник
6
конструкторы функций используются как классы, здесь нет classключевого слова, но вы можете сделать то же самое.
meder omuraliev
Там kindof является ключевым словом класса - класс зарезервирован для будущего использования
Грег
11
Кстати, именно поэтому вы используете .className, а не .class, чтобы установить класс CSS
Грег
23
Он должен быть написан заглавными буквами.
eomeroff
27

JavaScript является объектно-ориентированным языком программирования и используется именно для создания экземпляров. Он основан на прототипах, а не на классах, но это не значит, что он не объектно-ориентирован.

Майкл
источник
6
Мне нравится говорить, что JavaScript кажется еще более объектно-ориентированным, чем все эти языки на основе классов. В JavaScript все, что вы пишете, немедленно становится объектом, но в языках на основе классов вы сначала пишете объявления, а только потом вы создаете конкретные экземпляры (объекты) классов. И прототип JavaScript, кажется, смутно напоминает все эти VTABLE вещи для языков на основе классов.
JustAMartin
14

Javascript - это динамический язык программирования, который поддерживает парадигму объектно-ориентированного программирования и использует его для создания новых экземпляров объекта.

Классы не нужны для объектов - Javascript - это язык, основанный на прототипах .

Greg
источник
12

Уже есть несколько очень хороших ответов, но я публикую новый, чтобы подчеркнуть мое наблюдение в случае III ниже о том, что происходит, когда у вас есть явный оператор возврата в функции, которую вы newвызываете. Посмотрите на следующие случаи:

Случай I :

var Foo = function(){
  this.A = 1; 
  this.B = 2;
};
console.log(Foo()); //prints undefined
console.log(window.A); //prints 1

Выше приведен простой пример вызова анонимной функции, на которую указывает Foo. Когда вы вызываете эту функцию, она возвращается undefined. Поскольку явного оператора возврата нет, интерпретатор JavaScript принудительно вставляет return undefined;оператор в конец функции. Здесь окно является объектом вызова (контекстным this), который получает новый AиB свойство.

Случай II :

var Foo = function(){
  this.A = 1;
  this.B = 2;
};
var bar = new Foo();
console.log(bar()); //illegal isn't pointing to a function but an object
console.log(bar.A); //prints 1

Здесь интерпретатор JavaScript, увидев newключевое слово, создает новый объект, который действует как объект вызова (контекстныйthis ) анонимной функции, на которую указывает указатель Foo. В этом случае Aи Bстановятся свойства вновь создаваемого объекта (вместо оконного объекта). Поскольку у вас нет явного оператора return, интерпретатор JavaScript принудительно вставляет оператор return, чтобы вернуть новый объект, созданный из-за использования newключевого слова.

Случай III :

var Foo = function(){
  this.A = 1;
  this.B = 2;
  return {C:20,D:30}; 
};
var bar = new Foo();
console.log(bar.C);//prints 20
console.log(bar.A); //prints undefined. bar is not pointing to the object which got created due to new keyword.

Здесь снова интерпретатор JavaScript, увидев newключевое слово, создает новый объект, который действует как объект вызова (контекстуальный this) анонимной функции, на которую указывает указатель Foo. Снова,A и Bстановитесь свойствами вновь созданного объекта. Но на этот раз у вас есть явное выражение return, поэтому интерпретатор JavaScript не будет ничего делать самостоятельно.

В случае III следует отметить , что создаваемый объектnew ключевого слова, был потерян с вашего радара. barна самом деле указывает на совершенно другой объект, который не тот, который интерпретатор JavaScript создал из-за newключевого слова.

Цитата Дэвида Фланагана из JavaScripit: Полное руководство (6-е издание), гл. 4, страница № 62:

Когда выражение создания объекта оценивается, JavaScript сначала создает новый пустой объект, такой же, как тот, который создан инициализатором объекта {}. Затем он вызывает указанную функцию с указанными аргументами, передавая новый объект в качестве значения ключевого слова this. Затем функция может использовать это для инициализации свойств вновь созданного объекта. Функции, написанные для использования в качестве конструкторов, не возвращают значение, а значением выражения создания объекта является вновь созданный и инициализированный объект. Если конструктор возвращает значение объекта, это значение становится значением выражения создания объекта, и вновь созданный объект отбрасывается.

--- Дополнительная информация ---

Функции, используемые во фрагменте кода вышеупомянутых случаев, имеют специальные имена в мире JS, как показано ниже:

Случай I и II - функция конструктора

Случай III - Заводская функция. Фабричные функции не должны использоваться с newключевым словом, которое я сделал, чтобы объяснить концепцию в текущем потоке.

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

RBT
источник
ваш случай 3, наблюдение gr8
appu
5

иногда код легче слов:

var func1 = function (x) { this.x = x; }                    // used with 'new' only
var func2 = function (x) { var z={}; z.x = x; return z; }   // used both ways
func1.prototype.y = 11;
func2.prototype.y = 12;

A1 = new func1(1);      // has A1.x  AND  A1.y
A2 =     func1(1);      // undefined ('this' refers to 'window')
B1 = new func2(2);      // has B1.x  ONLY
B2 =     func2(2);      // has B2.x  ONLY

для меня, пока я не являюсь прототипом, я использую стиль func2, поскольку он дает мне немного больше гибкости внутри и снаружи функции.

rsbkk
источник
3
B1 = new func2(2); <- Почему этого не будет B1.y ?
sunny_dev
@sunny_dev Я не эксперт по JS, но, вероятно, потому что func2 возвращает непосредственно значение (объект z) вместо того, чтобы работать / возвращать с внутренними значениями (это)
Eagle
5

newКлючевое слово изменяет контекст , в котором функция времени запуска и возвращает указатель на этот контекст.

Если вы не используете newключевое слово, контекст, в котором Vehicle()выполняется функция, совпадает с контекстом, из которого вы вызываете Vehicleфункцию. thisКлючевое слово будет ссылаться на тот же контекст. Когда вы используете new Vehicle(), создается новый контекст, поэтому ключевое слово thisвнутри функции ссылается на новый контекст. В ответ вы получаете только что созданный контекст.

Джузер Али
источник
Это очень проницательный ответ с точки зрения охвата. Gr8 дополнение к ответу.
Appu
3

newКлючевое слово для создания новых экземпляров объектов. И да, javascript - это динамический язык программирования, который поддерживает парадигму объектно-ориентированного программирования. Соглашение об именовании объектов гласит: всегда используйте заглавную букву для объектов, которые должны быть созданы новым ключевым словом.

obj = new Element();
erenon
источник
2

Резюме:

newКлючевое слово используется в JavaScript для создания объекта из функции конструктора. newКлючевое слово должно быть перед вызовом функции конструкторы и будет делать следующие вещи:

  1. Создает новый объект
  2. Устанавливает прототип этого объекта в свойство prototype функции конструктора
  3. Привязывает thisключевое слово к вновь созданному объекту и выполняет функцию конструктора
  4. Возвращает вновь созданный объект

Пример:

function Dog (age) {
  this.age = age;
}

const doggie = new Dog(12);

console.log(doggie);
console.log(doggie.__proto__ === Dog.prototype) // true

Что именно происходит:

  1. const doggie говорит: нам нужна память для объявления переменной.
  2. Оператор присвоения =говорит: мы собираемся инициализировать эту переменную с помощью выражения после=
  3. Выражение есть new Dog(12). Движок JS видит новое ключевое слово, создает новый объект и устанавливает прототип в Dog.prototype
  4. Функция конструктора выполняется со thisзначением, установленным для нового объекта. На этом этапе возраст назначается вновь созданному объекту собачка.
  5. Вновь созданный объект возвращается и присваивается переменной doggie.
Виллем ван дер Веен
источник
1

newКлючевые слова создают экземпляры объектов с использованием функции в качестве конструктора. Например:

var Foo = function() {};
Foo.prototype.bar = 'bar';

var foo = new Foo();
foo instanceof Foo; // true

Экземпляры наследуются от prototypeфункции конструктора. Итак, приведенный выше пример ...

foo.bar; // 'bar'
eyelidlessness
источник
2
Ключевое слово new в основном ассоциирует функцию как конструктор; вам не нужно ничего возвращать. Вы можете просто сделать: function foo (x) {this.bar = x; } var obj = new foo (10); оповещения (obj.bar);
reko_t
Вам не нужно возвращать объекты из функции конструктора, если вы не хотите специально для этой цели. Например, если вам нужно возвращать конкретный экземпляр объекта вместо создания нового объекта каждый раз (по какой-либо причине). В вашем примере, однако, это совершенно не нужно.
Четан Шастри
Ну, это был пример. Вы можете вернуть объект. В этом сценарии используется много шаблонов, я предоставил один как «например», отсюда и мои слова «например».
век
1

Ну, JavaScript на Си может сильно отличаться от платформы к платформе, поскольку это всегда реализация оригинальной спецификации EcmaScript.

В любом случае, независимо от реализации, все реализации JavaScript, которые соответствуют спецификации EcmaScript, дадут вам объектно-ориентированный язык. Согласно стандарту ES:

ECMAScript - это объектно-ориентированный язык программирования для выполнения вычислений и манипулирования вычислительными объектами в среде хоста.

Итак, теперь, когда мы договорились, что JavaScript - это реализация EcmaScript и, следовательно, это объектно-ориентированный язык. Определение newоперации в любом объектно-ориентированном языке говорит, что такое ключевое слово используется для создания экземпляра объекта из класса определенного типа (включая анонимные типы, в случаях, подобных C #).

В EcmaScript мы не используем классы, как вы можете прочитать из спецификации:

ECMAScript не использует классы, такие как в C ++, Smalltalk или Java. Вместо этого объекты могут создаваться различными способами, в том числе посредством буквенной нотации или с помощью конструкторов, которые создают объекты и затем выполняют код, который инициализирует все или часть из них, назначая начальные значения их свойствам. Каждый конструктор - это функция, которая имеет свойство с именем - prototype ‖, которое используется для реализации наследования на основе прототипа и общих свойств. Объекты создаются с
помощью конструкторов в новых выражениях; например, new Date (2009,11) создает новый объект Date. Вызов конструктора без использования new имеет последствия, которые зависят от конструктора. Например, Date () создает строковое представление текущей даты и времени, а не объекта.

Жоау Пинью
источник