JavaScript Create Object - Як визначити об’єкти в JS

Об’єкти є основною одиницею інкапсуляції в об’єктно-орієнтованому програмуванні. У цій статті я опишу кілька способів побудови об’єктів у JavaScript. Вони є:

  • Буквальний об’єкт
  • Object.create ()
  • Заняття
  • Заводські функції

Об’єкт буквальний

По-перше, нам потрібно провести різницю між структурами даних та об’єктно-орієнтованими об’єктами. Структури даних мають загальнодоступні дані та не мають поведінки. Це означає, що вони не мають методів.

Ми можемо легко створити такі об’єкти, використовуючи синтаксис об’єктів. Це виглядає так:

const product = { name: 'apple', category: 'fruits', price: 1.99 } console.log(product);

Об'єкти в JavaScript - це динамічні колекції пар ключ-значення. Ключ завжди є рядком і повинен бути унікальним у колекції. Значення може бути примітивом, об’єктом або навіть функцією.

Ми можемо отримати доступ до властивості, використовуючи крапку або квадратне позначення.

console.log(product.name); //"apple" console.log(product["name"]); //"apple"

Ось приклад, коли значенням є інший об’єкт.

const product = { name: 'apple', category: 'fruits', price: 1.99, nutrients : { carbs: 0.95, fats: 0.3, protein: 0.2 } }

Вартість carbsвластивості - це новий об’єкт. Ось як ми можемо отримати доступ до carbsвласності.

console.log(product.nutrients.carbs); //0.95

Стенографічні назви властивостей

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

const name = 'apple'; const category = 'fruits'; const price = 1.99; const product = { name: name, category: category, price: price }

JavaScript підтримує так звані скорочені імена властивостей. Це дозволяє нам створити об'єкт, використовуючи лише ім'я змінної. Це створить властивість з такою ж назвою. Наступний літерал об’єкта еквівалентний попередньому.

const name = 'apple'; const category = 'fruits'; const price = 1.99; const product = { name, category, price }

Object.create

Далі давайте розглянемо, як реалізувати об’єкти з поведінкою, об’єктно-орієнтовані об’єкти.

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

Система прототипів дозволяє нам створювати об'єкти, які успадковують поведінку від інших об'єктів.

Давайте створимо об’єкт-прототип, який дозволяє нам додавати товари та отримувати загальну ціну з кошика для покупок.

const cartPrototype = { addProduct: function(product){ if(!this.products){ this.products = [product] } else { this.products.push(product); } }, getTotalPrice: function(){ return this.products.reduce((total, p) => total + p.price, 0); } }

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

const cartPrototype = { addProduct(product){/*code*/}, getTotalPrice(){/*code*/} }

Це cartPrototypeоб’єкт-прототип, який зберігає загальну поведінку, представлену двома методами, addProductта getTotalPrice. Його можна використовувати для побудови інших об’єктів, що успадковують цю поведінку.

const cart = Object.create(cartPrototype); cart.addProduct({name: 'orange', price: 1.25}); cart.addProduct({name: 'lemon', price: 1.75}); console.log(cart.getTotalPrice()); //3

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

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

це

Зверніть увагу, що ми використовуємо спеціальне ключове слово, що викликається thisдля доступу та модифікації даних на об’єкті.

Пам'ятайте, що функції - це незалежні одиниці поведінки в JavaScript. Вони не обов'язково є частиною об'єкта. Коли вони є, нам потрібно мати посилання, яке дозволяє функції отримувати доступ до інших членів того самого об’єкта. this- контекст функції. Це дає доступ до інших властивостей.

Дані

Ви можете здивуватися, чому ми не визначили та не ініціалізували productsвластивість самого об’єкта-прототипу.

Ми не повинні цього робити. Прототипи слід використовувати для обміну поведінкою, а не даними. Спільний доступ до даних призведе до наявності однакових продуктів на кількох об’єктах кошика. Розглянемо наведений нижче код:

const cartPrototype = { products:[], addProduct: function(product){ this.products.push(product); }, getTotalPrice: function(){} } const cart1 = Object.create(cartPrototype); cart1.addProduct({name: 'orange', price: 1.25}); cart1.addProduct({name: 'lemon', price: 1.75}); console.log(cart1.getTotalPrice()); //3 const cart2 = Object.create(cartPrototype); console.log(cart2.getTotalPrice()); //3

І об'єкти, cart1і cart2об'єкти, що успадковують загальну поведінку від, cartPrototypeтакож мають однакові дані. Ми цього не хочемо. Прототипи слід використовувати для обміну поведінкою, а не даними.

Клас

Система прототипів не є звичайним способом побудови об’єктів. Розробники більше знайомі з побудовою об’єктів поза класами.

Синтаксис класу дозволяє більш звичний спосіб створення об'єктів, що мають спільну поведінку. Він все ще створює той самий прототип за кадром, але синтаксис чіткіший, і ми також уникаємо попередньої проблеми, пов’язаної з даними. Клас пропонує конкретне місце для визначення даних, відмінних для кожного об'єкта.

Ось той самий об’єкт, створений із використанням синтаксису класу цукру:

class Cart{ constructor(){ this.products = []; } addProduct(product){ this.products.push(product); } getTotalPrice(){ return this.products.reduce((total, p) => total + p.price, 0); } } const cart = new Cart(); cart.addProduct({name: 'orange', price: 1.25}); cart.addProduct({name: 'lemon', price: 1.75}); console.log(cart.getTotalPrice()); //3 const cart2 = new Cart(); console.log(cart2.getTotalPrice()); //0

Notice that the class has a constructor method that initialized that data distinct for each new object. The data in the constructor is not shared between instances. In order to create a new instance, we use the new keyword.

I think the class syntax is more clear and familiar to most developers. Nevertheless, it does a similar thing, it creates a prototype with all the methods and uses it to define new objects. The prototype can be accessed with Cart.prototype.

It turns out that the prototype system is flexible enough to allow the class syntax. So the class system can be simulated using the prototype system.

Private Properties

The only thing is that the products property on the new object is public by default.

console.log(cart.products); //[{name: "orange", price: 1.25} // {name: "lemon", price: 1.75}]

We can make it private using the hash # prefix.

Private properties are declared with #name syntax. # is a part of the property name itself and should be used for declaring and accessing the property. Here is an example of declaring products as a private property:

class Cart{ #products constructor(){ this.#products = []; } addProduct(product){ this.#products.push(product); } getTotalPrice(){ return this.#products.reduce((total, p) => total + p.price, 0); } } console.log(cart.#products); //Uncaught SyntaxError: Private field '#products' must be declared in an enclosing class

Factory Functions

Another option is to create objects as collections of closures.

Closure is the ability of a function to access variables and parameters from the other function even after the outer function has executed. Take a look at the cart object built with what is called a factory function.

function Cart() { const products = []; function addProduct(product){ products.push(product); } function getTotalPrice(){ return products.reduce((total, p) => total + p.price, 0); } return { addProduct, getTotalPrice } } const cart = Cart(); cart.addProduct({name: 'orange', price: 1.25}); cart.addProduct({name: 'lemon', price: 1.75}); console.log(cart.getTotalPrice()); //3

addProduct and getTotalPrice are two inner functions accessing the variable products from their parent. They have access to the products variable event after the parent Cart has executed. addProduct and getTotalPrice are two closures sharing the same private variable.

Cart is a factory function.

The new object cart created with the factory function has the products variable private. It cannot be accessed from the outside.

console.log(cart.products); //undefined

Factory functions don’t need the new keyword but you can use it if you want. It will return the same object no matter if you use it or not.

Recap

Usually, we work with two types of objects, data structures that have public data and no behavior and object-oriented objects that have private data and public behavior.

Data structures can be easily built using the object literal syntax.

JavaScript offers two innovative ways of creating object-oriented objects. The first is using a prototype object to share the common behavior. Objects inherit from other objects. Classes offer a nice sugar syntax to create such objects.

The other option is to define objects are collections of closures.

For more on closures and function programming techniques check out my book series Functional Programming with JavaScript and React.

The Functional Programming in JavaScript book is coming out.