quotation

In May 1995, Eich wrote the first version of the scripting language in 10 days. The first code name for JavaScript was Mocha, a name given by Marc Andreesen. Netscape marketing renamed it LiveScript because of trademark issues and the fact that many products already used the Live prefix. At the end of November 1995, Navigator 2.0B3 was released, containing a prototype version of the language that was little changed. In early December 1995, the Java language had grown so large that Sun licensed the Java trademark to Netscape. The language was renamed again to its final name, JavaScript. Then, in January 1997, it was standardized and became ECMAScript.

Nearly a year or two in the client end to use JS more and more places, the author recently contacted JS, as a front-end small white, record their recent “step on the pit” growth experience.

I. Raw values and objects

In JavaScript, values are distinguished in two ways:

1. Original values: BOOL, Number, String, null, undefined. 2. Objects: Each object has a unique identity and is strictly equal to (===) itself.

Null, undefined has no attributes, not even toString().

False, 0, NaN, undefined, null, “”, all false.

The typeof operator differentiates a raw value from an object and detects the typeof the original value. The instanceof operator can detect whether an object is an instanceof a particular constructor or a subclass of it.

The operand typeof
undefined ‘undefined’
null object
Boolean value boolean
digital number
string string
function function
Other normal values object
The value created by the engine May return any string

Null returns an object, which is an unfixable bug that, if modified, will break the existing code architecture. But this does not mean that NULL is an object.

Because JavaScript values in the first generation of JavaScript engines are represented as 32-bit characters. The lowest three bits are used as an identifier to indicate whether the value is an object, integer, floating point, or Boolean. The object’s identity is 000, and to represent NULL, the engine uses a pointer to the machine language NULL, where all bits are zeros. Typeof is the flag bit for the value, which is why null is considered an object.

Therefore, whether a value is an object should be judged according to the following conditions:


function isObject (value) {
  return( value ! = =null 
    && (typeof value === 'object' 
    || typeof value === 'function'));
}Copy the code

Null is the element at the top of the prototype chain


Object.getPrototypeOf(Object.prototype)

< nullCopy the code

Undefined and null can be determined by strict equality:


if(x === null) {
  // Check whether it is null
}

if (x === undefined) {
  // Check whether it is undefined
}

if (x === void 0 ) {
  Void 0 === = undefined
}

if(x ! =null ) {
 // check that x is neither undefined nor null
 // This is equivalent to if (x! == undefined && x ! == null )
}Copy the code

There is a special case in the primitive value where NaN is a primitive value, but it is not equal to itself.


NaN= = =NaN
<falseCopy the code

Boolean, Number, String can convert a raw value to an object, or an object to a raw value.


// The original value is converted to an object
var object = new String('abc')


// The object is converted to its original value
String(123)
<'123'Copy the code

However, when converting an object to its original value, it is important to note that if valueOf() is used for the conversion, everything is correctly converted.


new Boolean(true).valueOf()
<trueCopy the code

However, when the constructor is used to convert the wrapper object to its original value, the BOOL value is not converted correctly.


Boolean(new Boolean(false))"trueCopy the code

The constructor can only correctly extract numbers and strings from the wrapped object.

2. Loose equality brings bugs

There are two ways to determine if two values are equal in JavaScript.

  1. Strictly equal (===) and strictly unequal (! ==) requires that the values being compared must be of the same type.
  2. Loose equal (==) and loose equal (! =) tries to convert two different types of values before comparing them using strict equivalence.

Loose equality leads to some bugs:


undefined= =null // undefined and null are loosely equal
<true

2= =true  // Do not mistake this for true
<false

1= =true 
<true

0= =false
<true 

' '= =false // Empty strings equal false, but not all non-empty strings are equal to true
<true

'1'= =true
<true

'2'= =true
<false

'abc'= =true // NaN === 1
<falseCopy the code

Here’s a picture of Strict equality and Loose Equality from someone on GitHub

But if Boolean() is used to convert, the situation is different:

value Convert to BOOL
undefined false
null false
BOOL Same as the input value
digital 0, NaN converts to false, and everything else is true
string “Is converted to false and all other strings are converted to true
object All is true

Why is the object always true here? In ECMAScript 1, it was stated that conversion through object configuration (such as the toBoolean() method) was not supported. Principle is a Boolean operator | | and && will keep the value of the operand. Therefore, if the chain uses these operators, the same value is checked multiple times. Such checks are inexpensive for primitive value types, but costly for objects that can be configured to convert booleans. So starting with ECMAScript 1, objects are always true to avoid these cost conversions.

Three Number.

All numbers in JavaScript have only one type and are treated as floating-point numbers, and JavaScript is internally optimized to distinguish between floating-point arrays and integers. JavaScript numbers are double precision (64-bit), based on the IEEE 754 standard.

Since all numbers are floating point numbers, there is a precision issue here. Do you still remember the cartoon about robots that circulated on the Internet some time ago?

The question of precision leads to some wonderful things


0.1 + 0.2 ;  / / 0.300000000000004

( 0.1 + 0.2 ) + 0.3;    / / 0.6000000000001
0.1 + ( 0.2 + 0.3 );    / / 0.6

(0.8+0.7+0.6+0.5) / 4   / / 0.65
(0.6+0.7+0.8+0.5) / 4   / / 0.6499999999999999Copy the code

Changing a position, adding a parenthesis, will affect the accuracy. To avoid this problem, it is recommended to convert to integers.


( 8 + 7 + 6 + 5) / 4 / 10 ;  / / 0.65
( 6 + 8 + 5 + 7) / 4 / 10 ;  / / 0.65Copy the code
value Convert to the Number value
undefined NaN
null 0
BOOL False = 0, true = 1
digital Same as the original value
string Parse numbers in strings (ignoring leading and trailing Spaces); Null characters are converted to 0.
object Call ToPrimitive(value, number) and convert to the original type

There are four special values in the numbers:

  1. Two error values: NaN and Infinity
  2. Two zeros, one plus zero, one minus zero. Zero is a plus and a minus sign. Because signs and values are stored separately.

typeof NaN
<"number"Copy the code

(Fun note: NaN is short for “not a number,” but it is a number.)

NaN is the only value in JS that is not strictly equal to itself:


NaN= = =NaN
<falseCopy the code

Array.prototype.indexOf cannot be used to find NaN (because indexOf arrays is strictly equal).


[ NaN ].indexOf( NaN )
<- 1Copy the code

There are two types of correct posture:

The first:


function realIsNaN( value ){
  return typeof value === 'number' && isNaN(value);
}Copy the code

The type determination is required because string conversions are converted to numbers and fail to NaN. So this is equal to NaN.


isNaN( 'halfrost' )
<trueCopy the code

The second approach is to use the IEEE 754 standard definition that NaN is unordered when compared to any value, including itself


function realIsNaN( value ){
  returnvalue ! == value ; }Copy the code

The other error value Infinity is caused by representing Infinity, or dividing by zero.

It can be judged directly by loose equality ==, or strict equality ===.

However, isFinite() is not used to determine Infinity, but to determine whether a value is an error (this means neither NaN nor Infinity, excluding both errors).

In ES6, two functions were introduced to determine Infinity and NaN. Number.isfinite () and Number.isnan () are recommended to use these two functions in the future.

JS has a safe interval between (-2^53, 2^53). So if a number exceeds a 64-bit unsigned integer number, it can only be stored as a string.

When parseInt() is used to convert to a number, some errors may occur and the result may not be trusted:


parseInt(1000000000000000000000000000.99999999999999999.10)
<1Copy the code

parseInt( str , redix? ) The first argument is converted to a string:


String(1000000000000000000000000000.99999999999999999)
<"1e+27"Copy the code

ParseInt doesn’t think e is an integer, so anything after e stops parsing, so it prints 1.

The % mod operator in JS is not what we normally think of as mod.


9 -%7
<2 -Copy the code

The remainder operator returns a result with the same symbol as the first operand. The modulo operation is the same sign as the second operand.

So the pit is that we usually judge whether a number is odd or even problems will be wrong:


function isOdd( value ){
  return value % 2= = =1;
}

console.log(- 3);  // false
console.log(2 -);  // falseCopy the code

The correct posture is:


function isOdd( value ){
  return Math.abs( value % 2) = = =1;
}

console.log(- 3);  // true
console.log(2 -);  // falseCopy the code

Four String.

A string comparator cannot compare diacritics and accents.


'ä' < 'b'
<false

'á' < 'b'
<falseCopy the code

Five Array.

You cannot create an array with a single number.


new Array(2)  // A number here represents the length of the array"[and]new Array(2.3.4) < [2.3.4]Copy the code

Deleting an element removes whitespace, but does not change the array length.


var array = [1.2.3.4]
array.length
<4
delete array[1]

array
<[1.3.4]
array.length
<4Copy the code

So the deletion here doesn’t quite match our previous deletion, the correct gesture is splice


var array = [1.2.3.4.56.7.8.9]
array.splice(1.3)
array
<[1.56.7.8.9]
array.length
<5Copy the code

Different traversal methods behave differently for gaps in the array

In the ES5:

methods For the vacancy
forEach() Traversal skips vacancies
every() Traversal skips vacancies
some() Traversal skips vacancies
map() The traversal skips the vacancy, but the final result preserves the vacancy
filter() Remove the gap
join() Convert empty, undefined, and null to an empty string
toString() Convert empty, undefined, and null to an empty string
sort() Leave empty when sorting
apply() Convert each vacancy to undefined

In ES6: it is specified that vacancies are not skipped through the duration and are converted to undefined

methods For the vacancy
Array.from() Vacancies are converted to undefined
. (Extension operators have) Vacancies are converted to undefined
copyWithin() Copy the vacancy together
fill() The traversal does not skip vacancies, treating them as normal elements
for… of Traversal does not skip vacancies
entries() Vacancies are converted to undefined
keys() Vacancies are converted to undefined
values() Vacancies are converted to undefined
find() Vacancies are converted to undefined
findIndex() All vacancies are converted to undefined P0P0

Set, Map, WeakSet, WeakMap

The data structure The characteristics of
Set Like an array, but with unique member values, note (this is an exception) that NaN equals itself
WeakSet Members can only be objects, not other types of values. Object references are weak references, so you cannot refer to WeakSet members and iterate over them (because traversal can disappear at any time).
Map Like an object, a collection of key-value pairs. Keys are not limited to strings, but can be of any type. It is a value-value mapping, as opposed to a string-value mapping for objects
WeakMap Similar to Map, except that it only accepts objects as key names (except null), and objects pointed to by keys are not counted in the garbage collection mechanism, nor can they be traversed or cleared

Seven. Cycle

Let’s start with a for-in pit:


var scores = [ 11.22.33.44.55.66.77 ];
var total = 0;
for (var score in scores) {
  total += score;
}

var mean = total / scores.length;

mean;Copy the code

The average person would see this problem and start doing it, summing it up and dividing by 7. If we make the elements of the array more complex:


var scores = [ 1242351.252352.32143.452354.51455.66125.74217 ];Copy the code

It doesn’t really matter how many elements are in the array. As long as the number of elements is 7, the final answer is 17636.571428571428.

The reason is that the for-in loop is array subscript, so total = ‘00123456’, and then the string is divided by 7.

A circular manner Traverse object Side effects
for It’s a little tricky to write
for-in Index value (key name), not array element Iterating over all (non-indexed) properties, as well as inherited properties (you can exclude inherited properties with the hasOwnProperty() method), is designed primarily for iterating over objects, not groups of numbers
forEach Inconvenient break, continue, return
for… of Iterator internally through the call Symbol. Iterator method to achieve traversal to obtain the key value Cannot iterate over ordinary objects because there is no Iterator interface

There are six methods for traversing an object’s properties in ES6:

A circular manner Traverse object
for… in Loop over the object’s own and inherited enumerable properties (excluding the Symbol property)
Object.key(obj) Returns an array containing all of the object’s own (not inherited) enumerable properties (not including the Symbol property)
Object.getOwnPropertyNames(obj) Returns an array containing all properties of the object itself (no Symbol property, but non-enumerable properties)
Object.getOwnPropertySymbols(obj) Returns an array containing all the Symbol properties of the object itself
Reflect.ownKeys(obj) Returns an array containing all properties of the object itself, whether the property name is Symbol or string or enumerable
Reflect.enumerate(obj) Returns an Iterator that iterates over all the enumerable attributes of the object itself and its inheritance (excluding the Symbol attribute), and for… Same in loop

Bugs associated with implicit conversions/casts


var formData = { width : '100'};

var w = formData.width;
var outer = w + 20;

console.log( outer === 120 ); // false;
console.log( outer === '10020'); // trueCopy the code

Operator overloading

You cannot overload or customize operators in JavaScript, including the equals sign.

10. Enhancements to function and variable declarations

Let’s take an example of function promotion.


function foo() {
  bar();
  function bar() {... }}Copy the code

The VAR variable also has the property of promotion. But when you assign a function to a variable, the boost disappears.


function foo() {
  bar(); / / the error!
  var bar = function () {... }}Copy the code

The above functions have no promotion effect.

Function declarations are completely improved, variable declarations are only partially improved. It is the declaration of a variable that elevates, not the assignment.

JavaScript supports lexical scoping, whereby, with rare exceptions, references to the variable foo are bound to the scope in which the variable foo was declared most recently. Block-level scope is not supported in ES5, where the scope of a variable definition is not the nearest enclosing statement or code block, but the functions that contain them. All variable declarations are promoted, the declaration is moved to the beginning of the function, and assignments remain in the same place.


function foo() {
  var x = - 10;
  if ( x < 0) {
    vartmp = -x; ... }console.log(tmp);  / / 10
}Copy the code

TMP has the effect of variable promotion.

Here’s another example:


foo = 2;
var foo; 
console.log( foo );Copy the code

The above example still prints 2, not undefined.

When this is compiled by the compiler, it actually looks like this:


var foo; 
foo = 2;
console.log( foo );Copy the code

Variable declarations are advanced and assignments remain in place. Here’s another example to illustrate the meaning of this sentence:


console.log( a ); 
var a = 2;Copy the code

The above code will compile to look like this:


var foo;
console.log( foo ); 
foo = 2;Copy the code

So the output is undefined.

If both the variable and the function are promoted, then the promotion of the function has a higher priority.


foo(); / / 1
var foo;
function foo() { 
    console.log( 1 );
}
foo = function() { 
    console.log( 2 );
};Copy the code

If the above is compiled, it will look like this:


function foo() { 
   console.log( 1 );
}
foo(); / / 1
foo = function() { 
   console.log( 2 );
};Copy the code

The final output is 1, not 2. This shows that function promotion takes precedence over variable promotion.

To avoid variable promotion, ES6 introduced the let and const keywords, which do not promote variables. The principle is that a variable is not available within a code block until it is declared using the let command, which is called a “temporal dead zone” (TDZ). TDZ’s approach is that as soon as you enter this area, the variable to be used already exists. The variable is still “promoted”, but cannot be retrieved, and can only be retrieved and used when the line where the variable is declared appears.

This approach to ES6 also brings block-level scope to JS (in ES5 there are only global scopes and function scopes), so it is not necessary to execute anonymous functions (IIFE) immediately.

Arguments are not arrays

Arguments is not an array, it just looks like an array. It has the length attribute, and its elements can be accessed through square brackets. You cannot remove its elements or call array methods on it.

Instead of using arguments variables inside functions, use rest operators (…) Instead. Because rest operators explicitly state the arguments you want to get, and arguments is just a similar array, rest operators provide a real array.

Here is an example of using arguments as an array:


function callMethod(obj,method) {
  var shift = [].shift;
  shift.call(arguments);
  shift.call(arguments);
  return obj[method].apply(obj,arguments);
}

var obj = {
  add:function(x,y) { return x + y ;}
};

callMethod(obj,"add".18.38);Copy the code

The above code directly returns an error:



Uncaught TypeError: Cannot read property 'apply' of undefined
    at callMethod (<anonymous>:5:21)
    at <anonymous>:12:1Copy the code

The reason for the error is that Arguments are not copies of function arguments; all named arguments are aliases of the corresponding indexes in the Arguments object. Thus, after removing arguments from the arguments object using the Shift method, obj is still an alias for arguments[0] and method is still an alias for arguments[1]. What appears to be a call to obj[add] is actually a call to 17[25].

There is another issue when using arguments references.


function values() {
  var i = 0 , n = arguments.length;
  return {
      hasNext: function() {
        return i < n;
      },
      next: function() {
        if (i >= n) {
            throw new Error("end of iteration");
        }
        return arguments[i++]; }}}var it = values(1.24.53.253.26.326,);
it.next();   // undefined
it.next();   // undefined
it.next();   // undefinedCopy the code

The above code wants to construct an iterator to iterate over the elements of the Arguments object. Arguments are implicitly bound to each function body. Each iterator next method has its own arguments, so when I call it. It’s not an argument in values anymore.

It’s also easy to change, as long as you declare a local variable that you can reference when next.


function values() {
  var i = 0 , n = arguments.length,a = arguments;
  return {
      hasNext: function() {
        return i < n;
      },
      next: function() {
        if (i >= n) {
            throw new Error("end of iteration");
        }
        returna[i++]; }}}var it = values(1.24.53.253.26.326,);
it.next();   / / 1
it.next();   / / 24
it.next();   / / 53Copy the code

IIFE introduces new scopes

In ES5 IIFE was designed to address JS’s lack of block-level scope, but in ES6 this is no longer needed.

13. The this problem in the function

The this variable in a method cannot be accessed in a nested function.


var halfrost = {
    name:'halfrost'.friends: [ 'haha' , 'hehe'].sayHiToFriends: function() {
      'use strict';
      this.friends.forEach(function (friend) {
          // 'this' is undefined here
          console.log(this.name + 'say hi to' + friend);
      });
    }
}

halfrost.sayHiToFriends()Copy the code

TypeError: Cannot read property ‘name’ of undefined is displayed.

There are two ways to solve this problem:

First: Store this in a variable.


sayHiToFriends: function() {
  'use strict';
  var that = this;
  this.friends.forEach(function (friend) {
      console.log(that.name + 'say hi to' + friend);
  });
}Copy the code

Second: use the bind() function

Use bind() to bind a fixed value to the callback’s this, the function’s this


sayHiToFriends: function() {
  'use strict';
  this.friends.forEach(function (friend) {
      console.log(this.name + 'say hi to' + friend);
  }.bind(this));
}Copy the code

The third way is to specify a value for this using the second argument to forEach.


sayHiToFriends: function() {
  'use strict';
  this.friends.forEach(function (friend) {
      console.log(this.name + 'say hi to' + friend);
  }, this);
}Copy the code

In ES6, it is recommended to use arrow functions where arrow functions are available.

Arrow functions are recommended for simple, single-line functions that will not be reused. If the function is complex and has many lines, it should be written in the traditional way.

The this object in the arrow function is the object when it is defined, not when it is used. There is a “binding relationship”.

The “binding” mechanism here is not caused by the arrow function, but by the fact that the arrow function does not have its own this, resulting in the inner this being the this of the outer code block. Because of this feature, the arrow function cannot be used in the following cases:

  1. Do not use it as a constructor. Do not use the new command because there is no this, otherwise an error will be thrown.
  2. You cannot use the argument object, which does not exist in the function body and can only be used with the REST argument. You can’t use super, new.target.
  3. You cannot use the yield command as a Generator function.
  4. You cannot use call(), apply(), or bind() to change the direction of this.

Xiv. Asynchrony

There are several types of asynchronous programming:

  1. The callback function callback
  2. Event listeners
  3. Publish/subscribe
  4. Promise object
  5. Async / Await

(This diary may never be finished……)