Recently, I was in charge of front-end technology. When I asked others questions, I found that THE basic JS knowledge was not solid enough, so I took time to sort it out:

1. JS data type

Js data types are divided into basic data types (7 types) and reference data types object. Base data type: null, undefined, number, string, Boolean, symbol (ES6), BigINT (can represent integers greater than 2^ 53-1) Reference data type: Object. Object includes array, function, Date, and RegExp

2. The way to judge the data type:

(1) Typeof: used to determine the basic data type except null, typeof NULL returns object. All reference data types except function return Object

var a = 1;
var b = 'lala';
var c = true;
var d = undefined;
var e = Symbol(a);var f = 1n;

var g = null;
var h = {};
var i = [];
var j = function() {};

console.log(typeof a);  // number
console.log(typeof b);  // string
console.log(typeof c);  // boolean
console.log(typeof d);  // undefined
console.log(typeof e);  // symbol
console.log(typeof f);  // bigint

console.log(typeof g);  // object
console.log(typeof h);  // object
console.log(typeof i);  // object
console.log(typeof j);  // function
Copy the code

(2) Instanceof is used to determine the reference data type, class instance. Instanceof will be searched up the prototype chain and return true or false

var h = {};
var i = [];
var j = function() {};
var k = new Date(a);var l = /[0-9]/g;

console.log(h instanceof Object);  // trye
console.log(i instanceof Array);  // true
console.log(j instanceof Function);  // true
console.log(k instanceof Date);  // true
console.log(l instanceof RegExp);  // true
Copy the code

(3) Object.prototype.toString.call()

Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call('lala');// "[object String]"
Object.prototype.toString.call(1);// "[object Number]"
Object.prototype.toString.call(true);// "[object Boolean]"
Object.prototype.toString.call(new Date());// "[object Date]"
Object.prototype.toString.call(/ [0-9]);// "[object RegExp]"
Copy the code

Handwritten instanceof

function myInstanceof(left, right) {

  // Use typeof to determine the underlying data type. If so, return false
  if(typeofleft ! = ='object' || left === null) return false;
  
  // getProtypeOf is an Object API that can get the parameters of the prototype Object
  let proto = Object.getPrototypeOf(left);
  
  while(true) {                  // Loop down until you find the same prototype object
    if(proto === null) return false;
    if(proto === right.prototype) return true;// Find the same prototype object, return true
    proto = Object.getPrototypeof(proto); }}// verify that myInstanceof is OK
console.log(myInstanceof(new Number(123), Number));    // true
console.log(myInstanceof(123.Number));
Copy the code

3. Deep and light copy

It starts with where the data is stored in memory, the base data type is stored in the stack, the reference data type is stored in the heap, and the stack stores the address of the access object.

For the underlying data type, it’s a copy of the value; But for reference data types, there are deep copies and shallow copies.

Shallow copy first creates an object and copies exactly the property values of the original object. If the property values are base data types, the copy is the value of the base data type; if the property values are reference data types, the copy is the memory address.

Deep copy copies all attributes and dynamically allocated memory to which the attributes point. Deep copy occurs when an object is copied along with the object it references. In other words, the heap memory is recreated. After copying, the data is stored in the new address, while the pointer points to the new address, and the original data is completely isolated.

Common shallow copy implementations are object.assign(target,… Sources) extension operator… Concat Copy array slice Copy array arr. Slice (begin, end);

// Object.assign()
let target = { a: 1 }
let source = { b: 2.c: { d: 3}};Object.assign(target, source);
console.log(target);  // { a: 1, b: 2, c: { d: 3 } };

target.b = 5; 
target.c.d = 4; 
console.log(source); // { b: 2, c: { d: 4 } };
console.log(target); // { a: 1, b: 5, c: { d: 4 } };

// ...
let obj = { a:1.b: { c:1}}letobj2 = { ... obj }let arr = [1.2.3];
let newArr = [...arr];


// concat()
let arr = [1.2.3];
let newArr = arr.concat();


// slice()
let arr = [1.2, {val: 4}];
let newArr = arr.slice();
Copy the code

But there are a few caveats to using the object.assign method:

  • It does not copy the object’s inherited properties;
  • It does not copy the object’s non-enumerable properties;
  • Properties of type Symbol can be copied.
let obj1 = { a: {b:1 }, sym:Symbol(1)}; 
Object.defineProperty(obj1, 'innumerable', {value:'Non-enumerable properties'.enumerable:false
});
let obj2 = {};
Object.assign(obj2,obj1)
obj1.a.b = 2;
console.log('obj1',obj1);  // {a: {b: 2}, sym: Symbol(1), innumerable: 'unenumerable attribute '}
console.log('obj2',obj2);  // { a: { b: 2 }, sym: Symbol(1)}
Copy the code

Deep copy implementation: json.stringify

However, there are a few things worth noting about using json.stringify to implement deep copy, which can be summarized as follows:

  1. If the value of the copied object is function, undefined, or symbol, the key/value pair will disappear in the string serialized by json. stringify.
  2. Copying the Date reference type becomes a string;
  3. Unable to copy non-enumerable properties;
  4. Unable to copy object’s prototype chain
  5. Copying a RegExp reference type becomes an empty object.
  6. Object containing NaN, Infinity, and -infinity, the result of JSON serialization is null;
  7. Looping applications that cannot copy objects, i.e. objects are looped (obj[key] = obj).
function Obj() { 
  this.func = function () { alert(1)};this.obj = {a:1};
  this.arr = [1.2.3];
  this.und = undefined; 
  this.reg = / 123 /; 
  this.date = new Date(0); 
  this.NaN = NaN;
  this.infinity = Infinity;
  this.sym = Symbol(1);
} 
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable', {enumerable:false.value:'innumerable'
});
console.log('obj1',obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log('obj2',obj2);
Copy the code

Handwritten shallow copy

const shallowClone = (target) = > {
  if (typeof target === 'object'&& target ! = =null) {
    let cloneTarget = Array.isArray(target) ? [] : {};
    for(let prop in target) {
      if (target.hasOwnProperty(prop)) {  // Iterate over the object's own enumerable properties (regardless of inherited properties and prototype objects)
          cloneTarget[prop] = target[prop]
      }
    }
    return cloneTarget
  } else {
    return target
  }
}
Copy the code

Handwritten deep copy

const deepClone = (target) = > {
  if (typeof target === 'object'&& target ! = =null) {
    let cloneTarget = Array.isArray(target) ? [] : {};
    for(let prop in target) {
      if (target.hasOwnProperty(prop)) {  // Iterate over the object's own enumerable properties (regardless of inherited properties and prototype objects)
          cloneTarget[prop] = deepClone(target[prop]);  // Recursion causes date and re to become {}}}return cloneTarget
  } else {
    return target
  }
}
Copy the code

Improved deep copy

Consider dates, re’s, and circular references

const deepClone = (target, map = new WeakMap(a)) = > {
	if (target === null) return null
  if (typeoftarget ! = ='object' || target.constructor === Date || target.constructor === RegExp) return target
  if (map.has(target)) return target  // Resolve circular references
  
  const deepTarget = Array.isArray(target) ? [] : {};
  map.set(target, true)
  
  for (let prop in target) {
    if (target.hasOwnProperty(prop)) {  // Iterate over the object's own enumerable properties (regardless of inherited properties and prototype objects)
      deepTarget[prop] = deepClone(target[prop], map)
     }
  }
  return deepTarget
}
Copy the code

4. The prototype

A prototype object

Consider three questions first:

  1. What is a prototype object?
  2. When is the prototype object generated?
  3. How are the prototype objects accessed?

What is a prototype object?

A prototype object is essentially an object. All functions have a prototype attribute that points to the prototype object of the function.

  • Constructor: Points to the constructor
  • Attributes and methods inherited from Object

When is the prototype object generated?

A prototype object is created when a function is created. Each declaration of a function does the following:

  • The browser creates an object in memory
  • Add a constructor attribute to the
  • The constructor attribute points to the function
  • Assigns the newly created object to the function’s Prototype property

How are the prototype objects accessed?

Prototype Creates an instance of the function using the __proto__ attribute. Each instance contains a pointer to the prototype object of the constructor.

5. The prototype chain

Prototype chain is to access an attribute or method on the instance, first look in the instance, find that is returned; If not, use the __proto__ attribute to find the constructor’s prototype object. If not, the constructor continues to search for the __proto__ property of prototype until object. prototype is found.

A diagram to understand the prototype, the prototype chain

conclusion

All functions are instances of Function, and Function is an instance of Function Function inherits from Object, and everything else inherits from Object

6. Inheritance

There are two functions, function A and function B, which implement function A inheriting the properties and methods of function B:

Prototype chain inheritance

A.prototype = new B()
A.prototype.constructor = A

var a1 = new A()
var a2 = new A()
Copy the code

Disadvantages:

  1. The reference type properties of the superclass B function prototype object are shared by instances A1, A2
  2. Instances that create subclass A cannot pass arguments to parent class B
  3. Subclasses cannot inherit from more than one function, where A can only inherit from B

Constructor inheritance

function A(e) {
  B.call(this, e)
}
Copy the code

Disadvantages: Subclasses do not have access to properties and methods on the parent class stereotype

Combination of inheritance

function B (name, age) {
  this.name = name
  this.age = age
}
B.prototype.setName = function (name) {
  this.name = name
}

function A (name, age, price) {
  B.call(this, name, age)
  this.price = price
}

A.prototype = new B ()
A.prototype.constructor = A

A.prototype.setPrice = function (price) {
	this.price = price
}

var sub = new A()
Copy the code

Disadvantages: calls B() twice: once b.call (); Once is new B()

Parasitic combinatorial inheritance

Create method, which takes two parameters: an Object to be used as a prototype for the new Object and, optionally, an Object to define additional properties for the new Object.

function B (name, age) {
  this.name = name
  this.age = age
}
B.prototype.setName = function (name) {
  this.name = name
}
t
function A (name, age, price) {
  B.call(this, name, age)
  this.price = price
}

// The first way
// Create an Object {} and assign _proto_ of the Object to object.create
// A.prototype.__proto__ = B.prototype
A.prototype = Object.create(B.prototype, {consturctor: A})

// The second way
//var F = function () {
//F.prototype = B.prototype; // Core code
//A.prototype = new F();
//A.prototype.contructor = A

A.prototype.setPrice = function (price) {
	this.price = price
}

var sub = new A()
Copy the code

ES6 Class Class

class B {
    static mood = 'good'  // Static attributes
    constructor () {
        this.money = 1000000
    }
    
    buybuybuy () {
        this.money -= 100
        console.log('money'.this.money)
    }
}

class A extends B {
	super()}var a1 = new A()
a1.buybuybuy()
Copy the code

7. Execution Context

Once the browser gets the source code, it does several things:

  • Word segmentation/lexical analysis () : Split the code to generate tokens;
  • Parsing/parsing () : convert tokens into AST abstract syntax trees according to the syntax;
  • Executable code: Parsers generate bytecode and interpret execution line by line, parsers monitor hot code, and compilers compile hot code into machine code.

What is an execution context?

Execution context, also known as execution context. Execution contexts fall into three types:

  • Global execution context: When the program starts, the global execution context is created and pushed onto the execution stack.
  • Function execution context: Creates the function execution context when the function is called and pushes the function onto the execution stack.
  • Eval Execution context: The specific execution context of the eval function.

The execution context has two phases: the creation phase and the execution phase.

Create a stage

The execution context mainly consists of two parts: lexical environment and variable environment.

LexicalEnvironment

Classification of lexical environments: global, function, module

Lexical environment:

  • Environment Record: Stores and initializes variables

    Declarative Environment Record: Holds elements defined directly by identifiers, such as Object Environment Records declared by const lets: syntactic environments used primarily for with.Copy the code
  • Outer environment: Creates scope chains that access references to the parent scope

  • ThisBinding: Determines the direction of this in the current environment

variableEnvironment

It’s also a lexical environment. The main difference is that variables declared through var and function declarations are stored in the variable environment.

In short, the execution context creation phase does three things:

  1. Initialize variables, functions, and parameters
  2. Create scope chains
  3. Binding this
executionContext = {
    variableObject: {
    	arguments: {},name: undefined.getData: undefined
    },  // Initialize variables, functions, parameters
    scopeChain: {},  // Create scope chain
    this: {} / / bind this
}
Copy the code

Execution phase

The implementation phase mainly does two things:

  1. Assign variables, function references, and assignments
  2. Execute the code

Variable promotion vs temporary dead zone Variables and function declarations declared by var have been initialized and assigned to undefined during the execution context creation phase. Variables defined by var can also be accessed with undefined before the code is executed to the var assignment line. This phenomenon is called variable promotion

In contrast, variables declared by const and let are initialized to flag bits in lexical environments. An error is reported when a variable is read before the let and const assignment line is executed. This feature is called a temporary dead band.

Execution context stack

The browser’s JS interpreter is single-threaded, meaning that the browser can only do one thing at a time. There is only one global execution context in the code, and an infinite number of function execution contexts, making up the execution context stack. The execution context of a function that is removed from the stack after the function completes execution.

8. Scope

The primary purpose of a scope is to isolate variables and functions and control their life cycles. It is mainly divided into three types:

  • Global scope
  • Function scope
  • Block-level scope

A scope is defined when the execution context is created, not when the code executes, and is therefore also known as a lexical scope.

Lexical scope vs dynamic scope

The difference between lexical scope and dynamic scope is that lexical scope is defined at the execution context creation stage, whereas dynamic scope is created at the code execution stage. To better understand the lexical scope that JS uses, take a look at an example:

var name = 'xuna'
function getName() {
    console.log(name)
}
function getName1() {
    var name = 'na.xu'
    return getName()
}
getName1() // xuna
Copy the code

The scope chain

When a function is nested within another function and a variable cannot be found in the lexical and variable Environment Record of the current execution context, the parent scope is accessed through the Outer Environment. If it is not found, the parent scope is searched layer by layer. Until the variable is found or the global scope is reached, such a chain is called a scope chain.

9. Closure

Closures typically occur when functions are nested and the inner function accesses the variables of the outer function. A closure is a function that has access to a variable in the scope of another function. When a function is removed from the stack after execution, the current execution context does not have direct access to the lexical scope of the function being removed from the stack, while another function retains a reference to the lexical scope of the function. This reference is called a closure.

Application of closures

Encapsulating private variables
function Person() {
	var money = 10000
  return buy() {
  	money -= 1}}var person = new Person()
person.buy() // Money is a private variable of person and can only be changed by buy ()
Copy the code
Cache data
function getDataList() {
	let data = null
  return {
  	getData() {
    	if(data) return Promise.resolve(data)
      return fetch().then(res= > data = res.json())
    }
  }
}

const list = getDataList()
list.getData()
Copy the code
Currie,