ES6 Digital Extension

Posted by kool_samule on Sat, 08 Jun 2019 22:47:52 +0200

Previous remarks

This article will introduce ES6 digital extension in detail.

 

Exponential operator

The only JS grammatical change introduced by ES2016 is the power operator, which is a mathematical operation that applies exponential to cardinality. The existing Math.pow() method of JS can perform power calculation, but it is also one of the few methods that need to be used instead of formal operators to do power calculation.

The power operator is the base of two asterisks (**) left operand and the right operand is the exponent.

let result = 5 ** 2;
console.log(result) // 25
console.log(result === Math.pow(5,2) ) // true

An exponential operator can be combined with an equal sign to form a new assignment operator (**=)

let a = 1.5;
a **= 2;
// Equivalent to a = a * a;

let b = 4;
b **= 3;
// Equivalent to b = b * b * b;

[Note] In V8 engines, exponential operators differ from Math.pow implementations, and there are subtle differences between them for very large operations.

Math.pow(99, 99) // 3.697296376497263e+197

99 ** 99 // 3.697296376497268e+197

[Operational order]

The power operator has the priority of all binary operators in JS (the priority of unary operators is higher than **), which means that it is first applied to all composite operations.

let result = 2 * 5 ** 2
console.log(result) // 50

Calculate 52 first, then multiply the value by 2, and the final result is 50.

[Operational Limitation]

The power operator does have some unusual limitations that other operators do not have. The unary expression on its left side can only use + + or -

//Syntax error
let result =-5 ** 2

In this example, -5 is written in a grammatical error because the order of operations is not clear. - Does it apply only to 5, or to the result of expression 5** 2? Disabling the binary expression on the left side of the power operator eliminates ambiguity. To specify the intention, brackets are needed to wrap - 5 or 5** 2.

//Can wrap 5**2
let result1 =-(5 ** 2) //-25

//It can also be wrapped.-5
let result2 = (-5) ** 2 // Equivalent to 25

If parentheses are placed at both ends of an expression, then - will apply to the entire expression; if parentheses are placed at both ends of - 5, it indicates that the second curtain of - 5 is to be calculated.

On the left side of the curtain operator, you can use + + and -, without parentheses, because both operators clearly define the behavior that acts on the operand. The prefix + + or -- changes the operands before all other operations occur, and the suffix version does not change until the entire expression has been computed. Both of these usages are safe on the left side of the operation.

let num1 = 2,
    num2 = 2;
console.log(++num1 ** 2) // 9
console.log(num1) // 3
console.log(num2--** 2) // 4
console.log(num2) // 1

In this example, num1 adds 1 before applying the power operator, so num1 becomes 3 and the result is 9, while num2 keeps the value of the power operator 2 and then subtracts 1.

 

Different binary systems

ES6 provides a new way to write binary and octal values, expressed by prefixes 0b (or 0B) and 0o (or 0O), respectively.

0b111110111 === 503 // true
0o767 === 503 // true

Starting with ES5, in strict mode, octal system is no longer allowed to be represented by prefix 0. ES6 makes it clear that prefix 0o should be used to represent octal system.

// Non-strict model
(function(){
  console.log(0o11 === 011);
})() // true

// Strict model
(function(){
  'use strict';
  console.log(0o11 === 011);
})() // Uncaught SyntaxError: Octal literals are not allowed in strict mode.

If you want to convert the string values of 0b and 0o prefixes to decimal values, use Number method

Number('0b111')  // 7
Number('0o10')  // 8

 

Number method

ES6 provides two new methods on Number object, Number.isFinite() and Number.isNaN().

[Number.isFinite()]

Number.isFinite() is used to check whether a value is finite.

console.log( Number.isFinite(15)); // true
console.log( Number.isFinite(0.8)); // true
console.log( Number.isFinite(NaN)); // false
console.log( Number.isFinite(Infinity)); // false
console.log( Number.isFinite(-Infinity)); // false
console.log( Number.isFinite('foo')); // false
console.log( Number.isFinite('15')); // false
console.log( Number.isFinite(true)); // false

The difference from the original isFinite() method is that the Number.isFinite() method has no implicit Number() type conversion and returns false for all non-numeric values.

console.log(isFinite(15)); // true
console.log(isFinite(0.8)); // true
console.log(isFinite(NaN)); // false
console.log(isFinite(Infinity)); // false
console.log(isFinite(-Infinity)); // false
console.log(isFinite('foo')); // false
console.log(isFinite('15')); // true
console.log(isFinite(true)); // true

ES5 can deploy the Number.isFinite method through the following code

(function (global) {
  var global_isFinite = global.isFinite;

  Object.defineProperty(Number, 'isFinite', {
    value: function isFinite(value) {
      return typeof value === 'number' && global_isFinite(value);
    },
    configurable: true,
    enumerable: false,
    writable: true
  });
})(this);

[Number.isNaN()]

Number.isNaN() is used to check whether a value is NaN

console.log(Number.isNaN('true')); //false
console.log(Number.isNaN('hello')); //false
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN(15)); // false
console.log(Number.isNaN('15')); // false
console.log(Number.isNaN(true)); // false
console.log(Number.isNaN('true'/0)); // true

Unlike the original isNaN() method, there is no implicit Number() type conversion and non-NaN returns false.

console.log(isNaN('true')); //true
console.log(isNaN('hello')); //true
console.log(isNaN(NaN)); // true
console.log(isNaN(15)); // false
console.log(isNaN('15')); // false
console.log(isNaN(true)); // false
console.log(isNaN('true'/0)); // true

ES6 ports the global methods parseInt() and parseFloat() onto Number objects, and the behavior remains unchanged.

[parseInt()]

// ES5 Writing of
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45

// ES6 Writing of
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45

The goal is to gradually reduce the global approach and make the language modular.

Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true

[Number.isInteger()]

Number.isInteger() is used to determine whether a value is an integer. It should be noted that within JS, integers and floating-point numbers are stored in the same way, so 3 and 3.0 are considered to be the same value.

Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false
Number.isInteger("15") // false
Number.isInteger(true) // false

ES5 can deploy Number.isInteger() through the following code

(function (global) {
  var floor = Math.floor,
    isFinite = global.isFinite;

  Object.defineProperty(Number, 'isInteger', {
    value: function isInteger(value) {
      return typeof value === 'number' &&
        isFinite(value) &&
        floor(value) === value;
    },
    configurable: true,
    enumerable: false,
    writable: true
  });
})(this);

 

Number constant

[Number.EPSILON] 

ES6 adds a minimal constant Number.EPSILON to the Number object

Number.EPSILON// 2.220446049250313e-16
Number.EPSILON.toFixed(20)// '0.00000000000000022204'

The purpose of introducing such a small amount is to set a range of errors for floating-point calculations.

0.1 + 0.2// 0.30000000000000004

0.1 + 0.2 - 0.3// 5.551115123125783e-17

5.551115123125783e-17.toFixed(20)// '0.00000000000000005551'

But if the error is less than Number.EPSILON, we can assume that the correct result is obtained.

5.551115123125783e-17 < Number.EPSILON // true

Therefore, Number.EPSILON is essentially an acceptable error range.

function withinErrorMargin (left, right) {
  return Math.abs(left - right) < Number.EPSILON;
}
withinErrorMargin(0.1 + 0.2, 0.3)// true
withinErrorMargin(0.2 + 0.2, 0.3)// false

The above code is a floating-point operation and deploys an error checking function.

[Safe Integer]

JS can accurately represent integers ranging from - 2 ^ 53 to 2 ^ 53 (excluding two endpoints), beyond which it cannot accurately represent this value.

Math.pow(2, 53) // 9007199254740992

9007199254740992  // 9007199254740992
9007199254740993  // 9007199254740992

Math.pow(2, 53) === Math.pow(2, 53) + 1 // true

In the above code, after exceeding the 53 power of 2, a number is not accurate.

[Number.MAX_SAFE_INTEGER,Number.MIN_SAFE_INTEGER]

ES6 introduces two constants Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER to represent the upper and lower limits of this range.

Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 // true
Number.MAX_SAFE_INTEGER === 9007199254740991 // true

Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER // true
Number.MIN_SAFE_INTEGER === -9007199254740991 // true

In the code above, you can see the limits that JS can accurately represent.

[Number.isSafeInteger()]

Number.isSafeInteger() is used to determine whether an integer falls within this range.

Number.isSafeInteger('a') // false
Number.isSafeInteger(null) // false
Number.isSafeInteger(NaN) // false
Number.isSafeInteger(Infinity) // false
Number.isSafeInteger(-Infinity) // false

Number.isSafeInteger(3) // true
Number.isSafeInteger(1.2) // false
Number.isSafeInteger(9007199254740990) // true
Number.isSafeInteger(9007199254740992) // false

Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false
Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false

The implementation of this function is very simple, that is, to compare it with the two boundary values of the safe integer.

Number.isSafeInteger = function (n) {
  return (typeof n === 'number' &&
    Math.round(n) === n &&
    Number.MIN_SAFE_INTEGER <= n &&
    n <= Number.MAX_SAFE_INTEGER);
}

When using this function in practice, we should pay attention to verifying whether the result of operation falls within the scope of safe integers, not only verifying the result of operation, but also verifying each value of the operation.

Number.isSafeInteger(9007199254740993) // false
Number.isSafeInteger(990) // true
Number.isSafeInteger(9007199254740993 - 990)  // true

9007199254740993 - 990
// Return result 9007199254740002
// The correct answer should be 9007199254740003

In the above code, 9007199254740993 is not a safe integer, but Number.isSafeInteger returns the result, showing that the result is safe. This is because this number is beyond the accuracy range, resulting in the computer internal, in the form of 9007199254740992 storage.

9007199254740993 === 9007199254740992 // true

Therefore, if you only verify that the result of the operation is a safe integer, you will probably get the wrong result. The following functions can verify both arithmetic numbers and results at the same time

function trusty (left, right, result) {
  if (
    Number.isSafeInteger(left) &&
    Number.isSafeInteger(right) &&
    Number.isSafeInteger(result)
  ) {
    return result;
  }
  throw new RangeError('Operation cannot be trusted!');
}
// RangeError: Operation cannot be trusted!
trusty(9007199254740993, 990, 9007199254740993 - 990)

trusty(1, 2, 3)// 3

 

Math object

ES6 adds 17 math-related methods to Math objects. All of these methods are static methods that can only be invoked on Math objects

[Math.trunc]

Math.trunc method is used to remove the decimal part of a number and return the integer part.

Math.trunc(4.1) // 4
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc(-4.9) // -4
Math.trunc(-0.1234) // -0

For non-numeric values, Math.trunc uses Number method internally to convert them to numeric values first

Math.trunc('123.456')// 123

Returns NaN for null values and values that cannot intercept integers

Math.trunc(NaN);      // NaN
Math.trunc('foo');    // NaN
Math.trunc();         // NaN

For environments where this method is not deployed, you can simulate it with the following code

Math.trunc = Math.trunc || function(x) {
  return x < 0 ? Math.ceil(x) : Math.floor(x);
};

[Math.sign]

Math.sign method is used to determine whether a number is positive, negative or zero. For non-numeric values, they are first converted to numeric values

It returns the following five values

The parameter is positive and returns + 1.
The parameter is negative and returns - 1.
Parameter 0, return 0;
Parameter is - 0, return - 0;
Other values, return NaN.
Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
Math.sign('9'); // +1
Math.sign('foo'); // NaN
Math.sign();      // NaN

For environments where this method is not deployed, you can simulate it with the following code

Math.sign = Math.sign || function(x) {
  x = +x; // convert to a number
  if (x === 0 || isNaN(x)) {
    return x;
  }
  return x > 0 ? 1 : -1;
};

[Math.cbrt]

Math.cbrt Method for Calculating Cubic Roots of a Number

Math.cbrt(-1) // -1
Math.cbrt(0)  // 0
Math.cbrt(1)  // 1
Math.cbrt(2)  // 1.2599210498948734

For non-numerical values, the Number method is also used internally to convert the Math.cbrt method to numerical values.

Math.cbrt('8') // 2
Math.cbrt('hello') // NaN

For environments where this method is not deployed, you can simulate it with the following code

Math.cbrt = Math.cbrt || function(x) {
  var y = Math.pow(Math.abs(x), 1/3);
  return x < 0 ? -y : y;
};

[Math.clz32]

JS integers are represented in 32-bit binary form. Math.clz32 method returns 32-bit unsigned integers of a number. How many leading zeros are there?

Math.clz32(0) // 32
Math.clz32(1) // 31
Math.clz32(1000) // 22
Math.clz32(0b01000000000000000000000000000000) // 1
Math.clz32(0b00100000000000000000000000000000) // 2

In the above code, the binary form of 0 is all 0, so there are 32 leading zeros; the binary form of 1 is 0b1, which only occupies one bit, so there are 31 leading zeros in 32 bits; the binary form of 1000 is 0b1111101000, which has a total of 10 bits, so there are 22 leading zeros in 32 bits.

The left shift operator (<<) is directly related to the Math.clz32 method

Math.clz32(0) // 32
Math.clz32(1) // 31
Math.clz32(1 << 1) // 30
Math.clz32(1 << 2) // 29
Math.clz32(1 << 29) // 2

For decimal numbers, Math.clz32 only considers integer parts.

Math.clz32(3.2) // 30
Math.clz32(3.9) // 30

For null values or other types of values, the Math.clz32 method converts them to values before calculating them.

Math.clz32() // 32
Math.clz32(NaN) // 32
Math.clz32(Infinity) // 32
Math.clz32(null) // 32
Math.clz32('foo') // 32
Math.clz32([]) // 32
Math.clz32({}) // 32
Math.clz32(true) // 31

[Math.imul]

The Math.imul method returns the result of multiplying two numbers in the form of 32-bit signed integers. It also returns a 32-bit signed integer.

Math.imul(2, 4)   // 8
Math.imul(-1, 8)  // -8
Math.imul(-2, -2) // 4

If only the last 32 bits are considered, Math.imul(a, b) and a * B have the same results in most cases, that is, the method is equivalent to (a * b)|0 (partial overflow over 32 bits). The reason why this method needs to be deployed is that JS has precision limitations, and the values of the 53th power over 2 can not be accurately expressed. That is to say, for multiplication of large numbers, low-order values are often inaccurate, and Math.imul method can return the correct low-order values.

(0x7fffffff * 0x7fffffff)|0 // 0

The multiplication formula above returns 0. However, since the lowest bit of both binary numbers is 1, this result must be incorrect, because according to the binary multiplication, the lowest bit of the calculation result should also be 1. This error is because their product exceeds the 53th power of 2, JS can not save additional precision, so the low value is changed to 0. Math.imul method can return the correct value 1

Math.imul(0x7fffffff, 0x7fffffff) // 1

[Math.fround]

Math.front method returns a single-precision floating-point form of a number

Math.fround(0)     // 0
Math.fround(1)     // 1
Math.fround(1.337) // 1.3370000123977661
Math.fround(1.5)   // 1.5
Math.fround(NaN)   // NaN

For integers, the Math.front method returns no different results, except for decimal numbers that cannot be accurately represented by 64 binary bits. At this point, the Math. front method returns the single-precision floating-point number closest to the decimal.

For environments where this method is not deployed, you can simulate it with the following code

Math.fround = Math.fround || function(x) {
  return new Float32Array([x])[0];
};

[Math.hypot]

Math.hypot method returns the square root of the sum of squares of all parameters

Math.hypot(3, 4);        // 5
Math.hypot(3, 4, 5);     // 7.0710678118654755
Math.hypot();            // 0
Math.hypot(NaN);         // NaN
Math.hypot(3, 4, 'foo'); // NaN
Math.hypot(3, 4, '5');   // 7.0710678118654755
Math.hypot(-3);          // 3

In the code above, the square of three plus the square of four equals the square of five.

If the parameter is not a numerical value, the Math.hypot method converts it to a numerical value. As long as one parameter cannot be converted to a value, it returns NaN

ES6 adds four logarithmic correlation methods

[Math.expm1]

Math.expm1(x) returns ex-1, that is, Math.exp(x) -1

Math.expm1(-1) // -0.6321205588285577
Math.expm1(0)  // 0
Math.expm1(1)  // 1.718281828459045

For environments where this method is not deployed, you can simulate it with the following code

Math.expm1 = Math.expm1 || function(x) {
  return Math.exp(x) - 1;
};

[Math.log1p(x)]

The Math.log1p(x) method returns the natural logarithm of 1+x, that is, Math.log(1+x). If x is less than - 1, return NaN

Math.log1p(1)  // 0.6931471805599453
Math.log1p(0)  // 0
Math.log1p(-1) // -Infinity
Math.log1p(-2) // NaN

For environments where this method is not deployed, you can simulate it with the following code

Math.log1p = Math.log1p || function(x) {
  return Math.log(1 + x);
};

[Math.log10(x)]

Math.log10(x) returns the logarithm of x based on 10. If x is less than 0, return NaN

Math.log10(2)      // 0.3010299956639812
Math.log10(1)      // 0
Math.log10(0)      // -Infinity
Math.log10(-2)     // NaN
Math.log10(100000) // 5

For environments where this method is not deployed, you can simulate it with the following code

Math.log10 = Math.log10 || function(x) {
  return Math.log(x) / Math.LN10;
};

[Math.log2(x)]

Math.log2(x) returns the logarithm of X at the bottom of 2. If x is less than 0, return NaN

Math.log2(3)       // 1.584962500721156
Math.log2(2)       // 1
Math.log2(1)       // 0
Math.log2(0)       // -Infinity
Math.log2(-2)      // NaN
Math.log2(1024)    // 10
Math.log2(1 << 29) // 29

For environments where this method is not deployed, you can simulate it with the following code

Math.log2 = Math.log2 || function(x) {
  return Math.log(x) / Math.LN2;
};

ES6 adds six hyperbolic function methods

Math.sinh(x) returns the hyperbolic sine of X
Math.cosh(x) returns the hyperbolic cosine of X
Math.tanh(x) returns the hyperbolic tangent of X
Math.asinh(x) returns the inverse hyperbolic sine of X
Math.acosh(x) returns the inverse hyperbolic cosine of X
Math.atanh(x) returns the inverse hyperbolic tangent of X

Topics: Javascript less