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.