Translate

Поиск по этому блогу

понедельник, 24 сентября 2018 г.

ES6: Деструктивное присваивание объектов (XIII).

Деструктивное присваивание объектов.





Все материалы по ES6


Лучше всего это рассмотреть на примере.

Представьте. что у нас есть некий объект и нам нужно получить доступ к его свойствам. В ES5 мы бы сделали просто - присвоили свойства объекта переменным вот таким образом:


var person = {
 firstName : 'John',
 lastName : 'Gray'
};

var firstName = person.firstName;
var lastName = person.lastName;

console.log( firstName, lastName );



В ES6 все это можно уместить в одну строку применив деструктивное присваивание объектов.

Для этого переменные мы объявим в фигурных скобках и в качестве значения присвоим объект.

Важно, чтобы названия переменных совпадали с названием свойств, в этом случае.


let person = {
 firstName : 'John',
 lastName : 'Gray'
};

let { firstName, lastName } = person;

console.log( firstName, lastName );



Если нам требуется использовать переменные с другим именем, то нам можно сделать вот так:


let person = {
 firstName : 'John',
 lastName : 'Gray'
};

let { firstName : first , lastName : last } = person;

console.log( first, last );



Мы просто указали новые названия переменных после двоеточия.

Возможно, было бы более понятным, если бы название переменной было слева, а значение свойств справа, но... Есть так, как есть.

Вместо переменной справа, мы можем всегда указать литерал объекта:



let { firstName : first , lastName : last } = { firstName : 'John',lastName : 'Gray'};

console.log( first, last );



В данном случае мы также в консоли получим John Gray.



Значения по умолчанию.

Значения по умолчанию можно указать, если у объекта нет таких свойств, или нет значения у такого свойства.


let { firstName : first , lastName : last, age = 35 } = { firstName : 'John',lastName : 'Gray'};

console.log( first, last, age );





Динамическое вычисление названий свойств.

Имена свойств можно определять динамически с помощью выражений в квадратных скобках.


let { ['first' + 'Name'] : first , lastName : last, age = 35 } = { firstName : 'John',lastName : 'Gray'};

console.log( first, last, age );



В консоли мы увидим тоже самое.

Извлечение значений свойств вложенных объектов.

Мы можем извлечь свойства объектов, являющихся свойствами других объектов.

На примере. Создадим новый объект у которого свойство social будет являться объектом.


let user = { 
 firstName : 'John',
 lastName : 'Gray',
 social : {
  facebook: 'GrayJohn-Facebook',
  twitter: 'J-G-Twitter'
 }
}



Таким образом, чтобы получить доступ к свойству фейсбук, нам необходимо -


let user = { 
 firstName : 'John',
 lastName : 'Gray',
 social : {
  facebook: 'GrayJohn-Facebook',
  twitter: 'J-G-Twitter'
 }
}

let { firstName : first , lastName : last, social:{facebook},  age = 35 } = user;

console.log( first, last, facebook, age );





Таким образом с вложенными объектами, мы вначале указываем название основного свойства - social, а потом после двоеточия и в фигурных скобках - название вложенного свойства.

Если бы свойство facebook содержало бы в себе еще один объект, то мы сделали бы вот так:


let { firstName : first , lastName : last, social:{facebook : { имя_свойства } },  age = 35 } = user;



Если мы хотим изменить имя переменной facebook, то мы можем указать новое имя свойства через двоеточие:


let { firstName : first , lastName : last, social:{ facebook : fb },  age = 35 } = user;

console.log( first, last, fb, age );



Результат в консоли не изменится.

Деструктивное присваивание объекта в качестве параметра функции.

Лучше всего рассмотреть на примере.

Создадим функцию, которая будет принимать два параметра, один из которых - config, будет объектом.

У этого объекта будет два свойства - data и cach.

Если бы мы писали код на ES5, то поступили бы так:


function post ( url, config ) {
 config.data;
 config.cach;
};



В ES6 мы сделаем это иначе.

Вначале создадим переменную в которую поместим результат вызова функции post с параметрами...

let user = { 
 firstName : 'John',
 lastName : 'Gray',
 social : {
  facebook: 'GrayJohn-Facebook',
  twitter: 'J-G-Twitter'
 }
}

let result = post ( 'api/users', { data : user, cach : false });



И теперь используя деструктивное присваивание, мы можем вместо переменной (параметра функции) - congig, сразу передать свойства data и cach в фигурных скобках:


let user = { 
 firstName : 'John',
 lastName : 'Gray',
 social : {
  facebook: 'GrayJohn-Facebook',
  twitter: 'J-G-Twitter'
 }
}

let { firstName : first , lastName : last, social:{ facebook : fb },  age = 35 } = user;

console.log( first, last, fb, age );



function post ( url, { data, cach } ) {
 console.log( data, cach );
};

let result = post ( 'api/users', { data : user, cach : false });







Если, вместо data, мы хотим сразу получить доступ к свойства firstName и lastName, то мы можем сделать таким образом:


function post ( url, { data : { firstName, lastName }, cach } ) {
 console.log( firstName, lastName, cach );
};

let result = post ( 'api/users', { data : user, cach : false });





обратите внимание на то, что в параметре присутствует свойство data, но мы его используем только для того, чтобы добраться до свойств firstName и lastName.

Если попробовать вывести в консоли свойство data, то получим ошибку - Uncaught ReferenceError: data is not defined.

Деструктивное присваивание для возврата значений.

Для примера создадим функцию, которая будет возвращать литерал объекта.


 function getUserInfo () {
  return  {
   firstName : 'John',
         lastName : 'Gray',
         social : {
  facebook: 'GrayJohn-Facebook',
  twitter: 'J-G-Twitter'
  }
 };
};

let { firstName, lastName, social:{ twitter}} = getUserInfo();
console.log(firstName, lastName, twitter);





Здесь все очень похоже на то, что было выше. Ничего сложного, если понимать, то как происходит деструктивное присваивание.


Хотите освоить самые современные методы написания React приложений? Надоели простые проекты? Нужны курсы, книги, руководства, индивидуальные занятия по React и не только? Хотите стать разработчиком полного цикла, освоить стек MERN, или вы только начинаете свой путь в программировании, и не знаете с чего начать, то пишите через форму связи, подписывайтесь на мой канал в Телеге, вступайте в группу на Facebook.


Удачного кодирования!                                                                                                                                                              

Телеграм канал - Full Stack JavaScript Developer
Помочь проекту (любая валюта). DONATE

воскресенье, 23 сентября 2018 г.

ES6: Деструктивное присваивание массивов (XII).

Деструктивное присваивание

Синтаксис деструктивного присваивания в выражениях JavaScript позволяет извлекать данные из массивов или объектов при помощи синтаксиса, подобного объявлению массива или литералов в объекте.

Говоря простыми словами - это синтаксис, который облегчает извлечение данных из массивов и объектов.





Все материалы по ES6


Первое, что мы сделаем, это запустим скрипт Babel, который будет следить за файлами и при их изменении создавать свежие версии.

Откроем папку проекта в командной строке (КС). Вводим команду:

npm run watch


И нажать Enter

В папке src создадим файл destructor.js и сразу укажу его в файле index.html

<script src="src/destructor.js"></script>

Последние версии браузеров поддерживают синтаксис деструктивного присваивания без ранспиляции.

Деструктивное присваивание массивов

Создадим массив. Для извлечения данных из этого массива в ES5:


var cats = ['cat1', 'cat2', 'cat3','cat4'];

var cat_1 = cats[0];
var cat_2 = cats[1];
var cat_3 = cats[2];
var cat_4 = cats[3];

console.log(cat_1, cat_2, cat_3, cat_4);



В ES6 это можно сделать проще.


let cat_1, cat_2, cat_3, cat_4; //объявили переменные
[ cat_1, cat_2, cat_3, cat_4 ] = cats; //присвоили им значения

console.log(cat_1, cat_2, cat_3, cat_4);



Мы получим тот же результат - вывод в консоли всех значений массива.

Еще немного упростим:


let [ cat_1, cat_2, cat_3, cat_4 ] = cats;

console.log(cat_1, cat_2, cat_3, cat_4);



Массив можно объявить сразу в выражении:


let [ cat_1, cat_2, cat_3, cat_4 ] = ['cat1', 'cat2', 'cat3','cat4'];

console.log(cat_1, cat_2, cat_3, cat_4);



Если посмотреть на то, что сделал Babel, то можно увидеть, что он просто создал переменные и присвоил им значения.

Файл dist/destructor.js


'use strict';

// var cat_1 = cats[0];
// var cat_2 = cats[1];
// var cat_3 = cats[2];
// var cat_4 = cats[3];

var cat_1 = 'cat1',
    cat_2 = 'cat2',
    cat_3 = 'cat3',
    cat_4 = 'cat4';


console.log(cat_1, cat_2, cat_3, cat_4);



Квадратные скобки при объявлении переменных говорит о том. что мы будем использовать деструктивное присваивание. В качестве значения мы присваиваем им массив или переменную, содержащую массив.

Далее, благодаря деструктивному присваиванию значения массива копируются в созданные нами переменные.

Таким образом мы получаем переменные со значениями массива.

Если переменных больше, чем значений массива.

Если переменных больше, чем значений массива, то значение переменной будет не определено -

undefined.


let items = [11, 55, 99];

let [ low, mid, height, best ] = items;

console.log( low, mid, height, best );



В консоли мы увидим:



Есть возможность игнорировать значения. Для этого мы не указываем переменную, но оставляем запятую.

Например так:


let items = [11, 55, 99];

let [ low,, best ] = items;

console.log( low, best );



В консоли получим 11 и 99.

В массиве больше значений, чем переменных.

Для последней переменной можно использовать синтаксис оставшихся параметров - три точки ....

В этом случае переменная будет принимать массив.


let items = [11, 55, 99];

let [ low,...rest] = items;

console.log( rest );



В консоли мы увидим, что в переменной rest содержится массив.



Мы можем использовать значение по умолчанию.

В массиве меньше значений, чем переменных.




let items = [11, 55];

let [ low, mid, height = 0 ] = items;

console.log( low, mid, height );



В консоли 11, 55 и 0.

Вложенный массаив.

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


let items = [11, 55, [ 2, 4] ];

let [ low, mid, height ] = items;

console.log( height );



Значением переменной height будет массив:



Для того, чтобы вытащить значения из вложенного массива, мы еще раз используем квадратные скобки.


let items = [11, 55, [ 2, 4] ];

let [ low, mid, [ two, four ] ] = items;

console.log( low, mid, two, four );





Как параметр функции.

Деструктивное присваивание мы можем использовать как параметр функции.

Создадим простую функцию и вызовем ее. В качестве аргументов передадим массив. Для того, чтобы вытащить значения из массива, мы можем использовать деструктивное присваивание.


function compute ( [ tree, five ] ) {
 console.log("tree = " + tree);
 console.log("five = " + five)
}

compute([3, 5]);



Как и ожидалось, в консоли мы получим :



Возврат значений функцией.

Деструктивное присваивание мы можем использовать для возврата значений из функций.

Допустим у нас есть функция, которая возвращает массив из трех значений.

Далее объявим переменную в которую пометим вызов этой функции.


function getNumbers () {
 return [ 33, 44, 77];
}

let numbers = getNumbers ();

console.log( numbers );



Как можно предположить, мы получим в консоли массив из трех элементов:



Теперь, используя деструктивное присваивание мы можем объявить переменные и присвоить им значения возвращенного массива.

Для этого в качестве значения мы присвоим результат вызова нашей функции.


function getNumbers () {
 return [ 33, 44, 77];
}

let numbers = getNumbers ();

console.log( numbers );

let [t_tree, f_four, s_seven] = getNumbers();

console.log( t_tree, f_four, s_seven );





Быстрый обмен значениям переменных.

Деструктивное присваивание используется для быстрого обмена значений переменных.

Проще всего это увидеть на примере:


let yes = 'yes';
let no = 'no';

[ yes, no ] = [ no, yes];

console.log("yes = " + yes);
console.log("no = " + no);



В консоли мы увидим:



Если поcмотреть на то, что сделал Babel dist/destructor.js


'use strict';

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = 

false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n 

= true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } 

finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } 

return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) 

{ return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable 

instance"); } }; }();

// let items = [11, 55, [ 2, 4] ];

// let [ low, mid, [ two, four ] ] = items;

// console.log( low, mid, two, four );

// function compute ( [ tree, five ]) {
//  console.log("tree = " + tree);
//  console.log("five = " + five)
// }

// compute([3, 5]);

function getNumbers() {
 return [33, 44, 77];
}

var numbers = getNumbers();

console.log(numbers);

var _getNumbers = getNumbers(),
    _getNumbers2 = _slicedToArray(_getNumbers, 3),
    t_tree = _getNumbers2[0],
    f_four = _getNumbers2[1],
    s_seven = _getNumbers2[2];

console.log(t_tree, f_four, s_seven);

var yes = 'yes';
var no = 'no';

var _ref = [no, yes];
yes = _ref[0];
no = _ref[1];


console.log("yes = " + yes);
console.log("no = " + no);



По сути, если разобраться то, он делает все тоже самое. что мы бы написали сами в ES5.








                                                                                                                                                             

суббота, 22 сентября 2018 г.

ES6: Стрелочные функции (XI).

В ES6 есть новый способ создания функций - С помощью оператора Стрелка => . Такие функции называются стрелочные. Они предлагают более компактный синтаксис. Они не имеют имени и они по-своему работают с this.





Все материалы по ES6


Первое, что мы сделаем, это запустим скрипт Babel, который будет следить за файлами и при их изменении создавать свежие версии.

Откроем папку проекта в командной строке (КС). Вводим команду:

npm run watch


И нажать Enter

В папке src создадим файл arr.js и сразу укажу его в файле index.html

<script src="src/arr.js"></script>

Последние версии браузеров поддерживают стрелочные функции без транспиляции и мой браузер входит в их число.

Давайте напишем функцию, которая складывает два числа и возвращает их сумму. Назовем функцию add.


function add ( x, y ) {
 return x + y;
}

console.log ( add (3, 6 ));



В консоли мы увидим результат - 9

Теперь, давайте переведем эту функцию стрелочную.

Уберем слово function, уберем имя функции и уберем фигурные скобки, и слово - return. После параметров поставим стрелку.


let add = ( x, y ) => x + y;

console.log ( add (4, 6 ));



Если посмтреть на тип переменной add используя оператор typeof:

console.log(typeof(add));

То мы увидим в консоли function

Это означает что стрелочные функции это обычные функции. И в этом можно убедиться посмотрев на транспилированный код.


"use strict";

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

var add = function add(x, y) {
  return x + y;
};
console.log(add(4, 6));
console.log(typeof add === "undefined" ? "undefined" : _typeof(add));



Мы видим, что Babel превратил наш код в простое выражение функцией.

Давайте напишем простую функцию которая будет возводить заданное число в квадрат.


let add = ( x, y ) => x + y;
console.log ( add (4, 6 ));
console.log(typeof(add));

let square = function( a ) {
 return a * a;
}
console.log( square ( 4 ));



Посмотрим в консоли:



Стрелочная функция будет выглядеть вот так:


let square = x => x * x;



Если стрелочная функция принимает только один параметр, то нет необходимости заключать его в скобки!
Давайте напишем функцию которая вообще не принимает параметров.


function givNumer () {
 return 33;
}

console.log( givNumer ());



Эта функция просто выводит в консоль число 33. Стрелочная:


let givNumer = () => 33;
console.log( givNumer ());



Создадим функцию которая не будет возвращать ничего. Она просто выведет сообщение в консоль браузера.


let log = function () {
 console.log( 'Hello World!' );
};
log();



Стрелочная:


let log = () => console.log('Hello World!!!');
log();



Создадим функцию тело которой будет состоять из двух строк.

Функция будет принимать два параметра. Теле функции создадим переменную. После этого вернём результат.


let mult = function ( a, b ) {
 let result = a * b;
 return result;
}

console.log( mult ( 4, 5 ));



Если в стрелочной функции несколько строк, то фигурные скобки - {} обязательны! И обязательно определить то, что возвращает эта функция, используя ключевое слово return


Стрелочная:


let mult = ( a, b ) => {
 let result = a * b;
 return result;
}
console.log( mult (4, 5 ));



Теперь создадим функцию, которая возвращает литерал объекта:


let literal = function () {
 return { name : 'John'};
}

console.log ( literal () );



В консоли мы увидим:



Теперь попробуем создать стрелочную функцию, которая будет возвращать литерал объекта.

Следует помнить, что если стрелочная функция возвращает литерал объекта, то нужны круглые скобки - ()


Стрелочная функция возвращающая литерал объекта:


let literal = () => ( { name : 'John'} );

console.log ( literal () );



Теперь попробуем использовать стрелочную функцию в качестве IIFE - Immediately-invoked function expression

Если сказать коротко, то это функция, которая выполняется сразу после объявления

Это выглядит вот так:


( function () {
 console.log('IIFE');
})();



Стрелочная IIFE - функция будет выглядеть вот так:


( () => console.log('IIFE'))();



Важной особенностью стрелочных функций является то, что стрелка должна идти сразу после параметров!


Её нельзя просто так взять и спустить на строку ниже. Выдаст ошибку!



Практическое применение стрелочных функций.

Стрелочные функции очень удобно применять с массивами.

Давайте создадим массив с некоторыми числами и назовем его numbers. Я думаю что вы знаете, что у массивов есть полезные методы, которые позволяют это массив перебирать, фильтровать и т.д.

Давайте вы считаем сумму всех переменных массива. Для этого я объявлю еще одну переменную - let sum = 0;

Воспользуемся методом forEach() который есть у каждого массива, мы переберем элементы и прибавим к сумме.


let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

let sum = 0;

numbers.forEach( function( num ) {
 sum += num;
});

console.log ( sum );



В консоли мы увидим 55. Давайте превратим эту функцию в стрелочную:

numbers.forEach( num => sum += num );

console.log(sum);



Таким образом, то что у нас ранее занимало три строки, теперь занимает одну.

Также мы можем возвести в квадрат каждый элемент массива.


let squared = numbers.map( n => n * n );

console.log ( squared );





Далее посмотрим как стрелочные функции работают с объектами.

Стрелочные функции и this.

Для этого я создам литерал объекта который сохраню в переменную рerson.

У объекта person будет свойство name со значением ‘Bob’ и свойства greet - поприветствовать.В консоль выведем приветствие также посмотрим назначение this.


let person = {
 name: 'Bob',
 greet: function () {
  console.log( 'Hello! My name is ' + this.name );
  console.log( this );
 }
};

person.greet();



В консоли браузера мы увидим приветствие и сам объект person.



Теперь мы заменим функцию на стрелочную и посмотрим, что произойдет с this.

Убрали слово function и добавили =>.


let person = {
 name: 'Bob',
 greet: () => {
  console.log( 'Hello! My name is ' + this.name );
  console.log( this );
 }
};

person.greet();




Теперь мы не получили значение имени и в качестве значения this - window!

Но почему? Дело в том, что значение this берется из контекста в котором функция объявлена.! Независимо от того, где эта функция будет выполнена. Это можно увидеть на картинке:

У нас есть программа.

В ней пока что кроме объекта window ничего нет. Добавили объект person. Заметьте, что у метода мы используем стрелочную функцию. Как мы и говорили - значение this будет браться из контекста. Контекст это окружение. В данном случае окружением объекта person, всех его свойств и методов, будет являться объект window. И если значение this будет браться из контекста, то this будет ссылаться на объект window.



Если мы рассмотрим обычную функцию, то мы знаем, что this ссылается на сам объект person. Вы можете спросить, почему значение this в стрелочных функциях берется из контекста? А ответ очень простой - их так сделали! :-) Дело в том, что стрелочные функции были созданы для решения проблем в другой ситуации. Давайте посмотрим на примере. Для того, чтобы увидеть проблему мы вернемся к нашей стрелочной функции.


let person = {
 name: 'Bob',
 greet: function () {
  console.log( 'Hello! My name is ' + this.name );
  console.log( this );
 }
};



Представим, что наш Bob достаточно занятой и ему нужно пара секунд, чтобы завершить свою работу. Ожидание в 2 сек. мы симулируем с помощью функции setTimeout();.В качестве первого параметра эта функция принимает функцию и вторым параметром - количество миллисекунд, которые необходимо подождать.


let person = {
 name: 'Bob',
 greet: function () {
         setTimeout( function () {
        console.log( 'Hello! My name is ' + this.name );
        console.log( this );
  }, 2000);
 }
};

person.greet();



Если у вас есть опыт работы с JavaScript, то я думаю, что вы понимаете в чем заключается проблема. Все равно, давайте посмотрим на то, что будет в браузере. Ровно две секунды спустя мы увидим в браузере такую картину.



Но почему? Если посмотреть наш код, то логично предположить. что this ссылается на объект person, так как мы используем обычную функцию. Дело в том, что setTimeout() принадлежит объекту window. Если написать так: window.setTimeout(), то как вы думаете на что ссылается thus? И в консоли мы получим тот же самый результат! В ES5 есть несколько способов решить эту проблему. Мы рассмотрим самый распространенный: Перед setTimeout() я объявлю еще одну переменную that и в качестве значения присвоим this. И теперь, в теле функции вместо this мы укажем that.


let person = {
 name: 'Bob',
 greet: function () {
  let that = this;
  setTimeout( function () {
       console.log( 'Hello! My name is ' + that.name );
       console.log( that );
  }, 2000);
 }
};

person.greet();



Теперь благодаря замыканию Функция которую мы отправляем в setTimeout() будет иметь доступ к переменной that, значением которой будет this, то есть, в данном случае, объект person.



Можно для наглядности посмотреть на то, что ссылаются наши that и this.


let person = {
 name: 'Bob',
 greet: function () {
  let that = this;
  setTimeout( function () {
     console.log( 'Hello! My name is ' + that.name );
        console.log( 'It is my That = ' + that );
        console.log( 'It is my This = ' + this );

  }, 2000);
 }
};

person.greet();



В консоли мы увидим подтверждение:



Мы видим, что this будет объектом окна - This = [object Window], а that будет объектом нашего person - That = [object Object].

В ES6 для решения этой проблемы мы можем просто использовать стрелочную функцию.


let person = {
 name: 'Bob',
 greet: function () {
  setTimeout( () => {
      console.log( 'Hello! My name is ' + this.name );
      console.log( 'It is my This = ' + this );
  }, 2000);
 }
};

person.greet();



В результате мы в консоли увидим:



В графическом примере для стрелочной функции контекстом будет служить объект person, а не объект window. именно поэтому this будет ссылаться на person.



Помимо компактного синтаксиса, стрелочные функции были введены для решения таких вот проблем.

В качестве ознакомления, вы можете посмотреть как решил это Babel


var person = {
 name: 'Bob',
 greet: function greet() {
  var _this = this;

  setTimeout(function () {
   console.log('Hello! My name is ' + _this.name);
   console.log('It is my This = ' + _this);
  }, 2000);
 }
};

person.greet();

Babel использовал тот же самый метод, что и мы в ES5. Вся разница в том, что мы называли переменную that, а Babel назвал - _this. Благодаря замыканию, функция которую мы отправляем в setTimeout, будет иметь доступ к переменной _this и как следствие - к объекту person.

Думаю, что самое трудное в этой части - это понять как работают замыкания.

Еще некоторые особенности стрелочных функций:
  1. Стрелочные функции нельзя использовать как конструкторы объектов. То есть с ними нельзя использовать оператор new.




  2. Получим ошибку:



  3. Со стрелочными функциями нельзя использовать методы:
    1. bind()
    2. call()
    3. apply()
    4. Это происходит потому, что мы не можем изменить значение this. Оно автоматически берется из контекста.
Еще информацию по ES6 и стрелочным функциям вы можете посмотреть в моем посте ES6 для начинающих. (1)                                                                                                                                                              

четверг, 20 сентября 2018 г.

ES6:Наследование (X).

Наследование в ES6

Создание объекта на основе класса является одной из сторон объектно-ориентированного программирования . Другая сторона это наследование . То есть способность объектов наследовать свойства и методы от других классов.



Все материалы по ES6


Если у вас есть опыт работы с другими объектно ориентированными языками, то вы почувствуете себя в своей тарелке.

Если вы работали только с JavaScript, но при этом понимаете как работают прототипы, то я уверен, что вы будете очень рады увидеть новый синтаксис, который, тем не менее, не вводит новые объектно-ориентированную модель наследование, а внутри используются все те же прототипы.

Для начала, в папке src я создам файл inheritance.js , что в переводе с английского означает наследование.

Далее, я запущу Babel, который будет следить за файлами и при их изменении сгенерирует свежие версии .

Открою папку в командной строке и наберу команду:

npm run watch.


Последние версии браузеров поддерживает новый синтаксис без транспиляции и мой браузер входит в число, но тем не менее в конце мы посмотрим на транспонированный код.

Также, необходимо указать этот файл в index.html

<script src="src/inheritance.js"></script>

Продолжая тему задач, я создам класс Task и в конструкторе выведу сообщение - “создание задачи”.


class Task {
 constructor() {
  console.log("Создание задачи");
 }
}



Далее, создадим ещё один класс, который будет представлять из себя подзадачу.

Класс SubTask - подзадачи является задачей, но также будет иметь родительскую задачу . Для того чтобы указать что SubTask является подклассом, используется ключевое слово extends которое по-русски означает "расширяет".

После названия, я ставлю пробел и напишу extends и далее название класса который мы расширяем.

Это всё что нужно для того чтобы определить наследование между двумя классами.


class SubTask extends Task {
 
}



После классов я создам объект, который будет представителем класса Task и Объект, который будет представителем класса SubTask.


let task = new Task();
let subtask = new SubTask();



Давайте посмотрим на оба объекта в консоли браузера и используя оператор instanceof проверим - является ли объект subtask представителем обоих классов?


console.log(subtask instanceof Task);
console.log(subtask instanceof SubTask);



Откроем файл в браузере и посмотрим в консоли.



Мы видим два сообщения о создании задачи.

Далее мы видим два объекта и два сообщения true

Тот факт, что мы видим два сообщения о создании задачи означает, что SubTask не имея конструктора, использует конструктор родителя.

Таким образом получается:
Если у подкласса нет конструктора то он будет тебя использовать родительский конструктор!


А сообщение true означает, что subtask. является представителем класса SubTask.

и также subtask является представителем класса Task.

Наследование свойств

Как мы знаем из прошлого поста ES6:Классы , классы могут иметь свойства.

Ограничимся двумя title - заголовок и done - выполнена задача или нет.

Конструктор может принимать параметры, которые далее могут быть использованы для создания свойств.

При создании объектов укажем заголовок задачи

В качестве задачи отправим -”Изучить JavaScript” , а подзадачи отправим - “ Изучить ES6”

Далее, конструктор будет принимать параметры title, которому мы присвоим свойство this.title . И инициализируем свойство this.done со значением false То есть задача по умолчанию будет не выполнена.

Заметьте, что мы инициализировали свойство в конструкторе у класса Task, но при этом в SubTask, мы отправляем заголовок.

и пока конструктора у класса SubTask нет.


class Task {
 constructor(title) {
  this.title = title;
  this.done = false;
  console.log('Создание задачи');
 }
}

class SubTask extends Task {

}


let task = new Task('Изучить JavaScript');
let subtask = new SubTask('Изучить ES6');

console.log(task);
console.log(subtask);

console.log(subtask instanceof Task);
console.log(subtask instanceof SubTask);



Давайте посмотрим что произойдет.



В консоли мы видим два сообщения о создании задачи. Дальше мы видим два объекта, у которых свойства изучения указаны правильно.

Это означает, что
если у подкладка нет конструктора то он воспользуются конструктора суперкласса или родительского класса


Тот, в свою очередь, правильно инициализирует свойства.

Но это не значит что мы не можем определить классу SubTask свой конструктор.

Давайте это сделаем. Пока не будем указывать параметры и просто посмотрим что произойдет?


class Task {
 constructor(title) {
  this.title = title;
  this.done = false;
  console.log('Создание задачи');
 }
}

class SubTask extends Task {
 constructor() {
  
 }

}


let task = new Task('Изучить JavaScript');
let subtask = new SubTask('Изучить ES6');

console.log(task);
console.log(subtask);

console.log(subtask instanceof Task);
console.log(subtask instanceof SubTask);



Посмотрим в консоли и :



И мы увидим сообщение об ошибке.

Это происходит потому что
если подклассу мы указываем конструктор, то этот конструктор должен вызвать конструктор родительского класса .


Мы можем это сделать используя ещё одно ключевое слово которое называется super.

Я просто напишу superи поставлю круглые скобки super (); и супер вызовет конструктор родительского класса.

Давайте посмотрим.



Вроде бы всё сработало но заметьте, что у subtask заголовок исеет значение - undefined.

Это происходит потому, что конструктор родительского класса ожидает title в качестве параметра.

Но в данном случае, мы его не отправляет для этого в конструкторе

Я укажу параметр title в конструкторе.

И в качестве аргументов super() Я тоже отправлю title.


class Task {
 constructor(title) {
  this.title = title;
  this.done = false;
  console.log('Создание задачи');
 }
}

class SubTask extends Task {
 constructor( title ) {
  super( title );

 }

}


let task = new Task('Изучить JavaScript');
let subtask = new SubTask('Изучить ES6');

console.log(task);
console.log(subtask);

console.log(subtask instanceof Task);
console.log(subtask instanceof SubTask);



Давайте посмотрим, что у нас получилось.



Теперь мы видим, что всё работает правильно

И вот эти строчки я удалю они нам больше не нужны


console.log(subtask instanceof Task);
console.log(subtask instanceof SubTask);



Для того чтобы убедиться, что SubTask использует свой конструктор, давайте выведем сообщение похожие на сообщение родительского класса, только задачу изменим на создание подзадачи.


class SubTask extends Task {
 constructor(title) {
  super(title);
    console.log('Создание подзадачи');
 }

}



Посмотрим. И теперь мы видим 3 сообщения - создание задачи, создание задачи и создание подзадачи.

Первое сообщение выводится при создании задачи Task

При создании подзадачи мы попадаем в конструктор дальше мы вызываем конструктор родительского класса и здесь выводится сообщение о создании задачи.

Дальше возвращаемся конструктор SubTask и будет сообщение о создание подзадачи.

Именно поэтому видим сообщения 3 раза.

Давайте посмотрим что будет если я конструктор родительского класса как-будто удалил (закомментирую) и вызов родительского класса я тоже пока удалю. И создание объекта task тоже удалю.

И соответственно вывод в консоли тоже удалю.


class Task {
 // constructor(title) {
 //  this.title = title;
 //  this.done = false;
 //  console.log('Создание задачи');
 // }
}

class SubTask extends Task {
 constructor(title) {
  // super(title);
    console.log('Создание подзадачи');
 }

}


// let task = new Task('Изучить JavaScript');
let subtask = new SubTask('Изучить ES6');

console.log(task);
console.log(subtask);



Посмотрим, сможет ли SubTask правильно инициализироваться без конструктора у Task?



И мы видимся ту же ошибку.

Это означает что:
если мы указываем для подкласса конструктор, то этот конструктор должен вызывать конструктор родительского класса.
Этот вызов используют ключевое слово super и должен быть первой строчкой до того как мы приступим к присвоению каких-либо свойств.


И в качестве примера, давайте присвоим классу SubTask свойство parent или родитель, в котором мы укажем родительскую задачу.


class Task {
 constructor(title) {
  this.title = title;
  this.done = false;
  console.log('Создание задачи');
 }
}

class SubTask extends Task {
 constructor( title, parent ) {
  super(title);
  this.parent = parent;
    console.log('Создание подзадачи');
 }

}


let task = new Task('Изучить JavaScript');
let subtask = new SubTask('Изучить ES6', task);

console.log(task);
console.log(subtask);



Давайте посмотрим что случилось.



и мы видим? что у объекта subtask появилась свойство - parent, которое содержит в себе задачу.

И это заголовок - “Изучить JavaScript “



Помимо наследования свойств, классы могут наследовать и поведение, то есть - методы.

Наследование методов.

Давайте добавим классу Task метод complete, который мы будем использовать для того, чтобы завершить задачу. И в теле методa мы укажем свойства this.done со назначением true.
И в консоли выведен сообщение о том что задача с таким заголовком выполнена.
Для этого я воспользуюсь шаблонной строкой и напишу “задача выполнена” используя местозаполнитель покажу. title - заголовок задачи.


 complete() {
  this.done = true;
  console.log(`Задача : "${this.title}" выполнена! `);
 }



В коде, после создания объектов, давайте завершим задачу и также завершим подзадачу:


task.complete();
subtask.complete();



Посмотрим в консоль.



Мы видим сообщение, что задачи выполнены.

Это означает что SubTask унаследовал методы complete. То есть - он ему доступен.

Таким же образом мы можем унаследовать свойства get, set и статические свойства.

То есть все члены класса.

Но что делать если нас не устраивает родительский метод?

Допустим мы хотим его полностью поменять или немного изменить?

Мы можем его перезаписать!

Перезапись родительского метода

Для этого в подклассе, мы объявляем метод с таким же названием как у родительского, в теле которого мы можем сделать всё что угодно.

Я пометил, что задача выполнена.

И в консоли выведу сообщение -

console.log(`Подзадача : "${this.title}" выполнена! `);

Код полностью:

class Task {
 constructor(title) {
  this.title = title;
  this.done = false;
  console.log('Создание задачи');
 }
 complete() {
  this.done = true;
  console.log(`Задача : "${this.title}" выполнена! `);
 }
}

class SubTask extends Task {
 constructor( title, parent ) {
  super(title);
  this.parent = parent;
    console.log('Создание подзадачи');
 }
 complete() {
  this.done = true;
  console.log(`Подзадача : "${this.title}" выполнена! `);
 }

}


let task = new Task('Изучить JavaScript');
let subtask = new SubTask('Изучить ES6', task);

task.complete();
subtask.complete();

console.log(task);
console.log(subtask);



Давайте посмотрим, что произошло.



И теперь вместо “задача” мы видим “подзадача”.

Это означает, что SubTask теперь использует свой метод complete(), а не родительский. Но заметьте, что нам пришлось указать this.done в двух местах - у родительского класса и у подкласса.

Конечно, мы повторили лишь одну строчку, но тем не менее в настоящем классе методы обычно выполняет чуть больше работы и для того чтобы не нарушать принципы D.R.Y (Don’t Repeat Yourself - не повторяйся) в унаследованном методе, мы можем вызвать метод родительского класса, а потом как либо его дополнить.

this.done = true; у подкласса я удалю использую ключевое слово super далее ставлю точку и выбираю метод родительского класса - в данном случае - super.complete();

Таким образом, как только вызовите метод complete(); у объекта subtask мы “перепрыгнем” в родительский класс пометим this.done = true;

Далее, мы выведем сообщение “задача изучить ЕS6 выполнено”. После чего мы “перепрыгнем” обратно и выведем сообщение “ подзадача изучить ES6 выполнено!”

Давайте посмотрим.



И действительно, мы видим:
  1. - сообщение “задача изучить ЕS6 выполнила”.
  2. - видим сообщение “Подзадача : "Изучить ES6" выполнена! “
  3. - видим что свойства done - имеет значение true.


То есть - первое, мы успешно перезаписали родительский метод и второе - в этом перезаписанных методе мы использовали метод родительского класса.

Теперь для полной картины, я добавлю к классу Task геттер и сеттер для свойства title.

Также добавлю статическое свойство и статический метод.

Начнем с геттера и сеттера.

Геттер и сеттер.

Для начала названия в title я добавлю нижнее подчеркивание.

После конструктора напишу:


 get title() {
  return this._title;
 }
 set title( value ) {
  this._title = value;
 }
}



get title(){} и в теле метода мы просто вернем. this._title

и set title, который примет параметр value.

Для простоты я не буду проверять параметр и просто присвоили его.

Далее я создам статический метод static getDefaultTitle(), который просто вернет заголовок задачи по умолчанию.


 static getDefaultTitle() {
  return 'Задача';
 }


И после класса Task я добавлю статическое свойство, которое будет содержать в себе общее количество созданных задач.

Task.count = 0;

Инициализируем со значением 0. и при создании задачи добавим свойству единицу -

Task.count +=1;

далее, посмотрим как класс Task унаследовал эти методы и свойства.


console.log(SubTask.getDefaultTitle()); - статический метод у класса Task
console.log(SubTask.count); - статическое сво-во.



а геттер и сеттер мы сможем увидеть сразу в консоли.

Давайте посмотрим на результат



Мы видим:
  1. - Задача - это вызов статического метода у SubTask.
  2. -видим цифру 2 - это статическое свойство SubTask.count.
  3. - у субкласса есть свойство title помимо свойства _title.


Таким образом мы унаследовали свойства, методы, статические свойства, статические методы ,и геттеры, и сеттеры.

Файл inheritance.js полностью.

class Task {
 constructor(title) {
  this._title = title;
  this.done = false;
  Task.count +=1;
  console.log('Создание задачи');
 }
 complete() {
  this.done = true;
  console.log(`Задача : "${this.title}" выполнена! `);
 }
 get title() {
  return this._title;
 }
 set title( value ) {
  this._title = value;
 }
 static getDefaultTitle() {
  return 'Задача';
 }
}

Task.count = 0;

class SubTask extends Task {
 constructor( title, parent ) {
  super(title);
  this.parent = parent;
    console.log('Создание подзадачи');
 }
 complete() {
  super.complete();
  console.log(`Подзадача : "${this.title}" выполнена! `);
 }

}


let task = new Task('Изучить JavaScript');
let subtask = new SubTask('Изучить ES6', task);

console.log(SubTask.getDefaultTitle());
console.log(SubTask.count);


task.complete();
subtask.complete();

console.log(task);
console.log(subtask);

И напоследок давайте взглянем на то, что сгенерировал Babel:


'use strict';

var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Task = function () {
 function Task(title) {
  _classCallCheck(this, Task);

  this._title = title;
  this.done = false;
  Task.count += 1;
  console.log('Создание задачи');
 }

 _createClass(Task, [{
  key: 'complete',
  value: function complete() {
   this.done = true;
   console.log('\u0417\u0430\u0434\u0430\u0447\u0430 : "' + this.title + '" \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0430! ');
  }
 }, {
  key: 'title',
  get: function get() {
   return this._title;
  },
  set: function set(value) {
   this._title = value;
  }
 }], [{
  key: 'getDefaultTitle',
  value: function getDefaultTitle() {
   return 'Задача';
  }
 }]);

 return Task;
}();

Task.count = 0;

var SubTask = function (_Task) {
 _inherits(SubTask, _Task);

 function SubTask(title, parent) {
  _classCallCheck(this, SubTask);

  var _this = _possibleConstructorReturn(this, (SubTask.__proto__ || Object.getPrototypeOf(SubTask)).call(this, title));

  _this.parent = parent;
  console.log('Создание подзадачи');
  return _this;
 }

 _createClass(SubTask, [{
  key: 'complete',
  value: function complete() {
   _get(SubTask.prototype.__proto__ || Object.getPrototypeOf(SubTask.prototype), 'complete', this).call(this);
   console.log('\u041F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0430 : "' + this.title + '" \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0430! ');
  }
 }]);

 return SubTask;
}(Task);

var task = new Task('Изучить JavaScript');
var subtask = new SubTask('Изучить ES6', task);

console.log(SubTask.getDefaultTitle());
console.log(SubTask.count);

task.complete();
subtask.complete();

console.log(task);
console.log(subtask);



Кода опять много, но тем не менее можно разобрать название класса Task , конструктор, методы геттеры и сеттеры, подкласс ... Ну и так далее . Таким образом, мы теперь разобрались как работают классы и наследование .

В следующем посте мы сравним объектно-ориентированное программирование, как это было в ES5 и как это есть в ES6.


UPDATE


Основные отличия ES6 от ES5.

  1. Функции в ES6 не поднимаются. То есть нельзя вызвать функцию до её объявления в коде.
  2. В ES6 переменные “не загрязняют” глобальное пространство имен.


В ES5 все переменные являются производными глобального объекта window и мы всегда можем это проверить простым выводом в консоль:


console.log(window.name_var === name_var);

// true



В ES6 это не так.

Объявление функций и классов.

Функции в джаваскрипт можно Объявлять двумя способами:

Объявление функции - function declaration


function имя_функции() {
 что-то делает.
}


Выражение функций - function expression.

Для того чтобы объявить функцию нужно в начале объявить переменную и в качестве значения присвоить функцию. Функция может быть безымянной или иметь название. Если указать название то это поможет при отладке.


let Task = function Task() {

} 



Далее мы можем создать новый объект используя функцию Task:


let task = new Task();



Как мы уже знаем, классы являются функциями и поэтому класс мы можем объявить двумя способами.

Class Declaration - объявление класса.

Это самый простой и знакомый нам способ.


class Task {
 constructor(){
  console.log(‘Creating a Task);
}
};



Class Expression - выражение класса.

Для этого создадим переменную и в качестве значения присвоим класс. Этот класс может иметь имя или быть безымянным. Если мы указываем имя то это поможет при дальнейшей отладке. В теле класса укажем конструктор.


let Task = class Task {
 constructor(){
  console.log(‘Creating a Task’);
}
};

let task = new Task();



Если посмотреть этот код в консоли браузера, то мы увидим :

Creating a Task

Даже если мы уберем имя класса Task, то в браузере мы увидим то же самое сообщение.

Теперь давайте попробуем создать наследование.

Создадим переменную, которой присвоим в качестве значения - безымянных класс, который является расширением родительского класса.

В теле класса укажем конструктор, который будет вызывать родительский класс (super()) и выведем сообщение.


let SybTask = class extends Task {
 constructor(){
  super();
  console.log(‘Creating a SubTask’);
}
};

let subtask = new SubTask();



Если посмотреть результат в браузере, то мы увидим два сообщения о создании задачи и одно сообщение о создании подзадачи.





                                                                                                                                                             

среда, 19 сентября 2018 г.

ES6: Классы (IX).

Классы в ES6

Классы являются основой объектно-ориентированное программирование но не в JavaScript.



Все материалы по ES6


В объектно-ориентированное программирование наследование основано на прототипах объектов, а не на классах.

Если вы когда-нибудь писали код на Java Script имитируя классы, ты уверен согласитесь синтаксис не самый элегантный и простой.

С появлением es6 JavaScript наконец-то добавлена поддержка классов. Новый синтаксис не вводит новую объектно-ориентированная модель наследования, внутри используется всё те же прототипы.

Синтаксис классов это так называемый syntactic Sugar или синтаксический сахар, который призван немного подсластить, то есть упростить работу с классами и наследованием в JavaScript.

Сегодня мы рассмотрим новый синтаксис классов, а в следующем посте - наследование.

Первое, что мы сделаем, это запустим скрипт Babel, который будет следить за файлами и при их изменении создавать свежие версии.

Откроем папку проекта в командной строке (КС). Вводим команду:

npm run watch


И нажать Enter

В папке src создадим файл classes.js и сразу укажу его в файле index.html

<script src="src/classes.js"></script>

HTML последние версии браузеров поддерживает класс без транспиляции, и мой браузер входит в их число, но тем не менее в конце, мы посмотрим на транспилированный код.

Начнем сначала а именно создание классов.

Создание классов

Класс это формы или шаблон по которому создаются объекты. Классы в JavaScript определяют свойства, то есть какой будет объект и методы - что этот объект будет делать. При создание классов используется ключевое слово class, после которого указывается имя класса. В качестве примера я создам класс названием Task - или задача. Тело класса находится в фигурных скобках и в нём будут находиться члены класса, методы и свойства. А в консоли мы посмотрим на класс используя оператор typeof.


class Task {

};

console.log( typeof Task );





В консоли мы видим function. Это означает, что классы это функции, которые создают объекты. Теперь перейдем к созданию объектов.

Объекты

Объект - это экземпляр или представитель класса.

То есть объект созданный по шаблону с определёнными свойствами и методами указанными в классе. Для создания объектов мы используем ключевое слово new.

После класса мы объявим переменную task с маленькой буквы и в качестве значения укажу new Task То есть мы вызываем функцию Task передний ней ключевое слово - new.

Теперь, давайте в консоли посмотрим на тип переменной task и также проверим является ли task, представителем класса Task -с большой буквы.

Мы можем это проверить используя оператор instanceof.


class Task {

};

let task = new Task();

console.log( typeof task );
console.log( task instanceof Task );





Мы видим, что переменная тоски является объектом или точнее, содержит ссылку на объект, и этот объект является представителем класса.

Конструктор.

Конструктор - это особый метод, который вызывается в момент создания объекта.

То есть когда мы используем ключевое слово new Он создаёт свойства и инициализирует. То есть подготавливает объект к использованию.

В классе может быть только один конструктор!

Если мы укажем несколько, то получим ошибку.

Если же мне укажем конструктор вообще-то JavaScript создаст пустой конструктор.

Для того чтобы объявить конструктор в теле класса пишем constructor() {} Внутри конструктора я выведу сообщение о том что происходит создание задачи.


class Task {
 constructor() {
  console.log('Происходит создание задачи');
 }
};

let task = new Task();

console.log( typeof task );
console.log( task instanceof Task );



Давайте посмотрим на результат браузере.



и мы увидим сообщение о создании задачи

тем самым мы увидели, что конструктор сработал.

Свойства

Свойства - это характеристики объекта.

Они описывают какой объект или что у него имеется.

Свойство указывается в конструкторе. Таким образом каждый представитель класса имеет эти свойства.

Для того чтобы создать свойства у объекта, в конструкторе используется ключевое слово this, которое указывает на объект, который мы создаем.

Например у класса Task будет свойства title или заголовок, который будет содержать заголовок задачи.

Для того, чтобы указать свойство я напишу слово this. Дальше названия свойства title и в принципе всё, но мы можем указать значение. И в качестве значения давайте укажем

"Выучить JavaScript"


class Task {
 constructor() {
  this.title = "Выучить JavaScript";
  console.log('Происходит создание задачи');
 }
};

let task = new Task();

console.log( typeof task );
console.log( task.title );



и в консоли давайте выведем значение title.



Конструктор может принимать параметры, которые мы можем указать в качестве аргументов при создании нового объекта. Давайте скопируем название задачи из конструктора и вставим его между круглыми скобками.

let task = new Task("Выучить JavaScript");

Далее, в конструкторе нам необходимо указать параметр title.

И давайте присвоим ему значение по умолчанию пустую строку.

И свойству при присвоении - значение параметра title.


class Task {
 constructor( title = '' ) {
  this.title = title;
  console.log('Происходит создание задачи');
 }
};

let task = new Task("Выучить JavaScript");

console.log( typeof task );
console.log( task.title );



И в консоли мы видим тот же самый результат.

Теперь мы можем создать вторую задачу и в качестве заголовка указать что-нибудь другое - номер "выучить ES6".


class Task {
 constructor( title = '' ) {
  this.title = title;
  console.log('Происходит создание задачи');
 }
};

let task = new Task("Выучить JavaScript");
let task2 = new Task("Выучить ES6");

console.log( typeof task );
console.log( task.title );
console.log( task2.title );





Давайте добавим классу Task ещё одно свойство которое будет называться done, в котором будет указываться выполнена задача или нет.

По умолчанию мы поставим false. То есть задачи по умолчанию будет не выполнена.

При создании объекта принимать аргумент done мы не будем.

Одно маленькое замечание - свойства указывается только в конструкторе! Если у вас есть опыт работы с другими языками и такими как Java, C++ или C# в которых свойство указывает отдельно от конструктора, а в конструкторе инициализируется, то в джаваскрипт свойство указываются исключительно в конструкторе!




Так как на фото выше мы сделать не можем!

Или убрать слово let и var или const всё это не будет работать!

Насколько я знаю это исправят в следующей версии JvaScript.

Далее мы рассмотрим создание методов методы.

Методы.

Методы это то что объект умеет делать, то есть его возможности.

По сути это функции, которые в классе называются методами.

Давайте добавим классу Task метод complete(){}, с помощью которого мы укажем что задача выполнена.

Чтобы указать метод мы пишем названия метода, после чего пара круглых скобок за ними пару фигурных скобок. В теле метода мы напишем this.done = true;

Заметьте что между методами в классах не ставится, если я поставлю то мы получим ошибку


И для того чтобы видеть результат - console.log(`Задача "${this.title}" выполнена!`);


class Task {
 constructor( title = '' ) {
  this.title = title;
                this.done = false;
  console.log('Происходит создание задачи');
 }
  complete() {
  this.done = true;
  console.log(`Задача "${this.title}" выполнена!`);
 }
};

let task = new Task("Выучить JavaScript");
let task2 = new Task("Выучить ES6");

console.log( typeof task );
console.log( task.title );
console.log( task2.title );

task2.complete();





В самом сообщении воспользуюсь шаблоны строкой.

Далее, придём к статическим свойствам и методам

Статические свойства и методы



Статические свойства.



Статические свойства - это свойства которые принадлежат самому классу, а не объектам, созданным на его основе.

Статические свойства часто используются для хранения вспомогательной информации и в отличие от многих других языков, например Java, C++ или C#, мы не можем Объявлять статические свойства используют ключевое слово static.

Если мы попробую написать будет ошибка.



Так что же делать если статическая переменная мы не можем объявить о самом классе?

В самом деле это делается очень просто.

После объявления класса, мы пишем название класса и далее, так как функции в JavaScript являются такими же объектами, как и всё остальное, мы можем просто присвоить свойство.

И сразу укажем значение.

И в конструкторе при создании задачи мы давали Task.count += 1;

И после того как мы создали задачу...

class Task {
 constructor( title = '' ) {
  this.title = title;
  this.done = false;
  Task.count += 1;
  console.log('Происходит создание задачи');
 }
  complete() {
  this.done = true;
  console.log(`Задача "${this.title}" выполнена!`);
 }
};

Task.count = 0; 

let task = new Task("Выучить JavaScript");
let task2 = new Task("Выучить ES6");


console.log( task.title );
console.log( task2.title );

console.log( Task.count );
task2.complete();



Давайте вывезем значение в консоль. Оно должно равняться двум.



И действительно, мы видим 2.

Статические методы.

Статические методы это методы которые принадлежат самому классу, а не объектам созданным на его основе.

Статические методы часто используются для создания вспомогательных функций.

Давайте добавим Task статический метод.

Для того чтобы создать статический метод убежим ключевое слово static и дальше название метода.

Статический метод getDefaultTitle() с помощью которого мы сможем получить значение заголовка по умолчанию если тот не был передан. Поэтому в теле метода мы просто вернем слово "Задача".


 static getDefaultTitle() {
   return "Задача";
 }



теперь в конструкторе мы можем указать ->


class Task {
 constructor( title = Task.getDefaultTitle() ) {
  this.title = title;
  this.done = false;
  Task.count += 1;
  console.log('Происходит создание задачи');
 }
 get done() {
  return this.done === true ? 'Выполнено' : 'Не выполнено';
 }
 complete() {
  this.done = true;
  console.log(`Задача "${this.title}" выполнена!`);
 }
 static getDefaultTitle() {
   return "Задача";
 }
};

Task.count = 0; 

let task = new Task("Выучить JavaScript");
let task2 = new Task("Выучить ES6");
let task3 = new Task();

console.log( task.title );
console.log( task2.title );
console.log( task3.title );

console.log( Task.count );
task2.complete();



Давайте создадим задачу 3 без аргументов посмотрим на результат.



И мы видим что заголовок задачи 3 просто задача.

Если мы попытаемся вызвать метод .getDefaultTitle() у образца объекта которые мы сохранили в переменной task или task2 - не важно, то мы получим ошибку!

Давайте попробуем так - task.getDefaultTitle();

Мы получим ошибку Uncaught TypeError: task.getDefaultTitle is not a function.То есть у объектов task этого метода нет!

Единственный способ получить к нему доступ - использовать название самого класса!

У классов, помимо свойство методов можно добавить особые свойства get и set.

Особые свойства get и set.

Особые свойства get и set еще называются геттер и сеттер которые внутри класса выглядит ведут себя как методы а снаружи выглядит и ведут себя как свойства.

эти свойства - методы позволяют получить доступ и присвоить значение "настоящим свойствам объекта". То есть выступать в роли фильтра.

Давайте добавим свойства get и set классу - Task.

Для начала я немного почищу код. Удалю все созданные объекты и их выводы, кроме одного - task.

Итак начнем.

Get или гетер.
Если вывести в консоль значения console.log(task.done); то мы увидим false.



И если задача была бы выполнена тогда true.

Эти значения не совсем понятны простым пользователям. Было бы неплохо если бы при обращении к свойствам появлялось понятное сообщение.

Эту проблему можем решить используя свойство Get.

Давайте добавим классу Task это свойство.

Для этого в классе после конструктора я напишу слова get далее - название done, пару кругу скобок и пару фигуры скобок. Очень похоже на метод.

Но на самом деле это и есть метод.

Свойство get связывает свойства объекта с функцией, которая будет вызываться при обращении к этому свойству или методу.

В теле метода мы вернём строку в соответствии со значением свойства done.

Если done будет равняться true, то мы вернем одно значение если false то другое.

Давайте посмотрим на результат и мы увидим ошибку.



Которая говорит нам о том что не удалось указать свойство get .

Дело в том что название свойств Get и Set не должны совпадать с названием основных свойств объекта!

В нашем примере объявляя свойство get done() мы перезаписываемый значение done которое изначально указанно в конструкторе.

Есть несколько способов решить эту проблему и все они сводятся к переименование названий.

В других языках, например C#, названия личных свойств, там они называются полями, пишется с маленькой буквы, а публичных свойств с большой.

В JavaScript не принято называть свойства или метода заглавные буквы.

Также JavaScript свойства и методы в классах не делятся на личные и публичные.

Они всегда публичные!

И чтобы решить эту проблему, перед названием свойства методов которые мы хотим скрыть, часто ставят нижнее подчеркивание.

Давайте перед свойством done поставить нижнее подчеркивание.

this._done = false;

Это не означает что мы скрыли свойство или метод. К ним также легко получить доступ, но теперь хотя бы понятно что эти свойства личные.


class Task {
 constructor( title = Task.getDefaultTitle() ) {
  this.title = title;
  this._done = false;
  Task.count += 1;
  console.log('Происходит создание задачи');
 }
 get done() {
  return this._done === true ? 'Выполнено' : 'Не выполнено';
 }
  complete() {
  this.done = true;
  console.log(`Задача "${this.title}" выполнена!`);
 }
  static getDefaultTitle() {
   return "Задача";
 }
};

Task.count = 0; 

let task = new Task("Выучить JavaScript");
console.log(task.done, task._done);





Давайте попробуем выполнить задачу


task.complete();
console.log(task.done, task._done);



И ещё раз посмотрим.

И мы получили ошибку!



Это произошло потому что мы объявили свойство get но в методе .complete() мы присваиваем this.done значение true, но при этом свойства set мы не указали.


  complete() {
  this.done = true;
  console.log(`Задача "${this.title}" выполнена!`);
 }



Давайте добавим классу Task свойство set.

Свойство set
. Для этого после get я напишу set дальше название свойства.

Отличие set от get заключается в том что set принимает параметр ,который мы можем присвоить свойству, предварительно проверив этот параметр.

Вы можете назвать его как угодно но чаще всего его называют value.

И в теле метода мы проверим наличие значения value и Является ли это значение типом boolean. Если мы прошли проверку это свойство мы присвоим this._done.

Если же мы проверку не прошли то в консоли мы выдадим ошибку.


class Task {
 constructor( title = Task.getDefaultTitle() ) {
  this.title = title;
  this._done = false;
  Task.count += 1;
  console.log('Происходит создание задачи');
 }
 get done() {
  return this._done === true ? 'Выполнено' : 'Не выполнено';
 }
 set done(value) {
  if(value!== undefined && typeof value === 'boolean') {
   this._done = value;
  } else {
   console.error("Ошибка! Укажите значение true или false.");
  }
 }
  complete() {
  this.done = true;
  console.log(`Задача "${this.title}" выполнена!`);
 }
  static getDefaultTitle() {
   return "Задача";
 }
};

Task.count = 0; 

let task = new Task("Выучить JavaScript");
console.log(task.done, task._done);
task.complete();
console.log(task.done, task._done);



Давайте посмотрим на результат и мы видим что задача успешно выполнена!



Давайте попробуем присвоить, допустим Один.


  complete() {
  this.done = 1;
  console.log(`Задача "${this.title}" выполнена!`);
 }



И мы видим сообщение об ошибке.

О том что мы указали неверные значения.



Свойств можем указать сколько угодно.

В нашем примере мы можем переделать свойств title для того чтобы она использовала get и set. нам даже не обязательно иметь внутреннее свойство для того чтобы воспользоваться get и set.

Тем самым мы получим так называемая псевдо-свойства.

У классов, также как у объектов в ES6, название свойств методов может быть вычислено динамически, используя новый синтаксис - квадратные скобки.

У нас в конце-концов получился вполне себе полноценный класс.

И напоследок Давайте взглянем на то что сгенерировал Babel.


'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Task = function () {
 function Task() {
  var title = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Task.getDefaultTitle();

  _classCallCheck(this, Task);

  this.title = title;
  this._done = false;
  Task.count += 1;
  console.log('Происходит создание задачи');
 }

 _createClass(Task, [{
  key: 'complete',
  value: function complete() {
   this.done = 1;
   console.log('\u0417\u0430\u0434\u0430\u0447\u0430 "' + this.title + '" \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0430!');
  }
 }, {
  key: 'done',
  get: function get() {
   return this._done === true ? 'Выполнено' : 'Не выполнено';
  },
  set: function set(value) {
   if (value !== undefined && typeof value === 'boolean') {
    this._done = value;
   } else {
    console.error("Ошибка! Укажите значение true или false.");
   }
  }
 }], [{
  key: 'getDefaultTitle',
  value: function getDefaultTitle() {
   return "Задача";
  }
 }]);

 return Task;
}();

;

Task.count = 0;

var task = new Task("Выучить JavaScript");
console.log(task.done, task._done);
task.complete();
console.log(task.done, task._done);



Здесь очень много сгенерированного кода но в целом, в принципе, можно различить название класса Task свойства и пр.

Если выписали объектно-ориентированный JavaScript используя ES5, то вы писали, скорее всего, что-то похожее на то что сгенерировал Babel. Не совсем конечно, но близко.

Лично я очень рад что в JavaScript наконец-то появились классы.

Но многие программисты могут со мной не согласится, так как JavaScriptэто больше функциональный язык нежели чем классический.

А прелесть JavaScript заключается в том, что если выходите пользоваться классами, то можете пользоваться классами если хотите писать функциональной код Можете писать функциональный код. Здесь каждый найдёт что-то по душе!

Далее рассмотрим наследование.



                                                                                                                                                             


Хотите освоить самые современные методы написания React приложений? Надоели простые проекты? Нужны курсы, книги, руководства, индивидуальные занятия по React и не только? Хотите стать разработчиком полного цикла, освоить стек MERN, или вы только начинаете свой путь в программировании, и не знаете с чего начать, то пишите через форму связи, подписывайтесь на мой канал в Телеге, вступайте в группу на Facebook.Пишите мне - kolesnikovy70 почта gmail.com