preface
Recent front end interviews have beenvolumeFly up, fromPrinciples of computer, principles of compilation, data structures, algorithms, design patterns, programming paradigmtoUnderstand and use compilation tools, format tools, Git, NPM, unit testing, Nginx, PM2, CI/CD.
Pick any part of this, and you can dig deep, deep, deep.
This article focuses on the JS foundation, I hope you can bring a little 🤏 help.
Warm tips: this article is suitable for the front-end introduction of students and recently in the preparation of a systematic review of JS foundation friends. Those who have been working for many years can skip this article
Chapter 1 “Relearning JavaScript” variables and types
1. Data types
There are seven basic data types: BigInt, Symbol, Undefined, Null, Boolean, Number and String
1 Complex data type: Object
Second, the underlying data structure
Try analyzing the Object implementation through the V8 source code: The root superclass of all data types in V8 is Object. Object is derived from HeapObject, which provides basic storage functions. JSReceiver is used for prototype lookup, and JSObject is Object in JS. Array/Function/Date inherit from JSObject. The FixedArray on the left is where the actual data is stored.Before creating a JSObject, we serialize the text properties of the Object we read to constant_properties, as follows:
var data = {
name: "yin".age: 18."-school-": "high school"};Copy the code
Will be sequenced as:
. /.. /v8/src/runtime/runtime-literals.cc 72 constant_properties:
0xdf9ed2aed19: [FixedArray]
– length: 6
[0] :0x1b5ec69833d1 <String[4]: name>
[1] :0xdf9ed2aec51 <String[3]: yin>
[2] :0xdf9ed2aec71 <String[3]: age>
[3] :18
[4] :0xdf9ed2aec91 <String[8]: -school->
[5] :0xdf9ed2aecb1 <String[11]: high school>
Copy the code
It is a FixedArray with six elements. Since data has three properties, each with a key and a value, Array has six. The first element is the first key, the second element is the first value, the third element is the second key, the fourth element is the second key, and so on.
Object provides a Print() function, which is useful for printing information about the Object. The above output has two types of data, one is String and the other is integer. FixedArray is an array-like class implemented by V8 that represents a contiguous segment of memory.
Reference from: www.rrfed.com/2017/04/04/…
Third, the Symbol
Symbol type in the actual development of the application, can be manually implemented a simple Symbol
3.1 Use Symbol as object attribute name (key)
Before that, we usually used strings to define or access properties of objects, such as the following code:
let obj = {
abc: 123.hello: "world"}; obj["abc"]; / / 123
obj["hello"]; // 'world'
Copy the code
Now, Symbol can also be used to define and access object attributes:
const PROP_NAME = Symbol(a);const PROP_AGE = Symbol(a);let obj = {
[PROP_NAME]: "One kilo code"}; obj[PROP_AGE] =18;
obj[PROP_NAME]; // 'one catty code'
obj[PROP_AGE]; / / 18
Copy the code
This leads to another interesting question: what difference does it make when enumerating the keys of an object using Symbol as its attribute key? In practice, we often need to use object.keys () or for… In to enumerate the attribute names of an object. How does a key of type Symbol behave differently in this respect? Look at the following example code:
let obj = {
[Symbol("name")]: "One kilo code".age: 18.title: "Engineer"};Object.keys(obj); // ['age', 'title']
for (let p in obj) {
console.log(p); // Output: 'age' and 'title', respectively
}
Object.getOwnPropertyNames(obj); // ['age', 'title']
Copy the code
The key of type Symbol cannot be passed through object.keys () or for… In, which is not included in the object’s own set of property names. Therefore, using this feature, we can use Symbol to define some properties that do not require external operation and access.
Because of this feature, when an object is converted to a JSON string using json.stringify (), the Symbol property is also excluded from the output:
JSON.stringify(obj); // {"age":18,"title":"Engineer"}
Copy the code
We can take advantage of this feature to better design our data objects, making “internal operations” and “external selective output” more elegant.
However, in this case, we can not get the object properties defined as Symbol? No. There will still be specific apis for Symbol, such as:
// Use the Object API
Object.getOwnPropertySymbols(obj); // [Symbol(name)]
// Use the new reflection API
Reflect.ownKeys(obj); // [Symbol(name), 'age', 'title']
Copy the code
3.2 Use Symbol instead of constant
Take a look at the following code, is it often in your code?
const TYPE_AUDIO = "AUDIO";
const TYPE_VIDEO = "VIDEO";
const TYPE_IMAGE = "IMAGE";
function handleFileResource(resource) {
switch (resource.type) {
case TYPE_AUDIO:
playAudio(resource);
break;
case TYPE_VIDEO:
playVideo(resource);
break;
case TYPE_IMAGE:
previewImage(resource);
break;
default:
throw new Error("Unknown type of resource"); }}Copy the code
As in the code above, we often define a set of constants to represent several different types of a business logic. We usually want a unique relationship between these constants. To ensure this, we need to assign a unique value to the constant (for example, ‘AUDIO’, ‘VIDEO’, ‘IMAGE’). It’s fine when you have fewer constants, but when you have more constants, you might have to think a little harder to come up with a better name for them.
Now with Symbol, we don’t have to:
const TYPE_AUDIO = Symbol(a);const TYPE_VIDEO = Symbol(a);const TYPE_IMAGE = Symbol(a);Copy the code
In this way, the values of the three constants are directly guaranteed to be unique! Isn’t that convenient?
3.3 Use Symbol to define private properties/methods of a class
We know that in JavaScript, there is no access control keyword private in object-oriented languages such as Java. All properties or methods defined on a class are publicly accessible. So this was a bit of a hassle when we were designing the API.
With Symbol and modularity, class private properties and methods are possible. Such as:
In the file A.js
const PASSWORD = Symbol(a);class Login {
constructor(username, password) {
this.username = username;
this[PASSWORD] = password;
}
checkPassword(pwd) {
return this[PASSWORD] === pwd; }}export default Login;
Copy the code
In the file B.js
import Login from "./a";
const login = new Login("admin"."123456");
login.checkPassword("123456"); // true
login.PASSWORD; // oh! no!
login[PASSWORD]; // oh! no!
login["PASSWORD"]; // oh! no!
Copy the code
Since the Symbol constant PASSWORD is defined in the module where a.js is located, it is not possible to create an identical Symbol (because symbols are unique). Therefore, the PASSWORD’s Symbol can only be used within a.js, so the class attributes defined by it cannot be accessed outside the module, achieving a private effect.
3.4 Manually implement Symbol
(function() {
var root = this;
var generateName = (function() {
var postfix = 0;
return function(descString) {
postfix++;
return "@ @" + descString + "_"+ postfix; }; }) ();var SymbolPolyfill = function Symbol(description) {
if (this instanceof SymbolPolyfill)
throw new TypeError("Symbol is not a constructor");
var descString =
description === undefined ? undefined : String(description);
var symbol = Object.create({
toString: function() {
return this.__Name__;
},
valueOf: function() {
return this; }});Object.defineProperties(symbol, {
__Description__: {
value: descString,
writable: false.enumerable: false.configurable: false,},__Name__: {
value: generateName(descString),
writable: false.enumerable: false.configurable: false,}});return symbol;
};
var forMap = {};
Object.defineProperties(SymbolPolyfill, {
for: {
value: function(description) {
var descString =
description === undefined ? undefined : String(description);
return forMap[descString]
? forMap[descString]
: (forMap[descString] = SymbolPolyfill(descString));
},
writable: true.enumerable: false.configurable: true,},keyFor: {
value: function(symbol) {
for (var key in forMap) {
if (forMap[key] === symbol) returnkey; }},writable: true.enumerable: false.configurable: true,}}); root.SymbolPolyfill = SymbolPolyfill; }) ();Copy the code
Four, the specific storage form of variables in memory
4.1 Stack memory and heap memory
Variables in JavaScript are divided into basic types and reference types:
- Basic types are simple data segments that are stored in stack memory. Their values have a fixed size, are stored in stack space, and are accessed by value
- A reference type is an object stored in the heap memory. The value size is not fixed. The access address of the object stored in the stack memory points to the object in the heap memory
4.2 Combined with code and figure to understand
let a1 = 0; / / stack memory
let a2 = "this is string"; / / stack memory
let a3 = null; / / stack memory
let b = { x: 10 }; // The variable b is in the stack, and {x: 10} is in the heap as an object
let c = [1.2.3]; // The variable c is in the stack, and [1, 2, 3] is in the heap as an object
Copy the code
When we want to access a reference data type in the heap:
- Gets an address reference to the object from the stack
- And get the data we need from the heap
4.3 Replication Occurs for basic Types
let a = 20;
let b = a;
b = 30;
console.log(a); / / 20
Copy the code
Combine with the following figure to understand:
When data in the stack memory is replicated, the system automatically allocates a new value for the new variable, and eventually these variables are independent of each other.
4.4 Reference Type Replication Occurs
let a = { x: 10.y: 20 };
let b = a;
b.x = 5;
console.log(a.x); / / 5
Copy the code
- The copy of the reference type also allocates a new value to the new variable b, which is stored in stack memory, except that the value is just an address pointer to the reference type
- They both point to the same value, that is, the same address pointer, and the actual object that is accessed in the heap memory is actually the same
- So when you change B.x, a.x also changes, which is a property of reference types
Combined with the following figure:
Conclusion:
5. Built-in objects and packing and unpacking operations
5.1 Built-in functions (Objects) in JS
String(), Number(), Boolean(), RegExp(), Date(), Error(), Array(), Function(), Object(), symbol(); Similar to the constructor of an object
- The variables constructed by these built-in functions are objects that encapsulate values of basic types such as:
var a = new String("abb"); // typeof(a)=object
Copy the code
All variables are objects except those constructed with Function() that are printed as functions by Typeof
- To know the true type of the constructed variable we can use:
Object.prototype.toString.call([1.2.3]); // "[object,array]"
Copy the code
The next value is the type of the parameter passed in
- Do not define variables if they are assigned in constant form (that is, using basic data types)
5.2 packing
Which is to convert a basic type to its corresponding object. Packing is divided into implicit and display
- Implicit packing: Every time a value of a primitive type is read, an object of that primitive type is created behind the scenes. Calling a method on this primitive type is actually calling a method on this primitive type object. The object of this basic type is temporary, it exists only at the moment of execution of the line of code that calls the method, and is destroyed immediately after the method is executed.
let num = 123;
num.toFixed(2); // '123.00'// The real step above the code in the background is
var c = new Number(123);
c.toFixed(2);
c = null;
Copy the code
-
Create an instance of type Number.
-
Methods are called on the instance.
-
Destroy the instance.
- Explicit packing: You can use built-in objects such as Boolean, Object, and String to display packing for basic types.
var obj = new String("123");
Copy the code
5.3 split open a case
Unboxing, as opposed to boxing, converts an object to a value of a primitive type. The abstract operation ToPrimitive is called inside the unboxing process. The operation takes two arguments, the first is the object to be converted, and the second parameter, PreferredType, is the type the object is expected to be converted to. The second argument is not required. By default, it is number, meaning that the object is expected to be of type number
-
Number is converted to object
-
First call the valueOf method on the object itself. If you return a value of the original type, you use the Number function directly on that value and return the result.
-
If valueOf returns the object again, continue calling the toString method on the object itself. If toString returns a value of the original type, then use the Number function on that value and return the result.
-
If toString returns an object again, an error is reported.
-
Number([1]); / / 1Transformation evolution: [1].valueOf(); / / [1];
[1].toString(); / / '1'; Number('1'); / / 1
Copy the code
-
String to object
-
First call the toString method of the object itself. If a value of the primitive type is returned, the String function is used on the value, returning the result.
-
If toString returns an object, the call to valueOf continues. If valueOf returns a valueOf the original type, then the String function is used on the value and the result is returned.
-
If valueOf returns an object again, an error is reported.
-
String([1.2]) / / 1, 2, ""Transformation and evolution: [1.2].toString(); / / 1, 2, ""
String("1, 2,"); / / 1, 2, ""
Copy the code
-
Boolean Transform object
Boolean Conversion objects are special in that they are true except for the following six values that are converted to false
undefined null false 0(including +0And -0) NaNEmpty string (' ')
Boolean(undefined) //false
Boolean(null) //false
Boolean(false) //false
Boolean(0) //false
Boolean(NaN) //false
Boolean(' ') //false
Boolean([]) //true
Boolean({}) //true
Boolean(new Date()) //true
Copy the code
Value types and reference types
6.1 Different memory allocations when declaring variables
- Raw values: Simple segments of data stored in a stack, that is, their values are stored directly where variables are accessed.
This is because these primitive types occupy a fixed amount of space, so they can be stored in a smaller area of memory – the stack. This storage facilitates quick lookup of the variable’s value.
- Reference value: an object stored in the heap, that is, a value stored in a variable is a pointer to the memory address where the object is stored.
This is because: the size of the reference value changes, so you can’t put it on the stack, otherwise it will slow down variable lookup. Instead, the value placed in the stack space of a variable is the address that the object is stored in the heap. The size of the address is fixed, so storing it on the stack has no negative impact on variable performance.
6.2 Different memory allocation mechanisms also bring different access mechanisms
-
In JS, direct access to objects stored in the heap memory is not allowed, so when accessing an object, first get the address of the object in the heap memory, and then according to this address to get the value of the object, this is the legend of access by reference.
-
Values of primitive types are directly accessible.
6.3 Differences in copying variables
-
Original value: When a variable that holds the original value is copied to another variable, a copy of the original value is assigned to the new variable. After that, the two variables are completely independent, they just have the same value, they are independent of each other.
-
Reference value: When a variable that holds the memory address of an object is copied to another variable, the memory address is assigned to the new variable. This means that both variables refer to the same object in the heap, and any changes made to either of them are reflected in the other. (When you copy an object, you don’t create an identical object in the heap, just a variable that holds a pointer to it.)
6.4 Differences in parameter passing (the process of copying arguments to parameters)
First, let’s be clear: Arguments to all functions in ECMAScript are passed by value. But why is there still a difference when it comes to the value of a primitive type versus a reference type? Because of the difference in memory allocation.
-
Raw value: just pass the value of the variable to the parameter, after which the parameter and the variable do not affect each other.
-
Reference value: an object variable whose value is the memory address of the object in the heap, keep that in mind!
So the value it passes is the memory address, and that’s why any changes to this parameter inside the function are external, because they all point to the same object.
The difference between null and undefined
7.1 define
Null type: The Null type also has a special value — Null. From a logical point of view, a null value represents an empty object pointer.
Undefined: The Undefined type has only one value, the special Undefined. When a variable is declared with var but not initialized, the value of the variable is undefined.
7.2 Application Scenarios of Null and undefined
Null means “no object”, meaning there should be no value there. Typical usage:
- As an argument to a function, indicating that the argument to the function is not an object.
- As the end point of the object prototype chain.
console.log(null instanceof Object); // false
Copy the code
Undefined means “missing value,” meaning there should be a value here, but it’s not defined yet. Typical usage:
- When a variable is declared but not assigned, it is undefined.
- When the function is called, the argument that should be provided is not provided, which equals undefined.
- Object has no attribute to which a value is undefined.
- A function that returns no value returns undefined by default.
7.3 Number Conversion value
Number(null) outputs 0, Number(undefined) outputs NaN
8. Determine the array type
There are at least three ways to determine JavaScript data types, their advantages and disadvantages, and how to accurately determine array types
8.1 typeof
- Applicable scenario
The typeof operator can accurately determine whether a variable is of the following primitive types:
typeof "ConardLi"; // string
typeof 123; // number
typeof true; // boolean
typeof Symbol(a);// symbol
typeof undefined; // undefined
Copy the code
You can also use it to determine the type of a function:
typeof function() {}; // function
Copy the code
-
Inapplicable Scenarios
When you use typeof to determine the typeof a reference, it seems a little weak:
typeof []; // object
typeof {}; // object
typeof new Date(a);// object
typeof /^\d*$/; // object
Copy the code
All reference types except functions are determined to be object.
Typeof null === ‘object’ is a bug that has been around since the first version of JavaScript, but has not been fixed since then due to compatibility problems caused by changes…
8.2 instanceof
The instanceof operator helps us determine what type of object the reference is:
[] instanceof Array; // true
new Date(a)instanceof Date; // true
new RegExp(a)instanceof RegExp; // true
Copy the code
Let’s review a few rules of the prototype chain:
- All reference types have object properties, that is, properties that can be freely extended
- All reference types have one
__proto__
(Implicit stereotype) property, which is a normal object - All functions have
prototype
(explicit stereotype) property, also a common object - All reference types
__proto__
Value that points to its constructorprototype
- When trying to get a property on an object, if the variable itself doesn’t have the property, it’s ignored
__proto__
To look for in
[] instanceof Array [] instanceof Array [] Instanceof Array Therefore, using instanceof to detect data types is not very accurate, which is not what it was designed for:
[] instanceof Object // true
function(){} instanceof Object // true
Copy the code
In addition, using Instanceof does not detect basic data types, so instanceof is not a good choice.
8.3 the toString
The toString function, mentioned above in the unboxing operation, can be called to convert from a reference type.
Every reference type has a toString method, and by default, the toString() method is inherited by every Object. If this method is not overridden in a custom object, toString() returns “[object type]”, where type is the type of the object.
const obj = {};
obj.toString(); // [object Object]
Copy the code
Note that toString only works if this method is not overridden in the custom object. In fact, most reference types such as Array, Date, RegExp, and so on override the toString method.
We can call it directlyObject
Not covered on the prototypetoString()
Method, usecall
To change thethis
Pointing to get what we want.
8.4 the jquery
Let’s take a look at how to determine the type in jquery source code:
var class2type = {};
jQuery.each(
"Boolean Number String Function Array Date RegExp Object Error Symbol".split(
""
),
function(i, name) {
class2type["[object " + name + "]"] = name.toLowerCase(); }); type:(obj) = > {
if (obj == null) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function"
? class2type[Object.prototype.toString.call(obj)] || "object"
: typeof obj;
};
isFunction: (obj) = > {
return jQuery.type(obj) === "function";
};
Copy the code
Primitive types directly using the typeof, use a reference type Object. The prototype. ToString. Call type. Determine an Array type Array can be used. The isArray (value) or the Object. The prototype. ToString. Call (value).
Implicit type conversions
Scenarios where implicit type conversions can occur and the principles of conversions, how can they be avoided or skillfully applied
Since JavaScript is a weakly typed language, conversions occur quite frequently, and the above mentioned boxing and unboxing is really a type conversion.
There are two types of conversions: implicit conversions, which are what the program does automatically, and casts, which are what we do manually.
Without further mention of casts here, let’s take a look at a few scenarios where implicit casts can be a headache and how to do them:
9.1 Type Conversion Rules
If an implicit conversion occurs, the following rules apply to the types conversion:
9.2 If statements and logical statements
In if and logic statements, if there is only a single variable, the variable is first converted to a Boolean value. Only the following cases are converted to false, and the rest are converted to true:
null;
undefined;
("");
NaN;
0;
false;
Copy the code
9.3 Various mathematical operators
When we use the mathematical operator (- * /) on non-number types, we first convert non-number types to Number;
1 - true; / / 0
1 - null; // 1
1 * undefined; // NaN
2 * ["5"]; // 10
Copy the code
Note that + is an exception. When we execute the + operator:
- When a side for
String
Type is recognized as a string concatenation and the other side is converted to a string type first. - When a side for
Number
Type, the other side is the original type, converts the original type toNumber
Type. - When a side for
Number
Type, the other side is the reference type, which will refer to the type andNumber
Concatenation after type conversion to string.
123 + "123"; // 123123 (Rule 1)
123 + null; // 123 (Rule 2)
123 + true; // 124 (Rule 2)
123 + {}; // 123[object object] (Rule 3)
Copy the code
9.4 = =
When == is used, if both sides are of the same type, the result is the same as ===, otherwise an implicit conversion will occur. When == is used, the conversion can be classified into several different cases (only considering the two sides are of different types) :
-
NaN
NaN always returns false when compared to any other type (including itself).
NaN= =NaN; // false
Copy the code
-
Boolean
Boolean compared to any other type, Boolean is first converted to the Number type.
true= =1; // true
true= ="2"; // false
true= = ["1"]; // true
true= = ["2"]; // false
Copy the code
Note one confusing point here: undefined, null, and Boolean comparisons. Although undefined, null, and false are all easily imagined to be false, they compare to false because false is first converted to 0:
undefined= =false; // false
null= =false; // false
Copy the code
-
String and Number
To compare String with Number, first convert String to Number.
123= ="123"; // true
""= =0; // true
Copy the code
-
Null, and undefined
Null == undefined compares to true, and null, undefined, and any other result compares to false.
null= =undefined; // true
null= =""; // false
null= =0; // false
null= =false; // false
undefined= =""; // false
undefined= =0; // false
undefined= =false; // false
Copy the code
-
Primitive types and reference types
When the original type is compared to the reference type, the object type is converted to the original type according to ToPrimitive rules:
"[object Object]"= = {};// true
"1, 2, 3"= = [1.2.3]; // true
Copy the code
Take a look at this comparison:
[] = =! [];// true
Copy the code
! Has a higher priority than ==,! [] is first converted to false, and then, according to the third point above, false is converted to Number type 0, and the left [] is converted to 0.
([null] = =false[undefined= =])// true
false; // true
Copy the code
According to the ToPrimitive rule of arrays, if an element is null or undefined, the element is treated as an empty string, so both [null] and [undefined] are converted to 0. So, having said all that, it is recommended to use === to determine whether two values are equal…
9.5 An interesting interview question
A: A == 1 && a == 2 && a == 3. Based on the unboxing conversion above, and the implicit conversion of ==, we can easily write the answer:
const a = {
value: [3.2.1].valueOf: function() {
return this.value.pop(); }};Copy the code
Ten, decimal accuracy
The reason for the loss of decimal precision, the maximum number that JavaScript can store, the maximum security number, the method of JavaScript processing large numbers, and the method of avoiding the loss of precision
10.1 Causes of loss of decimal precision
The binary implementation of the computer and the bit limit some numbers cannot be represented finitely. Just as some irrational numbers cannot be represented in a finite way, such as PI 3.1415926… , 1.3333… And so on. JS followIEEE 754Specification, using double precision storage (double precision), occupy 64 bit. As shown in figure
The 1 bit is used to represent the sign bit
The 11 bits are used for exponentials
52 bits represent mantissa
Floating point numbers, for example
1
2
0.1 >> 0.0001 1001 1001 1001… (1001 infinite loop)
0.2 >> 0.0011 0011 0011 0011… (0011 infinite loop)
At this point, we can only imitate decimal rounding, but binary only has 0 and 1, so it becomes 0 rounding to 1. This is the computer part of the floating point number operation error, loss of accuracy of the root cause. The maximum and minimum security values of JS can be obtained as follows:
console.log(Number.MAX_SAFE_INTEGER); / / 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); / / - 9007199254740991
Copy the code
With integers, the chances of front-end problems may be low, since very few businesses need to use very large integers, and as long as the result of the calculation does not exceed Math.pow(2, 53), you will not lose accuracy. If the maximum safe Number is exceeded, use BigInt(Number).
For decimal, the front end of the problem is still a lot of probability, especially in some e-commerce sites involving the amount of data. Solution: Put the decimals in place of the whole number (multiply by multiple), and then shrink back to the original multiple (divide by multiple), that is, try to avoid dealing with decimals in business.
Chapter 2 “Relearning JavaScript” prototypes and prototype chains
I. Prototype design patterns and prototype rules
Understand prototyping patterns and prototyping rules in JavaScript
1.1 Design Patterns
1.1.1 Factory mode
Create an object inside a function, assign properties and methods to the object, and return the object
function Person() {
var People = new Object(a); People.name ="CrazyLee";
People.age = "25";
People.sex = function() {
return "boy";
};
return People;
}
var a = Person();
console.log(a.name); // CrazyLee
console.log(a.sex()); // boy
Copy the code
1.1.2 Constructor mode
Instead of recreating the object inside the function, use this to refer to it
function Person() {
this.name = "CrazyLee";
this.age = "25";
this.sex = function() {
return "boy";
};
}
var a = new Person();
console.log(a.name); // CrazyLee
console.log(a.sex()); // boy
Copy the code
1.1.3 Prototype mode
Instead of defining the properties in the function, define the properties using the Prototype property so that all object instances can share their properties and methods.
function Parent() {
Parent.prototype.name = "carzy";
Parent.prototype.age = "24";
Parent.prototype.sex = function() {
var s = "Female";
console.log(s);
};
}
var x = new Parent();
console.log(x.name); // crazy
console.log(x.sex()); / / women
Copy the code
1.1.4 Mixed Mode
Prototype pattern + Constructor pattern. In this pattern, the constructor pattern is used to define instance properties, while the stereotype pattern is used to define methods and shared properties
function Parent() {
this.name = "CrazyLee";
this.age = 24;
}
Parent.prototype.sayname = function() {
return this.name;
};
var x = new Parent();
console.log(x.sayname()); // Crazy
Copy the code
1.1.5 Dynamic prototype mode
All information is encapsulated in the constructor, and by initializing the prototype in the constructor, this person can decide whether or not to initialize the prototype by determining whether the method is valid.
function Parent() {
this.name = "CrazyLee";
this.age = 24;
if (typeof Parent._sayname == "undefined") {
Parent.prototype.sayname = function() {
return this.name;
};
Parent._sayname = true; }}var x = new Parent();
console.log(x.sayname());
Copy the code
1.2 Prototype Rules
1.2.1 Prototype rules
- All reference types (array, object, function), have object characteristics, can be freely extended properties;
var arr = [];
arr.a = 1;
Copy the code
- For all reference types, there is one
__proto__
Property (implicit stereotype), the value of which is a normal object; - All functions have a prototype, and the property value is a normal prototype.
- For all reference types (arrays, objects, functions), the implicit stereotype points to the explicit stereotype of their constructor;
(obj.__proto__ === Object.prototype)
; - When trying to get a property of an object, if the object itself does not have the property, it will be removed
__proto__
(its constructor’s prototype).
1.2.2 Prototype objects
In js, one of the properties of the function object is the prototype object. Normal objects do not have a prototype property, but they do have a __proto__ property. The function of a stereotype is to add a uniform method to every object in the class. The methods and properties defined in the stereotype are shared by all instance objects.
var person = function(name){
this.name = name
};
person.prototype.getName=function(){ // Set the function object properties via person.prototype
return this.name;
}
var crazy= newThe person (" crazyLee "); crazy.getName();// crazyLee//crazy inheritance properties
Copy the code
1.2.3 prototype chain
When trying to get a property of an object f, if the object itself does not have the property, look in its __proto__ (its constructor’s prototype) obj.__proto__; __proto__.__proto__ (that is, the prototype of obj’s constructor’s prototype) is looked for when obj.__proto__ is not available;
Second, the instanceof
Instanceof of the underlying implementation principle, manual implementation of an Instanceof
function instance_of(L, R) {
//L represents the left expression and R represents the right expression
var O = R.prototype; // Take the display prototype of R
L = L.__proto__; // Take the implicit prototype of L
while (true) {
if (L === null) return false;
if (O === L)
// Return true if O explicit stereotype is strictly equal to L implicit stereotype
return true; L = L.__proto__; }}Copy the code
Three, inheritance,
There are several ways to implement inheritance and their pros and cons
3.1 Prototype chain inheritance
The basic idea of stereotype chain inheritance is to use stereotypes to make one reference type inherit the properties and methods of another reference type.
function SuperType() {
this.name = "yanxugong";
this.colors = ["pink"."blue"."green"];
}
SuperType.prototype.getName = function() {
return this.name;
};
function SubType() {
this.age = 22;
}
SubType.prototype = new SuperType();
SubType.prototype.getAge = function() {
return this.age;
};
SubType.prototype.constructor = SubType;
let instance1 = new SubType();
instance1.colors.push("yellow");
console.log(instance1.getName()); // 'yanxugong'
console.log(instance1.colors); // ["pink", "blue", "green", "yellow"]
let instance2 = new SubType();
console.log(instance2.colors); // ["pink", "blue", "green", "yellow"]
Copy the code
Disadvantages:
- When inheritance is implemented through a stereotype, the stereotype becomes an instance of another type, the original instance property becomes the current stereotype property, and the reference type property of the stereotype is shared by all instances.
- When creating an instance of a subtype, there is no way to pass arguments to the constructor of a supertype without affecting all object instances.
3.2 Borrowing constructors
Using the technique of constructors, the basic idea is to call the supertype constructor in the subtype constructor.
function SuperType(name) {
this.name = name;
this.colors = ["pink"."blue"."green"];
this.getColors = function() {
return this.colors;
};
}
SuperType.prototype.getName = function() {
return this.name;
};
function SubType(name) {
SuperType.call(this, name);
this.age = 22;
}
let instance1 = new SubType("yanxugong");
instance1.colors.push("yellow");
console.log(instancel.colors); // ['pink','blue','green','yellow']
console.log(instancel.getColors()); // ["pink", "blue", "green", "yellow"]
console.log(instancel.getName); // undefined
let instance2 = new SubType("Jack");
console.log(instance2.colors); // ['pink','blue','green']
console.log(instance2.getColors()); // ["pink", "blue", "green"]
console.log(instance2.getName); // undefined
Copy the code
Advantages:
- You can pass parameters to the superclass
- Solved the problem of containing reference type values in stereotypes that are shared by all instances
Disadvantages:
- Methods are defined in constructors, function reuse is not possible, and methods defined in a supertype prototype are invisible to subtypes.
3.3 Composite Inheritance
Composite inheritance refers to an inheritance pattern that combines prototype chains and borrowed constructor techniques together, thereby exploiting the best of both.
Basic ideas:
Using prototype chain to realize the inheritance of prototype properties and methods, and borrowing constructors to realize the inheritance of instance properties, not only by defining methods on the prototype to realize function reuse, but also ensure that each instance has its own properties.
function SuperType(name) {
this.name = name;
this.colors = ["pink"."blue"."green"];
}
SuperType.prototype.getName = function() {
return this.name;
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
return this.age;
};
let instancel = new SubType("yanxugong".20);
instancel.colors.push("yellow");
console.log(instancel.colors); // ['pink','blue','green','yellow']
console.log(instancel.sayAge()); / / 20
console.log(instancel.getName()); // yanxugong
let instance2 = new SubType("Jack".18);
console.log(instance2.colors); // ['pink','blue','green']
console.log(instance2.sayAge()); / / 18
console.log(instance2.getName()); // Jack
console.log(new SuperType("po"));
Copy the code
Disadvantages:
- In any case, the supertype constructor is called twice: once when the subtype prototype is created, and once inside the subtype constructor.
Advantages:
- You can pass parameters to the superclass
- Each instance has its own properties
- Function reuse is realized
3.4 Inheritance of the original type
The basic idea of prototype inheritance:
Stereotypes allow you to create new objects based on existing ones without having to create custom types.
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
Copy the code
Inside the object() function, we create a temporary constructor, take the passed object as the prototype for the constructor, and finally return a new instance of the temporary type. Essentially, object() makes a shallow copy of the passed object.
ECMAScript5 specifies the original type inheritance by adding the object.create () method. This method takes two arguments: an Object to be used as a stereotype for the new Object and (optionally) an Object to define additional properties for the new Object (which can override properties of the same name on the stereotype Object). With one argument passed, the object.create () and Object () methods behave the same.
var person = {
name: "yanxugong".hobbies: ["reading"."photography"]};var personl = Object.create(person);
personl.name = "jack";
personl.hobbies.push("coding");
var person2 = Object.create(person);
person2.name = "Echo";
person2.hobbies.push("running");
console.log(person.hobbies); // ["reading", "photography", "coding", "running"]
console.log(person.name); // yanxugong
console.log(personl.hobbies); // ["reading", "photography", "coding", "running"]
console.log(personl.name); // jack
console.log(person2.hobbies); // ["reading", "photography", "coding", "running"]
console.log(person2.name); // Echo
Copy the code
In cases where there is no need to create a constructor and only one object remains similar to another, prototype inheritance works.
Disadvantages:
- As with prototype chain implementation inheritance, attributes that contain the value of a reference type are shared by all instances.
3.5 Parasitic inheritance
Parasitic inheritance is a kind of thought closely related to original inheritance. The idea of parasitic inheritance is similar to the parasitic constructor and factory pattern, which is to create a function that only encapsulates the inheritance process, internally enhances the object in some way, and then returns the object as if it really did all the work.
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function createAnother(original) {
var clone = object(original); // Create a new object by calling a function
clone.sayHi = function() {
// Enhance the object in some way
console.log("hi");
};
return clone; // Return this object
}
var person = {
name: "yanxugong".hobbies: ["reading"."photography"]};var personl = createAnother(person);
personl.sayHi(); // hi
personl.hobbies.push("coding");
console.log(personl.hobbies); // ["reading", "photography", "coding"]
console.log(person); // {hobbies:["reading", "photography", "coding"],name: "yanxugong"}
Copy the code
A new object, personl, is returned based on person, which not only has all the properties and methods of Person, but also has its own sayHi() method. Parasitic inheritance is also a useful pattern in cases where you consider objects rather than custom types and constructors.
Disadvantages:
- Using parasitic inheritance to add functions to objects is inefficient due to the inability to reuse functions.
- As with prototype chain implementation inheritance, attributes that contain the value of a reference type are shared by all instances.
3.6 Parasitic combinatorial inheritance
Parasitic combinatorial inheritance, in which properties are inherited by borrowing constructors, and methods are inherited by a blend of prototype chains.
Basic ideas:
Instead of calling the constructor of the supertype to specify the stereotype of the subtype, all we need is a copy of the stereotype of the supertype, essentially using parasitic inheritance to inherit the stereotype of the supertype and then assigning the result to the stereotype of the subtype. The basic pattern of parasitic combinatorial inheritance is as follows:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); // Create an object
prototype.constructor = subType; // Enhance the object
subType.prototype = prototype; // Specify an object
}
Copy the code
- Create a copy of the supertype prototype
- Add the Constructor attribute to the replica that is created
- Assigns the newly created object to the prototype of the subtype
At this point, we can replace the assignment to the subtype prototype with a call to inheritPrototype:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); // Create an object
prototype.constructor = subType; // Enhance the object
subType.prototype = prototype; // Specify an object
}
function SuperType(name) {
this.name = name;
this.colors = ["pink"."blue"."green"];
}
SuperType.prototype.getName = function() {
return this.name;
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
return this.age;
};
let instancel = new SubType("yanxugong".20);
instancel.colors.push("yellow");
console.log(instancel.colors); // ['pink','blue','green','yellow']
console.log(instancel.sayAge()); / / 20
console.log(instancel.getName()); // yanxugong
let instance2 = new SubType("Jack".18);
console.log(instance2.colors); // ['pink','blue','green']
console.log(instance2.sayAge()); / / 18
console.log(instance2.getName()); // Jack
console.log(new SuperType("po"));
Copy the code
Advantages:
- The superclass constructor is called only once, which is more efficient. Avoid creating unnecessary properties on suberType. prototype while keeping the prototype chain intact.
- Parasitic combination inheritance is therefore the most rational inheritance paradigm for reference types.
Fourth, the case of prototype inheritance
Name at least one example of prototype inheritance being used in an open source project such as Node
4.1 Vue.extend( options )
-
Parameters:
{Object} options
-
Usage:
Using the base Vue constructor, create a “subclass”. A parameter is an object that contains component options.
Note that the data option is a special case – in vue.extend () it must be a function
<div id="mount-point"></div>
Copy the code
// Create the constructor
var Profile = Vue.extend({
template: "<p>{{firstName}} {{lastName}} aka {{alias}}</p>".data: function() {
return {
firstName: "Walter".lastName: "White".alias: "Heisenberg"}; }});// Create a Profile instance and mount it to an element.
new Profile().$mount("#mount-point");
Copy the code
Here are the results:
<p>Walter White aka Heisenberg</p>
Copy the code
4.2 Why extend
In the Vue project, after we have the initial root instance, all the pages are basically managed through the Router, and the components are locally registered through the import, so we don’t need to pay attention to the creation of the components, which is a little easier than extend. But there are several disadvantages to doing so:
- Component templates are predefined. What if I want to render components dynamically from the interface?
- Everything is in
#app
Under render, registered components are rendered in the current location. If I were to implement something likewindow.alert()
What if the prompt component asks you to call it like a JS function? At this time,Vue.extend + vm.$mount
That’s where the combination comes in.
The new operator
You can describe the detailed process of new an object by manually implementing a new operator
So what does the new operator do? What does it do? Think of it in the following code:
// Create a class (constructor)
function Otaku(name, age) {
this.name = name;
this.age = age;
// Attribute of self
this.habit = "pk";
}
// Add attributes and methods to the class stereotype
Otaku.prototype.strength = 60;
Otaku.prototype.sayYourName = function() {
console.log("I am " + this.name);
};
// Instantiate a Person object
const person = new Otaku("Xiao feng.".5000);
person.sayYourName(); // I am Qiao Feng
console.log(person); // Print the constructed instance
Copy the code
5.1 analytical
From the console printout, we can see that the new operator does a few things:
- Returns (generates) a new object
- A property in the class Otaku constructor is accessed
- Access the properties and methods on the Otaku prototype and set the point to this (to the newly generated instance object)
From the above analysis, we can know that there must be an Object in the new group, otherwise the creation of objects is a little unclear. Let’s start by writing:
// We need to return an object to implement the new operation
// Pass the required parameters: class + attribute
const person = new Otaku("Xiao feng.".5000);
const person1 = objectFactory(Otaku, "Jiu Mozhi".5000);
// Start implementing the objectFactory method
function objectFactory(obj, name, age) {}
// This method writes itself dead so that it can only construct obj of prototype obj and has only name and age attributes
// In js functions, arguments make the function arguments very flexible. You can use arguments to get the function arguments inside the function
function objectFactory() {
console.log(arguements); / / {' 0 ': [Function: Otaku],' 1 ', 'dove mo think,' 2 ': 5000}
// We print the result from the Arguments class array, which we can see contains the constructor and any other arguments we passed when we called the ObjectFactory
// How do you get the constructor and other parameters
// Since Arguments is a class array, there is no direct method to use. We can use the following two methods:
// 1. Array.from(arguments).shift(); // Convert to array. Use the array method shift to pop the first item
// 2. [].shift().call(arguments); // Let arguments borrow the shift method with call()
const Constructor = [].shift.call(arguments);
const args = arguments;
// Create an empty object
let obj = new Object(a);// The next idea is to give obj the prototype of the new object that points to the prototype of its constructor
// Pass properties to the constructor. Note: the this property of the constructor
// Constructor assigns a value to the obj property. This should refer to the obj object
// This is implemented using call and apply when the function is manually specified inside Coustructor
let result = Constructor.apply(obj, arguments);
// Make sure new comes out with an object
return typeof result === "object" ? result : obj;
}
Copy the code
The code above has too many comments, so delete the code after the comments:
function objectFactory() {
let Constructor = [].shift.call(arguments);
const obj = new Object(a); obj.__proto__ = Conctructor.prototype;let result = Constructor.apply(obj, arguments);
return typeof result === "object" ? result : obj;
}
Copy the code
There is another operation:
function myNew(Obj, ... args) {
var obj = Object.create(Obj.prototype); // Create a new object using the specified stereotype object and its properties
Obj.apply(obj, args); // Bind this to obj and set the properties of obj
return obj; // Return the instance
}
Copy the code
Class construction and inheritance
Understand the underlying principles of ES6 class construction and inheritance
6.1 ES6 Class usage
While javascript uses prototype-inheritance, we can implement class inheritance through archetypal features, ES6 gives us the same syntactic sugar as object-oriented inheritance.
class Parent {
constructor(a) {
this.filed1 = a;
}
filed2 = 2;
func1 = function() {};
}
class Child extends Parent {
constructor(a, b) {
super(a);
this.filed3 = b;
}
filed4 = 1;
func2 = function() {};
}
Copy the code
Let’s use Babel to explore how ES6 classes and inheritance are implemented.
6.2 Class implementation
Before conversion:
class Parent {
constructor(a) {
this.filed1 = a;
}
filed2 = 2;
func1 = function() {};
}
Copy the code
After the transformation:
function _classCallCheck(instance, Constructor) {
if(! (instanceinstanceof Constructor)) {
throw new TypeError("Cannot call a class as a function"); }}var Parent = function Parent(a) {
_classCallCheck(this, Parent);
this.filed2 = 2;
this.func1 = function() {};
this.filed1 = a;
};
Copy the code
At the bottom of the visible class is still the constructor:
- The _classCallCheck method is called to determine whether the current function call was preceded by the new keyword.
The constructor is preceded by the new keyword, which creates an empty object inside the constructor, pointing the proptype of the constructor to its __proto__, and pointing this to the empty object. As above, in _classCallCheck: this instanceof Parent returns true.
If the constructor is not preceded by a new then the proptype of the constructor does not appear on the prototype chain of this, returning false.
-
Assign variables and functions inside the class to this.
-
Executes the logic inside a Constuctor.
-
Return this (the constructor defaults to what we do at the end).
6.3 Inheritance Implementation
Before conversion:
class Child extends Parent {
constructor(a, b) {
super(a);
this.filed3 = b;
}
filed4 = 1;
func2 = function() {};
}
Copy the code
After the transformation:
Let’s look at the inner implementation of the Child first, and then look at how the inner function is implemented:
var Child = (function(_Parent) {
_inherits(Child, _Parent);
function Child(a, b) {
_classCallCheck(this, Child);
var _this = _possibleConstructorReturn(
this,
(Child.__proto__ || Object.getPrototypeOf(Child)).call(this, a)
);
_this.filed4 = 1;
_this.func2 = function() {};
_this.filed3 = b;
return _this;
}
return Child;
})(Parent);
Copy the code
- call
_inherits
The function inherits propType from the parent class.
_inherits internal implementation
function _inherits(subClass, superClass) {
if (typeofsuperClass ! = ="function"&& superClass ! = =null) {
throw new TypeError(
"Super expression must either be null or a function, not " +
typeof superClass
);
}
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);
}
Copy the code
-
Verify the parent constructor.
-
Typical parasitic inheritance: Create an empty object with the propType of the parent constructor and point that object to the PropType of the subclass constructor.
-
Point the parent constructor to the child constructor’s __proto__ (it’s not clear what this does, so it doesn’t make sense).
-
Use a closure to hold a reference to the parent class, and do the subclass construction logic inside the closure.
-
The new inspection.
-
Call the superclass constructor with the current this.
var _this = _possibleConstructorReturn(
this,
(Child.__proto__ || Object.getPrototypeOf(Child)).call(this, a)
);
Copy the code
The Child here. Proto | | Object. GetPrototypeOf (Child) is actually the father constructor (_inherits last operation), then the caller through the call to this current, and passing parameters. (This feels like it can be passed directly to the Parent)
function _possibleConstructorReturn(self, call) {
if(! self) {throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return call && (typeof call === "object" || typeof call === "function")? call : self; }Copy the code
Verify that this was initialized and that super was called, and return the assigned this.
-
Assign variables and functions inside the row subclass to this.
-
Executes the logic inside a subclass constuctor.
As you can see, ES6 actually gives us a simple way to write “combinatorial parasitic inheritance.”
6.4 super
Super represents the superclass constructor.
Super.fun1 () is equivalent to parent-.fun1 () or parent-.prototype.fun1 ().
Super () is equivalent to the Parent. The prototype. Construtor ()
When we don’t write a subclass constructor:
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 default constructor actively calls the constructor of the parent class, and by default passes arguments passed by its constructor to the parent class.
So we must proactively call super() when we declare constructor, otherwise we cannot call the parent constructor and complete inheritance.
A typical example is in the React Component where we declare that super(props) must be called after constructor because the parent class wants to do some initialization of props in the constructor.
Chapter 3, “Relearning JavaScript” scopes and closures
1. Scope
Understand scope, scope chains, and internals of JavaScript
1.1 scope
Javascript has a well-designed set of rules for storing variables and making them easy to find later. This set of rules is called scopes.
Scope is the execution environment of code, global execution environment is global scope, function execution environment is private scope, they are all stack memory.
1.2 Scope chain
When code is executed in an environment, a scope chain (a chain formed by scope) of variable objects is created. Since the lookup of variables is implemented along the scope chain, it is also called the mechanism of variable lookup.
- The front end of the scope chain is always the variable object in the context of the currently executing code
- The next object in the scope chain comes from the external environment, and the next variable object comes from the next external environment, all the way to the global execution environment
- The variable object of the global execution environment is always the last object on the scope chain
The internal environment can access all external environments through the scope chain, but the external environment cannot access any variables and functions of the internal environment.
1.3 Internal Principles
-
compile
Var a = 2; For example, the internal compilation process of javascript consists of the following three steps:
-
Word segmentation (tokenizing)
Break up a string of characters into meaningful blocks of code called tokens
var a = 2; Is decomposed into the following lexical units: var, a, =, 2,; . These lexical units form an array of lexical units stream
[ "var": "keyword"."a": "identifier"."=": "assignment"."2": "integer".";": "eos" (end of statement) ] Copy the code
-
Resolution (parsing)
Convert an array of lexical units into a hierarchical nested Tree that represents the Syntax structure of a program. This Tree is called an Abstract Syntax Tree (AST).
var a = 2; There is a top-level node called VariableDeclaration, followed by a child node called Identifier(whose value is a), and a child node called AssignmentExpression. And this node has a child node called Numericliteral, which has a value of 2
{ operation: "=".left: { keyword: "var".right: "a" } right: "2" } Copy the code
-
Code generation
The process of converting the AST into executable code is called code generation
var a=2; Into a set of machine instructions to create a variable called a (including memory allocation, etc.) and store the value 2 in a
In fact, the compilation process for a javascript engine is much more complex and involves a lot of optimizations, and the three steps above are a basic overview of the compilation process
Any snippet of code is compiled before execution, and in most cases the compilation occurs a few microseconds before execution. The first thing the javascript compiler will do is to set var a=2; The program is compiled, and then it is ready to execute, and it usually executes right away
-
-
perform
In short, the compilation process is the process in which the compiler decompresses the program into lexical units (tokens), then parses the lexical units into syntax trees (AST), and then turns the syntax trees into machine instructions waiting to be executed
In fact, the code compiles and executes. So let’s say var a = 2; For example, explain the compilation and execution process in depth
-
compile
-
The compiler looks at the scope to see if a variable named a already exists in the set of the same scope. If so, the compiler ignores the declaration and continues compiling. Otherwise it will require the scope to declare a new variable in the current scope’s collection, named a
-
Var a = 2; This code snippet compiles into machine instructions for execution
Duplicate declarations in javascript are legal, depending on how the compiler compiles
// Test appears for the first time in scope, so declare a new variable and assign 20 to test var test = 20; // Test already exists in scope, so replace assignment of 20 with assignment of 30 var test = 30; Copy the code
-
-
perform
-
The engine first queries the scope to see if a variable called a exists in the current set of scopes. If so, the engine will use this variable; If not, the engine continues to look for the variable
-
If the engine finally finds the variable A, it will assign 2 to it. Otherwise, the engine throws an exception
-
-
-
The query
In the first operation performed by the engine, a query is made against the variable A, which is called an LHS query. There are actually two types of engine queries: LHS queries and RHS queries
Taken literally, an LHS query is performed when a variable appears on the left side of an assignment and an RHS query when it appears on the right
More precisely, an RHS query is no different from simply finding the value of a variable, whereas an LHS query is trying to find the container of the variable itself so that it can be assigned a value. Right
function foo(a) { console.log(a); / / 2 } foo(2); Copy the code
In this code, there are four queries, which are:
1, foo (…). An RHS reference is made to Foo
2. The function passes a = 2 to make an LHS reference to a
3, the console. The log (…). Make an RHS reference to the console object and check whether it has a log method
4, console.log(a) makes an RHS reference to a and passes the resulting value to console.log(…)
-
nested
When a variable cannot be found in the current scope, the engine continues to search in the outer nested scope until the variable is found, or until it reaches the outermost (i.e., global) scope
function foo(a) { console.log(a + b); } var b = 2; foo(2); / / 4 Copy the code
Line RHS reference, not found; The engine then looks for b in the global scope, succeeds in finding it, makes an RHS reference to it, and assigns 2 to b
-
abnormal
Why is it important to distinguish BETWEEN LHS and RHS? This is because the two queries behave differently when the variables are not declared (they cannot be found in any scope)
-
RHS
- If the RHS query fails, the engine throws a ReferenceError exception
// This variable cannot be found during the RHS query on b. That is, it is an "undeclared" variable function foo(a) { a = b; } foo(); // ReferenceError: b is not defined Copy the code
- If the RHS query finds a variable, but attempts to perform an improper operation on its value, such as making a function call on a value of a non-function type, or referring to an attribute in null or undefined, the engine throws another type of exception: TypeError
function foo() { var b = 0; b(); } foo(); // TypeError: b is not a function Copy the code
-
LHS
- When the engine executes an LHS query, if the variable cannot be found, the global scope creates a variable with that name and returns it to the engine
function foo() { a = 1; } foo(); console.log(a); / / 1 Copy the code
- If an LHS query fails in strict mode and a global variable is not created and returned, the engine throws a ReferenceError exception similar to that used when an RHS query fails
function foo() { "use strict"; a = 1; } foo(); console.log(a); // ReferenceError: a is not defined Copy the code
-
-
The principle of
function foo(a) { console.log(a); } foo(2); Copy the code
The above code snippet illustrates the inner workings of scopes in the following steps:
[1] The engine needs foo(…) Function makes an RHS reference to find foo in global scope. Successfully found and executed
[2] The engine needs to pass a=2 to function foo, make an LHS reference to a, and look up a in the scope of function foo. Successfully found and assigned 2 to A
[3] The engine needs to execute console.log(…) , makes an RHS reference to the console object, and finds the console object in the scope of the foo function. Because console is a built-in object, it was found successfully
[4] The engine looks for log(…) in the console object. Method, successfully found
[5] The engine needs to execute console.log(a), make an RHS reference to a, look for a in the scope of function foo, and successfully find and execute it
[6] The engine then sends the value of a, i.e. 2, to console.log(…). In the
[7] Finally, the console outputs 2
Lexical scope and dynamic scope
2.1 Lexical scope
The first phase of the compiler’s work is called word segmentation, which is the decomposition of a string of characters into lexical units. This concept is fundamental to understanding lexical scope
Simply put, lexical scope is the scope defined at the lexical stage. It is determined by where variables and blocks are scoped when code is written, so that the scope remains unchanged when the lexical analyzer processes the code
- Relationship between
No matter where and how a function is called, its lexical scope is determined only by where the function is declared, right
function foo(a) {
var b = a * 2;
function bar(c) {
console.log(a, b, c);
}
bar(b * 3);
}
foo(2); / / 2, 4 12
Copy the code
There are three hierarchically nested scopes in this example. To help you understand, think of them as bubbles of progressive inclusion
Scope bubbles are determined by where the corresponding scope block code is written. Are they contained at a level
Bubble 1 contains the entire global scope with only one identifier: foo
Bubble 2 contains the scope created by Foo with three identifiers: A, bar, and b
Bubble 3 contains the scope created by bar with only one identifier: C
- To find the
The structure of the scoped bubbles and their positional relationships to each other provide the engine with sufficient positional information to find the location of identifiers
In the snippet, the engine executes console.log(…) Declare and find references to variables A, B, and C. It starts with the innermost scope, bar(…) The scope of the function begins to look. The engine cannot find a here, so it will go up to the nested foo(…) The search continues in the scope of A is found here, so the engine uses this reference. Same thing for B. For C, the engine is at bar(…) And found it in
[Note] The lexical scope lookup only looks for the level 1 identifier. If the code references foo.bar.baz, the lexical scope lookup only tries to look for the foo identifier. When this variable is found, the object property access rules take over access to the bar and baz properties, respectively
foo = {
bar: {
baz: 1,}};console.log(foo.bar.baz); / / 1
Copy the code
- shelter
Scope lookup starts at the innermost scope at runtime and works outward or upward until the first matching identifier is encountered
Identifiers of the same name can be defined in multiple nested scopes. This is called the “shadotting effect”, in which the inner identifier “overshadows” the outer identifier
var a = 0;
function test() {
var a = 1;
console.log(a); / / 1
}
test();
Copy the code
Global variables are automatically properties of the global object, so they can be accessed indirectly, not directly by the lexical name of the global object, but by reference to the properties of the global object
var a = 0;
function test() {
var a = 1;
console.log(window.a); / / 0
}
test();
Copy the code
This technique allows access to global variables that are obscured by variables of the same name. But non-global variables, if obscured, cannot be accessed in any way. Right
2.2 Dynamic scope
The most important feature of javascript, which uses lexical scope, is that its definition occurs during the writing phase of the code
So why introduce dynamic scope? Dynamic scope is actually a cousin of this, another important javascript mechanism. Scope confusion is mostly due to confusion between lexical scope and this mechanism
Dynamic scope doesn’t care how functions and scopes are declared or where they are declared, only where they are called from. In other words, the scope chain is based on the call stack, not nested scopes in the code
var a = 2;
function foo() {
console.log(a);
}
function bar() {
var a = 3;
foo();
}
bar();
Copy the code
[1] If in lexical scope, that is the current javascript environment. The variable a first looks in the function foo(). It is not found. So we go down the scope chain to the global scope, find it and assign it to 2. So the console outputs 2
[2] If in dynamic scope, similarly, the variable a first looks in foo() and is not found. Here, we’ll go down the call stack to where foo() was called, which is bar(), find and assign 3. So the console outputs 3
The difference between the two scopes is that, in short, lexical scopes are determined at definition time, whereas dynamic scopes are determined at runtime
Execution context
Understanding the execution context stack of JavaScript allows you to apply the stack information to quickly locate problems
3.1 Execution Context
- Global execution context: This is the default, most basic execution context. Code that is not in any function is in the global execution context. It does two things: 1. It creates a global object, which in the browser is called the Window object. 2. Point this to the global object. Only one global execution context can exist in a program.
- Function execution context: Each time a function is called, a new execution context is created for the function. Each function has its own execution context, but it is created only when the function is called. Any number of function execution contexts can exist in a program. Each time a new execution context is created, it performs a series of steps in a specific order, as discussed later in this article.
- Eval function execution context: The code that runs in the Eval function also gets its own execution context, but since Javascript developers don’t often use the Eval function, we won’t discuss it here.
3.2 execution stack
The execution stack, also known as the call stack in other programming languages, has a LIFO (last in, first out) structure that stores all execution contexts created during code execution.
When the JavaScript engine first reads your script, it creates a global execution context and pushes it onto the current execution stack. Every time a function call occurs, the engine creates a new execution context for that function and pushes it to the top of the current execution stack.
The engine will run a function whose execution context is at the top of the execution stack. When this function is complete, its corresponding execution context will be ejected from the execution stack and context control will be moved to the next execution context on the current execution stack.
Let’s look at the following code example to understand this:
let a = "Hello World!";
function first() {
console.log("Inside first function");
second();
console.log("Again inside first function");
}
function second() {
console.log("Inside second function");
}
first();
console.log("Inside Global Execution Context");
Copy the code
When the above code loads in the browser, the JavaScript engine creates a global execution context and pushes it onto the current execution stack. When the first() function is called, the JavaScript engine creates a new execution context for the function and pushes it to the top of the current execution stack.
When the second() function is called within the first() function, the Javascript engine creates a new execution context for the function and pushes it to the top of the current execution stack. When the second() function completes execution, its execution context is ejected from the current execution stack, and context control is moved to the next execution context on the current stack, that of the first() function.
When the first() function completes execution, its execution context is ejected from the current execution stack, and context control is moved to the global execution context. Once all the code is executed, the Javascript engine removes the global execution context from the execution stack.
3.3 How is an execution context created
Now that we’ve seen how JavaScript engines manage execution contexts, let’s understand how JavaScript engines create execution contexts.
The execution context is created in two phases: 1) the creation phase; 2) Execution phase
3.4 Creation Phase
Before any JavaScript code is executed, the execution context is in the create phase. In total, three things happen during the creation phase:
- Determines the value of this, also known as this Binding.
- The LexicalEnvironment component is created.
- The VariableEnvironment component is created.
Therefore, an execution context can be conceptually represented as follows:
ExecutionContext = {
ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }},Copy the code
This Binding:
In the context of global execution, the value of this points to the global object, and in the browser, the value of this points to the Window object.
In the context of function execution, the value of this depends on how the function was called. If it is called by an object reference, then the value of this is set to that object, otherwise the value of this is set to global object or undefined (in strict mode). Such as:
let person = {
name: "peter".birthYear: 1994.calcAge: function() {
console.log(2018 - this.birthYear); }}; person.calcAge();// 'this' points to 'person' because 'calcAge' is called by reference to the 'person' object.
let calculateAge = person.calcAge;
calculateAge();
// 'this' points to the global Window object because no object reference is given
Copy the code
3.4.1 Lexical Environment
The official ES6 documentation defines a lexical environment as:
A lexical environment is a canonical type that defines the association of identifiers with specific variables and functions based on lexical nesting structures in ECMAScript code. Lexical environments consist of environment records and external lexical environments that may be null references.
In short, a lexical environment is a structure that contains a map of identifier variables. (The identifier here represents the name of the variable/function, which is a reference to the actual object (including function type objects) or the original value).
In lexical environments, there are two components :(1) environment records and (2) references to external environments
- The environment record is the actual location where variable and function declarations are stored.
- A reference to an external environment means that it has access to its external lexical environment.
There are two types of lexical environments:
- The global environment (in the context of global execution) is a lexical environment that has no external environment. The external environment reference of the global environment isnull. It has a global object (the window object) with its associated methods and properties (such as array methods) and any user-defined global variables,
this
The value of refers to the global object. - Function environments, where variables defined by the user in the function are stored in the environment record. A reference to an external environment can be a global environment or an external function environment containing internal functions.
Note: For function environments, the environment record also contains a Arguments object that contains the mapping between the index and the arguments passed to the function, as well as the length (number) of the arguments passed to the function. For example, the arguments object for the following function looks like this:
function foo(a, b) {
var c = a + b;
}
foo(2.3);
/ / the arguments object
Arguments: {0: 2.1: 3.length: 2},
Copy the code
There are also two types of environmental records (as shown below) :
- Declarative environment records store variables, functions, and parameters. A function environment contains declarative environment records.
- The object environment record is used to define associations between variables and functions that occur in the context of global execution. The global environment contains object environment records.
Abstractly, the lexical environment looks like this in pseudocode:
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object".// The identifier is bound here
outer: <null>
}
}
}
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative".// The identifier is bound here
outer: <Global or outer function environment reference>}}}Copy the code
3.4.2 Variable Environment:
It is also a lexical environment whose EnvironmentRecord contains the bindings created by the VariableStatements in this execution context.
As mentioned above, the variable environment is also a lexical environment, so it has all the attributes of the lexical environment defined above.
In ES6, the LexicalEnvironment component differs from the VariableEnvironment component in that the former is used to store function declarations and variable (let and const) bindings, while the latter is used only to store variable (var) bindings.
Let’s combine some code examples to understand the above concepts:
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20.30);
Copy the code
The execution context is as follows:
GlobalExectionContext = {
ThisBinding: <Global Object>, LexicalEnvironment: {EnvironmentRecord: {Type: "Object", // Identifier binding here a: < uninitialized >, b: < uninitialized >, multiply: < func > } outer: <null> }, VariableEnvironment: { EnvironmentRecord: { Type: C: undefined,} outer: <null>}} FunctionExectionContext = {ThisBinding: <Global Object>, LexicalEnvironment: {EnvironmentRecord: {Type: "Declarative", // Arguments: {0:20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: G: undefined}, outer: <GlobalLexicalEnvironment>}}Copy the code
Note: The function execution context is created only when a call to the function multiply is encountered.
You may have noticed that variables defined by let and const do not have any value associated with them, but variables defined by var are set to undefined.
This is because during the creation phase, the code is scanned and parsed for variables and function declarations, where the function declarations are stored in the environment, and the variables are set to undefined (in the case of var) or remain uninitialized (in the case of let and const).
This is why you can access variables defined by var (although undefined) before declaration, but if you access variables defined by let and const before declaration, you will get a reference error.
This is what we call variable enhancement.
3.5 Execution Phase
This is the easiest part of the whole article. At this stage, all variables are assigned and the code is executed.
Note: During execution, if the Javascript engine cannot find the value of the let variable at the actual location declared in the source code, then undefined will be assigned to it.
3.6 Clipping the Error stack
Node.js only supports this feature, which is implemented through error. captureStackTrace, which takes an object as the first argument and the optional function as the second argument. It captures the current call stack and clips it. The captured call stack is recorded on the stack property of the first argument, and the clipped reference point is the second argument. That is, previous calls to this function are recorded on the call stack, and subsequent calls are not.
Let’s do this in code. First, capture the current call stack and put it on myObj:
const myObj = {};
function c() {}
function b() {
// Write the current stack to myObj
Error.captureStackTrace(myObj);
c();
}
function a() {
b();
}
// Call function a
a();
/ / print myObj. Stack
console.log(myObj.stack);
// The output will look like this
// at b (repl:3:7) <-- Since it was called inside B, the B call is the last entry in the stack
// at a (repl:2:1)
// at repl:1:1 <-- Node internals below this line
// at realRunInThisContextScript (vm.js:22:35)
// at sigintHandlersWrap (vm.js:98:12)
// at ContextifyScript.Script.runInThisContext (vm.js:24:12)
// at REPLServer.defaultEval (repl.js:313:29)
// at bound (domain.js:280:14)
// at REPLServer.runBound [as eval] (domain.js:293:12)
// at REPLServer.onLine (repl.js:513:10)
Copy the code
There is only a -> b in the call stack above, because we capture the call stack before B calls C. Now make a few changes to the above code and see what happens:
const myObj = {};
function d() {
// We store the current call stack on myObj, but remove the b and the part after b
Error.captureStackTrace(myObj, b);
}
function c() {
d();
}
function b() {
c();
}
function a() {
b();
}
// Execute the code
a();
/ / print myObj. Stack
console.log(myObj.stack);
// The output is as follows
// at a (repl:2:1) <-- As you can see here we only get frames before b was called
// at repl:1:1 <-- Node internals below this line
// at realRunInThisContextScript (vm.js:22:35)
// at sigintHandlersWrap (vm.js:98:12)
// at ContextifyScript.Script.runInThisContext (vm.js:24:12)
// at REPLServer.defaultEval (repl.js:313:29)
// at bound (domain.js:280:14)
// at REPLServer.runBound [as eval] (domain.js:293:12)
// at REPLServer.onLine (repl.js:513:10)
// at emitOne (events.js:101:20)
Copy the code
In this code, because we passed in B when we called error-capturestackTrace, the call stack after B is hidden.
Now you may ask, what’s the use of knowing all this? Try this trick if you want to hide a stack of errors from users that are not relevant to their business (such as the internal implementation of a library).
3.7 Error Debugging
3.7.1 Error Objects and Error handling
When a program runs incorrectly, an Error object is usually thrown. The Error object can serve as a prototype for user-defined Error object inheritance.
The err. prototype object contains the following properties:
Constructor – the constructor that points to an instance
Message – Error message
Name – The wrong name (type)
These are the standard properties of Err. prototype, but each runtime environment has its own specific properties. For example, Node, Firefox, Chrome, Edge, IE 10+, Opera, and Safari 6+
In such an environment, the Error object has the stack property, which contains the stack trace of the Error. The stack trail of an error instance contains all the stack structures since the constructor.
3.7.2 How Do I View the Call Stack
Look only at the call stack: console.trace
a();
function a() {
b();
}
function b() {
c();
}
function c() {
let aa = 1;
}
console.trace();
Copy the code
3.7.3 Debugger break point form
Four, this
How this works and how it can be used in different scenarios
4.1 Calling as an object method
In JavaScript, a function is also an object, so a function can be a property of an object. In this case, the function is called a method of the object, and this is naturally bound to the object when this invocation is used
var test = {
a: 0.b: 0.get: function() {
return this.a; }};Copy the code
4.2 As a function call
The function can also be called directly, with this bound to the global object. In the browser, window is the global object. For example, when the function is called, this is bound to the global object,
We then execute the assignment statement, which implicitly declares a global variable, which is clearly not what the caller wants.
function makeNoSense(x) {
this.x = x;
}
Copy the code
4.3 Called as a constructor
JavaScript supports object-oriented programming. Unlike mainstream object-oriented programming languages, javaScript does not have the concept of classes. Instead, it uses a prototype-based inheritance approach.
Accordingly, constructors in JavaScript are special and behave like normal functions if they are not called with new. As another well-established rule, constructors begin with a capital letter,
Remind the caller to call in the right way. If called correctly, this is bound to the newly created object.
function Point(x, y) {
this.x = x;
this.y = y;
}
Copy the code
4.4 Using call, apply, or bind
Once again, in JavaScript functions are objects, objects have methods, and apply and Call are methods of function objects.
These two methods are incredibly powerful in that they allow you to switch the context in which the function is executed, the object to which this is bound.
Many JavaScript tricks and libraries use this method. Let’s look at a specific example:
function Point(x, y) {
this.x = x;
this.y = y;
this.moveTo = function(x, y) {
this.x = x;
this.y = y;
};
}
var p1 = new Point(0.0);
var p2 = { x: 0.y: 0 };
p1.moveTo(1.1);
p1.moveTo.apply(p2, [10.10]);
Copy the code
Five, the closure
Closure implementation principle and function, can list several development closure practical application
5.1 Concept of closures
- A function that has access to variables in the scope of another function, usually by including a function within another function.
5.2 Functions of closures
- Access to function internal variables, and keep the function in the environment, is not handled by garbage collection
Since variables declared inside a function are local and accessible only inside the function, variables outside the function are visible to the inside of the function. This is a feature of the scope chain.
The child can look for a variable from the parent, step by step, until it is found
So we can create a function inside the function, so that the variables of the outer function are visible to the inner function, and then we can access its variables.
function bar() {
// The variable declared by the outer function
var value = 1;
function foo() {
console.log(value);
}
return foo();
}
var bar2 = bar;
// In fact, the bar() function is not disposed of by garbage collection
// This is what the closure does. Calling bar() will execute the inner function foo, which will then access the outer variable
bar2();
Copy the code
Foo () contains a closure for the internal scope of bar (), so that the scope is always alive and can’t be garbage collected. That’s what closures are for, so that foo () can reference it at any time.
5.3 Advantages of closures
- Facilitates calls to local variables declared in context
- The logic is tight, you can create a function in a function, to avoid the problem of passing parameters
5.4 Disadvantages of closures
- Because closures allow functions to remain in memory after execution without being destroyed, heavy use of closures can result in memory leaks and high memory consumption
5.5 Practical application of closures
function addFn(a, b) {
return function() {
console.log(a + "+" + b);
};
}
var test = addFn(a, b);
setTimeout(test, 3000);
Copy the code
Normally the first argument to setTimeout is a function, but it cannot pass a value. If you want to pass values in, you can call a function that returns a call to the inner function and pass the call to setTimeout. The parameters that the inner function needs to execute are passed to it by the outer function, which can also be accessed in the setTimeout function.
Stack overflows and memory leaks
Understand how stack overflows and memory leaks work and how to prevent them
6.1 Memory Leak
- After the application of memory execution is not timely clean up or destroy, occupy idle memory, memory leak too much, it will lead to the application of the later memory. Therefore, a memory leak can cause an internal memory overflow
6.2 Stack Overflow
- The memory space has been applied for, there is not enough memory to provide
6.3 Label clearance method
In some programming software, such as C language, you need to use malloc to apply for memory space, and then use free to free it, which needs to be manually cleared. Js has its own garbage collection mechanism, and the commonly used garbage collection method is the tag clearance.
Tag clearance: Add a tag to a variable after it enters the execution environment: Enter environment. Variables entering the environment are not released because they are likely to be used whenever the execution flow enters the responding environment. When a variable leaves the environment, it is marked “out of the environment.”
6.4 Common Causes of Memory Leaks
- Memory leaks caused by global variables
- closure
- No timer was cleared
6.5 Solutions
- Reduce unnecessary global variables
- Reduce the use of closures (because closures can leak memory)
- Avoid the occurrence of endless loops
How to handle asynchronous operations of loops
7.1 Using self-executing functions
1. When a self-executing function is used in a loop, the self-executing function is not run until after the loop has finished. For example, if you define an array outside of the self-executing function, append to the array inside the self-executing function, and you print out the array outside of the self-executing function, you’ll find that there’s nothing in the array, because the self-executing function doesn’t execute until after the loop has run.
When a self-executing function is used in a loop, if the self-executing function is nested with Ajax, the index I in the loop will not be passed into the Ajax. Instead, the index I will be assigned to a variable outside the Ajax, which will be called directly in the Ajax.
Example:
$.ajax({
type: "GET".dataType: "json".url: "* * *".success: function(data) {
//console.log(data);
for (var i = 0; i < data.length; i++) {
(function(i, abbreviation) {
$.ajax({
type: "GET".url: "/api/faults? abbreviation=" + encodeURI(abbreviation),
dataType: "json".success: function(result) {
// What to do after getting the data}}); })(i, data[i].abbreviation); }}});Copy the code
7.2 Using recursive functions
A recursive function is a function that is called inside the function itself. Be careful when using recursive functions. If you don’t handle them properly, you’ll end up in an infinite loop.
const asyncDeal = (i) = > {
if (i < 3) {
$.get('/api/changeParts/change_part_standard? part=' + data[i].change_part_name, function(res) {
// What to do after getting the datai++; asyncDeal(i); })}else {
// Do something after asynchronous completion}}; asyncDeal(0);
Copy the code
7.3 use the async/await
- Async/await characteristics
Async /await is more semantic, async is short for “asynchronous”, async function is used to declare that a function is asynchronous; Await, which can be thought of as short for async wait, is used to wait for an asynchronous method to complete execution;
Async /await is a solution to an asynchronous problem using synchronous thinking (the code will not proceed until the result is found).
Instead of traditional callback nesting, you can use multiple layers of async function to write synchronously
- Async function grammar
Automatically converts a regular function to a Promise, and the return value is also a Promise object
The callback function specified by the THEN method is executed only after the asynchronous operation within the async function has completed
Await can be used inside an asynchronous function
- Await the grammar
The await is placed before the Promise call, and the await forces the following dot code to wait until the Promise object is resolved, getting the value of resolve as the result of the operation of the await expression
Await can only be used inside async functions. It will report an error if used in normal functions
const asyncFunc = function(i) {
return new Promise(function(resolve) {
$.get(url, function(res) {
resolve(res);
});
});
};
const asyncDeal = async function() {
for (let i = 0; i < data.length; i++) {
let res = await asyncFunc(i);
// What to do after getting the data}}; asyncDeal();Copy the code
Viii. Modularity
To understand the practical problems that modularity solves, list several modularization solutions and understand the principles behind them
8.1 CommonJS Specification (synchronous loading modules)
Allow modules to load other modules they depend on via require and export exposed interfaces via exports or module.exports.
Usage:
/ / import
require("module");
require(".. /app.js");
/ / export
exports.getStoreInfo = function() {};
module.exports = someValue;
Copy the code
Advantages:
- Simple and easy to use
- Server modules are easy to reuse
Disadvantages:
- Synchronous loading is not suitable for use in a browser environment. Synchronous means blocking loading, and browser resources are loaded asynchronously
- Multiple modules cannot be loaded in parallel without blocking
Why can’t browsers use synchronous loading, but servers can?
- Because modules are on the server side, for the server side the module is loaded
- On the browser side, because the modules are on the server side, the loading time also depends on the speed of the network and other factors, if you need to wait a long time, the entire application will be blocked.
- Therefore, the browser-side modules cannot be loaded synchronously (CommonJs), but only asynchronously (AMD).
Refer to the CommonJs module to represent the node.js module system
8.2 AMD (Asynchronous Loading Module)
The module is loaded asynchronously, and the module loading does not affect the running of the following statements. All statements that depend on a module are defined in a callback function that is not executed until the load is complete.
Example:
/ / define
define("module"["dep1"."dep2"].function(d1, d2) {... });// Load the module
require(["module".".. /app"].function(module, app) {... });Copy the code
Require ([module], callback); The first argument, [module], is an array whose members are the modules to be loaded; The second parameter, callback, is the callback function after a successful load.
Advantages:
- Suitable for loading modules asynchronously in a browser environment
- Multiple modules can be loaded in parallel
Disadvantages:
- It increases the development cost, it is difficult to read and write the code, and the semantics of module definition is not smooth
- Not conforming to the general modular way of thinking, is a compromise to achieve
The implementation AMD specification represents require.js
RequireJS has a preexecution attitude towards modules. Because RequireJS is an implemented AMD specification, all dependent modules are executed first; RequireJS preempts the execution of dependent modules, effectively preempting require
RequireJS execution process:
- The require function checks the dependent modules and, according to the configuration file, obtains the actual path of the JS file
- Based on the actual path of the JS file, insert a Script node into the DOM and bind the onLoad event to get the notification that the module has completed loading.
- The callback function is called when the dependency script has all been loaded
8.3 CMD Specification (Asynchronous Loading Module)
The CMD specification is very similar to AMD, simple, and maintains great compatibility with CommonJS and Node.js Modules. In the CMD specification, a module is a file.
Definition modules use the global function define, which takes the factory argument, which can be a function, an object, or a string;
Function (require, exports, module)
- Require is a method that takes a module id as the only parameter used to get an interface provided by another module: require(ID)
- Exports are an object that provides a module interface to the outside world
- A module is an object that stores the properties and methods associated with the current module
Example:
define(function(require.exports.module) {
var a = require("./a");
a.doSomething();
// Rely on nearby writing, when to use and when to introduce
var b = require("./b");
b.doSomething();
});
Copy the code
Advantages:
- Rely on proximity, delay execution
- Can be easily run in Node.js
Disadvantages:
- Depending on SPM packaging, the module loading logic is heavy
- Implementation representative library Sea-.js: SeaJS’s attitude towards modules is lazy execution, SeaJS will only execute modules when it really needs to use (depend on) them
8.4 Differences between AMD and CMD
- For dependent modules, AMD executes ahead of time and CMD executes late. As of 2.0, however, RequireJS has also been changed to allow delayed execution (depending on how you write it). CMD advocates as lazy as possible.
- AMD advocates dependency prepositioning; CMD advocates relying on proximity, requiring only when a module is needed.
// AMD
define(['./a'.'./b'].function(a, b) { Dependencies must be written from the start
a.doSomething()
// Omit 100 lines here
b.doSomething()
...
});
// CMD
define(function(require.exports.module) {
var a = require('./a')
a.doSomething()
// Omit 100 lines here
var b = require('./b')
Dependencies can be written nearby
b.doSomething()
// ...
});
Copy the code
8.5 UMD
- UMD is a mashup of AMD and CommonJS
- AMD has developed asynchronous loading modules based on the browser first principle.
- CommonJS modules are developed with the server first principle, choosing to load synchronously, and its modules do not need to be wrapped.
- UMD checks whether node.js modules (exports) exist, and if so, uses node.js module mode. Determine whether AMD is supported (define exists). If yes, the module is loaded in AMD mode.
(function(window, factory) {
if (typeof exports= = ="object") {
module.exports = factory();
} else if (typeof define === "function" && define.amd) {
define(factory);
} else {
window.eventUtil = factory();
}
})(this.function() {
//module ...
});
Copy the code
8.6 ES6 Modularity
- ES6 implements modular functionality at the language standard level and is fairly simple enough to replace CommonJS and AMD specifications as a universal modular solution for browsers and servers.
- ES6 modules are designed to be as static as possible, so that module dependencies and input and output variables can be determined at compile time (CommonJS and AMD modules can only determine these things at run time).
Usage:
/ / import
import "/app";
import React from"React";import { Component } from"React";/ / export
export function multiply() {... };export var year = 2018;
export default. .Copy the code
Advantages:
- Easy static analysis
- Disadvantages of the EcmaScript standard for the future:
- The standard is not yet implemented on the native browser side
- New command word, only supported in the new version of Node.js.
8.7 Return to the question “The difference between require and import”
Require is used with the CommonJs specification, import is used with the Es6 module specification; So the difference between the two is essentially the difference between the two norms;
CommonJS:
- For basic data types, it is replication. That is, it is cached by the module; At the same time, a variable output from another module can be reassigned.
- For complex data types, it is a shallow copy. Because the objects referenced by both modules point to the same memory space, changes to the value of one module affect the other module.
- When a module is loaded using the require command, the code for the entire module is run.
- When the same module is loaded using the require command, the module is not executed again and the value is retrieved from the cache. That is, the CommonJS module will only run once on the first load, no matter how many times it is loaded, and will return the first run unless the system cache is manually cleared.
- When the loop is loaded, it belongs to load-time execution. That is, when the script code is required, it is all executed. Once a module is “looping loaded”, only the parts that have been executed are printed, not the parts that have not been executed.
ES6 module
- Values in the ES6 module belong to dynamic Read-only references.
- For read-only, you are not allowed to change the value of the imported variable. The imported variable is read-only, whether it is a basic or complex data type. When the module encounters the import command, it generates a read-only reference. When the script actually executes, the value is then applied to the loaded module based on the read-only reference.
- Dynamically, as the original value changes, so does the value loaded by the import. Both basic and complex data types.
- When looping, the ES6 module is a dynamic reference. As long as there is a reference between the two modules, the code can execute.
Finally: require/exports are required and generic; Because, in fact, any import/export you write right now ends up being compiled to require/exports.