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 ,NaN forfalse Everything 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.
- All synchronization tasks are executed on the main thread, forming an “execution stack” (the stack shown below).
- All asynchronous tasks are suspended until the callback function is queued in a task queue.
- 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.
- 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).
- To create a variable object, the process will: create
arguments
Object to initialize function parameter variables –> check to create the current context infunction
Function declaration (known as function declaration promotion) –> checks the context in which it was createdvar
Variable declaration (known as variable promotion),let const
The statement;
- Establish scope chains to determine the rules for finding variables in the current context;
- determine
this
Object pointing;
- Code execution phase:
- 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
- Method 1 calls the function directly
fn()
, which looks like a light rod commander,this
Point to thewindow
(In strict mode, yesundefined
). - Method 2 is a dot call
obj.fn()
At this time,this
Point to theobj
Object. Points in the callthis
Refers to the object before the dot. - In method 3
call
functionfn
In thethis
It points to the first parameter, and here it isobj
. It is usingcall
,apply
,bind
The delta function can take the delta functionthis
The variable points to the first parameter. - Method 4 using
new
Instantiate an objectinstance
, at this momentfn
In thethis
I’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.