Що таке "нове" ключове слово в JavaScript?


1744

newКлючове слово в JavaScript може бути досить заплутаним , коли він вперше зустрічається, так як люди схильні думати , що JavaScript не є об'єктно-орієнтована мова програмування.

  • Що це?
  • Які проблеми вона вирішує?
  • Коли це доречно і коли ні?

10
Також пов’язана тема - stackoverflow.com/questions/383402/…
Chetan Sastry

читайте ці приклади спочатку, developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Martian2049

Відповіді:


2144

Це робить 5 речей:

  1. Це створює новий об’єкт. Тип цього об’єкта - просто об’єкт .
  2. Він встановлює внутрішнє, недоступне, [[прототип]] (тобто __proto__ ) властивість цього нового об'єкта як зовнішній, доступний прототипний об'єкт функції кожного конструктора (кожен об'єкт функції автоматично має властивість прототипу ).
  3. Це робить thisзмінну точку на новостворений об’єкт.
  4. Він виконує функцію конструктора, використовуючи щойно створений об’єкт, коли thisзгадується.
  5. Він повертає новостворений об'єкт, якщо тільки функція конструктора не повертає nullпосилання на об'єкт. У цьому випадку замість цього повертається посилання на об'єкт.

Примітка: функція конструктора відноситься до функції після newключового слова, як у

new ConstructorFunction(arg1, arg2)

Після цього, якщо буде запропоновано невизначене властивість нового об’єкта, скрипт замість цього буде перевіряти [[прототип]] об'єкта. Так ви можете отримати щось схоже на традиційне успадкування класу в JavaScript.

Найскладніша частина цього питання - точка № 2. Кожен об'єкт (включаючи функції) має цю внутрішню властивість під назвою [[прототип]] . Його можна встановити лише під час створення об'єкта, або з новим , за допомогою Object.create , або на основі літералу (функції за замовчуванням для Function.prototype, числа до Number.prototype тощо). Його можна читати лише за допомогою Object.getPrototypeOf (someObject) . Там немає ні іншого шляху , щоб встановити або прочитати це значення.

Функції, крім прихованого [[прототипу]] властивості, також мають властивість, звану прототип , і саме це ви можете отримати доступ та змінити для надання успадкованих властивостей та методів для створених вами об'єктів.


Ось приклад:

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

Я прочитав тонну сміття на цю тему, перш ніж нарешті знайшов цю сторінку , де це дуже добре пояснено приємними схемами.


47
Просто хотів додати: насправді існує спосіб отримати доступ до внутрішнього [[прототипу]], автор __proto__. Однак це нестандартно і підтримується лише відносно новими браузерами (і не всіми). Існує стандартизований спосіб, а саме Object.getPrototypeOf (obj), але це Ecmascript3.1, а сам підтримується лише в нових браузерах - знову ж таки. Як правило, рекомендується не використовувати цю властивість, однак все там дуже швидко ускладнюється.
Блуб

9
Питання: що відбувається по-іншому, якщо ObjMakerце визначено як функцію, яка повертає значення?
Джим Блеклер

13
@LonelyPixel newіснує так, що вам не доведеться писати заводські методи для побудови / копіювання функцій / об'єктів. Це означає: "Скопіюйте це, зробивши його так само, як його батьківський клас", робіть це ефективно та правильно; зберігайте інформацію про спадщину, доступну лише мені, JS, внутрішньо ". Для цього він модифікує інакше недоступний внутрішній prototypeелемент нового об'єкта, щоб непрозоро інкапсулювати успадковані члени, імітуючи класичні ланцюжки успадкування OO (які не змінюються під час виконання). Ви можете імітувати це без цього new, але успадкування буде видозміненим. Добре? Погано? До вас.
Інженер

11
невеликий момент, який слід додати: виклик конструктору, коли йому передує нове ключове слово, автоматично повертає створений об'єкт; немає необхідності явно повертати його всередині конструктора.
Чарлі Робертс

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. Будь-яка функція може бути конструктором, вона просто не завжди має сенс.


7
Залежить від контексту виконання. У моєму випадку (сценарій Qt) це просто глобальний об'єкт.
Максим

2
це спричинить більше використання пам'яті?
Юрген Пол

2
тому що вікно - це об'єкт, який викликав функцію - повинен бути: тому що вікно - це об'єкт, який містить функцію.
Давид Хорват

1
@Taurus У веб-браузері неметативна функція буде методом windowнеявно. Навіть у закритті, навіть якщо анонім. Однак у прикладі це простий виклик методу у вікні: Foo();=> [default context].Foo();=> window.Foo();. У цьому виразі windowє контекст (не тільки абонент , який не має значення).
Давид Хорват

1
@Taurus В основному так. Однак у 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);

73
Я виявив, що javascript легше зрозуміти, ніж англійська: v
damphat

Відмінна відповідь. У мене є одне крихітне запитання: як це можливо func.prototypeбути null? Не могли б ви детальніше розібратися в цьому?
Том Пажурек

6
@tomp ви можете змінити властивість прототипу, просто написавши. A.prototype = null;У такому випадку new A()це призведе до об'єкта, що внутрішній прототип вказує на Objectоб'єкт: jsfiddle.net/Mk42Z
basilikum

2
Перевірка typeof може бути помилковою, оскільки об'єкт хоста може створити щось інше, ніж "object" або "function". Щоб перевірити, чи щось є об’єктом, я вважаю за краще Object(ret) === ret.
Оріол

2
@Oriol дякую за коментар. Це правда, що ви говорите, і будь-яке фактичне випробування повинно бути зроблено більш надійним чином. Однак я думаю, що для цієї концептуальної відповіді typeofтест просто полегшує розуміння того, що відбувається за лаштунками.
basilikum

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

Тепер ви можете прочитати відповідь вікі спільноти :)


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 not .class для встановлення класу CSS
Грег

23
Він повинен бути використаний з великої літери за умовами.
eomeroff

27

JavaScript - об'єктно-орієнтована мова програмування, яка використовується саме для створення екземплярів. Це на прототипі, а не на основі класів, але це не означає, що воно не орієнтоване на об'єкти.


6
Мені хотілося б сказати, що JavaScript, здається, ще більш об'єктно-орієнтований, ніж усі ці мови на основі класів. У JavaScript все, що ви пишете, одразу стає об'єктом, але мовами на основі класу ви спочатку пишете декларації і лише пізніше створюєте конкретні екземпляри (об’єкти) класів. І прототип JavaScript, здається, смутно нагадує все, що VTABLE вміст для мов на основі класів.
JustAMartin

14

Javascript - це динамічна мова програмування, яка підтримує об'єктно-орієнтовану парадигму програмування, і вона використовується для створення нових екземплярів об'єкта.

Класи не потрібні для об'єктів - Javascript - мова, заснована на прототипі .


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станьте властивості на новоствореному об'єкті (замість об'єкта вікна). Оскільки у вас немає явного твердження про повернення, то інтерпретатор 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станьте властивості на новоствореному об’єкті. Але цього разу у вас є явна заява про повернення, тому інтерпретатор JavaScript нічого не зробить.

Що слід зазначити у випадку III, це те, що об’єкт, який створюється завдяки newключовому слову, загубився з вашого радара. barнасправді вказує на зовсім інший об'єкт, який не є тим, який інтерпретатор JavaScript створив завдяки newключовому слову.

Цитуючи Девіда Фланагана з JavaScripit: Постійний посібник (6-е видання), гл. 4, сторінка № 62:

Коли оцінюється вираз створення об'єкта, JavaScript спочатку створює новий порожній об'єкт, як і той, який створюється ініціалізатором об'єктів {}. Далі він викликає вказану функцію із вказаними аргументами, передаючи новий об’єкт як значення цього ключового слова. Потім функція може використовувати це для ініціалізації властивостей новоствореного об'єкта. Функції, написані для використання в якості конструкторів, не повертають значення, а значенням виразу створення об'єкта є новостворений та ініціалізований об'єкт. Якщо конструктор повертає значення об'єкта, це значення стає значенням виразу створення об'єкта, а новостворений об'єкт відкидається.

--- Додаткова інформація ---

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

Випадок I та II - Функція конструктора

Випадок III - Заводська функція. Фабричні функції не слід використовувати з newключовим словом, яке я робив для пояснення концепції в поточній темі.

Про різницю між ними ви можете прочитати в цій темі.


ваш випадок 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, оскільки це дає мені трохи більше гнучкості всередині і поза функцією.


3
B1 = new func2(2); <- Чому цього не буде B1.y ?
sunny_dev

@sunny_dev Я не експерт JS, але, ймовірно, тому, що func2 повертає безпосередньо значення (z об'єкт), а не працює / повертається із внутрішніми значеннями (це)
Орел

5

newКлючове слово змінює контекст , в якому функція часу запуску і повертає покажчик на цей контекст.

Якщо ви не використовуєте newключове слово, контекст, під яким Vehicle()запускається функція, є тим самим контекстом, з якого ви викликаєте Vehicleфункцію. thisКлючове слово буде посилатися на той же контекст. При використанні new Vehicle()створюється новий контекст, тому ключове слово thisвсередині функції посилається на новий контекст. Що ви отримуєте взамін - це новостворений контекст.


Це дуже глибока відповідь з точки зору обсягу. Gr8 додаток до відповіді.
appu

3

newКлючове слово для створення нових екземплярів об'єктів. І так, JavaScript - це динамічна мова програмування, яка підтримує об'єктно-орієнтовану парадигму програмування. Конвенція про іменування об'єктів полягає в тому, що завжди використовуйте велику літеру для об'єктів, які повинні бути створені новим ключовим словом.

obj = new Element();

2

Підсумок:

newКлючове слово використовується в JavaScript для створення об'єкта з функції конструктора. newКлючове слово повинно бути перед викликом функції конструктори і буде робити такі речі:

  1. Створює новий об’єкт
  2. Встановлює прототип цього об'єкта у властивість прототипу функції конструктора
  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. Новостворений об’єкт повертається та присвоюється змінному собачці.

1

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

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

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

Екземпляри успадковуються від prototypeфункції конструктора. Отже, наведений приклад вище ...

foo.bar; // 'bar'

2
Нове ключове слово в основному пов'язує функцію як конструктор; вам нічого не потрібно повертати. Ви можете просто виконати: функцію foo (x) {this.bar = x; } var obj = новий foo (10); оповіщення (obj.bar);
reko_t

Вам не потрібно повертати об'єкти з функції конструктора, якщо ви спеціально не хочете цього робити. Наприклад, якщо вам доведеться кожного разу (з будь-якої причини) повертати певний примірник об'єкта замість того, щоб створювати новий об'єкт. Однак у вашому прикладі це зовсім непотрібно.
Chetan Sastry

Ну, це був приклад. Ви можете повернути об’єкт. Існує багато моделей, використовуваних у цьому сценарії, я подав його як "наприклад", звідси мої слова "наприклад".
безвічність

1

Ну, JavaScript може сильно відрізнятися від платформи до платформи, оскільки це завжди реалізація оригінальної специфікації EcmaScript.

У будь-якому випадку незалежно від реалізації всі реалізації JavaScript, які відповідають правилу специфікації EcmaScript, дадуть вам об'єктно-орієнтовану мову. Відповідно до стандарту ES:

ECMAScript - це об'єктно-орієнтована мова програмування для виконання обчислень та маніпулювання обчислювальними об'єктами в хост-середовищі.

Отже, тепер ми погодилися, що JavaScript є реалізацією EcmaScript, і тому це об'єктно-орієнтована мова. Визначення newоперації будь-якою об'єктно-орієнтованою мовою говорить про те, що таке ключове слово використовується для створення об’єктного об'єкта з класу певного типу (включаючи анонімні типи, у випадках, таких як C #).

У EcmaScript ми не використовуємо класи, як ви можете прочитати із специфікацій:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.