Engaged in the front-end industry so far, I feel the most progress is last year when I decided to change jobs and began to learn, especially when I read yCK’s nuggets booklet “The Way of front-end Interview”. It was during that period of study that I gradually had a vague outline of the front-end knowledge system and began to contact nuggets, an interesting technology platform. Now work dust settled, began to idle, commuting on the road and began to play games, home at night and began to play games do not read, leisure time began to micro letter group QQ group water injection. But distance 30 years old is getting closer and closer, in front of the road is more and more blurred. I know I don’t have much time to catch up. I’ve wasted my first three years on the job, and if I don’t make up for lost time in those two years, I’ll probably be stuck as a beginner/intermediate programmer forever. Keep in mind that I am still a half-way out of the primary front-end development engineer of non-technical classes, encourage yourself!

Xiao gang teacher

  • Base and reference types
  • Type judgment
  • Cast casting
  • scope
  • event loop
  • Execution context
  • Understand how functions are executed
  • This points to the
  • closure
  • Prototype and prototype chain
  • Js inheritance

Base and reference types

Js data types are divided into basic types and reference types. There are six basic types:

  • number
  • string
  • boolean
  • null
  • undefined
  • symbol(es6)

Reference types include object, array array, function, etc., collectively referred to as object types:

  • object

The string type is a string. In addition to single and double quotes, es6 introduced new backquotes to contain strings. The extension of backquotes is that you can use ${… } inserts variables and expressions into strings. Use as follows:

let n = 3
let m = (a)= > 4
let str = `m + n = ${m() + n}` // "m + n = 7"
Copy the code

Values of type number include integers, floating point numbers, NaN, Infinity, and so on. NaN is the only type in JS that is not equal to itself. NaN is returned when undefined mathematical operations occur, such as 1 * ‘asdf’, Number(‘asdf’). Floating-point operations may occur such as 0.1 + 0.2! == 0.3 problem, which is due to the accuracy of floating point operation, generally using toFixed(10) can solve this problem.

The Boolean, string, and number types, as primitive types, should not have functions to call, because primitive types have no stereotype chain to provide methods. However, these three types can call methods on object prototypes such as toString. Don’t believe it?

true.toString() // 'true'
`asdf`.toString() // 'asdf'
NaN.toString() // 'NaN'
Copy the code

So why can’t the number 1 call toString, you might say? In fact, it is not impossible to call:

1 .toString()
1..toString()
(1).toString()
Copy the code

All three of the above calls are fine, and the first dot after a number is interpreted as a decimal point, not a dot call. It’s just not recommended, and it doesn’t make sense.

Why can primitive types directly call methods that reference types? When parsing the above statement, the js engine will parse the three basic types into wrapper objects (new String() below), which refer to methods on Object.prototype. The general process is as follows:

'asdf'.toString()  ->  new String('asdf').toString()  -> 'asdf'
Copy the code

Null A special value whose meaning is “none”, “empty”, or “value unknown”.

Undefined means “not assigned”. Undefined unless the variable is declared and not assigned, or if the object’s attributes do not exist. Avoid using var a = undefined; Var a = {b: undefined} var a = null; Var o = {b: null}, to distinguish from “not assigned” default undefined.

The Symbol value represents a unique identifier. This can be created using the Symbol() function:

var a = Symbol('asdf')
var b = Symbol('asdf')
a === b // false
Copy the code

You can also create global identifiers so that you get the same identifier whenever you access the same name. As follows:

var a = Symbol.for('asdf')
var b = Symbol.for('asdf')
a === b // true
Copy the code

Can also be used as an object property, but cannot be used for… In ergodic:

let id = Symbol('id')
let obj = {
  [id]: 'ksadf2sdf3lsdflsdjf090sld'.a: 'a'.b: 'b'
}
for(let key in obj){ console.log(key) } // a b
obj[id] // "ksadf2sdf3lsdflsdjf090sld"
Copy the code

There are also many symbols built into the system, such as symbol.toprimitive Symbol.iterator. When a reference type is cast to a primitive type, the built-in symbol. toPrimitive function is triggered, or you can override the default cast behavior by manually adding the Symbol.

Object is a reference type. A reference type differs from a primitive type in that the primitive type stores a value. A reference type stores a pointer to the real memory address of an object. In JS, objects include Array Object Function RegExp Math, etc.

Js all function statements are executed in the execution stack, and all variables also hold values or references in the execution stack. The base type is stored in stack memory, holding the actual value; Reference types are stored in heap memory, and only Pointers to memory addresses are held on the stack.

var o = {
  a: 'a'.b: 'b'
}
var o2 = o // o2 copied the pointer to o, now they both point to the same memory address, now they are operating on the same memory address
o2.c = 'c' // now o.c is also 'c'
delete o2.b // Now o.B doesn't exist
o2.a = 'a2' // now o.a is also 'a2'
o2 = 'o2' // Now the variable o2 is assigned 'o2', which has no relation to the original memory address, but the variable o still refers to the old address
Copy the code

Type judgment

The typeof a reference type is different from that of a base type.

typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol(a)// 'symbol'
typeof null // 'object'
Copy the code

Typeof (NULL) === ‘object’ is a bug with a long history. In the first version of JS, null memory stores information starting with 000 and is judged as object. Although the internal type determination code has now been changed, the bug has to be retained with the release, because modifying this bug will cause bugs to appear on a large number of sites.

Typeof returns object for all reference types except function. However, we are definitely developing arrays that return array types, so Typeof is not very suitable for reference types. To determine the type of reference, use instanceof:

var obj = {}
var arr = []
var fun = (a)= > {}
typeof obj // 'object'
typeof arr // 'object'
typeof fun // 'function'
obj instanceof Object // true
arr instanceof Array // true
fun instanceof Function // true
Copy the code

You can see that the instanceof operator correctly determines the type of the reference type. Instanceof essentially checks whether the prototype object of the constructor on the right exists on the prototype chain on the left, and returns true if so. So whether arrays, objects, or functions… Instanceof Object all return true.

Finally to a type of universal judgment method: Object. The prototype. ToString. Call (…). , you can try it yourself.

Cast casting

JS is a weakly typed language, and casting between different types can occur under certain circumstances, such as when comparing equality.

Basic type equality compares whether the values are the same, object equality compares whether the memory address is the same. Here’s an interesting comparison:

[] [] = =// ?[] = =! []// ?
Copy the code

For reference types such as [] {} function (){} that are not assigned to variables, they are valid only in the current statement and are not equivalent to any other object. There’s no way to find a pointer to their memory address. So [] == [] is false.

For [] ==! [], which is much more complicated because it involves casting. To learn more about casts, see this article.

There are only three types of conversion in JS: toNumber, toString, and toBoolean. The normal conversion rules are as follows:

Raw value/type Destination type: number The results of
null number 0
symbol number Throw the wrong
string number '1' = > 1 '1a'=>NaN, if non-numbers are included, isNaN
An array of number [] = > 0 '1' = > 1 ['1', '2']=>NaN
object/function/undefined number NaN
Raw value/type Target type: string The results of
number string 1 = > '1'
array string [1, 2] = > '1, 2,'
Boolean values/functions /symbol string The original value is quoted, as in:'true' 'Sumbol()'
object string {}=>'[object Object]'
Raw value/type Target type: Boolean The results of
number boolean In addition to0,NaNforfalseEverything elsetrue
string boolean Except for the empty stringfalse, all the others aretrue
null/undefined boolean false
Reference types boolean true

Now uncover [] ==! [] Returns the truth of true:

[] = =! []// true
/* * First, the Boolean operator! Second, the operand has a Boolean value false, which is converted to a number: [] == false * [] == 0 * again, the operand [] is an object and is converted to the primitive type (valueOf() is the same as [] and toString() is the empty string) : "== 0 * Finally, the string is compared to a number and converted to a number: 0 == 0 */
NaN= =NaN // false NaN does not equal any value
null= =undefined // true
null= =0 // false
undefined= =0 // false
Copy the code

scope

The scope in JS is the lexical scope, which is determined by the position at which the function is declared. Lexical scope is a set of access rules for function identifiers that are generated at compile time. At the end of the day, the js scope is just an “empty disk”, where there are no real variables, but rules that define how variables are accessed. Unlike lexical scope, which is checked at compile time, dynamic scope is checked at function execution time. Js does not have dynamic scope, but JS’s this looks like dynamic scope, which will be discussed later. Languages are also divided into static languages, where data types are determined at compile time, such as Java, and dynamic languages, where data types are determined at run time, such as javascript.

A scope chain is essentially a list of Pointers to variable objects. It refers only to objects that do not contain actual variables, and is an extension of the concept of scope. A scope chain defines a set of rules for querying variables along the scope chain if they are not accessible by the current context.

event loop

Js is single threaded, all tasks need to queue, the previous task is finished, the next task will be executed. If the first task takes a long time, the second task has to wait forever. But IO devices (input and output devices) are slow (such as Ajax operations reading data from the network), and JS cannot wait for the IO device to complete before moving on to the next task, thus losing the meaning of the language. So JS tasks are divided into synchronous tasks and asynchronous tasks.

  1. All synchronization tasks are executed on the main thread, forming an “execution stack” (the stack shown below).
  2. All asynchronous tasks are suspended until the callback function is queued in a task queue.
  3. When all synchronization tasks in the execution stack are completed, the callback function of the first task queue is read and pushed to the execution stack to start execution.
  4. The main thread repeats the third step, which is how the event loop works.

In the figure above, when the main thread is running, the heap and stack are created. The heap is used to hold reference types such as array objects. The code in the stack calls various external apis, which add various events (click, load, done) to the “task queue”. As soon as the stack completes, the main thread reads the “task queue” and executes the corresponding callback function for those events.

There are two kinds of asynchronous tasks in the task queue, one is macro task, including Script setTimeout setInterval, and the other is micro task, including Promise Process. nextTick MutationObserver. Whenever a JS script is executed, the entire code in the script is executed first. When the synchronization task in the execution stack is completed, the first task in the microtask will be executed and pushed to the execution stack. When the execution stack is empty, the microtask will be read again and the cycle repeats until the list of microtasks is empty. When the list of microtasks is empty, the first task in the macro task will be read and pushed to the execution stack. When the execution stack is empty, the micro task will be read and executed again. The macro task will be read and executed again when the micro task is empty.

Execution context:

The execution context refers to the execution environment of the current function (or the global object Window) generated in the execution stack when a function is called. This environment is like a container boundary isolated from the outside world, containing accessible variables, this object, and so on. Such as:

letfn, bar; // enter the global context bar =function(x) {
  letb = 5; fn(x + b); }}}; fn =function(y) {
  letc = 5; console.log(y + c); //4, fn out of stack, bar out of stack}; bar(10); // enter the bar function contextCopy the code

Each time a function is called, a new execution context is created at the top of the execution stack. The bottom of the stack is always the global context, and the top is the context of the currently active executing code.

Understand how functions are executed

The focus of this article, so that you understand the implementation of the function to a higher level!

The execution of a function is divided into two stages, the first stage is the creation of the execution context stage, the second stage is the code execution stage:

  • Create the execution context phase (which occurs before the code inside the function is executed when the function is called).

  1. To create a variable object, the process will: createargumentsObject to initialize function parameter variables –> check to create the current context infunctionFunction declaration (known as function declaration promotion) –> checks the context in which it was createdvarVariable declaration (known as variable promotion),let constThe statement;

  1. Establish scope chains to determine the rules for finding variables in the current context;
  2. determinethisObject pointing;
  • Code execution phase:
  1. Execute the code inside the function. This stage completes variable assignments, function references, and other code execution.

Properties in variable objects cannot be accessed while they are being created until the execution stage. However, after the execution phase, the variable object is created and transformed into an active object, and its properties can be accessed, then the execution phase operation begins. That is, the only difference between a variable object and an active object is the different life cycle in the execution context.

See this article for more details on variable objects

This points to the

let fn = function(){
  alert(this.name)
}
let obj = {
  name: ' ',
  fn
}
fn() 1 / / method
obj.fn() 2 / / method
fn.call(obj) 3 / / method
let instance = new fn() 4 / / method
Copy the code
  1. Method 1 calls the function directlyfn(), which looks like a light rod commander,thisPoint to thewindow(In strict mode, yesundefined).
  2. Method 2 is a dot callobj.fn()At this time,thisPoint to theobjObject. Points in the callthisRefers to the object before the dot.
  3. In method 3callfunctionfnIn thethisIt points to the first parameter, and here it isobj. It is usingcall,apply,bindThe delta function can take the delta functionthisThe variable points to the first parameter.
  4. Method 4 usingnewInstantiate an objectinstance, at this momentfnIn thethisI’m pointing to the instanceinstance.

What if more than one rule happens at the same time? In fact, the above four rules have increasing precedence:

fn() < obj.fn() < fn.call(obj) < new fn()

First, the new call has the highest priority, as long as the new keyword is present, this refers to the instance itself; Then, if there is no new keyword and you have call, apply, or bind, this points to the first parameter; Then if there is no new, call, apply, bind, and only obj.foo(), this points to the object in front of the point; Finally, there is the light-rod commander foo(), where this points to window (strictly undefined).

Arrow functions have been added to es6. Arrow functions don’t have their own this, arguments, super, new.target. Arrow functions don’t have a prototype object that can’t be used as a constructor. Since there is no this of its own, this in the arrow function actually refers to this in the containing function. Neither a point call nor a call can change the this in the arrow function.

closure

For a long time I had a superficial understanding of closures as “functions defined inside a function”. In fact, this is just one of the necessary conditions for closure formation. It wasn’t until I read Kyle’s javascript you Don’t Know the definition of closures that I realized:

Closures occur when a function can remember and access its lexical scope.

let single = (function(){
  let count = 0
  return {
    plus(){
      count++
      return count
    },
    minus(){
      count--
      return count
    }
  }
})()
single.plus() / / 1
single.minus() / / 0
Copy the code

This is a singleton pattern that returns an object and assigns the value to the variable single, which contains two functions plus and minus, both of which use the variable count in their lexical scope. Normally count and its execution context are destroyed at the end of the function execution, but because count is still in use by the external environment, it is not destroyed at the end of the function execution, resulting in closures. Each time single.plus() or single.minus() is called, the count variable in the closure is modified, and both functions retain references to their lexical scope.

A closure is a special function that accesses variables inside a function and keeps the values of those variables in memory and not cleared by garbage collection after a function is called.

Watch a classic Amway:

1 / / method
for (var i = 1; i <= 5; i++) {
  setTimeout(function() {
    console.log(i)
  }, 1000)}2 / / method
for (let i = 1; i <= 5; i++) {
  setTimeout(function() {
    console.log(i)
  }, 1000)}Copy the code

In method 1, five timers are set in a loop. After one second, the timer callback function is executed to print the value of variable I. Of course, after one second I had increased to six, so the timer printed six five times. (The timer did not find variable I in the current scope, so I was found in the global scope along the scope chain)

In method 2, as the LET of ES6 will create local scopes, five scopes are set in a cycle, and the distribution of variable I in the five scopes is 1-5. A timer is set in each scope to print the value of variable I after one second. One second later, the timers find variables I 1-5 from their respective parent scopes. This is a new way of using closures to solve the problem of variable exceptions in loops.

Prototype and prototype chain

Almost all objects in JS have a special [[Prototype]] built-in property that specifies the object’s Prototype object. This property is essentially a reference to other objects. A private __proto__ attribute is exposed in the browser, which is the browser implementation of [[Prototype]]. Prototype: Object. Prototype: Object. __proto__ === Object. Object [[Prototype]] refers to a Prototype object, and the Prototype object itself is an object and has its own [[Prototype]] reference to other Prototype objects.

var obj = [1.2.3]
obj.__proto__ === Array.prototype // true
Number.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // true
obj.toString()
Copy the code

As can be seen from the above example, there is a prototype chain from OBj to NULL, as follows:

obj----__proto__---->Array.prototype----__proto__---->Object.prototype----__proto__---->null
Copy the code

When the last line of the above example calls the obj.toString() method, the JS engine looks for the toString method along this prototype chain. Js first looks for the toString method on the obj object itself; Prototype = Array. Prototype = Array. Prototype = Array. Not found, continue along the prototype chain on Object.prototype. I finally found the toString method on Object.prototype and called it in tears. That’s the basic function of the prototype chain. Prototype chains are also the essence of JS implementation inheritance, which will be covered in the next section.

Almost all objects in JS have a special [[Prototype]] built-in property. Because js can create objects with no built-in attribute [[Prototype]] :

var o = Object.create(null)
o.__proto__ // undefined
Copy the code

Object. Create is an ES5 method that is supported by all browsers. This method creates and returns a new object and assigns the new object’s prototype object as the first argument. In the above example, object.create (null) creates a new Object and assigns the Object’s prototype to NULL. Object o has no [[Prototype]] built-in attribute.__proto__ is null.

Js inheritance

Js inheritance is achieved through the prototype chain, specific can refer to my article, here I only talk about we may be relatively strange “behavior delegate”. Behavior delegation is a way to replace inheritance recommended by Kyle, the author of JavaScript you Don’t Know series. This mode mainly uses setPrototypeOf method to associate the built-in prototype of one object [[Protytype]] with another object, so as to achieve the purpose of inheritance.

let SuperType = {
  initSuper(name) {
    this.name = name
    this.color = [1.2.3]
  },
  sayName() {
    alert(this.name)
  }
}
let SubType = {
  initSub(age) {
    this.age = age
  },
  sayAge() {
    alert(this.age)
  }
}
Object.setPrototypeOf(SubType,SuperType)
SubType.initSub('17')
SubType.initSuper('gim')
SubType.sayAge() / / '17'
SubType.sayName() // 'gim'
Copy the code

The above example associates the parent object SuperType with the built-in stereotype of the child object SubType so that methods on the child object can be called directly on the parent object. The prototype chain generated by behavior delegation is simpler and clearer than the relationship generated by class inheritance.