JavaScript (data type + operator)

Posted by leetee on Sun, 16 Jan 2022 13:52:20 +0100

JavaScript (I) (data type + operator)

Introduction - JavaScript tutorial - online channel (wangdoc.com)

1, Data type

1. What is JavaScript language?

1.1 definitions

JavaScript is a lightweight scripting language. The so-called "script language" means that it does not have the ability to develop the operating system, but is only used to write "scripts" to control other large applications (such as browsers).

JavaScript is also an embedded language. The core syntax provided by it is not many, and can only be used to do some mathematical and logical operations. JavaScript itself does not provide any API related to I/O (input / output), but depends on the host environment. Therefore, JavaScript is only suitable for embedding into a larger application environment to call the underlying API provided by the host environment.

1.2 experimental environment

The browser console directly enters:

Ctrl + Shift + J(Windows / Linux)

Developer tools entry:

The shortcut key for the developer tool is F12, Ctrl + Shift + I (Windows / Linux), and then select the Console panel

After entering the console, you can Enter the code at the prompt, and then press Enter, and the code will execute. If you press Shift + Enter, the code will wrap and execution will not be triggered. It is recommended that you copy the code to the console to experiment when reading this tutorial.

2. Basic syntax of JavaScript

2.1 ===,==,!==

Why "strict equality operator" (= =) is preferred over "equality operator" (= =):

The difference between them is that the equality operator (= =) compares whether two values are equal, and the strict equality operator (= =) compares whether they are "the same value".

If two values are not of the same type, the strict equality operator (= =) directly returns false, and the equality operator (= =) converts them to the same type, and then compares them with the strict equality operator.

The strict equality operator has a corresponding "strict inequality operator" (! = =). Its algorithm is to find the result of the strict equality operator first, and then return the opposite value.

2.1.1 various situations of = = =

(1) Different types of values

If the two values are of different types, return false directly.

1 === "1" // false
true === "true" // false

The above code compares the value 1 with the string "1", and the Boolean value true with the string "true". Because of different types, the result is false.

(2) Original type value of the same class

When comparing the values (numeric value, string, Boolean value) of the original type of the same type, true will be returned if the values are the same, and false if the values are different.

1 === 0x1 // true

The above code compares decimal 1 with hexadecimal 1. Because the type and value are the same, it returns true.

Note that NaN is not equal to any value (including itself). In addition, positive 0 equals negative 0.

NaN === NaN  // false
+0 === -0 // true

(3) Composite type value

When comparing the data of two composite types (objects, arrays, functions), we do not compare whether their values are equal, but whether they point to the same address.

{} === {} // false
[] === [] // false
(function () {} === function () {}) // false

The above code compares two empty objects, two empty arrays and two empty functions respectively, and the results are not equal. The reason is that for the values of composite types, the strict equality operation compares whether they refer to the same memory address, and the values of empty objects, empty arrays and empty functions on both sides of the operator are stored in different memory addresses. Of course, the result is false.

If two variables refer to the same object, they are equal.

var v1 = {};
var v2 = v1;
v1 === v2 // true

Note that for the comparison of two objects, the strict equality operator compares addresses, and the greater than or less than operator compares values.

var obj1 = {};
var obj2 = {};

obj1 > obj2 // false
obj1 < obj2 // false
obj1 === obj2 // false

For the above three comparisons, the first two compare values and the last one compares addresses, so they all return false.

(4) undefined and null

undefined and null are strictly equal to themselves.

undefined === undefined // true
null === null // true

Since the default value after variable declaration is undefined, two variables that only declare unassigned values are equal.

var v1;
var v2;
v1 === v2 // true

2.1.2 = = various cases

The equality operator is exactly the same as the strict equality operator when it is used to compare data of the same type.

1 == 1.0
// Equivalent to
1 === 1.0

When comparing different types of data, the equality operator will first type convert the data, and then compare it with the strict equality operator. The following is divided into several cases to discuss the rules for comparing different types of values.

(1) Original type value

==The value of the original type is converted to a numeric value for comparison== Number('true ') becomes NaN

1 == true // true
// Equivalent to 1 === Number(true)

0 == false // true
// Equivalent to 0 === Number(false)

2 == true // false
// Equivalent to 2 === Number(true)

2 == false // false
// Equivalent to 2 === Number(false)

'true' == true // false
// Equivalent to Number('true') === Number(true)
// Equivalent to NaN === 1

'' == 0 // true
// Equivalent to Number('') === 0
// Equivalent to 0 = = = 0

'' == false  // true
// Equivalent to Number('') === Number(false)
// Equivalent to 0 = = = 0

'1' == true  // true
// Equivalent to Number('1') === Number(true)
// Equivalent to 1 = = = 1

'\n  123  \t' == 123 // true
// Because when a string is converted to a number, the leading and trailing spaces are omitted

The above code converts both string and Boolean values to numeric values, and then compares them. For specific type conversion rules for string and Boolean values, see chapter data type conversion.

(2) Object is compared with the original type value

When an object (here refers to a generalized object, including arrays and functions) is compared with the value of the original type, the object is converted to the value of the original type and then compared.

Specifically, first call the valueOf() method of the object. If you get the values of the original type, compare them with each other according to the rules in the previous section; If you still get an object, call the toString() method to get the string form, and then compare it.

The following is an example of comparing an array with the original type value.

// Comparison of arrays and values
[1] == 1 // true

// Comparison of array and string
[1] == '1' // true
[1, 2] == '1,2' // true

// Object vs. Boolean
[1] == true // true
[2] == true // false

In the above example, the JavaScript engine will first call the valueOf() method of the array on array [1]. Since the returned array is still an array, it will then call the toString() method of the array to obtain the string form, and then compare it according to the rules in the previous section.

Here is a more direct example.

const obj = {
  valueOf: function () {
    console.log('implement valueOf()');
    return obj;
  },
  toString: function () {
    console.log('implement toString()');
    return 'foo';
  }
};

obj == 'foo'
// Execute valueOf()
// Execute toString()
// true

In the above example, obj is an object with customized valueof () and toString () methods. When this object is compared with the string 'foo', it will call valueOf() and toString() methods in turn, and finally return 'foo', so the comparison result is true.

(3) undefined and null

undefined and null will return true only when compared with themselves or with each other; When compared with other types of values, the result is false.

undefined == undefined // true
null == null // true
undefined == null // true

false == null // false
false == undefined // false

0 == null // false
0 == undefined // false

(4) Disadvantages of equality operator

The type conversion hidden by the equality operator will bring some counterintuitive results.

0 == ''             // true
0 == '0'            // true

2 == true           // false
2 == false          // false

false == 'false'    // false
false == '0'        // true

false == undefined  // false
false == null       // false
null == undefined   // true

' \t\r\n ' == 0     // true

These expressions are different from intuition and are easy to make mistakes. Therefore, it is recommended not to use the equality operator (= =), and it is best to use only the strict equality operator (= =).

2.2 variable lift

The JavaScript engine works by parsing the code, getting all the declared variables, and then running line by line. As a result, all variable declaration statements will be promoted to the head of the code, which is called variable lifting.

var a;
console.log(a);
a = 1;

The final result is undefined, indicating that variable a has been declared but has not been assigned.

2.3 label

JavaScript language allows you to have a label in front of the statement, which is equivalent to a locator, which is used to jump to any position of the program.

The label can be any identifier, but it cannot be a reserved word. The statement part can be any statement.

Tags are usually used with break and continue statements to jump out of a specific loop.

top:
  for (var i = 0; i < 3; i++){
    for (var j = 0; j < 3; j++){
      if (i === 1 && j === 1) break top;
      console.log('i=' + i + ', j=' + j);
    }
  }

The above code is a double loop block. The top tag is added after the break command (note that top does not need quotation marks). When the conditions are met, the double-layer loop will jump out directly. If no label is used after the break statement, you can only jump out of the inner loop and enter the next outer loop.

Tags can also be used to jump out of code blocks.

foo: {
  console.log(1);
  break foo;
  console.log('This line will not output');
}
console.log(2);

The continue statement can also be used with tags.

top:
  for (var i = 0; i < 3; i++){
    for (var j = 0; j < 3; j++){
      if (i === 1 && j === 1) continue top;
      console.log('i=' + i + ', j=' + j);
    }
  }

In the above code, there is a tag name after the continue command. When the conditions are met, the current cycle will be skipped and the next cycle will be entered directly. If no label is used after the continue statement, you can only enter the next round of inner loop.

3. Data type

Every value of JavaScript language belongs to a certain data type. There are six types of JavaScript data. (ES6 adds a value of the seventh Symbol type, which is not covered in this tutorial.)

  • number: integers and decimals (such as 1 and 3.14).
  • string: text (such as Hello World).
  • boolean: two special values representing true and false, namely true (true) and false (false).
  • Undefined: indicates "undefined" or does not exist, that is, there is no value here because there is no definition at present.
  • Null: indicates a null value, that is, the value here is null.
  • object: a collection of values.

Generally, the three types of numeric value, string and Boolean value are collectively called primitive type values, that is, they are the most basic data types and cannot be subdivided. Objects are called complex type values, because an object is often a combination of values of multiple original types and can be regarded as a container for storing various values. As for undefined and null, they are generally regarded as two special values.

Object is the most complex data type, which can be divided into three subtypes.

  • Narrow object
  • array
  • function

Objects and arrays in the narrow sense are two different data combination methods. Unless otherwise stated, the "object" in this tutorial refers to objects in the narrow sense. Function is actually a method of processing data. JavaScript regards it as a data type and can assign values to variables, which brings great flexibility to programming and lays a foundation for "functional programming" of JavaScript.

3.1 value type operator

JavaScript has three ways to determine what type a value is.

  • typeof operator

    • Function returns function.

      function f() {}
      typeof f
      // "function"
      
    • Undefined returns undefined.

      typeof undefined
      // "undefined"
      

      Using this, typeof can be used to check an undeclared variable without reporting an error.

      v
      // ReferenceError: v is not defined
      
      typeof v
      // "undefined"
      

      In the above code, if the variable v is not declared with the var command, an error will be reported if it is used directly. However, if it is placed after typeof, it will not report an error, but return undefined.

    • Object returns object.

      typeof window // "object"
      typeof {} // "object"
      typeof [] // "object"
      

      In the above code, the type of empty array ([]) is also object, which means that within JavaScript, array is essentially a special object. By the way, the instanceof operator can distinguish between arrays and objects. For a detailed explanation of the instanceof operator, see the chapter "object oriented programming".

      var o = {};
      var a = [];
      
      o instanceof Array // false
      a instanceof Array // true
      
    • null returns an object.

      typeof null // "object"
      

      The null type is object, which is caused by historical reasons. In the first edition of JavaScript language in 1995, only five data types (object, integer, floating point number, string and Boolean value) were designed. Null was not considered, but only regarded as a special value of object. Later, null is independent. As a separate data type, in order to be compatible with the previous code, the return object of typeof null cannot be changed.

  • instanceof operator

  • Object.prototype.toString method

3.2 null and undefined

3.2.1 similarities

Both null and undefined can mean "no", which is very similar. Assigning a variable to undefined or null, to be honest, makes little difference in syntax.

var a = undefined;
// perhaps
var a = null;

In the above code, the variable a is assigned to undefined and null respectively. The effects of these two writing methods are almost equivalent.

In if statements, they are automatically turned to false, and the equality operator (= =) even directly reports that they are equal.

if (!undefined) {
  console.log('undefined is false');
}
// undefined is false

if (!null) {
  console.log('null is false');
}
// null is false

undefined == null
// true

From the above code, we can see how similar the two behaviors are!

3.2.2 why are null and undeded set?

Since the meaning and usage are almost the same, why set two such values at the same time? Isn't it an unreasonable increase in complexity and trouble for beginners? This is related to historical reasons.

When JavaScript was born in 1995, like Java, it only set null to indicate "None". According to the tradition of C language, null can be automatically converted to 0.

Number(null) // 0
5 + null // 5

In the above code, when null is converted to a number, it will automatically become 0.

However, Brendan Eich, the designer of JavaScript, feels that this is not enough. First of all, in the first version of JavaScript, null is regarded as an object just like in Java. Brendan Eich thinks that the value representing "None" should not be an object. Secondly, JavaScript at that time did not include an error handling mechanism. Brendan Eich felt that if NULL was automatically converted to 0, it was not easy to find errors.

Therefore, he designed another undefined. The difference is as follows: null is an object representing "null", which is 0 when converted to a value; Undefined is an original value indicating "there is no definition here". When converted to a value, it is NaN.

Number(undefined) // NaN
5 + undefined // NaN

3.2.3 usage

For null and undefined, it can be roughly understood as follows.

Null indicates a null value, that is, the value there is now null. When calling a function, if no value is set for a parameter, null can be passed in, indicating that the parameter is empty. For example, a function accepts an error thrown by the engine as a parameter. If there is no error during operation, the parameter will pass null, indicating that no error has occurred.

Undefined means "undefined". The following is a typical scenario that returns undefined.

// The variable is declared but not assigned
var i;
i // undefined

// When calling the function, the parameter that should be provided is not provided, and the parameter is equal to undefined
function f(x) {
  return x;
}
f() // undefined

// Object has no assigned property
var  o = new Object();
o.p // undefined

// If the function does not return a value, it returns undefined by default
function f() {}
f() // undefined

3.3 Boolean

The conversion rule is that except the following six values are turned to false, all other values are regarded as true.

  • undefined
  • null
  • false
  • 0
  • NaN
  • '' or '' (empty string)

Note that the Boolean values corresponding to the empty array ([]) and the empty object ({}) are true.

if ([]) {
  console.log('true');
}
// true

if ({}) {
  console.log('true');
}
// true

4. Numerical value

4.1 integer and floating point numbers

Inside JavaScript, all numbers are stored as 64 bit floating-point numbers, even integers. Therefore, 1 and 1.0 are the same, the same number.

1 === 1.0 // true

That is to say, there are no integers at the bottom of the JavaScript language, and all numbers are decimals (64 bit floating-point numbers). It is easy to be confused that some operations can only be completed by integers. At this time, JavaScript will automatically convert 64 bit floating-point numbers into 32-bit integers, and then perform operations. See "bit operations" in the chapter operators.

Since floating-point numbers are not exact values, special care should be taken when comparing and calculating decimals.

0.1 + 0.2 === 0.3
// false

0.3 / 0.1
// 2.9999999999999996

(0.3 - 0.2) === (0.2 - 0.1)
// false

4.2 numerical accuracy

According to the international standard IEEE 754, the 64 binary bits of JavaScript floating-point numbers, starting from the far left, are composed in this way.

  • Bit 1: sign bit. 0 indicates a positive number and 1 indicates a negative number
  • 2nd to 12th of 11: index part
  • 13th to 64th digits (52 digits in total): decimal part (i.e. significant digits)

The sign bit determines the positive and negative of a number, the exponential part determines the size of the value, and the decimal part determines the accuracy of the value.

The index part has a total of 11 binary bits, so the size range is 0 to 2047. IEEE 754 stipulates that if the value of the exponential part is between 0 and 2047 (excluding two endpoints), the first bit of the significant number is always 1 by default and is not saved in the 64 bit floating-point number. That is, the significant number is always 1 xx... The form of XX, where XX The part of XX is saved in 64 bit floating-point numbers, and the maximum length may be 52 bits. Therefore, JavaScript provides a significant number of up to 53 bits.

(-1)^Sign bit * 1.xx...xx * 2^Index part

The above formula is the actual representation of a number in JavaScript under normal conditions (the exponential part is between 0 and 2047).

The precision can only be up to 53 bits, which means that integers with an absolute value less than the 53rd power of 2, i.e. - 253 to 253, can be accurately represented.

After being greater than the 53rd power of 2, the result of integer operation begins to appear errors. Therefore, values greater than 2 to the 53rd power cannot maintain accuracy. Since the 53rd power of 2 is a 16 bit decimal value, the simple rule is that JavaScript can accurately handle 15 bit decimal numbers. (it should be clear here that the 15 digit decimal number must contain decimals. Here is the precision range, which can contain 15 digit decimal numbers, which is independent of the value range)

4.3 numerical range

According to the standard, the length of the exponential part of a 64 bit floating-point number is 11 binary bits, which means that the maximum value of the exponential part is 2047 (the 11th power of 2 minus 1). In other words, the maximum value of the exponential part of the 64 bit floating-point number is 2047. If half of it represents a negative number, the range of values that JavaScript can represent is 21024 to 2-1023 (open range), and numbers beyond this range cannot be represented.

If a number is greater than or equal to the 1024 power of 2, a "forward overflow" will occur, that is, JavaScript cannot represent such a large number, and Infinity will be returned.

Math.pow(2, 1024) // Infinity

If a number is less than or equal to the - 1075th power of 2 (the minimum value of the exponential part is - 1023, plus 52 digits of the decimal part), a "negative overflow" will occur, that is, JavaScript cannot represent such a small number, and 0 will be returned directly.

Math.pow(2, -1075) // 0

The following is a practical example.

var x = 0.5;

for(var i = 0; i < 25; i++) {
  x = x * x;
}

x // 0

In the above code, 0.5 is squared 25 times continuously. Because the final result is too close to 0 and beyond the representable range, JavaScript will directly convert it to 0.

JavaScript provides the max of the Number object_ Value and min_ The value property returns the specific maximum and minimum values that can be represented.

Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324

4.4 scientific notation

Numerical values can also be expressed by scientific counting method. Here are several examples of scientific counting method.

123e3 // 123000
123e-3 // 0.123
-3.1E+12
.1e-23

Scientific counting allows the letter E or e to be followed by an integer representing the exponential part of the value.

In the following two cases, JavaScript will automatically convert the numerical value into scientific counting method, and in other cases, it will be expressed directly in literal form.

(1) The number before the decimal point is more than 21 digits.

1234567890123456789012
// 1.2345678901234568e+21

123456789012345678901
// 123456789012345680000

(2) There are more than five zeros after the decimal point.

// More than 5 zeros immediately after the decimal point,
// It is automatically converted to scientific counting
0.0000003 // 3e-7

// Otherwise, keep the original literal form
0.000003 // 0.000003

4.5 hex

  • Octal: numeric value with prefix 0o or 0o, or eight Arabic numerals with leading 0 and only 0-7.
  • Hex: numeric value with prefix 0x or 0x.
  • Binary: numeric value with prefix 0b or 0b.
  • The 0 of 0x``0b``0o is a numeric 0, not a letter o

By default, JavaScript will automatically convert octal, hexadecimal and binary to decimal. Here are some examples.

0xff // 255
0o377 // 255
0b11 // 3

Generally, a value with a leading 0 is considered octal, but if the leading 0 is followed by the numbers 8 and 9, the value is considered decimal.

0888 // 888
0777 // 511

Leading 0 indicates octal, which is easy to cause confusion during processing. The strict mode of ES5 and ES6 have abolished this representation, but browsers continue to support this representation in order to be compatible with previous code.

4.6 special values

4.6.1 positive zero and negative zero

As mentioned earlier, one of the 64 bit floating-point numbers in JavaScript is a sign bit. This means that any number has a corresponding negative value, even 0.

There are actually two zeros in JavaScript: one is + 0 and the other is - 0. The difference is that the symbol bits of 64 bit floating-point notation are different. They are equivalent.

-0 === +0 // true
0 === -0 // true
0 === +0 // true

In almost all cases, positive and negative zeros are considered normal zeros.

The only difference is that when + 0 or - 0 is used as the denominator, the returned values are not equal.

(1 / +0) === (1 / -0) // false

The reason for this result in the above code is that + Infinity is obtained by dividing by positive zero and - Infinity is obtained by dividing by negative zero. The two are not equal (see below for Infinity).

4.6.2 NaN

(1) Meaning

NaN is a special value of JavaScript, which means "Not a Number". It mainly occurs when there is an error in parsing a string into a number.

5 - 'x' // NaN

When the above code runs, it will automatically convert the string x into a numeric value. However, since x is not a numeric value, the final result is NaN, indicating that it is "non numeric" (NaN).

In addition, NaN will appear in the operation results of some mathematical functions.

Math.acos(2) // NaN
Math.log(-1) // NaN
Math.sqrt(-1) // NaN

0 divided by 0 will also get NaN. Divide a non-zero value by 0 to get Infinity

0 / 0 // NaN
1 / 0 // Infinity

It should be noted that NaN is not an independent data type, but a special value. Its data type still belongs to Number, which can be seen clearly by using the typeof operator.

typeof NaN // 'number'

(2) Operation rules

NaN is not equal to any value, including itself.

NaN === NaN // false

The indexOf method of array uses strict equality operator internally, so this method is not valid for NaN.

[NaN].indexOf(NaN) // -1

NaN is treated as false in Boolean operations.

Boolean(NaN) // false

The operation of NaN with any number (including itself) yields NaN.

NaN + 32 // NaN
NaN - 32 // NaN
NaN * 32 // NaN
NaN / 32 // NaN

4.6.3 Infinity

(1) Meaning

Infinity means "infinity", which is used to represent two scenarios. One is that a positive value is too large or a negative value is too small to represent; The other is a non-zero value divided by 0 to get infinity.

// Scene 1
Math.pow(2, 1024)
// Infinity

// Scene 2
0 / 0 // NaN
1 / 0 // Infinity

In the above code, the first scenario is that the calculation result of an expression is too large to represent, so Infinity is returned. The second scenario is * * 0 divided by 0 will get NaN, instead of 0. If the value is divided by 0, Infinity will be returned**

Infinity is divided into positive and negative. Infinity represents positive infinity and - Infinity represents negative infinity.

Infinity === -Infinity // false

1 / -0 // -Infinity
-1 / -0 // Infinity

In the above code, a non-zero positive number divided by - 0 will get - Infinity, and a negative number divided by - 0 will get Infinity.

Because JavaScript does not report an error for positive overflow, negative overflow and division by 0, it is almost impossible for a simple mathematical operation to throw an error.

Infinity is greater than all values except NaN, - infinity is less than all values except NaN.

Infinity > 1000 // true
-Infinity < -1000 // true

Infinity always returns false when compared with NaN.

Infinity > NaN // false
-Infinity > NaN // false

Infinity < NaN // false
-Infinity < NaN // false

(2) Operation rules

The four operations of Infinity comply with the infinite mathematical calculation rules.

5 * Infinity // Infinity
5 - Infinity // -Infinity
Infinity / 5 // Infinity
5 / Infinity // 0

0 times infinity to return NaN; 0 divided by infinity returns 0; Infinity divided by 0 returns infinity.

0 * Infinity // NaN
0 / Infinity // 0
Infinity / 0 // Infinity

Infinity plus or multiplied by infinity returns infinity.

Infinity + Infinity // Infinity
Infinity * Infinity // Infinity

Infinity subtracts or divides infinity to get NaN.

Infinity - Infinity // NaN
Infinity / Infinity // NaN

When Infinity and null are calculated, null will be converted to 0, which is equivalent to the calculation with 0.

null * Infinity // NaN
null / Infinity // 0
Infinity / null // Infinity

Both Infinity and undefined calculations return NaN.

undefined + Infinity // NaN
undefined - Infinity // NaN
undefined * Infinity // NaN
undefined / Infinity // NaN
Infinity / undefined // NaN

4.6.4 parseInt()

(1) Basic usage

The parseInt method is used to convert a string to an integer.

parseInt('123') // 123

If there is a space at the beginning of the string, the space will be removed automatically.

parseInt('   81') // 81

If the parameter of parseInt is not a string, it will be converted to a string before conversion.

parseInt(1.23) // 1
// Equivalent to
parseInt('1.23') // 1

When a string is converted to an integer, characters are converted one by one. If a character cannot be converted to a number, it will not go on and return the converted part.

parseInt('8a') // 8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15

In the above code, the parameters of parseInt are all strings, and the result only returns the part of the string head that can be converted into a number.

If the first character of a string cannot be converted to a number (except the sign followed by a number), NaN is returned.

parseInt('abc') // NaN
parseInt('.3') // NaN
parseInt('') // NaN
parseInt('+') // NaN
parseInt('+1') // 1

Therefore, there are only two possibilities for the return value of parseInt, either a decimal integer or NaN.

If the string starts with 0x or 0x, parseInt parses it as a hexadecimal number.

parseInt('0x10') // 16

If the string starts with 0, it is parsed in hexadecimal.

parseInt('011') // 11

For numbers that are automatically converted to scientific notation, parseInt treats the representation of scientific notation as a string, resulting in some strange results.

parseInt(1000000000000000000000.5) // 1
// Equivalent to
parseInt('1e+21') // 1

parseInt(0.0000008) // 8
// Equivalent to
parseInt('8e-7') // 8

(2) Binary conversion

The parseInt method can also accept the second parameter (between 2 and 36), which represents the base of the parsed value and returns the decimal number corresponding to the value. By default, the second parameter of parseInt is 10, that is, the default is decimal to decimal.

parseInt('1000') // 1000
// Equivalent to
parseInt('1000', 10) // 1000

The following is an example of converting a specified hexadecimal number.

parseInt('1000', 2) // 8
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512

In the above code, 1000 of binary, hex and octal is equal to 8, 216 and 512 of decimal respectively. This means that the parseInt method can be used for binary conversion.

If the second parameter is not a numeric value, it will be automatically converted to an integer. Only when this integer is between 2 and 36 can we get meaningful results. If it exceeds this range, NaN is returned. If the second parameter is 0, undefined, and null, it is ignored.

parseInt('10', 37) // NaN
parseInt('10', 1) // NaN
parseInt('10', 0) // 10
parseInt('10', null) // 10
parseInt('10', undefined) // 10

If the string contains characters that are meaningless to the specified base, only convertible values are returned starting from the highest bit. If the highest bit cannot be converted, NaN is returned directly.

parseInt('1546', 2) // 1
parseInt('546', 2) // NaN

In the above code, for binary, 1 is a meaningful character and 5, 4 and 6 are meaningless characters, so the first line returns 1 and the second line returns NaN.

As mentioned earlier, if the first parameter of parseInt is not a string, it will be converted to a string first. This can lead to some unexpected results.

parseInt(0x11, 36) // 43
parseInt(0x11, 2) // 1

// Equivalent to
parseInt(String(0x11), 36)
parseInt(String(0x11), 2)

// Equivalent to
parseInt('17', 36)
parseInt('17', 2)

In the above code, hex 0x11 will be converted to decimal 17 first, and then to string. Then, the string 17 is interpreted in base 36 or binary, and finally the results 43 and 1 are returned.

This processing method requires special attention for the octal prefix 0.

parseInt(011, 2) // NaN

// Equivalent to
parseInt(String(011), 2)

// Equivalent to
parseInt(String(9), 2)

In the above code, 011 in the first line will be converted to string 9 first. Because 9 is not a valid binary character, NaN is returned. If parseInt('011', 2) is calculated directly, 011 will be treated as binary and return 3.

JavaScript no longer allows numbers with the prefix 0 to be treated as octal numbers, but requires that this 0 be ignored. However, in order to ensure compatibility, most browsers do not deploy this provision.

4.6.5 parseFloat()

The parseFloat method is used to convert a string to a floating point number.

parseFloat('3.14') // 3.14

If the string conforms to the scientific counting method, the corresponding conversion is performed.

parseFloat('314e-2') // 3.14
parseFloat('0.0314E+2') // 3.14

If the string contains characters that cannot be converted to floating-point numbers, it will not be converted back and the converted part will be returned.

parseFloat('3.14more non-digit characters') // 3.14

The parseFloat method automatically filters the leading spaces of the string.

parseFloat('\t\v\r12.34\n ') // 12.34

NaN is returned if the parameter is not a string or the first character of the string cannot be converted to a floating point number.

parseFloat([]) // NaN
parseFloat('FF2') // NaN
parseFloat('') // NaN

In the above code, it is particularly noteworthy that parseFloat will convert an empty string to NaN.

These characteristics make the conversion result of parseFloat different from the Number function.

parseFloat(true)  // NaN
Number(true) // 1

parseFloat(null) // NaN
Number(null) // 0

parseFloat('') // NaN
Number('') // 0

parseFloat('123.45#') // 123.45
Number('123.45#') // NaN

4.6.6 isNaN()

The isNaN method can be used to determine whether a value is NaN.

isNaN(NaN) // true
isNaN(123) // false

However, isNaN is only valid for numerical values. If other values are passed in, they will be converted to numerical values first. For example, when a string is passed in, the string will be converted to NaN first, so it will finally return true, which should be paid special attention to. That is, if isNaN is true, it may not be NaN, but a string.

isNaN('Hello') // true
// amount to
isNaN(Number('Hello')) // true

For the same reason, isNaN returns true for objects and arrays.

isNaN({}) // true
// Equivalent to
isNaN(Number({})) // true

isNaN(['xzy']) // true
// Equivalent to
isNaN(Number(['xzy'])) // true

However, for empty arrays and arrays with only one numeric member, isNaN returns false.

isNaN([]) // false
isNaN([123]) // false
isNaN(['123']) // false

The reason why the above code returns false is that these arrays can be converted into values by the Number function. See the chapter "data type conversion".

Therefore, it is best to judge the data type before using isNaN.

function myIsNaN(value) {
  return typeof value === 'number' && isNaN(value);
}

A more reliable way to judge NaN is to make use of the characteristic that NaN is the only value that is not equal to its own value.

function myIsNaN(value) {
  return value !== value;
}

4.6.7 isFinite()

The isfinish method returns a Boolean value indicating whether a value is a normal value.

isFinite(Infinity) // false
isFinite(-Infinity) // false
isFinite(NaN) // false
isFinite(undefined) // false
isFinite(null) // true
isFinite(-1) // true

Except for Infinity, - Infinity, NaN and undefined, isfinish returns true for all other values.

5. String

5.1 definitions

A string is zero or more characters arranged together in single or double quotation marks.

'abc'
"abc"

Inside a single quote string, double quotes can be used. Inside a double quoted string, you can use single quotes.

'key = "value"'
"It's a long journey"

Both of the above are legal strings.

If you want to use a single quote inside a single quote string, you must precede the internal single quote with a backslash to escape. Double quotation marks are used inside a double quotation mark string, as well.

'Did she say \'Hello\'?'
// "Did she say 'Hello'?"

"Did she say \"Hello\"?"
// "Did she say "Hello"?"

Because the attribute values of HTML language use double quotation marks, many projects agree that JavaScript language strings only use single quotation marks. This tutorial follows this Convention. Of course, you can use only double quotation marks. It is important to stick to one style and not use single quotation marks to represent strings and double quotation marks to represent strings.

By default, the string can only be written in one line. If it is divided into multiple lines, an error will be reported.

'a
b
c'
// SyntaxError: Unexpected token ILLEGAL

The above code divides a string into three lines, and JavaScript will report an error.

If a long string must be divided into multiple lines, you can use a backslash at the end of each line.

var longString = 'Long \
long \
long \
string';

longString
// "Long long long string"

The above code indicates that after adding a backslash, the original string written on one line can be divided into multiple lines. However, the output is still a single line, and the effect is exactly the same as that written on the same line. Note that the backslash must be followed by a newline character and no other characters (such as spaces), otherwise an error will be reported.

The connection operator (+) can connect multiple single line strings, split the long string into multiple lines for writing, and the output is also single line.

var longString = 'Long '
  + 'long '
  + 'long '
  + 'string';

If you want to output multiline strings, there is an alternative to using multiline annotations.

(function () { /*
line 1
line 2
line 3
*/}).toString().split('\n').slice(1, -1).join('\n')
// "line 1
// line 2
// line 3"

In the above example, the output string is multiple lines.

5.2 escape characters

Backslash (\) has a special meaning in the string and is used to represent some special characters, so it is also called escape character.

Special characters that need to be escaped with backslash include the following.

  • \0 : null(\u0000)
  • \b: Back key (\ u0008)
  • \f: Page feed (\ u000C)
  • \n: Newline (\ u000A)
  • \r: Enter (\ u000D)
  • \t: Tab (\ u0009)
  • \v: Vertical tab (\ u000B)
  • \': single quotation mark (\ u0027)
  • \": double quotation marks (\ u0022)
  • \: backslash (\ u005C)

These characters above are preceded by backslashes to indicate special meaning.

console.log('1\n2')
// 1
// 2

In the above code, \ n means line feed, and the output is divided into two lines.

If you use a backslash before a non special character, the backslash is omitted.

'\a'
// "a"

In the above code, a is a normal character. Adding a backslash in front of it has no special meaning, and the backslash will be automatically omitted.

If the normal content of the string needs to contain a backslash, a backslash needs to be added before the backslash to escape itself.

"Prev \\ Next"
// "Prev \ Next"

5.3 strings and arrays

A string can be treated as an array of characters, so you can use the square bracket operator of the array to return characters at a certain position (the position number starts from 0).

var s = 'hello';
s[0] // "h"
s[1] // "e"
s[4] // "o"

// Use square bracket operators directly on strings
'hello'[1] // "e"

undefined if the number in square brackets exceeds the length of the string, or if the number in square brackets is not a number at all.

'abc'[3] // undefined
'abc'[-1] // undefined
'abc'['x'] // undefined

However, the similarity between strings and arrays is nothing more. In fact, you cannot change a single character in a string.

var s = 'hello';

delete s[0];
s // "hello"

s[1] = 'a';
s // "hello"

s[5] = '!';
s // "hello"

The above code indicates that a single character in the string cannot be changed, added or deleted, and these operations will fail silently.

5.4 length attribute

The length property returns the length of the string, which cannot be changed.

var s = 'hello';
s.length // 5

s.length = 3;
s.length // 5

s.length = 7;
s.length // 5

The above code indicates that the length attribute of the string cannot be changed, but no error will be reported.

5.5 character set

JavaScript uses Unicode character sets. Inside the JavaScript engine, all characters are represented in Unicode.

JavaScript not only stores characters in Unicode, but also allows to directly use Unicode code points to represent characters in programs, that is, characters are written in the form of \ uxxxx, where xxxx represents the Unicode code point of the character. For example, \ u00A9 represents the copyright symbol.

var s = '\u00A9';
s // "©"

When parsing code, JavaScript will automatically recognize whether a character is expressed in literal form or Unicode form. When output to the user, all characters are converted to literal form.

var f\u006F\u006F = 'abc';
foo // "abc"

In the above code, the variable name foo in the first line is expressed in Unicode, and the second line is expressed literally. JavaScript will automatically recognize.

We also need to know that each character is stored in 16 bit (i.e. 2 bytes) UTF-16 format inside JavaScript. That is, the unit character length of JavaScript is fixed to 16 bits, that is, 2 bytes.

However, UTF-16 has two lengths: for characters with code points between U+0000 and U+FFFF, the length is 16 bits (i.e. 2 bytes); For characters with code points between U+10000 and U+10FFFF, the length is 32 bits (i.e. 4 bytes), and the first two bytes are between 0xD800 and 0xDBFF, and the last two bytes are between 0xDC00 and 0xDFFF. For example, the character corresponding to code point U+1D306 is 𝌆 which is written as UTF-16, which is 0xD834 0xDF06.

JavaScript's support for UTF-16 is incomplete. Due to historical reasons, it only supports two byte characters and does not support four byte characters. This is because when the first version of JavaScript was released, Unicode code points were only coded to U+FFFF, so two bytes are enough. Later, Unicode included more and more characters, and four byte encoding appeared. However, the standard of JavaScript has been finalized at this time, and the character length is uniformly limited to two bytes, resulting in the inability to recognize four byte characters. For the four byte character 𝌆 in the previous section, the browser will correctly recognize it as one character, but JavaScript cannot recognize it. It will think it is two characters.

'𝌆'.length // 2

In the above code, JavaScript thinks that the length of 𝌆 is 2, not 1.

To sum up, JavaScript always considers characters with code points between U+10000 and U+10FFFF as two characters (the length attribute is 2). Therefore, this must be taken into account when processing, that is, the string length returned by JavaScript may be incorrect.

5.6 Base64 transcoding

Sometimes, the text contains some non printable symbols, such as the symbols of ASCII codes 0 to 31, which cannot be printed. At this time, Base64 coding can be used to convert them into printable characters. Another scenario is that sometimes binary data needs to be passed in text format, so Base64 coding can also be used.

The so-called Base64 is an encoding method, which can convert any value into printable characters composed of 64 characters: 0 ~ 9, a ~ Z, a-z, + and / or. The main purpose of using it is not to encrypt, but to simplify the processing of the program without special characters.

JavaScript native provides two Base64 related methods.

  • btoa(): convert any value to Base64 encoding
  • atob(): Base64 encoding is converted to the original value
var string = 'Hello World!';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"

Note that these two methods are not suitable for non ASCII characters and will report errors.

btoa('Hello') // report errors

To convert non ASCII characters to Base64 encoding, you must insert a transcoding link in the middle, and then use these two methods.

function b64Encode(str) {
  return btoa(encodeURIComponent(str));
}

function b64Decode(str) {
  return decodeURIComponent(atob(str));
}

b64Encode('Hello') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "Hello"

6. Object

6.1 general

6.1.1 generation method

object is the core concept of JavaScript language and the most important data type.

What is an object? In short, an object is a set of "key value pairs", which is an unordered composite data set.

var obj = {
  foo: 'Hello',
  bar: 'World'
};

6.1.2 key name

All key names of objects are strings (ES6 also introduces Symbol value, which can also be used as key names), so they can be enclosed or not. The above code can also be written as follows.

var obj = {
  'foo': 'Hello',
  'bar': 'World'
};

If the key name is a numeric value, it will be automatically converted to a string. e in 1e2 is scientific notation, not characters

var obj = {
  1: 'a',
  3.2: 'b',
  1e2: true,
  1e-2: true,
  .234: true,
  0xFF: true
};

obj
// Object {
//   1: "a",
//   3.2: "b",
//   100: true,
//   0.01: true,
//   0.234: true,
//   255: true
// }

obj['100'] // true

In the above code, although all key names of object obj look like numeric values, they are actually automatically converted into strings.

If the key name does not meet the conditions of the identification name (for example, the first character is a number, or contains a space or operator), and it is not a number, quotation marks must be added, otherwise an error will be reported.

// report errors
var obj = {
  1p: 'Hello World'
};

// No error reporting
var obj = {
  '1p': 'Hello World',
  'h w': 'Hello World',
  'p+q': 'Hello World'
};

The three key names of the above object do not meet the conditions of identification name, so they must be enclosed in quotation marks.

Each key name of an object is also called "property", and its "key value" can be any data type== If the value of an attribute is a function, this attribute is usually called "method", which can be called like a function.

var obj = {
  p: function (x) {
    return 2 * x;
  }
};

obj.p(1) // 2

In the above code, the Property p of object obj points to a function.

If the value of an attribute is still an object, a chained reference is formed.

var o1 = {};
var o2 = { bar: 'hello' };

o1.foo = o2;
o1.foo.bar // "hello"

In the above code, if the attribute foo of object o1 points to object o2, you can chain reference the attribute of o2.

The attributes of the object are separated by commas. The last attribute can be followed by a trailing comma or not.

var obj = {
  p: 123,
  m: function () { ... },
}

In the above code, the comma after the m attribute can be used whether it is available or not.

Properties can be created dynamically and do not have to be specified when the object is declared.

var obj = {};
obj.foo = 123;
obj.foo // 123

In the above code, the foo attribute of the obj object is directly assigned. As a result, the foo attribute is created at runtime.

6.1.3 object reference

If different variable names point to the same object, they are all references to the object, that is, they point to the same memory address. Modifying one variable will affect all other variables.

var o1 = {};
var o2 = o1;

o1.a = 1;
o2.a // 1

o2.b = 2;
o1.b // 2

In the above code, o1 and o2 point to the same object, so add an attribute to any variable, and the other variable can read and write the attribute.

At this time, if you cancel the reference of a variable to the original object, it will not affect another variable.

var o1 = {};
var o2 = o1;

o1 = 1;
o2 // {}

In the above code, variables o1 and o2 point to the same object, and then the value of o1 becomes 1, * * which is equivalent to canceling the reference of variable o1 to the original object, * * at this time, it will not affect o2, and o2 still points to the original object.

However, this reference is limited to objects if two variables point to the same value of the original type. Then, variables are copies of values.

var x = 1;
var y = x;

x = 2;
y // 1

In the above code, when the value of x changes, the value of Y does not change, which means that y and x do not point to the same memory address.

6.1.4 expression or statement?

Objects are represented in curly braces, which leads to a problem: if the beginning of a line is a curly brace, is it an expression or a statement?

{ foo: 123 }

When the JavaScript engine reads the above line of code, it will find that it may have two meanings. The first possibility is that this is an expression that represents an object containing the foo attribute; The second possibility is that this is a statement representing a code block with a label foo pointing to expression 123.

To avoid this ambiguity, the JavaScript engine interprets the object or code block as a code block if it is unable to determine whether it is an object or a code block.

{ console.log(123) } // 123

The above statement is a code block and can only be executed if it is interpreted as a code block.

If you want to interpret as an object, you'd better put parentheses before braces. Because the inside of parentheses can only be expressions, make sure that braces can only be interpreted as objects.

({ foo: 123 }) // correct
({ console.log(123) }) // report errors

This difference is most evident in eval statements that evaluate strings.

eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}

In the above code, if there are no parentheses, eval understands it as a code block; After adding parentheses, it is understood as an object.

6.2 attribute operation

6.2.1 reading of attributes

There are two ways to read the properties of an object, one is to use the dot operator, and the other is to use the square bracket operator.

var obj = {
  p: 'Hello World'
};

obj.p // "Hello World"
obj['p'] // "Hello World"

The above code uses the dot operator and the square bracket operator respectively to read the attribute p.

Note that if the square bracket operator is used, the key name must be placed in quotation marks, otherwise it will be treated as a variable.

var foo = 'bar';

var obj = {
  foo: 1,
  bar: 2
};

obj.foo  // 1
obj[foo]  // 2

In the above code, when referencing the foo attribute of object obj, if the dot operator is used, foo is a string; If you use the square bracket operator, but do not use quotation marks, foo is a variable that points to the string bar. Variable foo=1, obj[foo]=obj[1]=obj[bar]=2

Expressions can also be used inside square bracket operators.

obj['hello' + ' world']
obj[3 + 3]

Numeric keys can be left without quotation marks because they are automatically converted to strings.

var obj = {
  0.7: 'Hello World'
};

obj['0.7'] // "Hello World"
obj[0.7] // "Hello World"

In the above code, the numeric key 0.7 of object obj can be added or not, because it will be automatically converted to a string.

Note that the numeric key name cannot use the point operator (because it will be treated as a decimal point), but only the square bracket operator.

var obj = {
  123: 'hello world'
};

obj.123 // report errors
obj[123] // "hello world"

In the first expression of the above code, the dot operator is used for the numeric key name 123, and an error is reported. The second expression uses the square bracket operator and the result is correct.

6.2.2 viewing attributes

To view all the properties of an object itself, you can use object Keys method.

var obj = {
  key1: 1,
  key2: 2
};

Object.keys(obj);
// ['key1', 'key2']

6.2.3 attribute deletion: delete command

The delete command is used to delete the attributes of an object. It returns true after the deletion is successful.

var obj = { p: 1 };
Object.keys(obj) // ["p"]

delete obj.p // true
obj.p // undefined
Object.keys(obj) // []

In the above code, the delete command deletes the p attribute of the object obj. After deletion, reading the p attribute will return undefined and object The return value of the keys method no longer includes this property.

Note that if you delete a nonexistent attribute, no error will be reported and true will be returned.

var obj = {};
delete obj.p // true

In the above code, the obj object does not have a p attribute, but the delete command still returns true. Therefore, an attribute cannot be determined to exist according to the result of the delete command.

In only one case, the delete command will return false, that is, the attribute exists and cannot be deleted.

var obj = Object.defineProperty({}, 'p', {
  value: 123,
  configurable: false
});

obj.p // 123
delete obj.p // false

In the above code, the p property of Object obj cannot be deleted, so the delete command returns false (for an introduction to the Object.defineProperty method, see the Object object chapter of the standard library).

In addition, it should be noted that the delete command can only delete the attributes of the object itself, not the inherited attributes (for inheritance, see the chapter of object-oriented programming).

var obj = {};
delete obj.toString // true
obj.toString // function toString() { [native code] }

In the above code, toString is an attribute inherited from object obj. Although the delete command returns true, the attribute has not been deleted and still exists. This example also shows that even if delete returns true, the property may still read the value.

6.2.4 whether the attribute exists: in operator

The in operator is used to check whether an object contains a property (note that it checks the key name, not the key value). If it does, it returns true; otherwise, it returns false. On the left is a string representing the property name, and on the right is an object.

var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true

One problem with the in operator is that it does not recognize which attributes are of the object itself and which attributes are inherited. As in the above code, the object obj itself does not have a toString attribute, but the in operator will return true because this attribute is inherited.

At this time, you can use the hasOwnProperty method of the object to determine whether it is the property of the object itself.

var obj = {};
if ('toString' in obj) {
  console.log(obj.hasOwnProperty('toString')) // false
}

6.2.4 traversal of attributes: for... in loop

for... The in loop is used to traverse all the properties of an object.

var obj = {a: 1, b: 2, c: 3};

for (var i in obj) {
  console.log('Key name:', i);
  console.log('Key value:', obj[i]);
}
// Key name: a
// Key value: 1
// Key name: b
// Key value: 2
// Key name: c
// Key value: 3

for... The in loop has two points to note.

  • It traverses all the enumerable properties of the object and skips the non - ergodic properties.
  • It traverses not only the properties of the object itself, but also the inherited properties.

For example, objects inherit the toString property, but for The in loop does not traverse this property.

var obj = {};

// The toString property exists
obj.toString // toString() { [native code] }

for (var p in obj) {
  console.log(p);
} // No output

In the above code, the Object obj inherits the toString property, which will not be used by for The in loop traverses to because it is "non traversable" by default. For the traversability of Object attributes, see the introduction of Object in the chapter of standard library.

If the inherited property is ergodic, it will be used by for The in loop traverses to. However, in general, you only want to traverse the properties of the object itself, so use for In, you should use the hasOwnProperty method to judge whether a property is the property of the object itself within the loop.

var person = { name: 'Lao Zhang' };

for (var key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key);
  }
}
// name

6.3 with statement

The format of the with statement is as follows:

with (object) {
  sentence;
}

Its function is to provide some writing convenience when operating multiple properties of the same object.

// Example 1
var obj = {
  p1: 1,
  p2: 2,
};
with (obj) {
  p1 = 4;
  p2 = 5;
}
// Equivalent to
obj.p1 = 4;
obj.p2 = 5;

// Example 2
with (document.links[0]){
  console.log(href);
  console.log(title);
  console.log(style);
}
// Equivalent to
console.log(document.links[0].href);
console.log(document.links[0].title);
console.log(document.links[0].style);

Note that if there is a variable assignment operation inside the with block, it must be an existing attribute of the current object, otherwise a global variable of the current scope will be created.

var obj = {};
with (obj) {
  p1 = 4;
  p2 = 5;
}

obj.p1 // undefined
p1 // 4

In the above code, the object obj has no p1 attribute. Assigning a value to p1 is equal to creating a global variable p1. The correct way to write it is to first define the attribute p1 of the object obj, and then operate it in the with block.

This is because the with block does not change the scope, and its interior is still the current scope. This causes a big disadvantage of the with statement, that is, the binding object is not clear.

with (obj) {
  console.log(x);
}

Simply from the above code block, it is impossible to judge whether x is a global variable or an attribute of object obj. This is not conducive to the debugging and modularization of the code, and the compiler cannot optimize this code, so it can only be left for runtime judgment, which slows down the running speed. Therefore, it is not recommended to use the with statement. Instead, consider using a temporary variable instead of with.

with(obj1.obj2.obj3) {
  console.log(p1 + p2);
}

// Can be written as
var temp = obj1.obj2.obj3;
console.log(temp.p1 + temp.p2);

7. Function

7.1 general

7.1.1 function declaration

JavaScript has three ways to declare functions.

(1) function command

The code block declared by the function command is a function. The function command is followed by the function name, followed by a pair of parentheses, which are the parameters of the incoming function. The function body is placed in braces.

function print(s) {
  console.log(s);
}

The above code names a print function. Later, you can call the corresponding code in the form of print(). This is called Function Declaration.

(2) Function expression

In addition to declaring functions with the function command, you can also use the writing method of variable assignment.

var print = function(s) {
  console.log(s);
};

This method assigns an anonymous function to a variable. At this time, this anonymous function is also called Function Expression, because only the expression can be placed to the right of the equal sign of the assignment statement.

When a function is declared with a function expression, the function command is not followed by a function name. If you add a function name, the function name is only valid inside the function body, but not outside the function body.

var print = function x(){
  console.log(typeof x);
};

x
// ReferenceError: x is not defined

print()
// function

The above code adds the function name x to the function expression. This x is only available inside the function body and refers to the function expression itself. It is not available elsewhere. This writing method has two uses: one is to call itself inside the function body, and the other is to facilitate debugging (when the debugging tool displays the function call stack, the function name will be displayed instead of an anonymous function). Therefore, the following formal declaration of functions is also very common.

var f = function f() {};

It should be noted that the expression of the = = function needs to add a semicolon at the end of the statement to indicate the end of the statement. The declaration of a function does not need a semicolon after the closing brace== In general, the difference between the two ways of declaring functions is very slight, which can be approximately regarded as equivalent.

7.1.2 repeated declaration of function

If the same function is declared more than once, the subsequent declaration will overwrite the previous declaration.

function f() {
  console.log(1);
}
f() // 2

function f() {
  console.log(2);
}
f() // 2

In the above code, the latter function declaration overrides the previous one. Moreover, due to the promotion of the function name (see below), the previous declaration is invalid at any time, which should be paid special attention.

7.1.3 parenthesis operators, return statements and recursion

When calling a function, use the parenthesis operator. In parentheses, you can add the parameters of the function.

function add(x, y) {
  return x + y;
}

add(1, 1) // 2

In the above code, this function is called when the function name is followed by a pair of parentheses.

The return statement inside the function body represents the return. When the JavaScript engine encounters a return statement, it directly returns the value of the expression following the return. Even if there are statements behind it, they will not be executed. That is, the expression carried by the return statement is the return value of the function. The return statement is not required. If not, the function will not return any value or return undefined.

A function can call itself, which is called recursion. The following is the code to calculate the Fibonacci sequence through recursion.

function fib(num) {
  if (num === 0) return 0;
  if (num === 1) return 1;
  return fib(num - 2) + fib(num - 1);
}

fib(6) // 8

In the above code, fib is called inside the fib function, and it is calculated that the 6th element of Fibonacci sequence is 8.

7.1.4 first class citizens

The JavaScript language regards a function as a value, which has the same status as other values (numeric, string, Boolean, etc.). Where values can be used, functions can be used. For example, you can assign a function to the properties of variables and objects, pass it to other functions as parameters, or return it as the result of the function. Function is just an executable value, and there is nothing special about it.

Because functions are equal to other data types, they are also called first-class citizens in JavaScript language.

function add(x, y) {
  return x + y;
}

// Assign a function to a variable
var operator = add;

// Function as parameter and return value
function a(op){
  return op;
}
a(add)(1, 1)
// 2

7.1.5 function name promotion

The JavaScript engine treats the function name as the variable name, so when the function command is used to declare the function, the whole function will be promoted to the code header like the variable declaration. Therefore, the following code will not report an error.

f();

function f() {}

On the surface, the above code seems to call function f before declaration. But in fact, due to "variable promotion", function f is promoted to the code header, that is, it has been declared before the call. However, if you define a function with an assignment statement, JavaScript will report an error.

f();
var f = function (){};
// TypeError: undefined is not a function

The above code is equivalent to the following form.

var f;
f();
f = function () {};

In the second line of the above code, when calling F, f is only declared and has not been assigned, which is equal to undefined, so an error will be reported.

Note that if the function command and var assignment statement are used to declare the same function as in the following example, the definition of var assignment statement will be used finally due to function promotion.

var f = function () {
  console.log('1');
}

function f() {
  console.log('2');
}

f() // 1

In the above example, the function f declared later on the surface should override the previous var assignment statement, but due to the function promotion, it is actually the opposite. When a function is declared with the function command (i.e. function f() {}), the entire function will be promoted to the code header like a variable declaration. The function f declared later is overwritten by the previously declared function because the function promotion was declared first.

7.2 properties and methods of functions

7.2.1 name attribute

The name property of the function returns the name of the function.

function f1() {}
f1.name // "f1"

If it is a function defined by variable assignment, the name property returns the variable name.

var f2 = function () {};
f2.name // "f2"

However, in the above case, this is only true if the value of the variable is an anonymous function. If the value of a variable is a named function, the name attribute returns the function name after the function keyword.

var f3 = function myName() {};
f3.name // 'myName'

In the above code, f3 Name returns the name of the function expression. Note that the real function name is still f3, and the name myName is only available inside the function body.

One use of the name attribute is to get the name of the parameter function.

var myFunc = function () {};

function test(f) {
  console.log(f.name);
}

test(myFunc) // myFunc

In the above code, the function test can know what the passed parameters are through the name attribute.

7.2.2 length attribute

The length property of the function returns the expected number of parameters passed in by the function, that is, the number of parameters in the function definition.

function f(a, b) {}
f.length // 2

The above code defines an empty function f, and its length attribute is the number of parameters at the time of definition. The length attribute is always equal to 2, regardless of how many parameters are entered during the call.

The length attribute provides a mechanism to judge the difference between parameters at definition time and call time, so as to realize "method overload" of object-oriented programming.

7.2.3 toString()

The toString() method of the function returns a string containing the source code of the function.

function f() {
  a();
  b();
  c();
}

f.toString()
// function f() {
//  a();
//  b();
//  c();
// }

In the above example, the toString() method of function f returns the source code of F, including line breaks.

For those native functions, the toString() method returns function (){[native code]}.

Math.sqrt.toString()
// "function sqrt() { [native code] }"

In the above code, math Sqrt () is a native function provided by the JavaScript engine, and the toString() method returns a hint of native code.

Comments inside functions can also be returned.

function f() {/*
  This is a
  multiline comment 
*/}

f.toString()
// "function f(){/*
//   This is a
//   multiline comment 
// */}"

Using this, you can realize multi line string in disguise.

var multiline = function (fn) {
  var arr = fn.toString().split('\n');
  return arr.slice(1, arr.length - 1).join('\n');
};

function f() {/*
  This is a
  multiline comment 
*/}

multiline(f);
// "This is a
//   Multiline comment“

In the above example, there is a multi line comment inside function f. after the toString() method obtains the source code of f, remove the first and last two lines to get a multi line string.

7.3 function scope

7.3.1 definitions

Scope refers to the scope of a variable. In the specification of ES5, JavaScript has only two scopes: one is the global scope, where variables always exist in the whole program and can be read everywhere; The other is function scope, where variables only exist inside the function. ES6 also adds a block level scope, which is not covered in this tutorial.

7.3.2 variable promotion inside function

Like the global scope, the "variable promotion" phenomenon will also occur within the function scope. No matter where the variable declared by var command is, the variable declaration will be promoted to the head of the function body.

function foo(x) {
  if (x > 100) {
    var tmp = x - 100;
  }
}

// Equivalent to
function foo(x) {
  var tmp;
  if (x > 100) {
    tmp = x - 100;
  };
}

7.3.3 scope of function itself

The function itself is also a value and has its own scope. Its scope is the same as that of a variable, that is, the scope in which it is declared, regardless of the scope in which it is run.

var a = 1;
var x = function () {
  console.log(a);
};

function f() {
  var a = 2;
  x();
}

f() // 1

In the above code, function x is declared outside function f, so its scope is bound to the outer layer, and the internal variable a will not take value in function f, so it outputs 1 instead of 2.

In short, the scope of function execution is the scope of definition, not the scope of call.

It is easy to make mistakes if function A calls function B without considering that function B will not reference the internal variables of function A.

var x = function () {
  console.log(a);
};

function y(f) {
  var a = 2;
  f();
}

y(x)
// ReferenceError: a is not defined

The above code takes function x as a parameter and passes it into function y. However, function x is declared outside function y, and the scope is bound to the outer layer. Therefore, the internal variable a of function y cannot be found, resulting in an error.

Similarly, the scope of the function declared inside the function body is bound inside the function body.

function foo() {
  var x = 1;
  function bar() {
    console.log(x);
  }
  return bar;
}

var x = 2;
var f = foo();
f() // 1

In the above code, a function bar is declared inside the function foo, and the scope of bar is bound to foo. When we take out the bar outside foo for execution, the variable x points to the X inside foo, not the X outside foo. It is this mechanism that constitutes the = = "closure" phenomenon = =.

7.4 parameters

7.4.1 general

When a function is running, it is sometimes necessary to provide external data. Different external data will get different results. This external data is called parameters.

function square(x) {
  return x * x;
}

square(2) // 4
square(3) // 9

x in the above formula is the parameter of the square function. You need to provide this value every time you run, otherwise you won't get the result.

7.4.2 omission of parameters

Function parameters are not required. JavaScript allows parameters to be omitted.

function f(a, b) {
  return a;
}

f(1, 2, 3) // 1
f(1) // 1
f() // undefined

f.length // 2

The function f of the above code defines two parameters, but no matter how many parameters (or no parameters) are provided at runtime, JavaScript will not report an error. The value of the omitted parameter becomes undefined. It should be noted that the length attribute of the function has nothing to do with the actual number of parameters passed in, but only reflects the expected number of parameters passed in by the function.

However, there is no way to omit only the front parameters and retain the rear parameters. If you must omit the previous parameter, only explicitly pass in undefined.

function f(a, b) {
  return a;
}

f( , 1) // SyntaxError: Unexpected token ,(...)
f(undefined, 1) // undefined

In the above code, if the first parameter is omitted, an error will be reported.

7.4.3 transmission mode

If the function parameter is a value of the original type (numeric value, string, Boolean value), the transfer method is pass by value. This means that modifying the parameter value in the function body will not affect the outside of the function.

var p = 2;

function f(p) {
  p = 3;
}
f(p);

p // 2

In the above code, the variable p is a value of the original type, and the way to pass it into the function f is to pass it by value. Therefore, inside the function, the value of P is a copy of the original value. No matter how you modify it, it will not affect the original value.

However, if the function parameter is a composite value (array, object, other functions), the transfer method is pass by reference. In other words, the address of the original value of the function is passed in. Therefore, modifying parameters inside the function will affect the original value.

var obj = { p: 1 };

function f(o) {
  o.p = 2;
}
f(obj);

obj.p // 2

In the above code, the address of the parameter object obj is passed into the function f. Therefore, modifying the obj attribute p inside the function will affect the original value.

Note that if the internal modification of the function is not a property of the parameter object, but replaces the whole parameter, the original value will not be affected.

var obj = [1, 2, 3];

function f(o) {
  o = [2, 3, 4];
}
f(obj);

obj // [1, 2, 3]

In the above code, inside the function f(), the parameter object obj is completely replaced with another value. The original value is not affected. This is because the value of formal parameter (o) is actually the address of parameter obj. Reassigning o will lead o to another address. Of course, the value saved on the original address will not be affected.

7.4.4 parameters with the same name

If there is a parameter with the same name, take the last value.

function f(a, a) {
  console.log(a);
}

f(1, 2) // 2

In the above code, the function f() has two parameters, and the parameter name is a. When taking values, the following a shall prevail, even if the following a has no value or is omitted.

function f(a, a) {
  console.log(a);
}

f(1) // undefined

When the function f() is called without providing the second parameter, the value of a becomes undefined. In this case, if you want to get the value of the first a, you can use the arguments object.

function f(a, a) {
  console.log(arguments[0]);
}

f(1) // 1

7.4.5 arguments object

(1) Definition

Since JavaScript allows functions to have an indefinite number of parameters, a mechanism is needed to read all parameters inside the function body. This is the origin of the arguments object.

The arguments object contains all the parameters of the function runtime. arguments[0] is the first parameter, arguments[1] is the second parameter, and so on. This object can only be used inside the function body.

var f = function (one) {
  console.log(arguments[0]);
  console.log(arguments[1]);
  console.log(arguments[2]);
}

f(1, 2, 3)
// 1
// 2
// 3

In normal mode, the arguments object can be modified at run time.

var f = function(a, b) {
  arguments[0] = 3;
  arguments[1] = 2;
  return a + b;
}

f(1, 1) // 5

In the above code, the parameters passed in when the function f() is called are modified to 3 and 2 inside the function.

In strict mode, the arguments object has no linkage relationship with the function parameters. That is, modifying the arguments object will not affect the actual function parameters.

var f = function(a, b) {
  'use strict'; // Turn on strict mode
  arguments[0] = 3;
  arguments[1] = 2;
  return a + b;
}

f(1, 1) // 2

In the above code, the function body is a strict mode. At this time, modifying the arguments object will not affect the real parameters a and b.

Through the length attribute of the arguments object, you can judge how many parameters the function calls with.

function f() {
  return arguments.length;
}

f(1, 2, 3) // 3
f(1) // 1
f() // 0

(2) Relationship with array

Note that although arguments looks like an array, it is an object. Array specific methods (such as slice and forEach) cannot be used directly on the arguments object.

If you want the arguments object to use the array method, the real solution is to turn arguments into a real array. Here are two common conversion methods: slice method and filling in a new array one by one.

var args = Array.prototype.slice.call(arguments);

// perhaps
var args = [];
for (var i = 0; i < arguments.length; i++) {
  args.push(arguments[i]);
}

(3) callee attribute

The arguments object has a callee attribute that returns its corresponding original function.

var f = function () {
  console.log(arguments.callee === f);
}

f() // true

You can use arguments Callee to call the function itself. This property is disabled in strict mode, so it is not recommended.

7.5 other knowledge points of function

7.5.1 closure

Closure is a difficulty and feature of JavaScript language. Many advanced applications rely on closure.

To understand closures, you must first understand the variable scope. As mentioned earlier, JavaScript has two scopes: global scope and function scope. The function can read global variables directly.

var n = 999;

function f1() {
  console.log(n);
}
f1() // 999

In the above code, function f1 can read the global variable n.

However, under normal circumstances, variables declared inside a function cannot be read outside the function.

function f1() {
  var n = 999;
}

console.log(n)
// Uncaught ReferenceError: n is not defined(

In the above code, the variable n declared inside the function f1 cannot be read outside the function.

If for various reasons, you need to get the local variables in the function. Under normal circumstances, this is impossible and can only be achieved through flexible methods. That is to define another function inside the function.

function f1() {
  var n = 999;
  function f2() {
  console.log(n); // 999
  }
}

In the above code, function f2 is inside function f1. At this time, all local variables inside f1 are visible to f2. But not the other way around. The local variables inside f2 are invisible to f1. This is the unique "chain scope" structure of JavaScript language. Child objects will look up all parent object variables level by level. Therefore, all variables of the parent object are visible to the child object, and vice versa.

Since f2 can read the local variable of f1, as long as f2 is taken as the return value, we can read its internal variable outside f1!

function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}

var result = f1();
result(); // 999

In the above code, the return value of function f1 is function f2. Since f2 can read the internal variable of f1, it can obtain the internal variable of f1 externally.

A closure is a function f2, that is, a function that can read internal variables of other functions. In JavaScript language, only child functions inside a function can read internal variables, so closures can be simply understood as "functions defined inside a function". The biggest feature of closure is that it can "remember" the birth environment. For example, f2 remembers the birth environment f1, so the internal variables of f1 can be obtained from f2. In essence, closure is a bridge connecting the inside and outside of a function.

Closures have two greatest uses: one is to read the variables inside the outer function, and the other is to keep these variables in memory all the time, that is, closures can make its birth environment exist all the time. Look at the following example. Closures make internal variables remember the operation result of the last call.

function createIncrementor(start) {
  return function () {
    return start++;
  };
}

var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7

In the above code, start is the internal variable of the function createIncrementor. Through closure, the state of start is retained, and each call is calculated based on the previous call. It can be seen that the closure inc makes the internal environment of the function createIncrementor exist all the time. Therefore, a closure can be seen as an interface to the internal scope of a function.

Why can closures return internal variables of outer functions? The reason is that the closure (inc in the above example) uses the outer variable (start), so the outer function (createIncrementor) cannot be released from memory. As long as the closure is not cleared by the garbage collection mechanism, the running environment provided by the outer function will not be cleared, and its internal variables always save the current value for the closure to read.

Another use of closures is to encapsulate private properties and private methods of objects.

function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}

var p1 = Person('Zhang San');
p1.setAge(25);
p1.getAge() // 25

In the above code, the internal variable of the function Person_ age, through the closures getAge and setAge, becomes a private variable that returns object p1.

Note that each time the outer function runs, a new closure will be generated, and this closure will retain the internal variables of the outer function, so the memory consumption is very large. Therefore, closures should not be abused, otherwise it will cause performance problems of web pages.

7.5.2 function expression called immediately (IIFE)

According to JavaScript syntax, parentheses () follow the function name to call the function. For example, print() means calling the print function.

Sometimes we need to call a function immediately after it is defined. At this time, you can't put parentheses after the definition of the function, which will cause a syntax error.

function(){ /* code */ }();
// SyntaxError: Unexpected token (

The reason for this error is that the keyword function can be used as either a statement or an expression.

// sentence
function f() {}

// expression
var f = function f() {}

As an expression, a function can be defined and called directly with parentheses.

var f = function f(){ return 1}();
f // 1

In the above code, the function is called directly with parentheses after the function definition, and no error is reported. The reason is that function is an expression, and the engine takes the function definition as a value. In this case, no error will be reported.

In order to avoid ambiguity in parsing, JavaScript stipulates that if the function keyword appears at the beginning of the line, the idiom sentence will be interpreted. Therefore, when the engine sees the function keyword at the beginning of the line, it thinks that this paragraph is the definition of the function and should not end in parentheses, so it reports an error.

The solution to call immediately after the function is defined is not to let function appear at the beginning of the line and let the engine understand it as an expression. The simplest treatment is to put it in a parenthesis.

(function(){ /* code */ }());
// perhaps
(function(){ /* code */ })();

The above two methods start with parentheses. The engine will think that it is followed by an expression rather than a function definition statement, so errors are avoided. This is called "immediately invoked function expression", or IIFE for short.

Note that the semicolon at the end of the above two writing methods is required. If the semicolon is omitted, an error may be reported if two iifes are connected.

// report errors
(function(){ /* code */ }())
(function(){ /* code */ }())

There is no semicolon between the two lines of the above code. JavaScript will interpret them together and interpret the second line as the parameters of the first line.

By extension, any method that allows the interpreter to process function definitions with expressions can produce the same effect, such as the following three writing methods.

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

It's even possible to write like this.

!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();

Typically, this "function expression executed immediately" is used only for anonymous functions. It has two purposes: first, it does not have to name the function to avoid polluting the global variables; Second, a separate scope is formed inside IIFE, which can encapsulate some private variables that cannot be read externally.

// Writing method I
var tmp = newData;
processData(tmp);
storeData(tmp);

// Writing method 2
(function () {
  var tmp = newData;
  processData(tmp);
  storeData(tmp);
}());

In the above code, writing two is better than writing one, because it completely avoids polluting global variables.

7.5.3 eval command

(1) Basic usage

The eval command takes a string as an argument and executes the string as a statement.

eval('var a = 1;');
a // 1

The above code runs the string as a statement and generates variable a.

If the parameter string cannot be run as a statement, an error will be reported.

eval('3x') // Uncaught SyntaxError: Invalid or unexpected token

The string placed in Eval should have its own meaning and cannot be used with commands other than eval. For example, the following code will report an error.

eval('return;'); // Uncaught SyntaxError: Illegal return statement

The above code will report an error because return cannot be used alone and must be used in a function.

If the parameter of eval is not a string, it will be returned as is.

eval(123) // 123

eval does not have its own scope and is executed within the current scope. Therefore, the value of variables in the current scope may be modified, resulting in security problems.

var a = 1;
eval('a = 2');

a // 2

In the above code, the eval command modifies the value of the external variable a. For this reason, Eval has security risks.

To prevent this risk, JavaScript stipulates that if strict patterns are used, variables declared inside eval will not affect the external scope.

(function f() {
  'use strict';
  eval('var foo = 123');
  console.log(foo);  // ReferenceError: foo is not defined
})()

In the above code, the internal function f is a strict mode. At this time, the foo variable declared inside eval will not affect the external.

However, even in strict mode, eval can still read and write variables in the current scope.

(function f() {
  'use strict';
  var foo = 1;
  eval('foo = 2');
  console.log(foo);  // 2
})()

In the above code, under strict mode, external variables are rewritten inside eval, which shows that security risks still exist.

In short, the essence of eval is to inject code in the current scope. It is generally not recommended because of security risks and unfavorable to the JavaScript engine to optimize the execution speed. Usually, the most common occasion for eval is to parse the string of JSON data, but the correct approach should be to use native JSON Parse method.

(2) Alias call for eval

As mentioned earlier, eval is not conducive to the execution speed of engine optimization. What's more troublesome is that in the following cases, the engine can't tell that eval is executed at the stage of static code analysis.

var m = eval;
m('var x = 1');
x // 1

In the above code, the variable m is an alias of eval. In the static code analysis phase, the engine cannot tell that m('var x = 1 ') is executing the eval command.

In order to ensure that the alias of eval does not affect code optimization, the JavaScript standard stipulates that whenever the alias is used to execute eval, the internal scope of eval is the global scope.

var a = 1;

function f() {
  var a = 2;
  var e = eval;
  e('console.log(a)');
}

f() // 1

In the above code, eval is an alias call, so even if it is in a function, its scope is still global, so the output a is a global variable. In this way, the engine can confirm that e() will not affect the current function scope, and this line can be excluded during optimization.

There are various forms of alias call of eval. As long as it is not called directly, it belongs to alias call, because the engine can only distinguish that eval() is a direct call.

eval.call(null, '...')
window.eval('...')
(1, eval)('...')
(eval, eval)('...')

The above forms are all alias calls of eval, and the scope is global scope.

8. Array

8.1 definitions

An array is a set of values arranged in order. The position of each value is numbered (starting from 0), and the whole array is represented by square brackets.

var arr = ['a', 'b', 'c'];

a, b and c in the above code form an array, and the square brackets at both ends are the flags of the array. a is position 0, b is position 1, and c is position 2.

In addition to assigning values during definition, arrays can also be defined first and then assigned.

var arr = [];

arr[0] = 'a';
arr[1] = 'b';
arr[2] = 'c';

Any type of data can be put into an array.

var arr = [
  {a: 1},
  [1, 2, 3],
  function() {return true;}
];

arr[0] // Object {a: 1}
arr[1] // [1, 2, 3]
arr[2] // function (){return true;}

The three members of the above array arr are object, array and function in turn.

If the elements of the array are still arrays, a multidimensional array is formed.

var a = [[1, 2], [3, 4]];
a[0][1] // 2
a[1][1] // 4

8.2 nature of arrays

In essence, an array is a special kind of object. The typeof operator returns an array of type object.

typeof [1, 2, 3] // "object"

The above code shows that the typeof operator considers the type of array as an object.

The particularity of an array is that its key names are a group of integers (0, 1, 2...) arranged in order.

var arr = ['a', 'b', 'c'];

Object.keys(arr)
// ["0", "1", "2"]

In the above code, object The keys method returns all the key names of the array. You can see that the key names of the array are integers 0, 1 and 2.

Because the key names of array members are fixed (the default is always 0, 1, 2...), the array does not specify a key name for each element, but each member of the object must specify a key name. JavaScript language stipulates that the key names of objects are all strings, so the key names of arrays are actually strings. The reason why it can be read with numeric value is that the key name of non string will be converted to string.

var arr = ['a', 'b', 'c'];

arr['0'] // 'a'
arr[0] // 'a'

The above code uses numeric value and string as key names respectively, and the results can read the array. The reason is that the numeric key name is automatically converted to a string.

Note that this is also true when assigning values. A value is always converted to a string and then assigned as a key name.

var a = [];

a[1.00] = 6;
a[1] // 6

In the above code, since 1.00 is converted into a string of 1, the value can be read through the number key 1.

As mentioned in the previous chapter, objects have two methods to read members: point structure (object.key) and square bracket structure (object[key]). However, point structures cannot be used for numeric key names.

var arr = [1, 2, 3];
arr.0 // SyntaxError

In the above code, the writing of arr.0 is illegal because a single value cannot be used as an identifier. Therefore, array members can only be represented by square brackets arr[0] (square brackets are operators and can accept numeric values).

8.3 length attribute

The length property of the array returns the number of members of the array.

['a', 'b', 'c'].length // 3

JavaScript uses a 32-bit integer to store the number of elements of the array. This means that the maximum number of array members is 4294967295 (232 - 1), that is, the maximum value of the length attribute is 4294967295.

As long as it is an array, it must have a length attribute. This property is a dynamic value equal to the maximum integer in the key name plus 1.

var arr = ['a', 'b'];
arr.length // 2

arr[2] = 'c';
arr.length // 3

arr[9] = 'd';
arr.length // 10

arr[1000] = 'e';
arr.length // 1001

The above code shows that the numeric keys of the array do not need to be continuous, and the value of the length attribute is always 1 greater than the largest integer key. In addition, this also shows that the array is a dynamic data structure, and the members of the array can be increased or decreased at any time.

The length attribute is writable. If you artificially set a value less than the current number of members, the number of members of the array will be automatically reduced to the value set by length.

var arr = [ 'a', 'b', 'c' ];
arr.length // 3

arr.length = 2;
arr // ["a", "b"]

The above code shows that when the length attribute of the array is set to 2 (that is, the maximum integer key can only be 1), the integer key 2 (value c) is no longer in the array and is automatically deleted.

An effective way to empty an array is to set the length property to 0.

var arr = [ 'a', 'b', 'c' ];

arr.length = 0;
arr // []

If the length is artificially set to be greater than the current number of elements, the number of members of the array will increase to this value, and the new positions are empty.

var a = ['a'];

a.length = 3;
a[1] // undefined

The above code indicates that when the length attribute is set to be greater than the number of arrays, the new positions will be read and returned undefined.

If you artificially set length to an illegal value, JavaScript will report an error.

// Set negative value
[].length = -1
// RangeError: Invalid array length

// The number of array elements is greater than or equal to the 32nd power of 2
[].length = Math.pow(2, 32)
// RangeError: Invalid array length

// Set string
[].length = 'abc'
// RangeError: Invalid array length

It is worth noting that since an array is essentially an object, you can add attributes to the array, but this does not affect the value of the length attribute.

var a = [];

a['p'] = 'abc';
a.length // 0

a[2.1] = 'abc';
a.length // 0

The above code sets the key of the array to string and decimal respectively, and the result does not affect the length attribute. Because the value of the length attribute is equal to the maximum number key plus 1, and the array has no integer key, the length attribute remains 0.

If the key name of the array is to add a value out of range, the key name will be automatically converted to a string.

var arr = [];
arr[-1] = 'a';
arr[Math.pow(2, 32)] = 'b';

arr.length // 0
arr[-1] // "a"
arr[4294967296] // "b"

In the above code, we added two illegal numeric keys to the array arr, and the length attribute did not change. These numeric keys become string key names. The reason why the last two lines get the value is that when taking the key value, the numeric key name will be changed to string by default.

8.4 in operator

The operator in that checks the existence of a key name applies to both objects and arrays.

var arr = [ 'a', 'b', 'c' ];
2 in arr  // true
'2' in arr // true
4 in arr // false

The above code shows that there is a key named 2 in the array. Since the key names are all strings, the value 2 will be automatically converted to a string.

Note that if a position in the array is empty, the in operator returns false.

var arr = [];
arr[100] = 'a';

100 in arr // true
1 in arr // false

In the above code, the array arr has only one member arr[100], and the key names in other locations will return false.

8.5 for... in loop and array traversal

for... The in loop can traverse not only the object, but also the array. After all, the array is only a special object.

var a = [1, 2, 3];

for (var i in a) {
  console.log(a[i]);
}
// 1
// 2
// 3

But for In traverses not only all numeric keys of the array, but also non numeric keys.

var a = [1, 2, 3];
a.foo = true;

for (var key in a) {
  console.log(key);
}
// 0
// 1
// 2
// foo

When traversing the array, the above code also traverses the non integer key foo. Therefore, it is not recommended to use for In traverses the array.

Array traversal can consider using a for loop or a while loop.

var a = [1, 2, 3];

// for loop
for(var i = 0; i < a.length; i++) {
  console.log(a[i]);
}

// while Loop 
var i = 0;
while (i < a.length) {
  console.log(a[i]);
  i++;
}

var l = a.length;
while (l--) {
  console.log(a[l]);
}

The above code is written in three ways to traverse an array. The last method is reverse traversal, that is, traversal from the last element to the first element.

The forEach method of Array can also be used to traverse the Array. See the chapter Array object in the standard library for details.

var colors = ['red', 'green', 'blue'];
colors.forEach(function (color) {
  console.log(color);
});
// red
// green
// blue

8.6 array vacancy

When a position in an array is an empty element, that is, there is no value between two commas, we call the array a hole.

var a = [1, , 1];
a.length // 3

The above code shows that the empty bits of the array do not affect the length attribute.

It should be noted that if there is a comma after the last element, no vacancy will be generated. In other words, with or without this comma, the result is the same.

var a = [1, 2, 3,];

a.length // 3
a // [1, 2, 3]

In the above code, there is a comma after the last member of the array, which does not affect the value of the length attribute. The effect is the same as without this comma.

The empty bits of the array can be read, and undefined is returned.

var a = [, , ,];
a[1] // undefined

Using the delete command to delete an array member will form a vacancy and will not affect the length attribute.

var a = [1, 2, 3];
delete a[1];

a[1] // undefined
a.length // 3

The above code deletes the second element of the array with the delete command. This position forms an empty bit, but it has no effect on the length attribute. That is, the length attribute does not filter the empty bits. Therefore, you must be very careful when using the length attribute for array traversal.

A position in the array is empty, which is different from an undefined position. If it is empty, use the forEach method of the array, for In structure, and object The keys method is traversed, and the empty bits will be skipped.

var a = [, , ,];

a.forEach(function (x, i) {
  console.log(i + '. ' + x);
})
// No output is generated

for (var i in a) {
  console.log(i);
}
// No output is generated

Object.keys(a)
// []

If a location is undefined, it will not be skipped during traversal.

var a = [undefined, undefined, undefined];

a.forEach(function (x, i) {
  console.log(i + '. ' + x);
});
// 0. undefined
// 1. undefined
// 2. undefined

for (var i in a) {
  console.log(i);
}
// 0
// 1
// 2

Object.keys(a)
// ['0', '1', '2']

That is to say, the empty bit means that the array does not have this element, so it will not be traversed, while undefined means that the array has this element, and the value is undefined, so the traversal will not be skipped.

8.7 array like objects

If all key names of an object are positive integers or zeros and have a length attribute, the object is very like an array, which is called "array like object" in syntax.

var obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};

obj[0] // 'a'
obj[1] // 'b'
obj.length // 3
obj.push('d') // TypeError: obj.push is not a function

In the above code, object obj is an array like object. However, "array like objects" are not arrays because they do not have array specific methods. Object obj does not have an array push method, and an error will be reported if this method is used.

The fundamental feature of "array like objects" is that they have the length attribute. As long as there is a length attribute, this object can be considered to be similar to an array. However, there is a problem. This length attribute is not a dynamic value and will not change with the change of members.

var obj = {
  length: 0
};
obj[3] = 'd';
obj.length // 0

The above code adds a numeric key to the object obj, but the length attribute remains unchanged. This shows that obj is not an array.

A typical "array like object" is the arguments object of the function, as well as most DOM element sets, as well as strings.

// arguments object
function args() { return arguments }
var arrayLike = args('a', 'b');

arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false

// DOM element set
var elts = document.getElementsByTagName('h3');
elts.length // 3
elts instanceof Array // false

// character string
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array // false

The above code contains three examples. None of them is an array (the instanceof operator returns false), but they all look very much like an array.

The slice method of array can turn "array like objects" into real arrays.

var arr = Array.prototype.slice.call(arrayLike);

In addition to converting to a real array, there is another way for "array like objects" to use the array method, which is to put the array method on the object through call().

function print(value, index) {
  console.log(index + ' : ' + value);
}

Array.prototype.forEach.call(arrayLike, print);

In the above code, arrayLike represents an array like object. The forEach() method of array can not be used, but forEach() can be grafted onto arrayLike through call().

The following example calls the forEach method on the arguments object through this method.

// forEach method
function logArgs() {
  Array.prototype.forEach.call(arguments, function (elem, i) {
    console.log(i + '. ' + elem);
  });
}

// Equivalent to a for loop
function logArgs() {
  for (var i = 0; i < arguments.length; i++) {
    console.log(i + '. ' + arguments[i]);
  }
}

String is also an object similar to array, so you can also use array prototype. forEach. Call traversal.

Array.prototype.forEach.call('abc', function (chr) {
  console.log(chr);
});
// a
// b
// c

Note that this method is slower than using the array native forEach directly, so it is best to first convert the "array like object" into a real array, and then directly call the forEach method of the array.

var arr = Array.prototype.slice.call('abc');
arr.forEach(function (chr) {
  console.log(chr);
});
// a
// b
// c

8.8 array like objects

If all key names of an object are positive integers or zeros and have a length attribute, the object is very like an array, which is called "array like object" in syntax.

var obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};

obj[0] // 'a'
obj[1] // 'b'
obj.length // 3
obj.push('d') // TypeError: obj.push is not a function

In the above code, object obj is an array like object. However, "array like objects" are not arrays because they do not have array specific methods. Object obj does not have an array push method, and an error will be reported if this method is used.

The fundamental feature of "array like objects" is that they have the length attribute. As long as there is a length attribute, this object can be considered to be similar to an array. However, there is a problem. This length attribute is not a dynamic value and will not change with the change of members.

var obj = {
  length: 0
};
obj[3] = 'd';
obj.length // 0

The above code adds a numeric key to the object obj, but the length attribute remains unchanged. This shows that obj is not an array.

A typical "array like object" is the arguments object of the function, as well as most DOM element sets, as well as strings.

// arguments object
function args() { return arguments }
var arrayLike = args('a', 'b');

arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false

// DOM element set
var elts = document.getElementsByTagName('h3');
elts.length // 3
elts instanceof Array // false

// character string
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array // false

The above code contains three examples. None of them is an array (the instanceof operator returns false), but they all look very much like an array.

The slice method of array can turn "array like objects" into real arrays.

var arr = Array.prototype.slice.call(arrayLike);

In addition to converting to a real array, there is another way for "array like objects" to use the array method, which is to put the array method on the object through call().

function print(value, index) {
  console.log(index + ' : ' + value);
}

Array.prototype.forEach.call(arrayLike, print);

In the above code, arrayLike represents an array like object. The forEach() method of array can not be used, but forEach() can be grafted onto arrayLike through call().

The following example calls the forEach method on the arguments object through this method.

// forEach method
function logArgs() {
  Array.prototype.forEach.call(arguments, function (elem, i) {
    console.log(i + '. ' + elem);
  });
}

// Equivalent to a for loop
function logArgs() {
  for (var i = 0; i < arguments.length; i++) {
    console.log(i + '. ' + arguments[i]);
  }
}

String is also an object similar to array, so you can also use array prototype. forEach. Call traversal.

Array.prototype.forEach.call('abc', function (chr) {
  console.log(chr);
});
// a
// b
// c

Note that this method is slower than using the array native forEach directly, so it is best to first convert the "array like object" into a real array, and then directly call the forEach method of the array.

var arr = Array.prototype.slice.call('abc');
arr.forEach(function (chr) {
  console.log(chr);
});
// a
// b
// c

2, Operator

9. Operator

9.1 arithmetic operator (addition operator)

9.1.1 basic rules

The addition operator (+) is the most common operator used to sum two numeric values.

1 + 1 // 2

JavaScript allows the addition of non numeric values.

true + true // 2
1 + true // 2

In the above code, the first line is the addition of two Boolean values, and the second line is the addition of numeric values and Boolean values. In both cases, Boolean values are automatically converted to numeric values and then added.

In particular, if two strings are added, the addition operator will become a connection operator and return a new string to connect the two original strings together.

'a' + 'bc' // "abc"

If one operator is a string and the other operator is a non string, the non string will be converted into a string and connected together.

1 + 'a' // "1a"
false + 'a' // "falsea"

The addition operator determines whether to perform addition or connection at run time. In other words, different operators lead to different grammatical behaviors, which is called "overload". Due to the overload of the addition operator, two operations may be performed. You must be careful when using it.

'3' + 4 + 5 // "345"
3 + 4 + '5' // "75"

In the above code, due to the operation order from left to right, different positions of strings will lead to different results.

Except for the addition operator, other arithmetic operators (such as subtraction, division, and multiplication) are not overloaded. Their rule is: all operators are converted to numerical values, and then the corresponding mathematical operations are carried out.

1 - '2' // -1
1 * '2' // 2
1 / '2' // 0.5

In the above code, the subtraction, division and multiplication operators automatically convert the string to a numeric value, and then operate.

9.1.2 addition of objects

If the operator is an object, it must be converted to the value of the original type before adding.

var obj = { p: 1 };
obj + 2 // "[object Object]2"

In the above code, the value of object obj converted to original type is [object Object], and then add 2 to get the above result.

Object to the value of the original type. The rules are as follows.

First, the valueOf method of the object is called automatically.

var obj = { p: 1 };
obj.valueOf() // { p: 1 }

Generally speaking, the valueOf method of an object always returns the object itself, and then automatically calls the toString method of the object to convert it into a string.

var obj = { p: 1 };
obj.valueOf().toString() // "[object Object]"

The toString method of the object returns [object Object] by default, so you get the result of the previous example.

After knowing this rule, you can define the valueOf method or toString method to get the desired result.

var obj = {
  valueOf: function () {
    return 1;
  }
};

obj + 2 // 3

In the above code, we define that the valueOf method of obj object returns 1, so obj + 2 gets 3. In this example, since the valueOf method directly returns a value of the original type, the toString method is no longer called.

The following is an example of a custom toString method.

var obj = {
  toString: function () {
    return 'hello';
  }
};

obj + 2 // "hello2"

In the above code, the toString method of object obj returns the string hello. As mentioned earlier, as long as one operator is a string, the addition operator becomes a connection operator and returns the connected string.

Here is a special case. If the operator is an instance of a Date object, the toString method will be executed first.

var obj = new Date();
obj.valueOf = function () { return 1 };
obj.toString = function () { return 'hello' };

obj + 2 // "hello2"

In the above code, the object obj is an instance of a Date object, and the valueOf method and toString method are customized. As a result, the toString method takes precedence.

9.1.3 remainder operator

It should be noted that the sign of the operation result is determined by the sign of the first operator.

-1 % 2 // -1
1 % -2 // 1

Therefore, in order to get the correct remainder of a negative number, you can first use the absolute value function.

// Wrong writing
function isOdd(n) {
  return n % 2 === 1;
}
isOdd(-5) // false
isOdd(-4) // false

// Correct writing
function isOdd(n) {
  return Math.abs(n % 2) === 1;
}
isOdd(-5) // true
isOdd(-4) // false

9.1.4 self increasing and self decreasing operators

One thing to note about the self increasing and self decreasing operators is that after the variable, it will first return the value before the variable operation, and then carry out the self increasing / self decreasing operation; Before the variable is placed, the auto increment / Auto decrement operation will be performed first, and then the value after the variable operation will be returned.

var x = 1;
var y = 1;

x++ // 1
++y // 2

9.1.5 exponential operator

Note that = =, the exponent operator is a right Union, not a left Union. That is, when multiple exponential operators are used together, the rightmost calculation is performed first==

// Equivalent to 2 * * (3 * * 2)
2 ** 3 ** 2
// 512

The following is the combination with bitwise operators (for bitwise operators, see the introduction later).

// Equivalent to x = x > > y
x >>= y

// Equivalent to x = x < < y
x <<= y

// Equivalent to x = x > > > y
x >>>= y

// Equivalent to x = x & Y
x &= y

// Equivalent to x = x | y
x |= y

// Equivalent to x = x ^ y
x ^= y

These compound assignment operators perform the specified operation first, and then return the obtained value to the variable on the left.

9.2 comparison operators

9.2.1 unequal operator: string comparison

Strings are compared in dictionary order.

'cat' > 'dog' // false
'cat' > 'catalog' // false

The JavaScript engine first compares the Unicode code point of the first character. If equal, compare the Unicode code points of the second character, and so on.

'cat' > 'Cat' // true'

In the above code, the Unicode code point (99) of lowercase C is greater than that of uppercase C (67), so it returns true.

Since all characters have Unicode code points, Chinese characters can also be compared.

'large' > 'Small' // false

In the above code, the Unicode code point of "large" is 22823 and "small" is 23567, so false is returned.

9.2.2 non equality operator: comparison of non strings

If at least one of the two operators is not a string, it needs to be divided into the following two cases.

(1) Original type value

The three types of value, string and Boolean value are collectively called primitive type values, that is, they are the most basic data types and cannot be subdivided.

If both operators are values of the original type, they are converted to values before comparison.

5 > '4' // true
// Equivalent to 5 > number ('4 ')
// I.e. 5 > 4

true > false // true
// Equivalent to number (true) > number (false)
// I.e. 1 > 0

2 > true // true
// Equivalent to 2 > number (true)
// I.e. 2 > 1

In the above code, both string and Boolean values will be converted to numeric values before comparison.

Here we need to pay attention to the comparison with NaN. Any value (including NaN itself) compared with NaN using the non equality operator returns false.

1 > NaN // false
1 <= NaN // false
'1' > NaN // false
'1' <= NaN // false
NaN > NaN // false
NaN <= NaN // false

(2) Object

If the operator is an object, it is converted to the value of the original type for comparison.

Object is converted to the value of the original type. The algorithm is to call the valueOf method first; If the returned object is still an object, then call the toString method. See the chapter "data type conversion" for details.

var x = [2];
x > '11' // true
// Equivalent to [2] valueOf(). toString() > '11'
// I.e. '2' > '11'

x.valueOf = function () { return '1' };
x > '11' // false
// Equivalent to [2] valueOf() > '11'
// I.e. '1' > '11'

The same is true for the comparison between two objects.

[2] > [1] // true
// Equivalent to [2] valueOf(). toString() > [1]. valueOf(). toString()
// I.e. '2' > '1'

[2] > [11] // true
// Equivalent to [2] valueOf(). toString() > [11]. valueOf(). toString()
// I.e. '2' > '11'

{ x: 2 } >= { x: 1 } // true
// Equivalent to {X: 2} valueOf(). toString() >= { x: 1 }. valueOf(). toString()
// That is' [object] '> =' [object] '

9.3 Boolean operators

9.3.1 general

Boolean operators are used to convert an expression to a Boolean value and contain a total of four operators.

  • Inverse operator:!
  • And operator:&&
  • Or operator:||
  • Ternary operator:?:

9.3.2 inverse operator (!) (refer to 10.2.3 Boolean())

For non Boolean values, the inverse operator converts them to Booleans. It can be remembered that the following six values are reversed to true and the other values are false.

  • undefined
  • null
  • false
  • 0
  • NaN
  • Empty string ('')
!undefined // true
!null // true
!0 // true
!NaN // true
!"" // true

!54 // false
!'hello' // false
![] // false
!{} // false

In the above code, no matter what type of value, after the inversion operation, it becomes a Boolean value.

If a value is inversed twice in a row, it is converted to the corresponding Boolean value, which is the same as the Boolean function. This is a common way to write type conversion.

!!x
// Equivalent to
Boolean(x)

In the above code, no matter what type of value x is, after two inversions, it becomes the same Boolean value as the result of the Boolean function. Therefore, double negation is a simple way to turn a value into a Boolean value.

9.3.3 and operator (& &)

Operators (& &) are often used to evaluate multiple expressions.

Its operation rule is: if the Boolean value of the first operator is true, the value of the second operator is returned (note that it is a value, not a Boolean value); If the Boolean value of the first operator is false, the value of the first operator is returned directly and the second operator is no longer evaluated.

't' && '' // ""
't' && 'f' // "f"
't' && (1 + 2) // 3
'' && 'f' // ""
'' && '' // ""

var x = 1;
(1 - 1) && ( x += 1) // 0
x // 1

In the last example of the above code, since the Boolean value of the first operator of the operator is false, its value 0 is directly returned instead of evaluating the second operator, so the value of variable x does not change.

This mechanism of skipping the second operator is called "short circuit". Some programmers like to use it instead of if structure. For example, the following is a piece of code of if structure, which can be rewritten with and operator.

if (i) {
  doSomething();
}

// Equivalent to

i && doSomething();

The two ways of writing the above code are equivalent, but the latter is not easy to see the purpose and debug. It is recommended to use it carefully.

And the operator can be used in conjunction with multiple operators. In this case, the value of the first expression whose Boolean value is false is returned. If the Boolean value of all expressions is true, the value of the last expression is returned.

true && 'foo' && '' && 4 && 'foo' && true
// ''

1 && 2 && 3
// 3

In the above code, in example 1, the first expression with false Boolean value is the third expression, so an empty string is obtained. In example 2, the Boolean values of all expressions are true, so the value 3 of the last expression is returned.

9.3.4 or operator (|)

The or operator (|) is also used to evaluate multiple expressions. Its operation rule is: if the Boolean value of the first operator is true, the value of the first operator is returned and the second operator is no longer evaluated; If the Boolean value of the first operator is false, the value of the second operator is returned.

't' || '' // "t"
't' || 'f' // "t"
'' || 'f' // "f"
'' || '' // ""

The short circuit rule also applies to this operator.

var x = 1;
true || (x = 2) // true
x // 1

In the above code, the first operator of the or operator is true, so it directly returns true and does not run the second operator. So the value of x doesn't change. This mechanism of controlling whether to run the second expression only through the value of the first expression is called "short cut".

The or operator can be used in conjunction with more than one. In this case, the value of the first expression whose Boolean value is true is returned. If all expressions are false, the value of the last expression is returned.

false || 0 || '' || 4 || 'foo' || true
// 4

false || 0 || ''
// ''

In the above code, in example 1, the expression with the first Boolean value of true is the fourth expression, so the value 4 is obtained. In example 2, the Boolean values of all expressions are false, so the value of the last expression is returned.

The or operator is often used to set the default value for a variable.

function saveText(text) {
  text = text || '';
  // ...
}

// Or write
saveText(this.text || '')

The above code indicates that if no parameter is provided during function call, the parameter is set to an empty string by default.

9.3.5 ternary conditional operator (?:)

The ternary conditional operator consists of a question mark (?) And colon (:), separating three expressions. It is the only operator in the JavaScript language that requires three operators. If the Boolean value of the first expression is true, the value of the second expression is returned; otherwise, the value of the third expression is returned.

Generally speaking, ternary conditional expressions and if Else statements have the same expression effect. The former can express, and the latter can also express. But there is a big difference between the two, if Else is a statement with no return value; A ternary conditional expression is an expression that has a return value. Therefore, when a return value is required, only ternary conditional expressions can be used, not if else.

console.log(true ? 'T' : 'F');

In the above code, console The parameter of the log method must be an expression. In this case, only ternary conditional expressions can be used. If you want to use if Else statement, you must change the writing method of the whole code.

9.4 binary operators

9.4.1 general

Binary bit operators are used to directly calculate binary bits. There are 7 in total.

  • Binary or operator (or): the symbol is |, which means that if both binary bits are 0, the result is 0, otherwise it is 1.
  • Binary and operator (and): the sign is &, which means that if both binary bits are 1, the result is 1, otherwise it is 0.
  • Binary no operator (not): the sign is ~, which indicates the negation of a binary bit.
  • xor operator (xor): the symbol is ^, which means that if the two binary bits are different, the result is 1, otherwise it is 0.
  • left shift operator: the symbol is < <. See the explanation below for details.
  • right shift operator: the symbol is > >. See the explanation below for details.
  • zero filled right shift: the symbol is > > >, as explained below.

These = = bit operators directly process each bit, so they are very low-level operations. The advantage is that they are very fast, but the disadvantage is very non intuitive. = = they cannot be used in many occasions, otherwise it will make the code difficult to understand and check errors.

It should be noted that the = = bit operator only works on integers. If an operator is not an integer, it will be automatically converted to an integer before execution== In addition, although in JavaScript, values are stored in the form of 64 bit floating-point numbers, 32-bit signed integers are used for bit operations, and the return value is also a 32-bit signed integer.

i = i | 0;

The meaning of the above line of code is to convert i (whether integer or decimal) to a 32-bit integer.

Using this feature, you can write a function to convert any value into a 32-bit integer.

function toInt32(x) {
  return x | 0;
}

The above function performs an or operation on any value and 0. This bit operation will automatically convert a value to a 32-bit integer. Here is the usage of this function.

toInt32(1.001) // 1
toInt32(1.999) // 1
toInt32(1) // 1
toInt32(-1) // -1
toInt32(Math.pow(2, 32) + 1) // 1
toInt32(Math.pow(2, 32) - 1) // -1

In the above code, toInt32 can convert decimals to integers. For general integers, the return value does not change. For 32-power integers greater than or equal to 2, digits greater than 32 bits are rounded off.

9.4.2 binary or operator (rounding)

Bit operation is only valid for integers. When a decimal is encountered, the decimal part will be rounded off and only the integer part will be retained. Therefore, the binary or operation of a decimal with 0 is equivalent to removing the decimal part of the number, that is, rounding.

2.9 | 0 // 2
-2.9 | 0 // -2

It should be noted that this rounding method does not apply to numbers exceeding the maximum value of 32-bit integers 2147483647.

2147483649.4 | 0;
// -2147483647

9.4.3 binary no operator (fastest rounding)

The binary no operator (~) changes each binary bit to the opposite value (0 to 1, 1 to 0). Its return result is sometimes difficult to understand because it involves the numerical representation mechanism inside the computer.

~ 3 // -4

The above expression performs binary no operation on 3 to obtain - 4. The reason for this result is that during bit operation, JavaScript converts all operators into 32-bit binary integers for operation.

The 32-bit integer form of 3 is 000000000000000000000000000000000000 11. After binary no operation, 11111111111111111111100 is obtained. Since the first bit (sign bit) is 1, this number is a negative number. JavaScript internally uses complement to represent negative numbers, that is, you need to subtract 1 from this number, take the inverse again, and then add a negative sign to get the hexadecimal value corresponding to this negative number. This number minus 1 equals 11111111111111111111111111011. Take it again to get 000000000000000000000000000 100. Plus the minus sign is - 4. Considering that such a process is troublesome, you can simply remember that a number is added to its own inverse value, which is equal to - 1.

~ -3 // 2

The above expression can be calculated as follows: the negative value of - 3 is equal to - 1 minus - 3, and the result is 2.

Two consecutive binary no operations on an integer to obtain itself.

~~3 // 3

All bit operations are valid only for integers. When binary no operation encounters decimals, the decimal part will also be rounded off and only the integer part will be retained. Therefore, two consecutive binary no operations on a decimal can achieve rounding effect.

~~2.9 // 2
~~47.11 // 47
~~1.9999 // 1
~~3 // 3

Rounding using binary no operation is the fastest of all rounding methods.

When binary no operation is performed on the string, the JavaScript engine will first call the Number function to convert the string into a numeric value.

// Equivalent to ~ Number('011 ')
~'011'  // -12

// Equivalent to ~ Number('42 cats')
~'42 cats' // -1

// Equivalent to ~ Number('0xcafebabe ')
~'0xcafebabe' // 889275713

// Equivalent to ~ Number('deadbeef ')
~'deadbeef' // -1

The Number function converts a string to a numeric value. See the chapter "data type conversion".

For other types of values, the binary no operation is first converted to a value with Number, and then processed.

// Equivalent to ~ Number([])
~[] // -1

// Equivalent to ~ Number(NaN)
~NaN // -1

// Equivalent to ~ Number(null)
~null // -1

9.4.4 XOR operator (exchange two variables)

The XOR operation (^) returns 1 when two binary bits are different and 0 when they are the same.

0 ^ 3 // 3

In the above expression, 0 (binary 00) and 3 (binary 11) perform XOR operation. Each binary bit is different, so 11 (i.e. 3) is obtained.

"Exclusive or operation" has a special application, which performs three exclusive or operations on two numbers a and B, a^=b; b^=a; a^=b;, sure exchange Their values. This means that using XOR operation can exchange the values of two variables without introducing temporary variables.

var a = 10;
var b = 99;

a ^= b, b ^= a, a ^= b;

a // 99
b // 10

This is the fastest way to swap the values of two variables.

XOR operations can also be used for rounding.

12.9 ^ 0 // 12

9.4.5 shift left operator (rounding)

The shift left operator (< <) means to move the binary value of a number to the left by the specified number of bits, and the tail is supplemented by 0, that is, multiplied by the specified power of 2. When moving to the left, the highest symbol bits move together.

// The binary form of 4 is 100,
// Shift left one bit to 1000 (i.e. 8 in decimal)
// It's equal to multiplying by 2 to the power of 1
4 << 1
// 8

-4 << 1
// -8

In the above code, - 4 shifts left one bit to get - 8 because the binary form of - 4 is 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100. When the number is converted to decimal system (minus 1, reverse, plus negative sign), it is -.

If you shift 0 bit to the left, it is equivalent to converting the value to a 32-bit integer, which is equivalent to rounding. It is valid for both positive and negative numbers.

13.5 << 0
// 13

-13.5 << 0
// -13

The shift left operator is very convenient for binary values.

var color = {r: 186, g: 218, b: 85};

// RGB to HEX
// (1 < < 24) is used to ensure that the result is 6 digits
var rgb2hex = function(r, g, b) {
  return '#' + ((1 << 24) + (r << 16) + (g << 8) + b)
    .toString(16) // Convert to hexadecimal, and then return the string
    .substr(1);   // Remove the highest bit of the string and return the next six strings
}

rgb2hex(color.r, color.g, color.b)
// "#bada55"

The above code uses the shift left operator to convert the RGB value of the color to the HEX value.

9.4.6 shift right operator (divided by the power of 2)

The shift right operator (> >) moves the binary value of a number to the right by a specified number of digits. If it is a positive number, the head is filled with 0; If it is negative, the head is filled with 1. The shift right operator is basically equivalent to dividing by 2 to the specified power (the highest bit, that is, the sign bit participates in the movement).

4 >> 1
// 2
/*
// Because the binary form of 4 is 000000000000000000000000000,
// Move one bit to the right to get 000000000000000000000000000000000000000000000000000000 10,
// Is decimal 2
*/

-4 >> 1
// -2
/*
// Because the binary form of - 4 is 11111111111111111111111111111100,
// Move one bit to the right, fill 1 in the head, and get 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
// That is - 2 in decimal
*/

The shift right operation can simulate the division of 2.

5 >> 1
// 2
// Equivalent to 5 / 2 = 2

21 >> 2
// 5
// Equivalent to 21 / 4 = 5

21 >> 3
// 2
// Equivalent to 21 / 8 = 2

21 >> 4
// 1
// Equivalent to 21 / 16 = 1

9.4.7 right shift operator of head zeroing

The only difference between the right shift operator (> > >) and the right shift operator (> >) is that when the binary form of a number moves to the right, the head will be filled with zero without considering the sign bit. Therefore, the operation always gets a positive value. For positive numbers, the result of this operation is exactly the same as the shift right operator (> >), and the difference is mainly negative numbers.

4 >>> 1
// 2

-4 >>> 1
// 2147483646
/*
// Because the binary form of - 4 is 11111111111111111111111111111100,
// Shift the signed bit to the right one bit to obtain 011111111111111111111111110,
// It is 2147483646 in decimal system.
*/

This operation actually converts a value to a 32-bit unsigned integer.

The quickest way to see how a negative integer is stored inside a computer is to use this operator.

-1 >>> 0 // 4294967295

The above code indicates that when - 1 is used as a 32-bit integer, the internal storage form is interpreted in unsigned integer format, and the value is 4294967295 (i.e. (2 ^ 32) - 1, equal to 11111111111111111111111).

9.4.8 switching function

Bitwise operators can be used as switches to set object properties.

Suppose an object has four switches, each of which is a variable. Then, a four bit binary number can be set, and each bit of it corresponds to a switch.

var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000

The above code sets four switches A, B, C and D, and each switch occupies A binary bit.

Then, the binary sum operation can be used to check whether the specified switch is turned on in the current setting.

var flags = 5; // Binary 0101

if (flags & FLAG_C) {
  // ...
}
// 0101 & 0100 => 0100 => true

The above code checks whether switch C is turned on. If it is opened, it will return true; otherwise, it will return false.

Now suppose we need to turn on the switches A, B and D, and we can construct A mask variable.

var mask = FLAG_A | FLAG_B | FLAG_D;
// 0001 | 0010 | 1000 => 1011

The above code performs binary or operation on three variables A, B and D to obtain 1011 with binary mask value.

With a mask, a binary or operation ensures that the specified switch is turned on.

flags = flags | mask;

In the above code, the calculated flags variable represents that the binary bits of the three switches are turned on.

Binary sum operation can turn off all items in the current setting that are different from the switch setting.

flags = flags & mask;

The XOR operation can toggle the current setting, that is, the first execution can get the opposite value of the current setting, and the second execution can get the original value.

flags = flags ^ mask;

Binary no operation can flip the current setting, that is, the original setting is 0 and becomes 1 after operation; Originally set to 1, it becomes 0 after operation.

flags = ~flags;

9.5 other operators and operation order

9.5.1 void operator

The void operator executes an expression and then returns no value, or undefined.

void 0 // undefined
void(0) // undefined

The above two ways to write the void operator are correct. The latter form is recommended, that is, parentheses are always used. Because the void operator has high priority, it is easy to cause wrong results if parentheses are not used. For example, void 4 + 7 is actually equivalent to (void 4) + 7.

Here is an example of the void operator.

var x = 3;
void (x = 5) //undefined
x // 5

The main purpose of this operator is the browser's Bookmarklet and inserting code into hyperlinks to prevent web pages from jumping.

Look at the code below.

<script>
function f() {
  console.log('Hello World');
}
</script>
<a href="http://example. com" onclick="f();  return false; "> Click</a>

In the above code, after clicking the link, the onclick code will be executed first. Since onclick returns false, the browser will not jump to example com.

The void operator can replace the above writing.

<a href="javascript: void(f())">written words</a>

The following is a more practical example. A user clicks a link to submit a form without generating a page Jump.

<a href="javascript: void(document.form.submit())">
  Submit
</a>

9.5.2 comma operator

The comma operator is used to evaluate two expressions and return the value of the latter expression.

'a', 'b' // "b"

var x = 0;
var y = (x++, 10);
x // 1
y // 10

In the above code, the comma operator returns the value of the latter expression.

One use of the comma operator is to perform some auxiliary operations before returning a value.

var value = (console.log('Hi!'), true);
// Hi!

value // true

In the above code, first perform the operation before the comma, and then return the value after the comma.

9.5.2 operation sequence

9.5.2.1 priority

The Operator Precedence of various operators in JavaScript is different. Operators with high priority are executed first, and operators with low priority are executed later.

4 + 5 * 6 // 34

In the above code, the multiplication operator (*) takes precedence over the addition operator (+), so the multiplication is performed first and then the addition is performed, which is equivalent to the following.

4 + (5 * 6) // 34

If multiple operators are mixed together, it often leads to confusing code.

var x = 1;
var arr = [];

var y = arr.length <= 0 || arr[0] === undefined ? x : arr[0];

In the above code, it is difficult to see the value of variable y, because this expression involves five operators. It is not easy to remember who has the highest priority.

According to the language specification, the priorities of the five operators are less than or equal to (< =), strictly equal (= =), or (|), ternary (?:), and equal sign (=). Therefore, the actual operation order of the above expression is as follows.

var y = ((arr.length <= 0) || (arr[0] === undefined)) ? x : arr[0];

It is very difficult and unnecessary to remember the priority of all operators.

9.5.2.2 role of parentheses

Parentheses (()) can be used to increase the priority of operations because it has the highest priority, that is, the expression in parentheses will be the first operation.

(4 + 5) * 6 // 54

In the above code, due to the use of parentheses, addition will be performed before multiplication.

The priority of operators is very complex and hard to specify. Therefore, it is recommended to always use parentheses to ensure that the operation order is clear and readable, which is very important for code maintenance and debugging.

By the way, parentheses are not operators, but a grammatical structure. It has two uses: one is to put expressions in parentheses to improve the priority of operations; The other is to follow the function and call the function.

Note that because parentheses are not operators, they have no evaluation function and only change the priority of operations.

var x = 1;
(x) = 2;//2

In the second line of the above code, if the parentheses have the function of evaluation, it will become 1 = 2, which will report an error. However, the above code can be run, which verifies that parentheses only change priority and do not evaluate.

This also means that if the entire expression is placed in parentheses, there will be no effect.

(expression)
// Equivalent to
expression

The function is placed in parentheses and returns the function itself. If parentheses follow a function, it means that the function is called.

function f() {
  return 1;
}

(f) // function f(){return 1;}
f() // 1

In the above code, if the function is placed in parentheses, it will return the function itself, and the parentheses followed by the function will call the function.

Only expressions can be placed in parentheses. If statements are placed in parentheses, an error will be reported.

(var a = 1)
// SyntaxError: Unexpected token var
9.5.2.3 left combination and right combination

A few operators are "right combination", the most important of which are assignment operator (=) and ternary conditional operator (?:).

w = x = y = z;
q = a ? b : c ? d : e ? f : g;

The above code is interpreted as follows.

w = (x = (y = z));
q = a ? b : (c ? d : (e ? f : g));

The above two lines of code are combined with the operands on the right.

In addition, the exponential operator (* *) is also a right combination.

2 ** 3 ** 2
// Equivalent to 2 * * (3 * * 2)
// 512

Topics: Javascript Front-end