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 (ECMA-262)
The definition of 'Object.create' in that specification.
Living Standard

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

BCD tables only load in the browser

Див. також