The reason for organizing this JS system of knowledge is inspired by the series of questions of god’s triple soul

In the face of so many new technologies that are constantly iterating, I always feel that I don’t have enough time to learn and the effect is not good, and I am worried about whether MY focus was wrong at the beginning. My focus should not be on dazzling technologies, but on the construction of my knowledge system.

Although I was writing code every day, many concepts of what I was writing sounded familiar, but I couldn’t explain why. In order to clear up these problems in my mind, I started the construction of this knowledge system.

This is the first in a two-part series that covers data types, variables, scopes, this, and closures based on the execution context. Then we talk about object – oriented ideas and modular specifications.

As the Soul Quest has inspired me, I hope the knowing what series has inspired you. In addition, due to the limited level of personal knowledge, if there is any wrong understanding, please criticize and correct.

The mind map of js knowledge system is shown below

1. Js data type

1. What type of language is JS

JavaScript is a weakly typed, dynamic language.

  • Dynamic, which means you can use the same variable to hold different types of data.
  • Weakly typed, which means you don’t have to tell the JavaScript engine what the value of this or that variable is; the JavaScript engine will figure it out as it runs the code.
We call static languages that require validation of variable data types before they can be used. Conversely, we refer to a language that needs to check data types during runtime as a dynamic language. Languages that support implicit typing are called weakly typed, and languages that do not are called strongly typed.Copy the code

2. What are the data types of JS

There are 8 types of JS data, 7 basic types and 1 reference type.

However, we should be clear that js variables do not have data types, variables are just a placeholder to save, can hold any type of data at any time, the value has data types.

Primitive type values refer to simple data segments, while reference type values refer to objects that may be composed of multiple values.

  • Value access difference

Primitive type values are accessed by value because you can manipulate the actual value stored in the variable.

The value of a reference type is accessed by reference, which is an object stored in memory. Js cannot directly manipulate the memory space of an object. We actually manipulate the reference of an object rather than the actual object.

  • Value copy difference

Primitive types copy copies of values

A reference type copies a reference to a value

  • Basic types of

    Null – There is only one value Null, Boolean – only true and false values Number – Represents integer and floating point 64 bit binary values BigInt – Large integer that can represent the integer with arbitrary precision String – Represents a character sequence consisting of zero or more 16-bit Unicode characters Symbol – Unique and unmodifiable Symbol type, usually used as the key of an Object

  • Reference types

    Object represents a collection of attributes

    An object in JS is actually a collection of data and functions. Js has built-in objects that provide properties and methods unique to each subtype. The built-in objects in js include: Object, Array, Date, RegExp, Function, Error, Boolean, Number, String, and Global, Math.

3. Basic packaging types

To facilitate manipulation of primitive types, JS provides three special reference types: Boolean, Number, and String. They have special behaviors corresponding to their respective base types.

Look at the following example

'javascript'.substring(4)//"script"
Copy the code

Why can a string just call the subString method? In fact, every time a primitive type is read, an object of the corresponding primitive wrapper type is created behind the scenes, allowing us to call methods to manipulate the data. How do you do that?

Create an instance of String; 2. Call the specified method on the instance. 3. Destroy the instance

var s = new String( 'javascript') s.. substring(4)
s=null
Copy the code

4. The floating point number

A floating point value must contain a decimal point and must be followed by at least one digit.

The range of floating point numbers is about 5e-324(Number.MIN_VALUE) and 1.798e+308(Number.MAX_VALUE). The maximum precision of floating point numbers is 17 decimal digits, but they are far less precise than integers when performing arithmetic calculations. Why is the result false

0.1 + 0.2= = =0.3 //false
Copy the code

Mainly is the digital storage calculation is used in binary, after the calculation is completed and then changed to decimal, so resulting in floating point error. In particular, 0.1 and 0.2 will loop indefinitely after being converted to binary, and the extra bits will be truncated due to the standard bit limit. At this point, there has been a loss of precision. After being added, the binary digits truncated due to the floating point decimal limit will become 0.30000000000000004 when converted to decimal.

How can we determine that 0.1+0.2 is equal to 0.3

The most common way to do this is to set an error range, often called “machine precision”, which is 2^-52 for a Number in JS, ES6 which is defined in number.epsilon. You can use this value to determine that two values are equal.

function closeEqual(n1,n2){
    return Math.abs(n1-n2)<Number.EPSILON
}
var a= 0.1+0.2
var b=0.3
closeEqual(a,b)//true
Copy the code

2. Detection of JS data types

1. typeof

 // Determine the base type
 console.log(typeof null)//object
 console.log(typeof undefined)// undefined
 console.log(typeof 1)// number
 console.log(typeof '1')// string
 console.log(typeof true)// boolean
 console.log(typeof 1n)// bigint
 console.log(typeof Symbol())// symbol
 // Determine the reference type
 console.log(typeof {})// object
 console.log(typeof [])// object
 console.log(typeof function(){})//function
Copy the code

For primitive types, everything except null is detected as object, and for reference types, everything except function is detected as function.

2. instanceof

Instanceof is used to check whether the constructor’s prototype property is present in the prototype chain of an instance object. The main purpose of instanceof is to determine whether an instance is of a type.

console.log("1" instanceof String)//false
console.log(1 instanceof Number)//false
console.log(true instanceof Boolean)//false
console.log([] instanceof Array)//true
console.log(function () {} instanceof Function)//true
console.log({} instanceof Object)//true
Copy the code

Instanceof can be used for reference type detection as long as the constructor’s prototype is true on the instance’s prototype chain, but not for primitive types, and not for null and undefined.

  • Instanceof can determine the base type
class MyNumber{
    static [Symbol.hasInstance](instance){
        return typeof instance === 'number'}}console.log(1 instanceof MyNumber)
Copy the code

Symbol. HasInstance is used to determine whether an object is an instance of a constructor. You can customize instanceof behavior.

  • Implement instanceof functionality manually

In fact, the main implementation principle of Instanceof is as long as the right variable’s prototype is on the left variable’s prototype chain. Thus, instanceof iterates through the prototype chain of the left variable until it finds the prototype of the right variable. If the lookup fails, it returns false, telling us that the left variable is not an instanceof the right variable.

//left: current instance object, right: current constructor.
function myInstanceof(left, right) {
    // The basic data type returns false
    if(typeofleft ! = ='object' || left === null) return false;
    //getProtypeOf is a method that comes with Object objects and gets the prototype Object for the parameter (specified Object)
    let proto = Object.getPrototypeOf(left);
    while(true) {
        // Not yet found
        if(proto == null) return false;
        // Find the same prototype object
        if(proto == right.prototype) return true;
        proto = Object.getPrototypeOf(proto); }}console.log(myInstanceof("111".String)); //false
console.log(myInstanceof(new String("111"), String));//true
Copy the code

3. constructor

Constructor is used to identify object types.

console.log(("1").constructor === String);//true
console.log((1).constructor === Number);//true
console.log((true).constructor === Boolean);//true
console.log((Symbol()).constructor === Symbol);//true
console.log((1n).constructor === BigInt);//true
console.log(([]).constructor === Array);//true
console.log((function() {}).constructor === Function);//true
console.log(({}).constructor === Object);//true
Copy the code

Without null and undefined, constructor seems to be used to detect js primitives and reference types. But when it comes to archetypes and inheritance, there are problems, as follows:

function fun(){}
fun.prototype = new Array(a);let f = new fun();
console.log(f.constructor===fun);//false
console.log(f.constructor===Array);//true
Copy the code

When the object’s prototype changes, the constructor becomes invalid.

4. Object.prototype.toString.call()

var test = Object.prototype.toString;

console.log(test.call("str"));//[object String]
console.log(test.call(1));//[object Number]
console.log(test.call(true));//[object Boolean]
console.log(test.call(null));//[object Null]
console.log(test.call(1n));//[object BigInt]
console.log(test.call(Symbol()));//[object Symbol]
console.log(test.call(undefined));//[object Undefined]
console.log(test.call([]));//[object Array]
console.log(test.call(function() {}));//[object Function]
console.log(test.call({}));//[object Object]

console.log(test.call([]).slice(8, -1).toLowerCase())//array
Copy the code

It can be seen that the Object. The prototype. ToString. Call () all of the data types can be used to detect js, said the toString returns a string representation of the Object. Specific can use Object. The prototype. ToString. Call () slice (8, 1). ToLowerCase ()

Js data type conversion

1. Convert to a string

The ES specification defines abstract operations (that is, operations for internal use only) and conversion rules for casting, and the ToString abstraction handles non-string-to-string casts.

Conversion rules:

  • nullconvert'null'
  • undefinedconvertundefined
  • trueconvert'true'.falseconvert'false'
  • Numeric conversion follows a general rule, using exponential form for extremely small numbers
  • Normal objects unless customtoString()Method, otherwise returns an internal property[[Class]]As mentioned above[object Object]
  • Object subtypetoString()The redefined call returns the result
console.log(String(null))//'null'
console.log(String(undefined)) //'undefined'
console.log(String(true)) //'true'
console.log(String(-0)) //'0' is not itself
console.log(String(0)) / / '0'
console.log(String(+0)) //'0' is not itself
console.log(String(-Infinity)) //'-Infinity'
console.log(String(Symbol())) //'Symbol()'
console.log(String(1n)) / / '1'
console.log(String({})) //'[object Object]'
console.log(String([1[2.3]])) //'1,2,3' is not itself
console.log(String(function () {})) //'function(){}'
Copy the code

2. Convert to numbers

The ToNumber abstraction operation handles the conversion of non-numeric types to numeric types.

Conversion rules:

  • nullconvert0
  • undefinedconvertNaN
  • trueconvert1.falseconvert0
  • String conversion follows the numeric constant rule, return on conversion failureNaN
  • The object type is cast to the corresponding primitive type value, or if the resulting value type is not a number, the cast follows the above rules

Converting an object to a primitive type calls the built-in [ToPrimitive] function, for which the logic is as follows:

  1. If the symbol.toprimitive () method is called first and then returned
  2. Calls valueOf(), which returns if cast to the original type
  3. Call toString(), which returns if cast to primitive type
  4. If none of them return the original type, an error is reported
var obj = {
  value: 3.valueOf() {
    return 4;
  },
  toString() {
    return '5'},Symbol.toPrimitive]() {
    return 6}}console.log(obj + 1); / / output 7

Copy the code

3. Convert to a Boolean value

The ToBoolean abstraction operation handles the conversion of non-boolean types ToBoolean types.

Conversion rules:

  • Values that can be cast to false:null,undefined,false,+ 0,0,NaN 和 ' '
  • All values outside the false list are true values

Boolean implicit casts occur in the following cases.

  • if (..) A conditional expression in a statement.
  • for ( .. ; . ; ..) The conditional expression in the statement (second).
  • while (..) And do.. while(..) A conditional judgment expression in a loop.
  • ? The conditional judgment expression in:.
  • Logical operators | | (logical or) and && (and logic) than the left operand () as a conditional expression.

4. = = and = = =

For example, ‘1’===1 is false because one side is a string and the other is a number.

== is not as strict as ===. For the general case, it returns true as long as the values are equal, but == also involves some type conversions, which are as follows:

  • If the types on both sides are the same, the value is compared, for example, 1==2, return false
  • Checks if null and undefined(other values do not compare to them), and returns true if so
  • Check whether the type is String and Number. If yes, convert String to Number and compare
  • If so, convert the Boolean to Number and compare
  • If one is Object and the other is String, Number, or Symbol, the Object is converted to a String and then compared

console.log({a: 1} = =true);//false
console.log({a: 1} = ="[object Object]");//true
/ / note
NaN= = =NaN//false
+0= = = -0//true
Copy the code

5. | | and &&

&& and | | called logical operators, &&, | | operators, the return value of the Boolean type is not necessarily, but the value of two operands one.

var a = 42;
var b = "abc";
var c = null;
a || b; / / 42
a && b; // "abc"
c || b; // "abc"
c && b; // null
Copy the code

| | and && operation process about as follows

&& and | | first to the first operand (a and c) implement the condition judgment, if it is not a Boolean value (as above) to ToBoolean casts, and then execute condition judgment.

For | |, if the condition judgment result to true will return to the first operand (a and c) values, if false is returned (b) the value of the second operand.

&&, on the other hand, returns the value of the second operand (b) if true or the value of the first operand (a and c) if false.

4 Execution Context

1. The execution context contains content

Execution content is the context in which JavaScript executes a piece of code, such as calling a function. The execution context of the function is entered, and the execution context of the function, such as this, variables, objects, and functions, is determined. The execution context consists mainly of variable objects (VO), scope chains, and this

2. Type of execution context

Which code is compiled and creates the execution context before execution? There are generally the following three situations

  1. Global execution context. When JavaScript executes global code, the global code is compiled and the global execution context is created, and there is only one global execution context for the lifetime of the page.
  2. Function execution context. When a function is called, the code inside the function is compiled and the function execution context is created. Generally, the function execution context is destroyed after the function is executed.
  3. Eval Execution context. When eval is used, the eval code is compiled and the execution context is created.

3. Execute the context stack

The stack the JavaScript engine uses to manage the execution context is called the execution context stack or call stack. Each function has its own execution context. When the execution stream enters a function, the execution context of the function is pushed into a call stack. After the function is executed, the call stack pops up its execution context, returning control to the previous execution context. The call stack is a mechanism by which the JavaScript engine keeps track of function execution

5 Variable Object

A variable object is a part of the execution context. It stores all the variables and functions defined in the execution context. Active Object When the context of the variable object is Active EC (the context of the function being executed), it is called active object

6 scope

1. Scope

Scope is the area in the program source code where variables are defined, the valid range of variables.

2. Type of scope

  1. Global scopes are variables and functions defined globally (variable objects in the global execution context). Objects in a global scope can be accessed anywhere in the code, and their life cycle follows the life of the page.
  2. A function scope is a variable or function defined inside a function (the variable object in the context of the function’s execution) that can only be accessed inside the function. After the function is executed, the variables defined inside the function are destroyed.
  3. A block-level scope is a piece of code wrapped in braces, such as a function, a judgment statement, a loop statement, or even a single {} can be considered a block-level scope.

3. Scope rules

Scope rules dictate where variables are stored, their lifetime, and how to access them when needed

Lexical scope

Lexical scoping means that the scope is determined by the position of the function declaration in the code, so lexical scoping is static. Javascript uses lexical scoping rules to predict how code will look for identifiers during execution. When code is executed in an environment, a scope chain of variable objects is created.

5. Scope chain nature

A scope chain is essentially a list of Pointers to variable objects. It contains its own variable object and its parent’s scope chain [[scope]], but it only references but does not actually contain the object

6. The purpose of scope chains

The purpose of scope chains is to ensure orderly access to all variables and functions that the execution context has access to.

7. Identifier resolution

Identifier resolution is the process of searching identifiers (variables and functions) level by level along the scope chain. The search process always starts at the front of the scope chain, and then steps back until an identifier is found (failure to find an identifier usually results in an error). Search for variables and functions in its own variable object (scope) first, and then search for variable objects in the upper level of the scope chain (scope), until the search along the scope chain to the global variable object (scope), until the search, if not, usually an error is reported,

7 this

This binding rule: This object is bound at run time based on the execution context of the function, and refers to the environment object in which the function is executed. That is, this is the binding that happens when the function is called, and what it points to depends entirely on where the function is called. This has the following binding rules.

Code examples:

var a = 'global'
function foo() {
    console.log(this.a);
}
function bar(b){
    console.log(b)
}
var obj = {
    a: 'local'.foo: foo
}
function doFoo(fn) {
    fn()
}
var barz = obj.foo;
Copy the code

1. Global execution context

In non-strict mode, the global execution context this points to window by default, and in strict mode to undefined

2. Call the function directly (default binding)

foo()//global
// The following is an implicit loss type which actually applies the default binding
barz(); //global
// The callback loses the this binding
doFoo(obj.foo) //global
setTimeout(obj.foo,1000)//global
setTimeout(function foo(){
    console.log(this.a,'set')},1000)//global
Copy the code

A direct call to this corresponds to the global execution context, pointing to the window. One of the most common problems with this binding is that implicitly bound functions lose the binding object, that is, they apply the default binding, binding this to a global object or undefined, depending on whether it is in strict mode.

3. Objects. Formal calls to methods (implicit binding)

`obj.foo() //local`
Copy the code

The this of the method refers to this object

4. Bind this using call, apply, bind

foo.call(obj)//local
foo.apply(obj)//local
foo.bind(obj)()//local
Copy the code

This refers to the bound object

5. DOM event binding

This in onclick and addEventerListener refers by default to the element of the binding event. IE8 and below do not support addEventerListener, use attachEvent, where this points to window([object window]) by default

6. New + constructor (new binding)

`new bar('new')`//new
Copy the code

This refers to the instance object

7. Arrow function

The arrow function does not create its own this. Instead, it determines the current this based on the current lexical scope. It inherits this from the outer non-arrow function.

setTimeout(() = >{
    console.log(this.a,'arrow')},1000)//global
Copy the code

8. Priority

Priorities: New > Call, apply, bind> objects. Method > Direct call

8 closure

1. What are closures

  • The Little Red Book: Closures are functions that have access to variables in the scope of another function.

  • From a theoretical and practical point of view to discuss closures:

    • Theory: Closures are functions that have access to free variables.

    Free variables are variables that are used in a function but are neither function arguments nor local variables of the function. Global variables are also free variables, because access to global variables in functions is also access to free variables, which means that all functions can be understood as closures.

    • Practical point of view: The following functions count as closures.

    1. It exists even if the context in which it was created has been destroyed (for example, the inner function returns from the parent function) 2. Free variables are referenced in the code

  • In my personal understanding, a closure is generated when a function can remember and access its lexical scope. A closure is a special kind of scope. The combination of a function’s scope chain’s reference to a variable in the upper scope and this function is called a closure.

2. Why do JS have closures

The most fundamental reason is that, in JS, functions are first-class citizens, functions can be returned as a function value, can be passed in as a function parameter, can also be assigned to a variable as a value. The funarg problem occurs when a function is called, breaking the stack-based memory allocation pattern. And the mainstream JS engine is lazy parsing, in order to solve this problem, the introduction of closure mechanism to solve the funarg problem.

  • Inert parsing

If the parser encounters a function declaration during parsing, it skips the code inside the function and does not compile for it, but only the top-level code.

  • Funarg problem
A functional argument (" Funarg ") - is an argument which value is A function. Functional arguments (" Funarg ") -- are arguments whose values are functions.Copy the code

Each function has its own execution context. When the execution stream enters a function, the execution context of the function is pushed into a call stack. After the function is executed, the call stack pops up its execution context and returns control to the previous execution context. The execution context of the function will be destroyed, and the variables and functions in the corresponding function will also be destroyed. However, if there are free variables in a function, an error will occur when the function that calls these free variables is executed again, because the free variables are missing.

  • How was the closure mechanism introduced through the following example
function foo() {
    var d = 20
    return function inner(a, b) {
        var c = a + b + d
        return c
    }
}
var f = foo()
Copy the code

We can analyze the above code execution process:

  1. When called, foo returns its inner function to the global variable f;
  2. Foo then completes execution, and the execution context is destroyed by V8(the JS engine);
  3. Although the execution context of function foo is destroyed, the surviving inner function refers to variable D in function foo’s scope.

As is common practice, d has been destroyed by V8, but since the surviving function inner still refers to the variable d in foo, there are two problems:

  1. Should the variable d be destroyed when foo completes execution? If it should not be destroyed, what strategy should be used?
  2. If lazy parsing is used, V8 will only parse foo when it is executed, but not the inner function, and will not know if the inner function refers to the variable D of foo.

So the normal way to handle this is that the execution context of foo is destroyed, but the variables in foo referenced by inner cannot be destroyed. V8 needs to make special treatment for this case, and it needs to ensure that even if foo is finished executing, However, the d variable in foo remains in memory and cannot be destroyed with the execution context of foo. This introduces closures, which are designed to solve these problems.

In general, there are three reasons why JS has closures:

2. The JS engine manages execution context based on the call stack. 3. The JS engine is compiled lazily.

3. How closures are implemented (formation mechanism)

  • Closures are actually implemented through the JS engine’s pre-parser

At the execution stage of foo, although the inner function in foo is not parsed and executed, V8 still needs to determine whether the inner function references variables in foo. The module responsible for this task is called the preparser.

  • How does a preparser implement closures

V8 introduced the pre-parser, for example, when parsing top-level code and encountering a function, the pre-parser does a quick pre-parsing of the function instead of just skipping it, with two main purposes.

  1. Is to determine whether the current function has some syntax errors.
  2. Besides check syntax errors, pre parser is another important function is to check inside the function refers to the external variables, if the reference external variables, advance the parser will stack variables in duplicate to the heap, in the next to the function, directly use the references in the heap, it solves the problems brought by the closure.

Examine the following code example

The first line in outer interrupts debugging, and Scope clearly shows the situation after outer passes pre-parsing. The Closure(outer) object created by the pre-parser will determine if all internal functions refer to its variables, and then place a1 and B1 in the Closure(outer) object created by the pre-parser. A1 and b1 will be undefined until outer is called.

The following is the closure generation process.

  • conclusion

Since JavaScript is a language that naturally supports closures, and closures refer to variables outside the scope of the current function, when V8 parses a function, it also needs to determine whether the function's internal function refers to a variable declared inside the current function. If it does, it needs to put the variable in the heap. This variable is not released even after the current function has finished executing.

In summary, there are two core steps to generating closures:

  1. The first step is to pre-scan the internal function;
  2. The second step is to save the external variables referenced by the inner function to the heap.

Closures and memory management

There is a scare story about closures causing memory leaks, so try to minimize their use. Shall we analyze the following code to see if this is the case?

function foo(){
    var data = 1
    function bar(){
        console.log(data)
    }
    return bar
}
var doFoo=foo()
doFoo()
Copy the code

The above code forms a closure that overwrites foo’s internal scope, and since the doFoo that references the closure is a global variable, the closure will always exist. If doFoo is used more often in the future, data in a closure will have the same effect on memory as data in a global scope. This is not a memory leak. If you need to reclaim these variables in the future, just set doFoo to NULL.

    function foo() {
        var data = 1
        function bar() {
            console.log(data)
        }
        return bar
    }
    function baz() {
        var getBar = foo();
        getBar()
    }
    baz();
Copy the code

The above code also creates a closure that overwrites foo’s internal scope. Since the reference to the closure’s getBar is a local variable, after baz is executed, getBar is destroyed, there are no references to the closure, the closure will be garbage collected later, and there are no memory leaks caused by the closure.

A closure is essentially a function's scope chain's reference to a variable in its parent scope, the function is in, the function's scope chain's reference to the closure is in, so the closure is in, the function is no longer referenced, the closure is destroyed.

If the function referencing the closure is a global variable, the closure will remain in place until the page closes; However, memory leaks can occur if the closure is no longer used and is not dereferenced. If the data is not used, the reference is finally released by setting its value to NULL. However, dereferencing a worthy reference does not automatically reclaim the memory occupied by the value. The real purpose of dereferencing is to take the value out of the execution environment so that the garbage collector can reclaim it the next time it runs.

If the function that references the closure is a local variable, then the JavaScript engine’s garbage collector will reclaim the memory if the closure is no longer in use the next time the JavaScript engine performs garbage collection.

5. Features of closures

  1. New functions can be defined inside functions
  2. You can access variables defined in the parent function in the inner function
  3. The function can be output as a return value

6. Representation and application of closures

Function output as return value, see the example in closures and memory management. Examples include jQuery in application module mode, LoDash. 2. Functions are passed as arguments. See the example of creating a closure in a loop. In practice, we use timers, event listeners, Ajax requests, or other asynchronous tasks that actually use closures (if the callback function references variables from an external function).

7. Pros and cons of closures

Advantages of closures

  • Want a variable to be stored in memory for a long time;
  • The existence of private members to avoid global variable pollution;

Disadvantages of closures

  • Resident memory, increase memory usage;
  • Improper use causes memory leakage.

8. Create closures in the loop

The [[scope]] of multiple subfunctions all point to the parent at the same time and are completely shared. They access the same variable object. So when the parent variable object is modified, all child functions are affected.

How to solve the following loop output problem

for (var i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i)
    }, i * 1000)}Copy the code

Our expectation for this code is a frequency output of 1-5 per second. But in reality, this code will print 6’s five times at a rate of one per second. SetTimeout is a macro task. Due to the single-thread eventLoop mechanism in JS, macro tasks are executed only after the same step task is executed in the main thread. Therefore, the callbacks in setTimeout are executed successively after the loop ends. Now the loop is over, and I becomes 6. They share the global scope I, so they all print 6.

The solution

At each iteration, a new scope containing I is created using IIFE(self-executing function). Since the timer callback holds a reference to I in IIFE, that is, the closure that overwrites the IIFE scope is created at the same time, the callback accesses the value of I in its respective closure.

for (var i = 1; i <= 5; i++) {
    (function (j) {
        setTimeout(function timer() {
            console.log(j)
        }, j * 1000)
    })(i)
}
Copy the code

We can see how I changes in a specific Closure by executing the for loop step by step in the timer interrupt mode

2. Use let, which declares a block scope for each iteration in the for loop. We use IIFE to create a new scope at each iteration. That is, each iteration we create a block scope to solve the looping problem.

for (let i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i)
    }, i * 1000)}Copy the code

In the timer interrupt mode, we go through the for loop step by step and we can see how the I changes in the specific Block

3. Pass a third parameter to the timer, which is passed to the timer as an argument. Since function arguments are passed by value, even if I in the global scope changes, I passed to the callback function does not change.

for (var i = 1; i <= 5; i++) {
    setTimeout(function timer(j) {
        console.log(j)
    }, i * 1000,i)
}
Copy the code

We can see the change process of I in the specific Local by executing the for loop code step by step in the timer interrupt point mode

JS object oriented

1. Understand object-oriented programming

What is object orientation

What is object oriented? In Java, a classic statement is: everything is an object. The idea of object oriented is mainly object oriented, which abstracts a problem into a concrete object, and encapsulates the abstracted object and its attributes and methods into a class.

Object orientation is to decompose the transaction that constitutes the problem into various objects. The purpose of establishing objects is not to complete a step, but to describe the behavior of something in the whole step of solving the problem.

The difference between object-oriented and procedural

Object oriented and procedure oriented are two different programming ideas. We often hear the comparison between the two. When we start programming, we should mostly use procedure oriented programming, but as we grow up, object oriented programming is better

In fact, object-oriented and process-oriented are not completely opposite, nor are they completely independent.

The combination of procedure and data is a common expression used to describe “objects” in object orientation, which contain procedures in the form of methods.

I think the main difference between object orientation and procedure orientation is that procedure orientation is mostly verb oriented and the way to solve the problem is to call different functions step by step in order. While object oriented is mainly based on nouns, the problem is abstracted out of concrete objects, and this object has its own attributes and methods, in solving the problem is to combine different objects together to use.

So the nice thing about object orientation is that it’s more extensible and it eliminates the problem of code reuse.

  • Process oriented is to analyze the steps needed to solve the problem, and then use functions to implement these steps step by step, use when you can call one by one.
  • Object orientation is to decompose the transaction that constitutes the problem into various objects. The purpose of establishing objects is not to complete a step, but to describe the behavior of something in the whole step of solving the problem.

Concrete implementation let’s look at the most classic “put the elephant in the refrigerator” this problem

A process-oriented solution

The answer to “put an elephant in the fridge” in procedural programming is well known in three steps:

  1. Open the door (of a refrigerator);
  2. Put in (a refrigerator, an elephant);
  3. Close the door (refrigerator).
Object-oriented solution
  1. Refrigerator. Open the door ()
  2. Refrigerator. To fill (an elephant).
  3. Refrigerator. Close the door ()

It can be seen that the focus of object oriented and process oriented is different. Process oriented is based on verbs. To complete an event is to call different action functions in sequence. Object oriented is subject and predicate oriented. Think of subject and predicate as objects that have their own properties and methods. For example, the refrigerator has its own ID attribute and a way to open the door. Then you can call the refrigerator open door method and pass it a parameter elephant. \

Simple examples The benefits of object orientation and procedure orientation are not immediately apparent. Take a look at the shopping cart below

Object – oriented practical thinking

Shopping cart example

All things are objects, so, everything has characteristics (attributes) and actions (methods), generally get a demand classification, or when you browse a web page to see a picture, the mind should have the ability to extract the attributes and methods, then you are qualified.

When you build anything, think big *, then work on the details, and then put it together, just like you build a car. For example, in the figure above, the red ones are properties, the yellow ones are methods, abstracting properties and methods, and everything else is dead.

Process oriented thought implementation

If you’re in early school, you might want to use this kind of globalized variables, also known as function-oriented programming, which has the disadvantage of being messy and redundant

// Commodity attributes
var name = 'macbook pro'
var description = ' '.' var price = 0; // addOne:funcion(){alert('Add an item')},
reduceOne:function(){alert('Reduce one item')}, var card = ['macbook pro' ,'dellFunction addToCart:function(){alert(');Add to shopping cart') } addToCart()Copy the code
Realization of singleton pattern idea

If you’re thinking singleton, you might do this, but it’s still not a good idea. Too many objects can cause variable duplication, and small items are acceptable

var product={
        name:'macbook pro'.description:' '.price:6660.addOne:funcion(){},
        reduceOne:function(){},
        addToCart:function(){
            alert('Add to cart')}}/* Shopping cart */
    var cart={
        name:'Shopping cart'.products: [].allPrice:5000.sum:0
    }
Copy the code
Object oriented thought implementation

If it is a person with certain experience, may do so.

function Product(name,price,des) {
        /* Attribute behavior can be null or given the default value */
        this.name = name;
        this.price = price;
        this.description = des;
    }
Product.prototype={
    addToCart:function(){
        alert('Add to cart')}addOne:funcion(){},
    reduceOne:function(){},
     /* Bind the element */
    bindDom:function(){
    // Do string concatenation here,
    / / such as
    var str = ' '
    str +='
      
The price is :'
+this.privce+'</div>' return str }, } function Card(products,allPrice,sum) { /* Attribute behavior can be null or given the default value */ this.products = products; this.allPrice = allPrice; this.sum = sum } Product.prototype={ getAllPrice:function(){ alert('Calculate the total price of goods in the shopping cart')}}Copy the code

By creating various objects such as macBooks

// Data from the background
var products= [
    {name:'macbook'.price:21888},
    {name:'dell'.price:63999}]var str = ' '
for(var i = 0,len=products.length; i<len; i++) {var curName = products[i].name
    var curName = new Product()
    curName.name=products[i].name;
    curName.price=products[i].price;
    str+= curName.bindDom()
}
Copy the code
MVVM mode idea realization

The core of MVVM is data-driven ViewModel, which is a relational mapping of View and Model. Viewmodels are like Value Converter stations that transform data objects in the Model to make the data easier to manage and use. MVVM is essentially based on the operation of data to operate the View and then operate DOM, with the help of MVVM without direct operation of DOM, developers only need to complete the View template containing the declaration binding, write ViewModel has business, including data model and display logic, make the View fully automated.

Vue, for example, does not need to fetch the DOM, so when rendering, define the components one by one. Properties are all defined in data, methods are defined in methods, and vUE takes care of the rest.

data:{
        name =' ',
        price=' ',
        description = ' '
},
methods: {addToCart:function(){
            alert('Add to cart')}addOne:funcion(){},
    reduceOne:function(){},}Copy the code

The Page-level component then imports the product component and loops through the product component. A component is also an object, and its essence is object-oriented thinking.

2. Prototypes and prototype chains

Prototype and prototype chain is the basis of building JS object-oriented system, let’s first understand the prototype and prototype chain

Understand the relationships between stereotypes, constructors, and instances

Each function we create has a Prototype property, which is a pointer to an object whose purpose is to contain properties and methods that can be shared by all instances of a particular type. If taken literally, prototype is the prototype object of the instance of the object created by calling the constructor.

In JavaScript, whenever a function data type(normal function, class) is defined, a Prototype property is created for the function according to a specific set of rules. This property refers to the function’s prototype object. Stereotype objects contain properties and methods that can be shared by all instances of a particular type. In addition, by default, all prototype objects automatically get a constructor property that contains a pointer to the function where the Prototype property is located.

When a function is called new, it becomes a constructor and returns a brand new instance object that has a __proto__ attribute pointing to the constructor’s prototype object.

function Foo(name){
    this.name=name
}

var newFoo = new Foo('dongnan')
console.log(newFoo)// Print the result as shown below
Copy the code

The constructor Foo, a prototype of Foo, and an instance of Foo, newFoo, are shown below.

Prototype chain

Javascript objects point to the prototype Object of the current Object through __proto__, and the prototype Object of the current Object points to the parent prototype Object through __proto__, until it points to the prototype Object of the Object, thus forming a chain pointing to the prototype, that is, the prototype chain.

When we access the properties and methods of an object, we look them up along the stereotype chain. Find order, 1) current instance, 2) prototype Object of current instance, 3) prototype Object of parent class, N) prototype Object of Object.

3. Packaging

Object orientation has three main features: encapsulation, inheritance and polymorphism. For ES5, there is no concept of class, and due to the function-level scope of JS (variables inside a function cannot be accessed outside the function), we can simulate the concept of class. In ES5, a class actually holds the variable of a function, which has its own properties and methods. The process of putting attributes and methods together as a class is called encapsulation.

Encapsulation: Encapsulate objective things into abstract objects, hide the implementation details of attributes and methods, and only expose interfaces.

The factory pattern

The pattern of creating objects with a particular interface, encapsulated in functions, is called the factory pattern

function createPerson(name, age, job) {
    var o = new Object(a); o.name = name; o.age = age; o.job = job; o.sayName =function () {
        alert(this.name);
    };
    return o;
}
var person1 = createPerson("Nicholas".29."Software Engineer");
var person2 = createPerson("Greg".27."Doctor");
Copy the code

The disadvantage of this kind of Object is that you don’t know the type of the Object, because objects are created through Object.

Constructor pattern

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
        alert(this.name);
    };
}
var newperson1 = new Person("Nicholas".29."Software Engineer");
var newperson2 = new Person("Greg".27."Doctor");
Copy the code

The difference between this approach and the factory model

  • Create object not displayed
  • Assign properties and methods directly to this object
  • No return statement

To create a new instance of Person, you must use the new operator. Calling the constructor in this way actually goes through four steps:

Create a new object; (2) Assign the scope of the constructor to the new object (thus this refers to the new object); (3) Execute the code in the constructor (add attributes to the new object); (4) Return a new object.

The constructor pattern solves the problem of object recognition, but the main problem with using constructors is that each method has to be recreated on each instance. To be clear, creating functions this way results in different scope chains and identifier resolution, but the mechanism for creating new instances of Function remains the same. Therefore, functions of the same name on different instances are not equal. How to solve this problem

alert(newperson1.sayName == newperson2.sayName); //false
Copy the code

The prototype pattern

Create objects from prototype objects

function Person(){}
Person.prototype.name='dongnan'
Person.prototype.age='21'
Person.prototype.sayName=function(){
    console.log(this.name)
}

var person1=new Person()
person1.sayName()//dongnan

var person2 = new Person()
person2.sayName()//dongnan
Copy the code

The shortcomings of the stereotype pattern, through the stereotype sharing method is suitable, but the shared attributes do not meet the original purpose of creating instance objects (instances generally need to have all their own attributes)

Using a combination of constructor patterns and stereotype patterns (recommended)

The most common way to create custom types is to combine the constructor pattern with the stereotype pattern. The constructor pattern is used to define instance properties, while the stereotype pattern is used to define method and shared properties. As a result, each instance has its own copy of instance properties, but also shares references to methods, maximizing memory savings. In addition, this blending pattern supports passing parameters to constructors; It’s the longest of two modes.

function Person(name,age){
    this.name=name
    this.age=age
    this.friends=['zk'.'zh']
}
Person.prototype={
    constructor:Person,
    sayName:function(){
        console.log(this.name)
    }
}
var person1 = new Person('js'.29)
var person2 = new Person('vue'.10)
person1.friends.push('react')
console.log(person1.friends)//["zk", "zh", "react"]
console.log(person2.friends)//["zk", "zh"]
console.log(person1.friends===person2.friends)//false
console.log(person1.sayName===person2.sayName)//true
Copy the code

This is the default mode for defining reference types

Dynamic prototype pattern

Developers with experience with other OO languages are likely to get very confused when they see individual constructors and prototypes. The dynamic stereotype pattern is one solution that tries to solve this problem by encapsulating all information in constructors, while preserving the benefits of using both constructors and stereotypes by initializing stereotypes in constructors (only when necessary). In other words, you can determine whether a stereotype needs to be initialized by checking whether a method that should exist is valid. Let’s look at an example.

function Person(name, age, job) {
    / / property
    this.name = name;
    this.age = age;
    this.job = job;
    / / method
    if (typeof this.sayName ! ="function") {
        console.log(1)
        Person.prototype.sayName = function () {
            console.log(this.name); }; }}var friend = new Person("Nicholas".29."Software Engineer");
friend.sayName();
Copy the code

Parasitic constructor pattern

In this example, the Person function creates a new object, initializes it with the appropriate properties and methods, and then returns the object. This pattern is exactly the same as the factory pattern, except that we use the new operator and call the wrapper function we use a constructor. By default, the constructor returns a new object instance without a value. By adding a return statement to the end of the constructor, you can override the value returned when the constructor is called. This pattern can be used in special cases to create constructors for objects. Suppose we want to create a special array with extra methods. Because you cannot modify the Array constructor directly, you can use this pattern.

function SpecialArray() {
    // Create an array
    var values = new Array(a);/ / add value
    values.push.apply(values, arguments);
    // Add method
    values.toPipedString = function () {
        return this.join("|");
    };

    // Return an array
    return values;
}
var colors = new SpecialArray("red"."blue"."green");
alert(colors.toPipedString()); //"red|blue|green"
console.log(colors,'colors')
Copy the code

The returned object has no relationship to the constructor or to the constructor’s stereotype properties, and is not recommended when other schemas are available.

Secure constructor pattern

Durable Objects Douglas Crockford invented the durable objects concept in JavaScript. A secure object is an object that has no public attributes and whose methods do not reference this. Secure objects are best used in secure environments where the use of this and new is prohibited, or to prevent data from being altered by other applications, such as Mashup applications. The secure constructor follows a similar pattern to the parasitic constructor, but with two differences: the newly created object instance method does not reference this; The second is to call the constructor without using the new operator. For the sake of a safe constructor, you can rewrite the previous Person constructor as follows.

function Person(name,age){
    // Create the object to return
    var o = new Object(a)// Define private variables and methods
    // Add method
    o.sayName = function(){
        console.log(name)
    }
    // Return the object
    return o
}
var friend =Person('dongnan'.21)
friend.sayName()
Copy the code

This security provided by the secure constructor pattern makes it well suited for certain secure execution environments.

4. Inheritance

Inheritance: An object can use all the functionality of another object and extend those functionality. The process of inheritance is the process of going from general to particular.

Prototype chain

The basic idea is to use stereotypes to make one reference type inherit the properties and methods of another.

function SuperType() {
    this.property = true;
}
SuperType.prototype.getSuperValue = function () {
    return this.property;
};

function SubType() {
    this.subproperty = false;
}
// Inherits SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
    return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
Copy the code

While the prototype chain is powerful enough to implement inheritance, it has some problems. The main problem comes from stereotypes that contain reference-type values, and stereotype attributes that contain reference-type values are shared by all instances. The second problem with stereotype chains is that you cannot pass arguments to the constructor of a supertype when creating instances of a subtype. It is not usually used alone.

Borrowing constructor

In order to address the problem of including reference type values in prototypes, developers have begun to use a technique called constructor constructor stealing (sometimes called forged objects or classical inheritance). The basic idea of this technique is fairly simple: call the supertype constructor inside the subtype constructor.

function SuperType(name){
    this.name=name
}
function SubType(name){
    // Inherits SubperType and passes parameters
    SuperType.call(this,name)
    // Instance properties
    this.age=21
}
var instance = new SubType('dongnan')
console.log(instance.name)//dongnan
console.log(instance.age)/ / 21
Copy the code

The problem with this pattern is that methods are defined in constructors, so function reuse is out of the question. Furthermore, methods defined in the stereotype of a supertype are also invisible to subtypes, resulting in only the constructor schema being used for all types. Rarely used alone

Combination of inheritance

Combination inheritance, sometimes called pseudo-classical inheritance, refers to an inheritance pattern that combines a chain of archetypes and techniques borrowed from constructors to take advantage of the best of both. The idea behind this is to use stereotype chains to inherit stereotype attributes and methods, while borrowing constructors to inherit instance attributes. In this way, function reuse is achieved by defining methods on prototypes, while ensuring that each instance has its own attributes.

function SuperType(name){
    this.name=name
    this.colors=['red'.'blue'.'green']
}

SuperType.prototype.sayName=function(){
    console.log(this.name)
}

function SubType(name,age){
    // Inherit attributes
    SuperType.call(this,name)// Call SuperType the first time
    this.age=age
}
// Inheritance method
SubType.prototype=new SuperType()// Call SuperType a second time
SubType.prototype.constructor=SubType
SubType.prototype.sayAge=function(){
    console.log(this.age)
}

var instance1 = new SubType('dongnan'.21)
instance1.colors.push('black')
console.log(instance1.colors)//["red", "blue", "green", "black"]
instance1.sayAge()/ / 21
instance1.sayName()//dongnan

var instance2 = new SubType('fusheng'.29)
console.log(instance2.colors)// ["red", "blue", "green"]
instance2.sayAge()/ / 29
instance2.sayName()//fusheng
Copy the code

Composite inheritance avoids the defects of stereotype chains and borrowed constructors, and combines their advantages to become the most common inheritance pattern in JavaScript. Also, instanceof and isPrototypeOf() can be used to identify objects created based on composite inheritance.

The biggest problem with composite inheritance is that in any case, the supertype constructor is called twice: once when the subtype stereotype is created and once inside the subtype constructor. Yes, subtypes eventually contain all of the instance properties of the supertype object, but we have to override those properties when we call the subtype constructor.

Prototype inheritance

Stereotypes allow you to create new objects based on existing objects without having to create custom types.

var person ={
    name:"dongnan".friends: ['df1'.'df2']}var anotherPerson =Object.create(person)
anotherPerson.name='fusheng'
anotherPerson.friends.push('df3')

var yetAnotherPerson = Object.create(person)
yetAnotherPerson.name='liuji'
yetAnotherPerson.friends.push('df4')

console.log(person.friends)// ["df1", "df2", "df3", "df4"]
Copy the code

Object.create a simplified version of the internal implementation

function Object(o){
    function F(){}
    F.prototype=o
    return new F()
}
Copy the code

Parasitic inheritance

The idea of parasitic inheritance is similar to that of the parasitic constructor and factory pattern, which is to create a function that simply encapsulates the inheritance process, enhances the object internally in some way, and finally returns the object as if it had really done all the work.

function createAnother(original){
    var clone = Object.create(original)
    clone.sayHi = function(){
        console.log('hi')}return clone
}

var person ={
    name:"dongnan".friends: ['df1'.'df2']}var anotherPerson=createAnother(person)
anotherPerson.sayHi()//hi
Copy the code

Parasitic combinatorial inheritance (recommended)

This method of inheritance optimizes composite inheritance. The disadvantage of composite inheritance is that the constructor is called when inheriting from the superclass function. We just need to optimize this.

function SuperType(name) {
    this.name = name
    this.colors = ['red'.'blue'.'green']
}

SuperType.prototype.sayName = function () {
    console.log(this.name)
}

function SubType(name, age) {
    // Inherit attributes
    SuperType.call(this, name)
    this.age = age
}
// Inherit the SupType prototype
SubType.prototype = Object.create(SuperType.prototype,{
    constructor: {value:SubType,
        enumerable:false.writable:true.configurable:true
    }
})
SubType.prototype.sayAge = function () {
    console.log(this.age)
}

var instance = new SubType('dongnan'.'21')

instance.sayName()//dognnan
instance.sayAge()/ / 21
console.log(instance instanceof SubType)//true
console.log(instance instanceof SuperType)//true
Copy the code

The core of this inheritance implementation is to assign the stereotype of the parent class to the child class and set the constructor to the child class, so that the parent class is called only once, and thus solve the problem of useless parent class attributes, and can find the constructor of the child class correctly.

5. How does Babel compile ES6 classes

Es6 inheritance can be implemented directly using class. But class is an ES6 thing, and in order to make it browser-compatible, we usually compile ES6 code through Babel. Let’s take a look at what code compiled with Babel looks like.

function _possibleConstructorReturn(self, call) {
    // ...
    return call && (typeof call === 'object' || typeof call === 'function')? call : self; }function _inherits(subClass, superClass) {
    // ...
    // See
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            enumerable: false.writable: true.configurable: true}});if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}


var Parent = function Parent() {
    // Verify if Parent constructs this
    _classCallCheck(this, Parent);
};

var Child = (function (_Parent) {
    _inherits(Child, _Parent);

    function Child() {
        _classCallCheck(this, Child);

        return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this.arguments));
    }

    return Child;
}(Parent));

Copy the code

The code above is compiled to hide some non-core code, so we’ll start with the _inherits function.

The code for setting the subclass prototype is exactly the same as that for parasitic composite inheritance, which is the best way to do it. Object. SetPrototypeOf (subClass, superClass), which is used to inherit static methods from the parent class.

Then the Child constructor piece of code is basically the same as the previous implementation. So in general, Babel implements inheritance in the same way as parasitic combinatorial inheritance, implementing more than one static method that inherits its parent class.

6. Talk about inheritance from the design idea

Problems with inheritance

If there are different brands of cars, each car has drive, Music, addOil these three methods.

class Car{
  constructor(id) {
    this.id = id;
  }
  drive(){
    console.log("wuwuwu!");
  }
  music(){
    console.log("lalala!")}addOil(){
    console.log("Oh dear!)}}class otherCar extends Car{}
Copy the code

It is now possible to implement vehicle functions and thus extend different vehicles.

But here’s the problem. New energy vehicles are also cars, but they don’t need addOil(fuel).

If the class of new energy vehicles inherit Car, there are also problems, commonly known as the “gorilla and banana” problem. The gorilla had bananas, but now I have a gorilla when I only need bananas. In other words, I don’t need this method right now, but for inheritance reasons, I’ve given it to subclasses.

The biggest problem with inheritance is that you can’t decide which attributes to inherit; all attributes have to be inherited.

Of course, you might say, can create a parent class, remove the approach to refuel, but it is also a problem, on the one hand, the parent class is unable to describe the details of all subclasses, for different subclasses features to increase different parent class, the code is bound to a large number of repeat, on the other hand is changed once a subclass, parent will update accordingly, The code is too coupled and not maintainable.

How to solve the problem of inheritance

Inheritance is more about describing what a thing is, and a bad description will lead to all kinds of problems, so is there a way to solve these problems? The answer is a combination.

What is a combination? You can think of it as you have all kinds of parts, and you can make all kinds of products out of those parts, and composition is more about describing what one thing can do.

Now let’s take the case of the previous car and implement it by combination.

function drive(){ console.log("wuwuwu!" ); } function music(){ console.log("lalala!" } function addOil(){console.log(" oho! ) } let car = compose(drive, music, addOil); let newEnergyCar = compose(drive, music); Copy the codeCopy the code

As you can see from the above pseudocode, composition is better than inheritance. Whatever you want to describe, you can do it by combining several functions. The code is clean and reusable.

This is the trend in programming syntax today, as Golang, for example, is entirely combination-oriented.

10 JS modular specification

1. What problems should modularity solve and how to achieve modularity

Since the release of JavaScript in 1995, browsers have loaded JS modules using simple script tags. As early as 1996, there were many server-side JavaScript implementations, such as Nodejs, released in 2009. Both browser-side and server-side JavaScript, JavaScript itself had no modular architecture until the ES6 specification was proposed.

What is modularity

Modularity is the organization of code by breaking up a complex system into independent modules. Good writers divide their books into chapters. Good programmers divide their programs into modules. Good modules are highly independent and have specific functions that can be modified, removed or added as needed without breaking the entire system.

What are the benefits of modularity

These are the main benefits of modularity:

  • The namespace

In JavaScript, the interfaces of every JS file are exposed to the global scope, everyone can access them, and it is easy to cause name conflicts and pollute the global. Modularity can create private Spaces for variables to avoid namespace contamination.

  • reusability

Have you ever copied previously written code into a new project at some point? If you modularize this code, you can use it over and over again, and if you need to change it, you only need to change the module, not every piece of code in the project.

  • maintainability

Modules should be independent, and a well-designed module should minimize its dependence on parts of the code base so that it can be cut and modified independently. When modules are separated from other code fragments, it is much easier to update individual modules, and you can do versioning with each change.

Although modularity has many benefits, it is not easy to implement modular development

Traditional modular development

  • Naming conflicts

When multiple JS files have the same name for variables and methods, resulting in naming conflicts, the Java namespace approach can be used.

/ / code from: https://github.com/seajs/seajs/issues/547
var org = {};
org.CoolSite = {};
org.CoolSite.Utils = {};

org.CoolSite.Utils.each = function (arr) {
  // Implementation code
};

org.CoolSite.Utils.log = function (str) {
  // Implementation code
};
Copy the code

Similar to the way classes are used in other programming languages such as Java or Python, public and private methods and variables can be stored in a single object. The problem of exposing variables to the global scope is solved by writing the methods that will be exposed to the global scope outside the closure and wrapping private variables and methods inside the closure scope.

// The global agent is accessible
var global = 'Hello World';
(function() {
  // Only accessible within closures
   var a = 2; }) ()Copy the code
  • Cumbersome file dependencies

While this approach has its advantages, it also has its disadvantages.

  • The module defined by the Immediately executed factory Function (IIFE: Immediately Invoked Function Expression).
  • References to dependencies are done by global variable names loaded through HTML script tags.
  • Dependencies are very weak: developers need to know the correct order of dependencies. For example, Backbone files cannot precede the jQuery tag.
  • Additional tools are required to replace a set of script tags with a single tag to optimize deployment.

This is difficult to manage on large projects, especially when scripts have many dependencies in an overlapping and nested manner. Handwritten script markup is not scalable, and it does not have the ability to load scripts on demand.

Is there a way to request a dependent module within a module instead of globally requesting a dependent module? CommonJS, AMD, CMD, UMD, etc., usually a file is a module, has its own scope, only to expose specific variables and functions. These modular specifications tell developers:

  • How to import module dependencies (Imports)
  • How to define modules (code)
  • How to export a module’s interface \

Since the idea of modular development was put forward, whether it is browser side or server side Javascript development, developers have been exploring the modular specification and implementation to meet the actual needs, they have to solve the same problem, namely modular development and module dependence, but they are initiated by different reasons.

The history of modularity

  • The historical process of modularization

    • In 2009, Ryan Dahl, an American programmer, created the Node. js project. The module system of Node. js is based on the CommonJS module specification.
    • However, require in the CommonJS specification is synchronous, which is not acceptable on the browser side. So there was an AMD specification, and in 2010 RequireJS was implemented as an AMD specification.
    • Since 2012, Yu Bo felt that RequireJS was not perfect enough and many of her suggestions to the RequireJS team were not accepted, so she wrote Sea-.js herself and formulated CMD specification, and Sea-.js followed CMD specification.
    • ECMAScript6 standard was officially released in June 2015, realizing module functions at the level of language standards, which can completely replace CommonJS and AMD specifications and become a common module solution for browsers and servers. (This is the future)
    • In October 2015, UMD emerged to integrate CommonJS and AMD’s approach to module definition specifications. At this time, the ES6 module standard was just coming out, and many browsers did not support the ES6 modular specification.
    • Browserify was launched in 2016
    • Webpack was released in 2017
  • The history of modularity

2. CommonJS

Mozilla engineer Kevin Dangoor launched the ServerJS project in January 2009 to standardize the modularity of JavaScript used on the server side, And the standardization of Filesystem API, I/O Streams, Socket IO and other server-side development areas.

And want these to work on as many operating systems and interpreters as possible, including three major operating systems (Windows, Mac, Linux) and four major interpreters (SpiderMonkey, Rhino, V8, JavaScriptCore), Then there is the “browser “(itself a unique environment).

In August 2009, ServerJS was renamed CommonJS to demonstrate that the API it defines can be widely used. A lot of subsequent development jokes have suggested that the CommonJS module format is browser-unfriendly (it doesn’t support asynchronous writing) and that the browser is a class II citizen, which is better suited to the ServerJS name.

NodeJS

Node.js was implemented by American programmer Ryan Dahl on May 31 and introduced for the first time at the JSConf conference on November 8.

Node.js, which uses the CommonJS specification directly to implement a modular architecture, is so popular that most Web developers still refer to the Node.js modular architecture as the CommonJS specification. It has four important environment variables that support modular implementations: Module, exports, require, and Global. Module. exports defines the interface for the current module’s exports (not recommended), and requires loads the module.

// Define the module math.js
var basicNum = 0;
function add(a, b) {
  return a + b;
}
module.exports = { // Write functions and variables that need to be exposed here
  add: add,
  basicNum: basicNum
}

// Introduce the module index.js
// When referencing a custom module, the argument contains the path and can omit.js
var math = require('./math');
math.add(2.5);

// No path is required when referencing core modules
var http = require('http'); http.createService(...) .listen(3000);

Copy the code

CommonJS loads modules synchronously. On the server side, the module files are stored on local disk and are very fast to read, so this should not be a problem. However, on the browser side, it is more reasonable to use asynchronous loading for network reasons.

3. Module Loader

Back in 2009, web developers were worried about a bunch of

CommonJS is dedicated to the server-side ecosystem of JavaScript. Modules are loaded synchronously, and the syntax is very simple and friendly to server-side development. But this is unacceptable on the browser side, as it takes longer to read a module from the network than from disk, and as long as the script to load the module is running, it prevents the browser from running anything else until the module is loaded.

In the CommonJS forum, Kevin Dangoor leads a discussion about asynchronously loading CommonJS modules and solicits ideas for browser-side module loading. There are also a number of forum posts on how to asynchronously load Commonjs modules in browsers.

  • For those who propose a transport scheme, before running it on the browser, the module should be converted into codes that conform to transport specifications through conversion tools.
  • Some propose that XHR load module code text and execute it in the browser using eval or new Function.
  • Some proposed that we should directly improve CommonJS and launch a pure asynchronous module loading scheme;

James Burke, the author of the third solution, believes that the module format of CommonJS does not support asynchronous loading on the browser side, and CommonJS modules need to be loaded through other ways such as XHR, which is very unfriendly to web front-end developers. The authors argue that the best practice for browser-side development is to load only one module per page.

RequireJS in AMD

James Burke wrote at length in CommonJS in the Browser in December 2009 about directly modifying the CommonJS module format for browser-side development. But Kevin Dangoor, the creator of CommonJS, disagreed with this solution, which led to RequireJS

James Burke wrote the AMD specification and implemented RequireJS, an AMD-compliant module loader, in 2010.

The AMD specification uses asynchronous loading of modules, which does not affect the execution of subsequent statements. All statements that depend on this module are defined in a callback function that will not run until the load is complete. Here is an introduction to implement the modularization of the AMD specification with require.js: specify the reference path with require.config(), define the module with define(), and load the module with require().

First we need to import the require.js file and an entry file main.js. Configure require.config() in main.js and specify the base module to use in the project.

/** 网页中引入require.js及main.js **/
<script src="js/require.js" data-main="js/main"></script>

/** main.js entry file/main module **/
// First specify the module path and reference name with config()
require.config({
  baseUrl: "js/lib".paths: {
    "jquery": "jquery.min".// The actual path is js/lib/jquery.min.js
    "underscore": "underscore.min",}});// Perform basic operations
require(["jquery"."underscore"].function($, _){
  // some code here
});

Copy the code

When referencing modules, we place the module name in [] as the first argument to reqiure(); If we define modules that themselves depend on other modules, we need to place them in [] as the first argument to define().

// Define the math.js module
define(function () {
    var basicNum = 0;
    var add = function (x, y) {
        return x + y;
    };
    return {
        add: add,
        basicNum :basicNum
    };
});
// Define a module that depends on glo.js
define(['underscore'].function(_){
  var classify = function(list){
    _.countBy(list,function(num){
      return num > 30 ? 'old' : 'young'; })};return {
    classify :classify
  };
})

// Reference the module, put the module in []
require(['jquery'.'math'].function($, math){
  var sum = math.add(10.20);
  $("#sum").html(sum);
});

Copy the code

Generally speaking, AMD relies on front-loading, asynchronous loading, all import and then execute,

SeaJS in CMD

Yubo thinks RequireJS is not perfect:

  • There is an objection to the execution time

    • Reqiurejs module is executed immediately after it is loaded. Seajs saves the factory function after the module is loaded, and executes the corresponding factory function of the module when it is executed to require to return the exported result of the module.
  • Module writing style is controversial

    • Amd-style exports that pass in parameters to dependent modules break the nearest declaration principle.

Bob has developed a new Module Loader: SeaJS, which was announced in CommonJS Group in November 2011 (Announcing SeaJS: A Module Loader for the Web), sea-.js follows the CMD specification.

CMD is another JS modularization solution, which is similar to AMD, except that AMD advocates relying on front-loading and up-front execution, while CMD advocates relying on nearby and delayed execution. This specification was actually created during the promotion of Sea-js.

/** AMD **/
define(["a"."b"."c"."d"."e"."f"].function(a, b, c, d, e, f) { 
     // all modules to be used are declared and initialized (loaded and executed ahead of time) first
    a.doSomething();// This is only the exports of module A
    if (false) {
        // Even if a module B is not available, b has already been downloaded and executed in advance
        b.doSomething()
    } 
});

/** CMD **/
define(function(require.exports.module) {
    var a = require('./a'); // If required
    a.doSomething();
    if (false) {
        var b = require('./b'); b.doSomething(); }});/** sea.js **/
// Define the module math.js
define(function(require.exports.module) {
    var$=require('jquery.js');
    var add = function(a,b){
        return a+b;
    }
    exports.add = add;
});
// Load the module
seajs.use(['math.js'].function(math){
    var sum = math.add(1+2);
});

Copy the code

4. Es6 Module

In June 2015, ECMAScript6 standard was officially released. The ES modular specification was proposed to integrate CommonJS, AMD and other existing module solutions to achieve modularity at the level of language standards and become a common module solution for browsers and servers.

The module functions are completed by export and import commands. Export is used to export modules, and import is used to import modules.

// Import a single interface
import {myExport} from '/modules/my-module.js';
// Import multiple interfaces
import {foo, bar} from '/modules/my-module.js';

// Export the function defined earlier
export { myFunction }; 

// Export constants
export const foo = Math.sqrt(2);

Copy the code

ES Module differs from CommonJS and Loaders schemes in the following aspects:

  • Declarative rather than imperative, orimportA Declaration Statement, not an expression, cannot be used in ES ModuleimportDeclare a dependency with a variable, or introduce a dependency dynamically:
  • The CommonJS module prints a copy of the value, the ES6 module prints a reference to the value.
  • importIs pre-parsed and pre-loaded, unlike RequireJS, which executes and then issues a request

For pragmatic Node.js developers, these differences put the massive community code created by NPM in an awkward position, requiring a lot of work to upgrade and integrate. David Herman explains that the benefits of the ES Module far outweigh the inconvenience:

  • Static imports ensure that they are compiled into variable references that can be optimized by the parser (via JIT compilation) for more efficient execution when run in the current execution environment
  • Static export makes variable detection more accurateIn code checking tools such as JSHint and ESLint, whether a variable is defined is a very popular feature, while staticexportCould make the test more accurate
  • More complete loop dependency processingIn existing CommonJS implementations such as Node.js, circular dependencies are not completed by passingexportsObject, for direct referencesexports.fooOr parent module overwritemodule.exportsBecause ES Module passes references, you don’t have these problems

Others include greater compatibility with possible future additions to standards (macros, type systems, etc.).

ES6 Module in Browser

Before the ES Module standard came out, although the community implemented a large number of Loader, but the browser itself has not selected the Module scheme, supporting ES Module is relatively less concern for the browser.

Because the ES Module execution environment is different from normal scripts, the browser chooses to add

<script>
import foo from "./foo.js"
</script>

<script type="javascript">
import bar from "./bar.js"
</script>

Copy the code

ES Module is currently supported by several evergreen browsers. The last one is Firefox, which officially supports ES Module in Firefox 60, released on May 8, 2018.

In addition, browsers add

// In browsers, import statements can only be used in tags of scripts that declare type="module".
<script type="module" src="./app.js"></script>
// Use the nomodule attribute in the script tag to ensure backward compatibility.
<script nomodule src="./app.bundle.js"></script>

Copy the code

ES6 Module in Node.js

But on the Node.js side, ES Module encounters a lot more noise. Former Node.js leader Isaacs Schlutuer even went so far as to say that the ES Module was too utopian and faceless.

The first dilemma is how to support module execution mode, whether to auto-detect, ‘use module’, add module attributes as a special entry in package.json, or simply add a new extension.

Finally node.js chose to add a new extension.mjs:

  • in.mjsCan be used freely inimport , exportandimport()
  • in.mjsCannot be used inrequire
  • in.jsCan be used inrequireandimport()
  • in.jsCannot be used inimportexport

That is, the two module systems are completely independent. In addition, the dependency lookup method has been changed to require. Extensions are:

{ '.js': [Function].'.json': [Function].'.node': [Function]}Copy the code

Now (with — experimental-Modules on) :

{ '.js': [Function].'.json': [Function].'.node': [Function].'.mjs': [Function]}Copy the code

But two separate sets of module systems also lead to a second twist: how do module systems communicate with each other? This isn’t a problem for browsers, but node.js has to consider the massive CommonJS modules in NPM.

  • ES6 Module loads CommonJS

In.mjs, developers can import CommonJS (although they can only import default) :

/ / right
import foo from './foo'
/ / error
import {method} from './foo'
Copy the code

The import command for ES6 modules can load CommonJS modules, but only as a whole, not as a single output.

This is because ES6 modules need to support static code analysis, and the CommonJS module’s output interface is module.exports, which is an object and cannot be analyzed statically, so it can only be loaded as a whole.

  • CommonJS loads ES6 Module

In.js, developers naturally cannot import ES Module, but they can import() :

import('./foo').then(foo= > {
  // use foo
})

(async function() {
  const bar = await import('./bar')
  // use bar}) ()Copy the code

One reason require() does not support the ES6 module is that it is loaded synchronously, and ES6 modules can use top-level await commands internally, so it cannot be loaded synchronously.

Note that unlike the browser, which determines the running mode by import, the running mode of the script in Node.js is bound to the extension. That is, dependencies are found differently:

  • in.jsrequire('./foo')Looking for the./foo.jsor./foo/index.js
  • in.mjsimport './bar'Looking for the./bar.mjsor./bar/index.mjs

Taking advantage of these features, we can now upgrade existing NPM modules to ES Modules and still support CommonJS.

Dynamic import

Static imports are the best choice for initializing load dependencies. Using static imports is easier to benefit from code static analysis tools and tree shaking. But when you want to load modules under certain conditions or on demand, you need to introduce dependencies dynamically, for example:

if(process.env.NODE_ENV ! = ='production') {
  require('./cjs/react.development.js')}else {
  require('./cjs/react.production.js')}if (process.env.BROWSER) {
  require('./browser.js'} copy the codeCopy the code

To this end, Domenic Denicola drew up proposals for import standards.

// This is a phase 3 proposal.
var promise = import("module-name");
Copy the code

Script tags in HTML do not need to declare type=”module”, except to handle dynamic dependencies.

<script>
import('./foo.js').then(foo= > {
  // use foo
})
</script>
Copy the code

We can also use import() to import ES modules in Node.js (.js files) :

import('./foo.mjs').then(foo= > {
  // use foo
})
Copy the code

Writing modular JavaScript code for browsers and Node.js using ES Modules is completely feasible. Do we need compilation or packaging tools?

5. Module Bundler

There are also drawbacks to using a module loader on the browser side. For example, the RequireJS encoding mode is not friendly, it is troublesome to load modules of other specifications, and it is executed in advance. SeaJS rules are always changing, leading to various problems in the upgrade. However, CommonJS is very convenient and stable to use on the server side.

  • NPM install Installs the module
  • Just introduce it using require

Is it possible to introduce modules in the browser using the CommonJS specification and make it easy to call modules from other specifications?

One solution is precompilation, where we write code to define and import modules in the CommonJS specification, and then compile the modules and dependencies into a JS file called BundleJS.

Browserify and WebPack are both pre-compiled modular solutions that end up generating a bundle from a build and resolving dependencies during the build.

Browserify

Substack, an early and active member of the Node.js community, developed Browserify from a simple point of view: Browserify lets you organize browser-side Javascript code in a similar way to Node’s require(), Pre-compilation allows the front-end Javascript to directly use some of the libraries installed on Node NPM, or to introduce non-CommonJS modules using browserify.transform (browserify.transform configuration transform plug-in).

Browserify’s require is consistent with Node.js and does not support asynchronous loading. The community has been keen to see Browserify support asynchronous loading, but the authors insist that Browserify’s require should be consistent with Node.js.

Webpack

Webpack, released a year after Browserify, combines the strengths and weaknesses of CommonJS and AMD, and can be written as CommonJS, with on-demand and asynchronous loading of all resources after compilation.

One of the most outstanding features of Webpack is its module parsing granularity and powerful packaging capability, and the other is its scalability. Related conversion tools (Babel, PostCSS, CSS Modules) can be quickly accessed into plug-ins and can also be customized Loader. Taken together, these characteristics are all to the detriment. It also supports ES Module:

import defaultExport from "module-name";
import * as name from "module-name";
import { export } from "module-name";
Copy the code

This is the benefit of the build tool, which is much more scalable than traditional browser loaders and can easily add transpiler support such as Babel and Traceur.

6. ES6 Module and CommmonJS

Loading way

  • The CommonJS module is run time loaded, and the ES6 module is compile time output interface.

    • Runtime loading: CommonJS modules are objects; That is, the entire module is loaded on input, an object is generated, and methods are read from that object. This loading is called “runtime loading.” Even if it’s executed againrequireCommand, the module will not be executed again, but will be evaluated in the cache. That is, no matter how many times the CommonJS module is loaded, it only runs once on the first load and returns the result of the first run when it is loaded later, unless the system cache is manually cleared.
    • Load at compile time: ES6 modules are not objects, but passexportThe command explicitly specifies the code to output,importIs in the form of a static command. In theimportYou can specify that an output value is loaded instead of the entire module, which is called “compile-time loading.”

Synchronous/asynchronous

The CommonJS module require() is a synchronously loaded module, while the ES6 module import command is asynchronously loaded, with a separate module-dependent parsing phase.

The way a value is output

  • The CommonJS module prints a copy of the value, the ES6 module prints a reference to the value.

    • The CommonJS module outputs a copy of the value, meaning that once a value is output, changes within the module do not affect that value.
    • ES6 modules operate differently from CommonJS. The JS engine encountered a module load command while statically analyzing the scriptimport, a read-only reference is generated. When the script is actually executed, it will be evaluated in the loaded module based on the read-only reference. In other words, ES6importIt’s kind of like a symbolic link on Unix, where the original value changes,importThe loaded value will also change. Therefore, ES6 modules are referenced dynamically and do not cache values. Variables in modules are bound to the module in which they are located.
        // export.js
        var foo = 'bar';
        module.exports={foo}
        setTimeout(() = > foo = 'baz'.500);

        // common.js
        import {foo} from './export.js';
        console.log(foo);//bar
        setTimeout(() = > console.log(foo), 500);//bar
Copy the code

In common.js, foo is still bar after 500ms

        // export.js
        export var foo = 'bar';
        setTimeout(() = > foo = 'baz'.500);

        // es6.js
        import {foo} from './export.js';
        console.log(foo);//bar
        setTimeout(() = > console.log(foo), 500);//baz
Copy the code

In es6.js, foo changes to baz after 500ms

CommonJS loads an object (that is, the module.exports property) that is generated only after the script runs. An ES6 module is not an object, and its external interface is a static definition that is generated during the code static parsing phase.

The treatment of cyclic loading

  • An important feature of the CommonJS module is that it executes on load, meaning that the script code is executed atrequire“, it will all be executed. Once a module is “looping”, only the parts that have been executed are printed, not the parts that have not been executed
  • ES6 handles “cyclic loading” in a fundamentally different way than CommonJS. ES6 modules are referenced dynamically if usedimportLoading variables from a module (i.eimport foo from 'foo'), those variables are not cached, but instead become a reference to the loaded module, requiring the developer to ensure that the value is actually retrieved. Essentially, mutual imports, along with static validation to verify the validity of the two import statements, virtually combine the space (via bindings) of connected modules, such that module A can call module B and vice versa, which is symmetric with their original declaration in the same scope.

7. The difference between modularity

8. The state of modularity

As Yubo said in his point in the history of front-end modular development, with the W3C and other specifications and the rapid development of browsers, front-end modular development will gradually become the infrastructure. Everything will eventually become history.

We don’t have to worry about which modularity solution to use in our development. ES6 solves this problem at the level of language standards.

  • CommonJS

    • Nodejs has become the server-side JavaScript standard
  • Module Loader(obsolete)

    • RequireJS is no longer maintained
    • Seajs is no longer maintained. In 2015, the author tweeted that the Sea-js tree should be given a tombstone.
  • ES6 Module

    • Syntax is supported in major browsers and Nodejs8.5 and above.
  • Module Bundler

    • Webpack [GitHub] is now the most popular packaging tool
    • Browserify [GitHub] now updates less frequently

References:

“How Browsers Work and Practice” geek Time “Illustrated Google V8” Geek Time “JavaScript Advanced Programming (3rd Edition)” JavaScript You Don’t Know (1st Middle Volume) “JavaScript Design Patterns and Development Practices” Introduction to ECMAScript

Question of native JS soul, how many can you catch? (1) (Recommended reading) Native JS soul ask (middle), check whether you are really familiar with JavaScript? 20000 words | front-end based gleanings of 90 q play JavaScript data types The way of the front-end advanced

Js: Object-oriented programming, introduce you to encapsulation, inheritance and polymorphic JavaScript object-oriented practical thinking

Front-end modular: CommonJS, AMD, CMD, ES6 front-end high-frequency interview questions The front two years – earning 30 k | the nuggets technical essay You may not know JavaScript modularization front-end modular development that unofficial history historical front end the history of the development of the value of the front-end module status of front-end module Front-end interview essential JavaScript modular comprehensive parsing

Try to explain the asynchronous processing of JavaScript in a popular way (I) — understand asynchrony