Додаток А: Дрібніші зміни
Поруч з масштабними змінами, про які вже розповідалось у цій книзі, ECMAScript 6 вводить ряд інших змін, які є менш важливими, проте такими ж корисними для покращення JavaScript. Ці зміни включають покращення цілих чисел, введення нових методів для обчислень, вдосконалення Unicode–ідентифікаторів та формалізація властивості __proto__
. Я опишу їх у цьому додатку.
Робота з цілими числами
JavaScript використовує систему кодування IEEE 754 для представлення цілих та дійсних чисел, яка була причиною численних непорозумінь протягом багатьох років. Мова докладає великих зусиль, щоб гарантувати, що розробникам не потрібно турбуватися про деталі кодування чисел, але проблеми все ще з’являються час від часу. ECMAScript 6 прагне вирішити це, роблячи цілі числа легшими для ідентифікації та роботи з ними.
Ідентифікація цілих чисел
По–перше, ECMAScript 6 додає метод Number.isInteger()
, який може визначити чи значення відображає ціле число у JavaScript. Оскільки JavaScript використовує IEEE 754 для відображення чисел обох типів, дійсні та цілі числа зберігаються по—різному. Метод Number.isInteger()
використовує переваги цього і коли метод викликається для значення, рушій JavaScript дивиться на представлення значення, щоб визначити чи значення є цілим числом. Це означає, що для чисел, які виглядають як дійсні, проте зберігаються як цілі, метод Number.isInteger()
поверне true
. Наприклад:
console.log(Number.isInteger(25)); // true
console.log(Number.isInteger(25.0)); // true
console.log(Number.isInteger(25.1)); // false
У цьому коді, Number.isInteger()
повертає true
і для 25
, і для 25.0
, не дивлячись на те, що останнє виглядає як дійсне. Просте додавання десяткової точки до числа не робить його автоматично дійсним числом у JavaScript. Оскільки 25.0
насправді є простим 25
, воно просто зберігається як ціле число. Однак, число 25.1
зберігається як дійсне число, оскільки воно є десятковим дробом.
Безпечні цілі числа
IEEE 754 може точно представляти цілі числа між -2^53^ та 2^53^, а поза цим “безпечним” проміжком, двійкове представлення починають повторно використовуватись для декількох числових значень. Це означає, що JavaScript може безпечно, представляти цілі числа у проміжку IEEE 754. Наприклад, розгляньте такий код:
console.log(Math.pow(2, 53)); // 9007199254740992
console.log(Math.pow(2, 53) + 1); // 9007199254740992
Цей приклад містить помилку, обидва різних числа представляються як однакові цілі числа у JavaScript. Цей ефект стає більш помітнішим при подальшому виході за межі безпечного діапазону.
ECMAScript 6 вводить метод Number.isSafeInteger()
для кращої ідентифікації цілих чисел, які мова може представити точно. Він також додає властивості Number.MAX_SAFE_INTEGER
та Number.MIN_SAFE_INTEGER
для представлення найбільшого та найменшого значення з проміжку цілих чисел, відповідно. Метод Number.isSafeInteger()
забезпечує, щоб значення було цілочисельним і потрапляло у безпечний проміжок цілих числових значень, як у цьому прикладі:
var inside = Number.MAX_SAFE_INTEGER,
outside = inside + 1;
console.log(Number.isInteger(inside)); // true
console.log(Number.isSafeInteger(inside)); // true
console.log(Number.isInteger(outside)); // true
console.log(Number.isSafeInteger(outside)); // false
Число inside
є найбільшим безпечним цілим числом, тому true
повертається з обох методів Number.isInteger()
та Number.isSafeInteger()
. Число outside
є першим підозрілим цілочисельним значенням і воно не вважається безпечним, не зважаючи на те, що воно залишається цілим числом.
Зазвичай, за виконання арифметичних операцій або порівнянь, вам потрібно працювати лише з безпечними цілими числами, тому використовувати Number.isSafeInteger()
як частину валідації вводу було б хорошою ідеєю.
Нові математичні методи
Новий акцент на іграх та графіці, який призвів до того, ECMAScript 6 додав у JavaScript типізовані масиви, також призвів до усвідомлення того, що рушії JavaScript можуть робити багато математичних обчислень більш ефективно. Проте стратегії оптимізації, як от asm.js, які для підвищення швидкодії працюють на підмножині JavaScript, потребують більше інформації для виконання обчислень найшвидшим шляхом. Наприклад, розуміння того, коли числа мають трактуватись як 32-бітні цілі числа або 64-бітні дійсні числа є важливим для апаратних операцій, які є набагато швидшими за програмні операції.
В результаті, ECMAScript 6 додає кілька методів у об’єкт Math
, для підвищення швидкості загальних математичних обчислень. Підвищення швидкості загальних обчислень також покращує загальну швидкість додатків, які виконують велику кількість обчислень, як от графічні програми. Нові методи перелічені нижче:
Math.acosh(x)
повертає обернений гіперболічний косинусx
;Math.asinh(x)
повертає обернений гіперболічний синусx
;Math.atanh(x)
повертає обернений гіперболічний тангенсx
;Math.cbrt(x)
повертає корінь кубічний зx
;Math.clz32(x)
повертає кількість ведучих нульових бітів у 32-бітному цілочисельному представленніx
;Math.cosh(x)
повертає гіперболічний косинусx
;Math.expm1(x)
повертає результат віднімання 1 від експоненціальної функції зx
;Math.fround(x)
повертає найближче дійсне число одинарної точності відx
;Math.hypot(...values)
повертає квадратний корінь суми квадратів кожного з аргументів;Math.imul(x, y)
повертає результат виконання істинного 32-бітного множення двох аргументів;Math.log1p(x)
повертає натуральний логарифм від1 + x
;Math.log10(x)
повертає логарифм за основою 10 відx
;Math.log2(x)
повертає логарифм за основою 2 відx
;Math.sign(x)
повертає -1, якщоx
є від’ємним, 0 якщоx
рівний +0 або -0 та 1, якщоx
є додатнім;Math.sinh(x)
повертає гіперболічний синусx
;Math.tanh(x)
повертає гіперболічний тангенсx
;Math.trunc(x)
видаляє дробову частину з дійсного числа та повертає ціле число.
Детальне пояснення кожного нового методу та того, що вони роблять, виходить за межі цієї книги. Проте, якщо ваш додаток має робити загальні обчислення, не забудьте перевірити нові методи Math
перед тим, як писати імплементацію самостійно.
Unicode–ідентифікатори
ECMAScript 6 пропонує кращу підтримку Unicode, ніж попередні версії JavaScript, і він також змінює те, які символи можуть використовуватись в якості ідентифікаторів. У ECMAScript 5 вже можливо було використовувати Unicod–послідовності для ідентифікаторів. Наприклад:
// Валідно у ECMAScript 5 та 6
var \u0061 = "abc";
console.log(\u0061); // "abc"
// еквівалентно до:
console.log(a); // "abc"
Після оператора var
, у цьому прикладі, для звернення до змінної ви можете використовувати і \u0061
, і a
. У ECMAScript 6 ви також можете використовувати кодові керівні послідовності в якості ідентифікаторів, ось так:
// Валідно у ECMAScript 5 та 6
var \u{61} = "abc";
console.log(\u{61}); // "abc"
// еквівалентно до:
console.log(a); // "abc"
Цей приклад просто заміняє \u0061
на його кодовий еквівалент. У всіх інших він робить те саме, що і попередній приклад.
Крім того, ECMAScript 6 формально визначає валідні ідентифікатори у відповідності до Unicode Standard Annex #31: Unicode Identifier and Pattern Syntax, який дає такі вказівки:
- Першим символом повинен бути
$
,_
або будь–який інший Unicode–символ з похідною основною властивістюID_Start
. - Кожен символ субпослідовності повинен бути
$
,_
,\u200c
(zero-width non-joiner),\u200d
(zero-width joiner) або будь–який інший Unicode–символ з похідною основною властивістюID_Start
.
Похідні основні властивості ID_Start
та ID_Continue
визначені в синтаксисі ідентифікаторів та шаблонів Unicode (Unicode Identifier and Pattern Syntax) як спосіб ідентифікації символів, які є прийнятними для використання в якості ідентифікаторів, як от змінних або доменних імен. Ця специфікація не стосується лише JavaScript.
Формалізація властивості __proto__
Ще до того як ECMAScript 5 було завершено, декілька рушіїв JavaScript вже імплементували власну властивість __proto__
, яка може використовуватись для отримання та встановлення властивості [[Prototype]]
. Практично __proto__
був передвісник для методів Object.getPrototypeOf()
та Object.setPrototypeOf()
. Очікування того, що рушії JavaScript видалять цю властивість не має змісту (було багато популярних JavaScript–бібліотек, які використовували __proto__
), тому ECMAScript 6 також формалізує поведінку __proto__
. Проте формалізація з’являється в Додатку Б до ECMA-262 разом з таким попередженням:
Ці нововведення не вважаються частиною ядра мови ECMAScript. Програмістам не слід використовувати або покладатись на існування цих нововведень та особливостей при написанні нового коду на ECMAScript. Імплементаціям ECMAScript не рекомендується реалізувати ці нововведення якщо ця імплементація не є частиною веб–браузера або не потребує запуску застарілого ECMAScript–коду з яким зустрічаються веб–браузери.
Специфікація ECMAScript рекомендує використання Object.getPrototypeOf()
та Object.setPrototypeOf()
замість __proto__
, оскільки __proto__
має такі характеристики:
- Ви можете визначити
__proto__
лише раз у об’єктному літералі. Якщо ви визначите дві властивості__proto__
, тоді кинеться помилка. Це єдина властивість об’єктного літералу з таким обмеженням. - Обчислювана форма
["__proto__"]
поводиться як звичайна властивість і не встановлює та не повертає прототип об’єкта. Всі правила, які стосуються властивостей об’єктних літералів застосовуються у цій формі, на відміну від необчислюваної форми, яка має виключення.
Вам слід уникати використання властивості __proto__
, проте спосіб того, як специфікація її визначає, є цікавим. У рушіях ECMAScript 6, Object.prototype.__proto__
визначається як властивість–аксесор, у якої метод get
викликає Object.getPrototypeOf()
, а метод set
викликає Object.setPrototypeOf()
. Це не залишає справжніх відмінностей від використання __proto__
та Object.getPrototypeOf()
/Object.setPrototypeOf()
, за виключенням того, що __proto__
дозволяє вам встановлювати прототип у об’єктному літералі. Ось як це працює:
let person = {
getGreeting() {
return "Hello";
}
};
let dog = {
getGreeting() {
return "Woof";
}
};
// person є прототипом
let friend = {
__proto__: person
};
console.log(friend.getGreeting()); // "Hello"
console.log(Object.getPrototypeOf(friend) === person); // true
console.log(friend.__proto__ === person); // true
// встановлюємо dog в якості прототипа
friend.__proto__ = dog;
console.log(friend.getGreeting()); // "Woof"
console.log(friend.__proto__ === dog); // true
console.log(Object.getPrototypeOf(friend) === dog); // true
Замість виклику Object.create()
для створення об’єкта friend
, цей приклад створює об’єктний літерал, який присвоює значення властивості __proto__
. З іншого боку, при створенні об’єкту через метод Object.create()
, вам би довелось визначати повні дескриптори властивостей для кожної додаткової властивості.