5 недостатков JavaScript, исправленных в ES6
JavaScript

5 недостатков JavaScript, исправленных в ES6

Оригинал: 5 JavaScript “Bad” Parts That Are Fixed In ES6, Raja Rao

Нововведения ECMAScript 6 (ES6) можно условно разделить на являющиеся синтаксическим сахаром (например, class), улучшающие JavaScript (например, import) и исправляющие недостатки JavaScript (например, ключевое слово let). В большинстве статей они комбинируются. Я хочу сосредоточиться только на ключевых особенностях ES6, исправляющих слабые стороны языка.

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

Ок, давайте начнем.

1. Блочная область видимости

В ES5 есть только область видимости уровня функции (т.е. необходимо оборочивать код в функцию для создания области видимости), что всегда вызывало массу вопросов. В ES6 появилась область видимости уровня блока (область видимости в пределах фигурных скобок), для этого необходимо использовать ключевые слова let или const вместо var.

Предотвращает подъем переменных за пределы области видимости

Пример ниже показывает, что переменная bonus не поднимается за пределы блока if.


// es5.js
"use strict";

var base = 1000;
var bonus = 0;

function getSalary(addBonus) {
   if (addBonus) {
       var bonus = 100;
       return base + bonus;
   }
   return base + bonus;
}

console.log(getSalary(false)); //NaN
console.log(getSalary(true)); //1100

// es5SameAs.js
"use strict";

var base = 1000;
var bonus = 0;

function getSalary(addBonus) {
   var bonus; // < -- Подъем переменной
   if (addBonus) {
       bonus = 100;
       return base + bonus;
   }
   return base + bonus;
}

console.log(getSalary(false)); //NaN
console.log(getSalary(true)); //1100

// es6.js
"use strict";

var base = 1000;
var bonus = 0;

function getSalary(addBonus) {
   if (addBonus) {
       // переменные, объявленные с помощью let, не всплывают
       let bonus = 100;
       return base + bonus;
   }
   return base + bonus;
}

console.log(getSalary(false)); //1000
console.log(getSalary(true)); //1100

Предотвращает двойное объявление переменной

В ES6 недопустимо двойное объявление переменной, если вы объявляете ее с помощью let или const в той же области видимости. Это помогает избежать двойного определения функций из разных библиотек (как функция ‘add’ в примере ниже).


// es5.js
var i = 0;
var i = 1;

var add = function(a, b) {
   return a + b;
};

var add = function(a, b) {
   return a + b;
};

// es6.js
"use strict";

let i = 0;
let i = 1; // Duplicate declaration error

const add = function(a, b) {
   return a + b;
};

const add = function(a, b) { // Duplicate declaration error
   return a + b;
};

Устраняет необходимость IIFE

В ES5, как в примере ниже, мы должны были использовать немедленно вызываемые функции, чтобы избежать попадания переменных в глобальную область видимости. В ES6 мы можем просто использовать для этого фигурные скобки ({}) и ключевые слова let и const.


// es5.js
// Библиотека (создается с помощью IIFE)
(function() {
   if (window.betterjQuery) {
       console.log('Version ' + window.betterJQuery.version + ' already exists');
       return;
   }

   var version = 'Version 1';
   var name = 'Better jQuery';
   var betterJQuery = {
       name: name,
       version: version,
       ajax: function() {

       }
   };
   window.betterJQuery = betterJQuery;
}());


console.log(version); //Reference Error
console.log(name); //Reference Error
console.log(betterJQuery.version); // Version 1

// es6.js
// Библиотека (создается с помощью блочной области видимости и let и const)
{
   if (window.betterjQuery) {
       console.log('Version ' + window.betterJQuery.version + ' already exists');
       return;
   }

   const version = 'Version 1';
   const name = 'Better jQuery';
   let betterJQuery = {
       name: name,
       version: version,
       ajax: function() {

       }
   };
   window.betterJQuery = betterJQuery;
};


console.log(version); //Reference Error
console.log(name); //Reference Error
console.log(betterJQuery.version); // Version 1

Babel — инструмент для конвертации ES6 в ES5

В конечном итоге у нас должна быть возможность запустить ES6 код в обычном браузере. Babel — это наиболее популярный инструмент для конвертации ES6 в ES5. Он доступен как консольная утилита, модуль Node.js или online-конвертер. Я использую модуль node.js для своих приложений и online-версию для быстрого просмотра различий.

На рисунке ниже показано, как Babel переименовывает переменные, чтобы эмулировать работу let и const!

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

В ES5, если у вас есть функция внутри цикла (например, for(var i = 0; i < 3; i++) {...}) и эта функция пытается получить значение переменной i, вы получаете проблемы, связанные со всплытием. В ES6, если использовать let, такой проблемы нет.


// es5.js
"use strict";

var arr = [];
for (var i = 0; i < 3; i++) { // "i" всплывает в глобальную область видимости
   arr.push(function() {
       return i; // ссылка на глобальную i
   });
}

console.log(i); // < == i равно 3

for (var j = 0; j < 3; j++) {
   console.log(arr[j][]); // три раза напечатает 3
}

// es6.js
"use strict";

var arr = [];
for (let i = 0; i < 3; i++) { // let создает "i" для каждого цикла
   arr.push(function() {
       return i; // ссылка на локальную i
   });
}

console.log(i); // < == Refefence error

for (var j = 0; j < 3; j++) {
   console.log(arr[j][]); // напечатает 0, 1, 2
}
Обратите внимание: В данном случае нельзя использовать const, поскольку константа не может быть счетчиком цикла.

2. Лексическое значение this (с помощью стрелочных функций)

В ES5 значение this в функции может меняться в зависимости от того, где и как вызывается функция, это всегда вызывало много боли у разразработчиков. ES6 устраняет эту основную проблему, делая this «лексическим».

Лексическое this — это особенность, которая заставляет this всегда указывать на тот объект, в рамках которого физически находится функция.

Проблема и два способа решения в ES5:

В примере ниже мы пытаемся вывести имя пользователя и зарплату. Значение salary мы получаем с сервера (якобы). Обратите внимание, когда приходит ответ от сервера значением this становится объект window, а не person.


// es5.js
function getSalaryFromServer(id, callback) {
   setTimeout(function() {
       callback(1000);
   }, 0);
};

var person = {
   id: '1',
   firstName: 'raja',
   lastName: 'rao',
   printNameAndSalary: function() {
       console.log(this.id); // '1'
      
       getSalaryFromServer(this.id, function(salary) {
           console.log(this.firstName); // undefined!!!
           console.log('salary = ' + salary); // 1000
       });
   }
}

person.printNameAndSalary();

// es5Sol1.js
function getSalaryFromServer(id, callback) {
   setTimeout(function() {
       callback(1000);
   }, 0);
};

var person = {
   id: '1',
   firstName: 'raja',
   lastName: 'rao',
   printNameAndSalary: function() {
       console.log(this.id); // '1'
      
       getSalaryFromServer(this.id, function(salary) {
           console.log(this.firstName); // undefined!!!
           console.log('salary = ' + salary); // 1000
       });
   }
}

person.printNameAndSalary();

// es5Sol2.js
function getSalaryFromServer(id, callback) {
   setTimeout(function() {
       callback(1000);
   }, 0);
};

var person = {
   id: '1',
   firstName: 'raja',
   lastName: 'rao',
   printNameAndSalary: function() {
       console.log(this.id); // '1'
       var self = this;

       getSalaryFromServer(this.id, function(salary) {
           console.log(self.firstName); // использовать self
           console.log('salary = ' + salary); // 1000
       });
   }
}

person.printNameAndSalary();

Решение в ES6

Использование стрелочной функции => делает this лексическим автоматически.


// es6.js
function getSalaryFromServer(id, callback) {
   setTimeout(function() {
       callback(1000);
   }, 0);
};

var person = {
   id: '1',
   firstName: 'raja',
   lastName: 'rao',
   printNameAndSalary: function() {
       console.log(this.id); // '1'

       getSalaryFromServer(this.id, (salary) => { // стрелочная функция
           console.log(this.firstName); // 'raja'
           console.log('salary = ' + salary); // 1000
       });
   }
}

person.printNameAndSalary();

На рисунке ниже показано, как Babel конвертирует стрелочные функции в функции ES5, чтобы они работали в текущих версиях браузеров.

3. Работа с arguments

В ES5 arguments ведет себя как массив (мы можем пройтись по нему циклом), но это не массив. Т.е. методы массивов, такие как sort, slice и т.д., не доступны.

В ES6 мы можем использовать так называемые rest-параметры. Они представляют собой многоточие и название объекта, например ...args. Rest-параметр является массивом, можно использовать все методы массива.


// es5.js
function mySort(id, callback) {
   var args = Array.prototype.slice.call(arguments); // < -- конвертируем arguments в массив
   return args.sort(function(a, b) { return a - b; });
};

console.log(mySort(10, 2, 3)); // [2, 3, 10]

// es6.js
function mySort(...args) { // < -- Rest-параметрs
   return args.sort((a, b) => a - b });
};

console.log(mySort(10, 2, 3)); // [2, 3, 10]

4. Классы

Концептуально, в JavaScript нет такого понятия как класс, в отличие от объектно-ориентированных языков программирования, таких как Java. Но разработчики долгое время использовали функции, которые создавали объекты при вызове с ключевым словом new.

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

В ES6 появился новый синтаксис, схожий с соответствующим в других языках программирования. Ниже показано сравнение классов ES5 и ES6.


// es5.js
"use strict";

// Родительский класс
var Shape = function(id, x, y) {
   this.id = id;
   this.location(x, y);
};

Shape.prototype.location = function(x, y) {
   this.x = x;
   this.y = y;
};

Shape.prototype.toString = function() {
   return "Shape(" + this.id + ")";
};

Shape.prototype.getLocation = function() {
   return {
       x: this.x,
       y: this.y
   };
};


// Дочерний класс
var Circle = function(id, x, y, radius) {
   Shape.call(this, id, x, y);
   this.radius = radius;
};
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;

Circle.defaultCircle = function() { // статический метод
   return new Circle('default', 0, 0, 100);
};

Circle.prototype.toString = function() {
   return "Circle > " + Shape.prototype.toSting.call(this);
};


var defaultCircle = Circle.defaultCircle(); // вызов статического метода

var myCircle = new Circle('123', '5px', '10px', 5);  // новый экземпляр
console.log(myCircle.toString()); // Circle > Shape(id = 123)
console.log(myCircle.getLocation()); // { x: '5px', y: '10px }

// es6.js
"use strict";

// Родительский класс
class Shape {
   constructor(id, x, y) {
       this.id = id;
       this.location(x, y);
   }

   location(x, y) { // < -- функция прототипа
       this.x = x;
       this.y = y;
   }

   toString() { // < -- функция прототипа
       return `Shape(id = ${this.id})`;
   }

   getLocation() { // < -- функция прототипа
       return {
           x: this.x,
           y: this.y
       };
   }
}


// Дочерний класс
class Circle extends Shape {
   constructor(id, x, y, radius) {
       super(id, x, y); // вызов конструктора Shape
       this.radius = radius;
   }

   static defaultCircle() { // статический метод
       return new Circle('default', 0, 0, 100);
   }

   toString() {
       return 'Circle > ' + super.toString();
   }
}

var defaultCircle = Circle.defaultCircle(); // вызов статического метода

var myCircle = new Circle('123', '5px', '10px', 5);  // новый экземпляр
console.log(myCircle.toString()); // Circle > Shape(id = 123)
console.log(myCircle.getLocation()); // { x: '5px', y: '10px }

5. Строгий режим

Строгий режим («use strict») помогает обнаружить наиболее общие проблемы JavaScript и повысить «безопасность». В ES5 строгий режим не обязателен, но в ES6 он необходим для большинства особенностей. Поэтому большинство людей используют строгий режим по умолчанию, а многие инструменты, такие как Babel, автоматически добавляют «use strict» в начало файла, позволяя нам писать лучший JavaScript.

Спасибо!

Рассылка
Подпишитесь на рассылку и получайте дайджест новостей и статей.
Никакого спама!
Подписаться