JavaScript Обекти

Въведение

Въведение

Основни понятия

Object = {properties + methods}


Обект: В класическото ООП програмиране (Java, C#, C++), обекта е структура от данни, която обединява в едно цяло данните и действията (функциите), които могат да се извършват върху тях.
Данните в един обект се наричат свойства (properties), а функциите - методи (methods).
За разлика от масивите, които са подредена структура от данни, то обектите са неподредена структура. Т.е. при обектите не разграничаваме първи, втори и пр. елемент.

            // пример за обект, съхраняващ информация за 1 студент::
            let student1 = {
                // properties:
                "firstName" : "Pesho",
                "surName" : "Petrov",

                // methods
                "greet": function() {
                    console.log(`Hello, I'm ${this.firstName}`);
                }
            };
        
Пример за обект:

                // define student1 object:
                let student1 = {
                    "firstName" : "Pesho",
                    "surName" : "Petrov",
                    "greet": function() {
                        console.log(`Hello, I'm ${this.firstName}`);
                    }
                };
            
Който може да си преставим като:
Редът на създаване на свойствата няма никакво значение, тъй като данните в обектите не са подредени! Те си имат имена, т.е. property names.
За разлика от класическото ООП, в JavaScript обект може да бъде и само съвкупност от данни (без функции), обединени в едно цяло. В другите езици подобна структура се нарича dictionary (Python), associative array (Perl, PHP, ...)

                let dictionary = {
                    // съвкупност от 'key':'value' двойки
                    'apple': 'ябълка',
                    'banana': 'банан',
                    'orange': 'портокал'
                }
            
Всяко едно свойство на обект съдържа данни - може да бъде всеки един тип данни, но за разлика от масива, данните тук се асоциират с имена (property names, keys) а не с цифрови индекси.
Когато в дадено свойство имаме функция, тогава това свойство се нарича метод на обекта

            // пример за обект, съхраняващ информация за 1 студент::
            let student1 = {
                // properties:
                "name":"Pesho",
                "address":{
                    "town":"Sofia",
                    "zip": 1508
                }
                "scores": [3,4,5],


                // methods
                "greet": function() {
                    console.log(`Hello, I'm ${this.name}`);
                }
            };
        

Ето как, съвсем обобщено, изглежда един обект в паметта:

Създаване на единичен обект

Създаване на единичен обект

Чрез литерал - предпочитания вариант

Когато е необходимо да създадем един обект, то в къдрави скоби изброяваме двойките property:value, разделени със запетая.

                const obj = {
                    propName1:propValue1,
                    propName2:propValue2,
                    ...
                    propNameN:propValueN,
                }
            

                // създаване празен обект:
                var obj = {};

                // създаване на обект с 4 свойства (properties):
                var car1 = {
                    "brand" : "ford",
                    "year"  : 2016,
                    "color" : "red",
                    "doors" : 3
                }
            
Забележете, че след последния елемент на обекта не се слага запетайка!

Чрез глобалния обект Object

Този начин за създаване на обект, се използва изключително рядко:

                var obj = new Object();
            
Но внимавайте да не объркате този вариант с използването на конструктор на обекти, който ще бъде разгледан по-нататък:

                var obj = new Car();
            

Достъп до свойствата на обект

Достъп до свойствата на обект

Чрез точка (Dot Notation)

Когато знаем името на пропъртито, което искаме да достъпим, използваме точковата нотация:

                object_name.property_name
            
Нека е даден следния обект:

                const developer1 = {
                    "firstName" : "Ivan",
                    "surName"   : "Ivanov",
                    "skills"     : ["HTML", "CSS", "JavaScript"],

                    "applyForJob": function(){
                        const skillsStr = this.skills.toString();
                        console.log(`Hi, I'm ${this.firstName} ${this.surName}. My strongest skills are ${skillsStr}.`);
                    }
                }
            
Можем да достъпим данните в него:

                console.log( developer1.firstName ); //Ivan
                console.log( developer1.surName );   //Ivanov

                console.log( developer1.skills );
                // ["HTML", "CSS", "JavaScript"]

                console.log( developer1.skills[0] ); //"HTML"

                console.log( developer1.applyForJob() );
                // Ivan is applying for job!

                developer1.firstName = "Stoyan";
                // промяна на свойството "firstName"
            
Забележете, че това което пишем след точката, се интерпретира от JS буквално като име на пропърти.

Чрез квадратни скоби (Square-bracket Notation)

Когато не знаем името на пропъртито, което искаме да достъпим (например, то е в променлива), то тогава използваме нотацията с квадратни скоби:

                object_name[variable_holding_property_name]
            
Нека е даден обект представящ цени на плодове, и трябва да достъпим цената на плод, която е зададена в променлива:

                const prices = {
                    'apples': 2.50,
                    'oranges': 3.45,
                    'bananas': 2.80
                }

                // името на пропъртито, което искаме да достъпим е дадено в променлива:
                const fruitName = 'apples'

                // използваме записът с квадратни скоби:
                console.log( prices[fruitName] );

                // следният запис е еквивалентен:
            
Забележете, че когато знаем името на пропъртито, следните 2 записа са еквивалентни:

                console.log( prices['apples'] ); // 2.50
                console.log( prices.apples ); // 2.50

                // естествено, предпочитаме да използваме вторият вариант
            
Но следните 2 записа не са:

                const fruitName = 'apples';

                console.log( prices[fruitName] ); // 2.50
                console.log( prices.fruitName ); // undefined, защото се търси пропърти с име 'fruitName' в обекта prices, а такова пропърти няма.
            

Няколко думи за this

Няколко думи за this

В следния пример е даден обект, който съдържа пропъртита (данни) и методи (функции, работещи върху тези данни)
В примера е използвана автоматичната променлива this, в която имаме самия обект: student1
В зависимост от контекста, в който използваме this, може да имаме различни обекти, но темата се разглежда по-подробно в курсовете на напреднали. Това, което е важно, е че когато използваме this в обект литерал (т.е. в {...}), то в this винаги имаме същия този обект.

                let student1 = {
                    // properties:
                    "name":"Pesho",
                    "address":{
                        "town":"Sofia",
                        "zip": 1508
                    },
                    "scores": [3,4,5],

                    // methods
                    "greet": function() {
                        console.log(`Hello, I'm ${this.name}. My max sore is: ${this.findMaxScore()}`);
                    },
                    "findMaxScore": function () {
                        let maxScore = 0;
                        for (let i = 0; i < this.scores.length; i++) {
                            const currentScore = this.scores[i];
                            if(currentScore>=maxScore){
                                maxScore=currentScore
                            }
                        }
                        return maxScore;
                    }
                };

                student1.greet()
            
Бихте могли да използвате директно името на обекта, т.е. вместо this да напишете student1, но това не е добра практика, тъй като кода не би бил гъвкав и лесно преносим. Представете си, ако трябва да използвате методите greet() и findMaxScore() в други обекти...

Създаване на множество еднотипни обекти

Създаване на множество еднотипни обекти

Чрез литерали (неефективен вариант):

В реалните приложения често се налага да работим с множество еднотипни обекти - обекти, който имат еднаква структура, т.е. един и същ брой и имена на пропъртита и методи, които ще се различават само по стойностите.
Например, ако искаме да представим информацията за два (или повече) обекта от тип developer, то бихме имали доста повтарящ се код (имената на пропъртитата) - крайно неефективен вариант:

                var developer1 = {
                    "firstName" : "Ivan",
                    "surName"   : "Ivanov",
                    "skills"     : ["HTML", "CSS", "JavaScript"],

                    "applyForJob": function(){
                        var skillsStr = this.skills.toString();

                        console.log(`Hi, I'm ${this.firstName} ${this.surName}. My strongest skills are ${skillsStr}.`);
                    }
                }
                var developer2 = {
                    "firstName" : "Maria",
                    "surName"   : "Mineva",
                    "skills"     : ["Python", "Machine Learning", "Data Science"],

                    "applyForJob": function(){
                        var skillsStr = this.skills.toString();

                        console.log(`Hi, I'm ${this.firstName} ${this.surName}. My strongest skills are ${skillsStr}.`);
                    }
                }
            

Чрез Factory function

По-ефективно е да създадем функция, която да генерира нашите обекти. Освен, че ще намали кода, този вариант има и предимството, че всички конструирани обекти гарантирано ще имат еднаква структура.

                // функция, създаваща обекти от тип developer:
                function developerFactory(firstName, surName, skills){
                    var obj = {};

                    obj.firstName = firstName;
                    obj.surName = surName;
                    obj.skills = skills;

                    obj.applyForJob = function(){
                        var skillsStr = this.skills.toString();
                        console.log(`Hi, I'm ${obj.firstName} ${obj.surName}. My strongest skills are ${skillsStr}.`);
                    }

                    return obj;
                }

                //сега лесно създаваме обектите от тип developer:
                var developer1 = developerFactory("Ivan", "Ivanov",
                ["HTML", "CSS", "JavaScript"]
                );
                var developer2 = developerFactory("Maria", "Mineva",
                ["Python", "Machine Learning", "Data Science"],
                );

                // и сме сигурни, че всеки обект има пропърти 'firstName':

            

Чрез Constructor functuon

За да бъде синтаксисът в JavaScript по-близък до този в класическите ООП езици (C++, Java, C# и пр.) в JavaScript може да използваме и Конструктор на обекти:

                // Конструктор за обекти от тип Developer. Важно е да бъде извикан с "new"! Поради това конвенцията е името да започва с главна буква.
                function Developer(firstName, surName, skills){
                    this.firstName = firstName;
                    this.surName = surName;
                    this.skills = skills;

                    this.applyForJob = function(){
                        var skillsStr = this.skills.toString();

                        console.log(`Hi, I'm ${this.firstName} ${this.surName}. My strongest skills are ${skillsStr}.`);
                    }
                }

                //сега лесно създаваме обектите от тип Developer:
                var developer1 = new Developer("Ivan", "Ivanov",
                ["HTML", "CSS", "JavaScript"]
                );

                var developer2 = new Developer("Maria", "Mineva",
                ["Python", "Machine Learning", "Data Science"],
                );
            
В ES6 се добавя и синтаксис за класове (class). По-подробно тези теми се разглеждат в Advanced модула на курса.

Представяне на основните типове данни в паметта

Представяне на основните типове данни в паметта

Съхраняване на примитивни стойности

Примитивните стойности се съхраняват "по стойност". Това означава, че когато присвояваме примитивна стойност на променлива, самата стойност се записва в паметта.
Примитивните типове обикновено се съхраняват в област от паметта наречена стек (stack). Стекът е бързо достъпна, но ограничена по размер памет.
Стойността на примитивните типове не може да бъде променяна (т.е. те са непроменими - immutable). Когато променяме стойността на променлива от примитивен тип, всъщност се създава нова стойност, а не се променя старата (старата стойност се изчиства от паметта автоматично от garbage collector, ако нямаме референция която да сочи към нея).
Примитивните типове се копират по стойност (copy by value):

                let x = 5;
                let y = x;  // в y се копира стойността 5

                x = 10;    // Променяме стойността на x

                console.log(x);  // 10
                console.log(y);  // 5  (y запазва оригиналната стойност)
            

Съхраняване на обекти

Обектите се съхраняват "по референция", затова често се наричат и Референтни типове данни. Това означава, че когато присвояваме обект на променлива, в нея се записва референция към адреса на обекта в паметта, а не стойностите на самият обект.
Референтните типове обикновено се съхраняват в област от паметта наречена heap. Heap-a е по-голямо, но по-бавно достъпно пространство в паметта.
Стойностите на обектите са променяеми (mutable). Когато се променя обект, променяме стойностите, които са в обекта, а не самия обект.
Обектите се копират по референция (copy by reference):

                let obj1 = { x: 1 };
                let obj2 = obj1; // в obj2 се копира референцията към obj1, не стойностите

                obj1.x = 2; // Променяме стойността на x в obj1

                console.log(obj1.x);  // 2
                console.log(obj2.x);  // 2  (и obj2 се променя, защото е референция към същия обект)
            

"Copy by value" vs "Copy by reference"

primitive-vs-reference-data-types-in-javascript
Изображението е от Primitive vs Reference data types in JavaScript by Alamin Shaikh

Повече за обекти

Примери и задачи

Примери и задачи

Object Definition Example

See the Pen Object Definition Example by Iva Popova (@webdesigncourse) on CodePen.

Square braces notation example

See the Pen Square braces notation example by Iva Popova (@webdesigncourse) on CodePen.

Three ways of creating (students) objects - example

See the Pen Three ways of creating (students) objects by Iva Popova (@webdesigncourse) on CodePen.

Object Constructor Example & HW

See the Pen Object Constructor Example & HW_1 by Iva Popova (@webdesigncourse) on CodePen.

findYoungestPerson - example

See the Pen findYoungestPerson - example by Iva Popova (@webdesigncourse) on CodePen.

Array of objects example & HW

See the Pen Array of objects example & HW by Iva Popova (@webdesigncourse) on CodePen.

Calculate Rectangle Area - Task

See the Pen Calculate Rectangle Area - Task by Iva Popova (@webdesigncourse) on CodePen.

Functions as properties values - Example

See the Pen Functions as properties values - Example by Iva Popova (@webdesigncourse) on CodePen.

Functions as properties values - Task

See the Pen Calculator object Task by Iva Popova (@webdesigncourse) on CodePen.

Всички примери

Повече примери и задачи по темата:
JS_Objects @codepen

These slides are based on

customised version of

Hakimel's reveal.js

framework