In addition to the major changes covered in this book, ECMAScript 6 incorporates several other smaller changes that are helpful in improving JavaScript. Those changes include making integers easier to use, adding new methods for calculations, tweaking Unicode identifiers, and formalizing the __proto__ property, all of which I describe in this appendix.
JavaScript uses the IEEE 754 encoding system to represent integers and floats, which has caused much confusion over the years. The language takes great pains to ensure that developers don’t need to be concerned about the details of number encoding, but problems still occur from time to time. ECMAScript 6 addresses these problems by making integers easier to identify and work with.
ECMAScript 6 added the Number.isInteger() method, which can determine whether a value represents an integer in JavaScript. Although JavaScript uses IEEE 754 to represent both types of numbers, floats and integers are stored differently. The Number.isInteger() method takes advantage of that storage difference, and when the method is called on a value, the JavaScript engine looks at the underlying representation of the value to determine whether that value is an integer. As a result, numbers that look like floats might actually be stored as integers and cause Number.isInteger() to return true. For example:
console.log(Number.isInteger(25)); // true
console.log(Number.isInteger(25.0)); // true
console.log(Number.isInteger(25.1)); // false
In this code, Number.isInteger() returns true for both 25 and 25.0, even though the latter looks like a float. Simply adding a decimal point to a number doesn’t automatically make it a float in JavaScript. Because 25.0 is really just 25, it is stored as an integer. However, the number 25.1 is stored as a float because there is a fraction value.
IEEE 754 can only accurately represent integers between -253 and 253, and outside this “safe” range, binary representations are reused for multiple numeric values. That means JavaScript can only safely represent integers within the IEEE 754 range before problems become apparent. For instance, consider this code:
console.log(Math.pow(2, 53)); // 9007199254740992
console.log(Math.pow(2, 53) + 1); // 9007199254740992
This example doesn’t contain a typo, yet two different numbers are represented by the same JavaScript integer. The effect becomes more prevalent the further the value is outside the safe range.
ECMAScript 6 introduced the Number.isSafeInteger() method to better identify integers that the language can accurately represent. It also added the Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties to represent the upper and lower bounds of the integer range, respectively. The Number.isSafeInteger() method ensures that a value is an integer and falls within the safe range of integer values, as in this example:
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
The number inside is the largest safe integer, so it causes the methods Number.isInteger() and Number.isSafeInteger() to return true. The number outside is the first questionable integer value, and it isn’t considered safe, even though it’s still an integer.
Most of the time, you only want to deal with safe integers when you’re doing integer arithmetic or comparisons in JavaScript; therefore, using Number.isSafeInteger() as part of input validation is a good idea.
The new emphasis on gaming and graphics that led ECMAScript 6 to include typed arrays in JavaScript also led to the realization that a JavaScript engine could do many mathematical calculations more efficiently. But optimization strategies like asm.js, which works on a subset of JavaScript to improve performance, need more information to perform calculations in the fastest way possible. For instance, knowing whether the numbers should be treated as 32-bit integers or as 64-bit floats is important for hardware-based operations, which are much faster than software-based operations.
As a result, ECMAScript 6 added several methods to the Math object to improve the speed of common mathematical calculations. Improving the speed of common calculations also improves the overall speed of applications that perform many calculations, such as those in graphics programs. Table A-1 shows the new methods.
Table A-1: Math Object Methods in ECMAScript 6
Method |
Returns |
Math.acosh(x) |
The inverse hyperbolic cosine of x |
Math.asinh(x) |
The inverse hyperbolic sine of x |
Math.atanh(x) |
The inverse hyperbolic tangent of x |
Math.cbrt(x) |
The cubed root of x |
Math.clz32(x) |
The number of leading zero bits in the 32-bit integer representation of x |
Math.cosh(x) |
The hyperbolic cosine of x |
Math.expm1(x) |
The result of subtracting 1 from the exponential function of x |
Math.fround(x) |
The nearest single-precision float of x |
Math.hypot(...values) |
The square root of the sum of the squares of each argument |
Math.imul(x, y) |
The result of performing true 32-bit multiplication of the two arguments |
Math.log1p(x) |
The natural logarithm of 1 + x |
Math.log2(x) |
The base 2 logarithm of x |
Math.log10(x) |
The base 10 logarithm of x |
Math.sign(x) |
-1 if x is negative, 0 if x is +0 or -0, or 1 if x is positive |
Math.sinh(x) |
The hyperbolic sine of x |
Math.tanh(x) |
The hyperbolic tangent of x |
Math.trunc(x) |
An integer (removes fraction digits from a float) |
Explaining each new method and what it does in detail is beyond the scope of this book. But if your application needs to do a reasonably common calculation, be sure to check the new Math methods before implementing it yourself.
ECMAScript 6 offers better Unicode support than earlier versions of JavaScript, and it also changes which characters you can use as identifiers. In ECMAScript 5, it was possible to use Unicode escape sequences for identifiers. For example:
// valid in ECMAScript 5 and 6
var \u0061 = "abc";
console.log(\u0061); // "abc"
// equivalent to:
console.log(a); // "abc"
After the var statement in this example, you can use either \u0061 or a to access the variable. In ECMAScript 6, you can also use Unicode code point escape sequences as identifiers, like this:
// valid in ECMAScript 5 and 6
var \u{61} = "abc";
console.log(\u{61}); // "abc"
// equivalent to:
console.log(a); // "abc"
This example just replaces \u0061 with its code point equivalent. Otherwise, the code does the same thing as the previous example.
Additionally, ECMAScript 6 formally specifies valid identifiers in terms of Unicode Standard Annex #31, “Unicode Identifier and Pattern Syntax” (http://unicode.org/reports/tr31/), which includes the following rules:
• The first character must be $, _, or any Unicode symbol with a derived core property of ID_Start.
• Each subsequent character must be $, _, \u200c (a zero-width non-joiner), \u200d (a zero-width joiner), or any Unicode symbol with a derived core property of ID_Continue.
The ID_Start and ID_Continue derived core properties are defined in “Unicode Identifier and Pattern Syntax” as a way to identify symbols that are appropriate for use in identifiers, such as variables and domain names. The specification is not specific to JavaScript.
Even before ECMAScript 5 was completed, several JavaScript engines already implemented a custom property called __proto__ that could be used to get and set the [[Prototype]] property. Effectively, __proto__ was an early precursor to the Object.getPrototypeOf() and Object.setPrototypeOf() methods. Expecting all JavaScript engines to remove this property was unrealistic (multiple popular JavaScript libraries used __proto__), so ECMAScript 6 also formalized the __proto__ behavior. But the formalization appears in Appendix B of ECMA-262 along with this warning:
These features are not considered part of the core ECMAScript language. Programmers should not use or assume the existence of these features and behaviours when writing new ECMAScript code. ECMAScript implementations are discouraged from implementing these features unless the implementation is part of a web browser or is required to run the same legacy ECMAScript code that web browsers encounter.
The ECMAScript specification recommends using Object.getPrototypeOf() and Object.setPrototypeOf() instead because __proto__ has the following characteristics:
• You can only specify __proto__ once in an object literal. If you specify two __proto__ properties, an error is thrown. This is the only object literal property with that restriction.
• The computed form ["__proto__"] acts like a regular property and doesn’t set or return the current object’s prototype. All rules related to object literal properties apply in this form, as opposed to the non-computed form, which has exceptions.
Although you should avoid using the __proto__ property, the way the specification defines that property is noteworthy. In ECMAScript 6 engines, Object.prototype.__proto__ is defined as an accessor property whose get method calls Object.getPrototypeOf() and whose set method calls the Object.setPrototypeOf() method. Therefore, the only real difference between using __proto__ and the Object.getPrototypeOf() or Object.setPrototypeOf() methods is that __proto__ allows you to set the prototype of an object literal directly. Here’s how that works:
let person = {
getGreeting() {
return "Hello";
}
};
let dog = {
getGreeting() {
return "Woof";
}
};
// prototype is 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
Instead of calling Object.create() to make the friend object, this example creates a standard object literal that assigns a value to the __proto__ property. On the other hand, when you’re creating an object with the Object.create() method, you must specify full property descriptors for any additional object properties.