Translate

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

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

ES6: Обещания - Promise (XIV-1).

Обещания - Promise

Сегодня мы рассмотрим обещания в ES6 или как они называются по-английски Promise.



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


ES6 для начинающих (2)

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

Давайте разберемся Зачем вообще нужны эти обещания?

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

Допустим, мы кликаем на кнопку и появляется сообщение.

Традиционно, для реализации такого рода взаимодействия, используется функция обратного вызова (callbacks). Если вы когда-нибудь писали функцию которую должна сработать при нажатии на кнопку, то вы знаете что такое функция обратного вызова.



Помимо работы с событиями DOM, функция обратного вызова также используется для общения с сервером посредством ajax.

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

При этом мы не хотим сидеть и ждать, когда все эти операции закончится.

Приложение должно работать дальше. Если использовать функции обратного вызова для каждой из этих операций то код может превратиться в так называемый callback hell - когда у функции обратного вызова есть своя функция обратного вызова, у которой есть своя функция обратного вызова и вы вbдите как всё это выглядит.



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

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



Заметьте насколько он компактен и легко читается.

Давайте наглядно посмотрим как работают обещание.

Допустим вы хотите поехать отдыхать в другую сторону.

Для этого вам необходимо:
  1. - сначала получить визу
  2. - забронировать номер в отеле,
  3. - и в конце купить билет на самолет.


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

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

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

Пока обещание выполняется оно находится в состоянии Pending ( ожидание ). Далее обещание может быть сдержанным, в таком случае она будет в состоянии Resolved, или несдержанно. В таком случае мы получим ошибку Error и обещание будет находиться в состоянии Rejected.



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

Если какой-то из шагов не выполняется, то вы полностью отменяете отпуск.

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

Допустим имеется функция applyForVisa(); - подать документы на визу.

Эта функция асинхронная и поэтому она вернет обратно - обещание.

let promise = applyForVisa();

Это обещание мы сохраним в переменной promise.

У обещания есть метод .then() что по-русски можно перевести как далее.

Данный метод принимает два аргумента.


let promise = applyForVisa();

promise.then( resolve, reject );



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

В зависимости от результата выполнения функции applyForVisa(); сработает либо функция resolve или функция reject.

В нашем случае в качестве функции resolve мы отправляем функцию bookHotel или забронировать отель. В качестве функции reject мы отправляем cancelVacation, то есть отменить отпуск.



Как я уже сказал, результатом applyForVisa(); является обещание, но нам не обязательно хранить его в переменной, поэтому сразу после вызова функции applyForVisa(); мы можем использовать метод .then() и в качестве аргументов отправить соответствующие функции.



В функцию .then() не обязательно отправлять два аргумента. Мы можем отправить только один - bookHotel на тот случай если обещание будет выполнено.

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

Он называется .catch().


applyForVisa()
 .then( bookHotel )
 .catch( cancelVacation );



Он принимает лишь один аргумент, которым будет являться функция, которая должна сработать если обещали не будет выполнено.

В данном случае cancelVacation.

Метод .catch() имеет смысл использовать если у нас имеется несколько шагов.


applyForVisa()
 .then( bookHotel )
 .then( buyTickeys )
 .catch( cancelVacation );



То есть мы бронируем отель - bookHotel.
Далее Мы покупаем билеты buyTickeys
и если вдруг на каком-то из шагов происходит ошибка, то мы и ловим (.catch) функцией cancelVacation.

Заметьте как просто читается код.
  • Подать заявление на визу.
  • Далее - забронировать отель.
  • Далее - купить билеты.
  • В случае если что-то не получится, то - отменить отпуск.
Асинхронный код выглядит так, как будто он синхронный.

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

Отличается то, как мы пишем код.Его структура.Её гораздо проще понимать.

Теперь, перейдем к созданию собственных обещаний.



                                                                                                                                                             

понедельник, 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();



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





                                                                                                                                                             


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