Работа с цели числа

JavaScript използва IEEE 754 система на кодиране за представлявяне на цели и десетични числа, което доведе до много объркване през годините. Езика минава през големи усилия за да гарантира, че програмистите не трябва да се притесняват за детайлите на number кодирането, но проблеми все още се създават от време на време. 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, обаче се съхранява, като десетично, защото има стойност на фракция.

Safe Integers

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 е първата съмнителна стойност за цяло число, така че вече не се смята за безопасна, въпреки че тя все още е цяло число.

През по-голямата част от времето вие ще искате да се справяте с безопасни числа при аритметика или сравняване в JavaScript, така че използването на Number.isSafeInteger(), като част от входа за валидиране е добра идея.

Нови Math методи

Новия акцент върху игри и графики доведе до ECMAScript 6 да включва typed arrays в 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) - Връща най-близката единична точност на float за 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 ако х е отрицателно, 0 ако x е 0 или - 0 или 1 ако х е положително.
  • Math.sinh(x) - Връща хиперболичния синус на x.
  • Math.tanh(x) - Връща хиперболичния тангенс на x.
  • Math.trunc(x) - Премахва цифри след десетичната запетая и връща цяло число.

Извън обхвата на тази книга е да се обясни по подробно всеки нов метод и това което прави. Въпреки това, ако търсите по-разумно общо изчисление, не забравяйте да проверите за нови Math методи, преди да го изпълните сами.

Unicode идентификатори

ECMAScript 6 предлага по-добра Unicode поддръжка от предишните версии на JavaScript и също така променя начина, по-който характерите могат да се използват, като идентификатори. В ECMAScript 5 вече е възможно да се използват Unicode екраниращи последователности за идентификатори. Например:

// Валидно в ECMAScript 5 и 6
var \u0061 = "abc";

console.log(\u0061);        // "abc"

// еквивалентно на
// console.log(a);          // "abc"
				

След var изявлението в този пример, можете да използвате \u0061 или а за достъп до променливата. В ECMAScript 6 можете да използвате Unicode точково кодиране за екраниращи последователности, като идентификатори, като това:

// Валидно в 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, който дава следните правила:

  1. Първия характер трябва да бъде $, _, или всеки Unicode символ с извлечено основно свойство от ID_Start.
  2. Всеки следващ характер трябва да бъде $, _, \u200c (zero-width non-joiner), \u200d (zero-width joiner), или всеки Unicode символ с извлечено основно свойство от ID_Continue.

На ID_Start и ID_Continue извлечените основни свойства са определени в Unicode Identifier и Pattern Syntax, като начин за идентифициране на символи, които са подходящи за използване в идентификатори на променливи и имена на домейни. Спецификацията не е специфична за JavaScript.

Формализиране на proto свойство

Дори преди ECMAScript 5 да е завършен, няколко JavaScript машини вече изпълняваха потребителско свойство, наречено __proto__ , което може да се използва за получаване и задаване на [[Prototype]]. Ефективно, __proto__ е ранен предшественик на методите Object.getPrototypeOf() и Object.setPrototypeOf(). Очаква се всички JavaScript машини да премахнат това не реалистично свойство (това са популярни JavaScript библиотеки използващи __proto__ ), така че ECMAScript 6 също формализира това __proto__ поведение. Въпреки, че формализирането е в приложение B на ECMA-262, заедно с това предупреждение:

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

Спецификацията на ECMAScript препоръчва да използвате Object.getPrototypeOf() и Object.setPrototypeOf() вместо него, защото __proto__има следните характеристики:

  1. Можете да зададете __proto__ само веднъж в даден обект . Ако зададете две __proto__ свойства ще бъде хвърлена грешка. Това е единственото свойство на обект с такова ограничение.

  2. Изчислената форма ["__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

// set prototype to 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() метода, ще трябва да уточните пълните описания на свойства за каквито и да било допълнителни обектни свойства.