Object.create()

Метод Object.create() створює новий об'єкт, використовуючи існуючий об'єкт як прототип для новоствореного об'єкта.

Синтаксис

Object.create(proto, [propertiesObject])

Параметри

proto
Об'єкт, що має бути прототипом для новоствореного об'єкта.
propertiesObject Optional
Якщо вказаний та не дорівнює undefined, об'єкт, чиї власні перелічувані властивості (тобто, властивості, визначені на самому об'єкті, а не перелічувані властивості, отримані через ланцюжок прототипів) визначають дескриптори властивостей, що мають бути додані до новоствореного об'єкта, з відповідними іменами властивостей. Ці властивості відповідають другому аргументу Object.defineProperties().

Значення, що повертається

Новий об'єкт з зазначеним прототипом та властивостями.

Винятки

Виняток TypeError, якщо параметр propertiesObject дорівнює null або це об'єкт, що не є обгорткою простого типу.

Приклади

Класичне наслідування через Object.create()

Нижче наведено приклад використання Object.create() для отримання класичного наслідування. Це приклад одиночного наслідування, єдиного, яке підтримує JavaScript.

// Shape - батьківський клас
function Shape() {
  this.x = 0;
  this.y = 0;
}

// метод батьківського класу
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Фігуру переміщено.');
};

// Rectangle - дочірній клас
function Rectangle() {
  Shape.call(this); // виклик батьківського конструктора.
}

// дочірній клас розширює батьківській клас
Rectangle.prototype = Object.create(Shape.prototype);

//Якщо ви не призначите Object.prototype.constructor значення Rectangle, 
//він візьме prototype.constructor класу Shape (батьківського).
//Щоб уникнути цього, ми призначаємо prototype.constructor значення Rectangle (дочірній).
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

console.log('Чи є rect екземпляром Rectangle?', rect instanceof Rectangle); // true
console.log('Чи є rect екземпляром Shape?', rect instanceof Shape); // true
rect.move(1, 1); // Виводить 'Фігуру переміщено.'

Якщо ви бажаєте успадкуватись від кількох об'єктів, можна використати домішки.

function MyClass() {
  SuperClass.call(this);
  OtherSuperClass.call(this);
}

// успадкувати від одного класу
MyClass.prototype = Object.create(SuperClass.prototype);
// змішати з іншим
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// перепризначити конструктор
MyClass.prototype.constructor = MyClass;

MyClass.prototype.myMethod = function() {
  // зробити щось
};

Метод Object.assign() копіює властивості з прототипу OtherSuperClass у прототип MyClass, роблячи їх доступними для усіх екземплярів MyClass. Object.assign() був запроваджений у ES2015 і може бути відтворений поліфілом. Якщо необхідна підтримка більш старих переглядачів, можна використати jQuery.extend() або _.assign().

Використання аргументу propertiesObject у Object.create()

var o;

// створити об'єкт з прототипом null
o = Object.create(null);


o = {};
// є еквівалентом:
o = Object.create(Object.prototype);


// У цьому прикладі ми створюємо об'єкт з парою
// зразкових властивостей. (Зауважте, що другий параметр
// встановлює ключі у відповідності до *дескрипторів властивості*.)
o = Object.create(Object.prototype, {
  // foo - це звичайна властивість-значення
  foo: {
    writable: true,
    configurable: true,
    value: 'hello'
  },
  // bar - це властивість-аксесор (має гетер та сетер)
  bar: {
    configurable: false,
    get: function() { return 10; },
    set: function(value) {
      console.log('Призначення `o.bar` значення', value);
    }
/* з аксесорами ES2015 наш код може виглядати так
    get() { return 10; },
    set(value) {
      console.log('Призначення `o.bar` значення', value);
    } */
  }
});


function Constructor() {}
o = new Constructor();
// є еквівалентом:
o = Object.create(Constructor.prototype);
// Звісно, якщо у функції Constructor присутній
// код ініціалізації, 
// Object.create() не може його відобразити


// Створити новий об'єкт, чиїм прототипом є новий, порожній
// об'єкт, та додати єдину властивість 'p' зі значенням 42.
o = Object.create({}, { p: { value: 42 } });

// за замовчуванням властивості НЕДОСТУПНІ для запису,
// переліку чи налаштування:
o.p = 24;
o.p;
// 42

o.q = 12;
for (var prop in o) {
  console.log(prop);
}
// 'q'

delete o.p;
// false

// щоб визначити властивість у ES3
o2 = Object.create({}, {
  p: {
    value: 42,
    writable: true,
    enumerable: true,
    configurable: true
  }
});
/* є еквівалентом:
o2 = Object.create({p: 42}) */

Користувацькі та нульові об'єкти

Новий об'єкт, створений на основі користувацького об'єкта (особливо об'єкт, створений на основі об'єкта null, який по суті є користувацьким об'єктом, що НЕ МАЄ членів), може поводитись неочікувано. Особливо під час налагодження, оскільки звичайні функції утиліт для перетворення/видалення об'єктних властивостей можуть генерувати помилки або просто втрачати інформацію (особливо якщо використовують перехоплювачі помилок, що ігнорують помилки). Наприклад, ось два об'єкти:

oco = Object.create( {} );   // створити нормальний об'єкт
ocn = Object.create( null ); // створити "нульовий" об'єкт

> console.log(oco) // {} -- Виглядає нормально
> console.log(ocn) // {} -- Поки що теж виглядає нормально

oco.p = 1; // створити просту властивість на нормальному об'єкті
ocn.p = 0; // створити просту властивість на "нульовому" об'єкті

> console.log(oco) // {p: 1} -- Досі виглядає нормально
> console.log(ocn) // {p: 0} -- Теж виглядає нормально. АЛЕ СТРИВАЙТЕ...


Як показано вище, поки що все виглядає нормальним. Однак, при спробі використати ці об'єкти, їхні відмінності швидко стають очевидними:

> "oco is: " + oco // виводить "oco is: [object Object]"

> "ocn is: " + ocn // викидає помилку: Cannot convert object to primitive value

Перевірка лише декількох з багатьох базових вбудованих функцій більш чітко демонструє величину проблеми:

> alert(oco) // виводить [object Object]
> alert(ocn) // викидає помилку: Cannot convert object to primitive value

> oco.toString() // виводить [object Object]
> ocn.toString() // викидає помилку: ocn.toString is not a function

> oco.valueOf() // виводить {}
> ocn.valueOf() // викидає помилку: ocn.valueOf is not a function

> oco.hasOwnProperty("p") // виводить "true"
> ocn.hasOwnProperty("p") // викидає помилку: ocn.hasOwnProperty is not a function

> oco.constructor // виводить "Object() { [native code] }"
> ocn.constructor // виводить "undefined"


Як вже сказано, ці відмінності можуть швидко зробити процес відлагодження навіть простих на вигляд проблем дуже заплутаним. Наприклад:

Проста звичайна функція налагодження:

// вивести пари ключ-значення властивостей верхнього рівня наданого об'єкта
function ShowProperties(obj){
  for(var prop in obj){
    console.log(prop + ": " + obj[prop] + "\n" )
  }
}


Не такі прості результати: (особливо якщо перехоплювач помилок сховав повідомлення про помилки)

ob={}; ob.po=oco; ob.pn=ocn; // створити складний об'єкт з наданих вище тестових об'єктів в якості значень властивостей

> ShowProperties( ob ) // вивести властивості верхнього рівня
- po: [object Object]
- Error: Cannot convert object to primitive value

Зауважте, що виводиться тільки перша властивість.


(Але якщо такий самий об'єкт був просто створений в іншому порядку -- принаймні, в деяких реалізаціях...)

ob={}; ob.pn=ocn; ob.po=oco; // створити знову такий самий об'єкт, але створити ті самі властивості в іншому порядку

> ShowProperties( ob ) // вивести властивості верхнього рівня
- Error: Cannot convert object to primitive value

Зауважте, що жодна властивість не виводиться.

Зауважте, що такий відмінний порядок може виникнути статично, через відмінний зафіксований код, як ось тут, а може й динамічно, через порядок, в якому виконуються під час запуску гілки коду з додаванням властивостей, що залежить від вхідних даних та/або випадкових змінних. Знову ж таки, реальний порядок перебору не гарантований, в якому б порядку не додавалися члени об'єкта.

Деякі рішення, що не працюють

Гарне вирішення проблеми відсутніх об'єктних методів не завжди є очевидним.

Пряме додавання відсутнього об'єктного метода зі стандартного об'єкта НЕ працює:

ocn = Object.create( null ); // створити "нульовий" об'єкт (такий самий, як і раніше)

ocn.toString = Object.toString; // оскільки йому бракує методу, призначити його прямо зі стандартного об'єкта

> ocn.toString // виводить "toString() { [native code] }" -- схоже, що відсутній метод тепер додано
> ocn.toString == Object.toString // виводить "true" -- схоже, це той самий метод, що й у стандартному об'єкті

> ocn.toString() // error: Function.prototype.toString requires that 'this' be a Function


Пряме додавання відсутнього об'єктного метода у "прототип" нового об'єкта також не працює, оскільки у нового об'єкта немає справжнього прототипа (що й є справжньою причиною УСІХ цих проблем), і його не можна додати прямо:

ocn = Object.create( null ); // створити "нульовий" об'єкт (такий самий, як і раніше)

ocn.prototype.toString = Object.toString; // Error: Cannot set property 'toString' of undefined

ocn.prototype = {};                       // спробувати створити прототип
ocn.prototype.toString = Object.toString; // оскільки об'єкту бракує методу, призначити його прямо зі стандартного об'єкта

> ocn.toString() // error: ocn.toString is not a function


Додавання відсутнього об'єктного метода використанням стандартного об'єкта в якості прототипа нового об'єкта також не працює:

ocn = Object.create( null );        // створити "нульовий" об'єкт (такий самий, як і раніше)
Object.setPrototypeOf(ocn, Object); // встановити значенням прототипу нового об'єкта стандартний об'єкт

> ocn.toString() // error: Function.prototype.toString requires that 'this' be a Function

Деякі вдалі рішення

Як вже сказано, пряме додавання відсутнього об'єктного методу зі стандартного об'єкта НЕ працює. Однак, пряме додавання загального метода ПРАЦЮЄ:

ocn = Object.create( null ); // створити "нульовий" об'єкт (такий самий, як і раніше)

ocn.toString = toString; // оскільки об'єкту бракує методу, призначити його прямо з загальної версії

> ocn.toString() // виводить "[object Object]"
> "ocn is: " + ocn // виводить "ocn is: [object Object]"


ob={}; ob.pn=ocn; ob.po=oco; // створити складний об'єкт (такий самий, як і раніше)

> ShowProperties(ob) // вивести властивості верхнього рівня
- po: [object Object]
- pn: [object Object]

Однак, встановлення загального прототипу прототипом нового об'єкта працює навіть краще:

ocn = Object.create( null );                  // створити "нульовий" об'єкт (такий самий, як і раніше)
Object.setPrototypeOf(ocn, Object.prototype); // встановити значенням прототипу нового об'єкта "загальний" об'єкт (НЕ стандартний об'єкт)

(На додачу до функцій, пов'язаних з рядками, що наведені вище, це також додає:)

> ocn.valueOf() // виводить {}
> ocn.hasOwnProperty("x") // виводить "false"
> ocn.constructor // виводить "Object() { [native code] }"

// ...та вся решта властивостей та методів Object.prototype.

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

Поліфіл

Цей поліфіл покриває основний сценарій використання, а саме, створення нового об'єкта, для якого був обраний прототип, але не бере до уваги другий аргумент.

Зауважте, що в той час як використання null в якості [[Prototype]] підтримується в реальному методі ES5 Object.create, цей поліфіл не може це підтримувати через обмеження, притаманне версіям ECMAScript нижче 5.

 if (typeof Object.create !== "function") {
    Object.create = function (proto, propertiesObject) {
        if (typeof proto !== 'object' && typeof proto !== 'function') {
            throw new TypeError("Прототипом об'єкта може бути тільки об'єкт: " + proto);
        } else if (proto === null) {
            throw new Error("Реалізація Object.create у цьому браузері є шимом та не підтримує 'null' в якості першого аргументу.");
        }

        if (typeof propertiesObject != 'undefined') {
            throw new Error("Реалізація Object.create у цьому браузері є шимом та не підтримує другий аргумент.");
        }

        function F() {}
        F.prototype = proto;

        return new F();
    };
}

Специфікації

Специфікація Статус Коментар
ECMAScript 5.1 (ECMA-262)
The definition of 'Object.create' in that specification.
Standard Початкове визначення. Реалізоване у JavaScript 1.8.5.
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'Object.create' in that specification.
Standard
ECMAScript Latest Draft (ECMA-262)
The definition of 'Object.create' in that specification.
Draft

Сумісність з веб-переглядачами

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung InternetNode.js
createChrome Full support 5Edge Full support 12Firefox Full support 4IE Full support 9Opera Full support 11.6Safari Full support 5WebView Android Full support YesChrome Android Full support 18Firefox Android Full support 4Opera Android Full support 12Safari iOS Full support YesSamsung Internet Android Full support Yesnodejs Full support Yes

Legend

Full support  
Full support

Див. також