MEMEPh. ideas that are worth sharing...

Javascript Interview Core Test Site (Basic Edition)

Introduction


Javascript is the focus of front-end interviews. This article focuses on sorting out the common knowledge points in Javascript, and then analyzes some easy-to-appear topics. Limited to the length of the article, it is impossible to explain all the knowledge points. This article only lists some important and difficult points. If you want to know more, please click on my blog .

 

1. the variable type


1. JS data type classification

According to the transmission method of variable type in JavaScript, it is divided into basic data type and reference data type. The basic data types include Undefined, Null, Boolean, Number, String, and Symbol (new in ES6, representing unique values), while reference data types are collectively called Object objects, including objects, arrays, and functions.

There are differences in the way parameters are passed:

Topic: The difference between primitive types and reference types

Basic types and reference types are stored in different locations in memory. Basic types are directly stored in the stack, while objects of reference types are stored in the heap. At the same time, a pointer is stored in the stack, and this pointer points to the entity in the heap. starting position. Let's take a look at the main differences between the two through a small topic:

// basic type
var a = 10
var b = a
b = 20
console.log(a) // 10
console.log(b) // 20

In the above code, ab is a value type, and the two modify the assignment respectively without any influence on each other. Look again at the reference type example:

// reference type
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a) // {x: 100, y: 200}
console.log(b) // {x: 100, y: 200}


In the above code, ab is a reference type. After executing b = a, modify the attribute value of b, and the value of a also changes. Because a and b are both reference types and point to the same memory address, that is, both refer to the same value, so when b modifies the attribute, the value of a changes accordingly

 

2. Judgment of data type

 

1)typeof

typeof returns a string representing the data type . The returned results include: number, boolean, string, symbol, object, undefined, function and other 7 data types, but cannot judge null, array, etc.

typeof Symbol(); // symbol is valid
typeof ''; // string is valid
typeof 1; // number is valid
typeof true; //boolean valid
typeof undefined; //undefined is valid
typeof new Function(); // function is valid
typeof null; //object is invalid
typeof [] ; //object is invalid
typeof new Date(); //object is invalid
typeof new RegExp(); //object is invalid


2)instanceof

instanceof is used to determine whether A is an instance of B. The expression is: A instanceof B, if A is an instance of B, it returns true, otherwise it returns false. The instanceof operator is used to test whether an object has a constructor's prototype property in its prototype chain, but it cannot detect null and undefined

[] instanceof Array; //true
{} instanceof Object;//true
new Date() instanceof Date;//true
new RegExp() instanceof RegExp//true
null instanceof Null//error
undefined instanceof undefined//error

 

3)constructor

The constructor function is very similar to instanceof. But the constructor detection Object is not the same as instanceof, and can also handle the detection of basic data types.
However, the constructor of the function is unstable. This is mainly reflected in the rewriting of the prototype of the class. In the process of rewriting, it is very likely that the previous constructor will be overwritten, so the detected result will be inaccurate.

 

4)Object.prototype.toString.call()

Object.prototype.toString.call() is the most accurate and common way .

Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]

 

3. Shallow copy and deep copy

Shallow copy only copies the pointer to an object, not the object itself, the old and new objects still share the same memory .

Shallow copy implementation (see shallow copy and deep copy for details ):

A deep copy is to copy all the reference structures of the data when copying the data . Simply put, there are two data structures in memory that are identical and independent of each other, and the reference type is copied instead of just copying its reference relationship.

Implementation of deep copy:

The principle of recursive implementation of deep copy: to copy a piece of data, we must traverse its properties. If the properties of the object are still objects, continue to use this method, and so on.

//Define the function function to detect the data type
function checkedType(target) {
  return Object.prototype.toString.call(target).slice(8, -1)
}
// Implement deep clone --- object/array
function clone(target) {
/ / Determine the data type of the copy
//Initialize the variable result to become the final cloned data
  let result,
    targetType = checkedType(target)
  if (targetType === 'Object') {
    result = {}
  } else if (targetType === 'Array') {
    result = []
  } else {
    return target
  }
// Traverse the target data
  for (let i in target) {
//Get each value of the traversed data structure.
    let value = target[i]
/ / Determine whether each value in the target structure exists an object/array
    if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
 //Objects/Arrays are nested within Objects/Arrays
//Continue to traverse to get the value
      result[i] = clone(value)
    } else {
      //The value obtained is a basic data type or a function.
      result[i] = value
    }
  }
  return result
}

 

2. Scope and Closures


1. Execution context and execution stack

The execution context is the abstract concept of the environment in which the current JavaScript code is parsed and executed. Any code running in JavaScript runs in the execution context.
The life cycle of an execution context consists of three phases: creation phase → execution phase → recycling phase, and we focus on the creation phase.

The creation phase (when a function is called, but before any of its internal code is executed) does three things:

function test(arg){
// 1. The formal parameter arg is "hi"
// 2. Because function declarations have higher priority than variable declarations, arg is function at this time
console.log(arg);
var arg = 'hello'; // 3.var arg variable declaration is ignored, arg = 'hello' is executed
function arg(){
console.log('hello world')
} 
console.log(arg);
}
test('hi');
/* output:
function arg() {
console.log('hello world');
} 
hello
*/

This is because when the function is executed, a new private scope is first formed, and then the following steps are followed:

There are many functions, and there are multiple function execution contexts. Each time a function is called, a new execution context is created. How to manage so many execution contexts created?

The JavaScript engine creates the execution stack to manage the execution context. The execution stack can be considered as a stack structure that stores function calls, following the principle of first-in, last-out .


From the above flowchart, we need to remember a few key points:

 

2. Scope and scope chain

ES6 comes JavaScript with global scope, function scope and block scope (new in ES6). We can understand it this way: the scope is an independent site, so that variables will not be leaked and exposed. That is to say, the biggest use of scope is to isolate variables, and variables with the same name in different scopes will not conflict .
Before introducing the scope chain, we must first understand free variables. In the following code, console.log(a) needs to get the a variable, but a is not defined in the current scope (compare b). A variable that is not defined in the current scope, this becomes a free variable.

var a = 100
function fn() {
    var b = 200
    console.log(a) // where a is a free variable here
    console.log(b)
}
fn()


How to get the value of the free variable - look to the parent scope (the parent scope that created the function) . What if the parent doesn't have one? Look up layer by layer until you find the global scope and still don't find it, then give up. This layer-by-layer relationship is the scope chain.

function F1() {
    var a = 100
    return function () {
        console.log(a)
    }
}
function F2(f1) {
    var a = 200
    console.log(f1())
}
var f1 = F1()
F2(f1) // 100 

In the above code, the value of the free variable a is searched from the function F1 instead of F2, because when the free variable is searched from the scope chain, it is based on the scope chain when the function is defined, not when the function is executed.

 

3. What is a closure

The concept of closure is also a relatively abstract concept in JavaScript. I personally understand that a closure is a function in a function (other languages ​​cannot do this), the function inside can access the variables of the outside function, and the outside variables belong to the internal function. part.

The role of closures:

Closures cannot be abused, otherwise it will cause memory leaks and affect the performance of web pages. After the closure is used up, to release the resource immediately, point the reference variable to null.

There are two main application scenarios for closures:

function outer() {
var num = 0 //internal variable
return function add() {
//By returning the add function by return, it can be accessed outside the outer function.
num++ //The internal function has a reference as part of the add function
console.log(num)
} 
}
var func1 = outer() //
func1() //Actually call the add function, output 1
func1() //output 2
var func2 = outer()
func2() // output 1
func2() // output 2

 

4.This comprehensive analysis

First understand a very important concept - the value of this can only be confirmed when it is executed, and it cannot be confirmed when it is defined! Why - because this is part of the execution context, and the execution context needs to be determined before the code is executed, not when it is defined. See the following example:

// case 1
function foo() {
console.log(this.a) //1
}
var a = 1
foo()

// case 2
function fn(){
console.log(this);
}
var obj={fn:fn};
obj.fn(); //this->obj

// case 3
function CreateJsPerson(name,age){
//this is an instance of the current class p1
this.name=name; //=>p1.name=name
this.age=age; //=>p1.age=age
}
var p1=new CreateJsPerson("Yin Huazhi",48);

// case 4
function add(c, d){
return this.a + this.b + c + d;
}
var o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

// case 5
<button id="btn1">arrow function this</button>
<script type="text/javascript">
let btn1 = document.getElementById('btn1');
let obj = {
name: 'kobe',
age: 39,
getName: function () {
btn1.onclick = () => {
console.log(this);//obj
} };
} 
};
obj.getName();
</script>

Next, we explain the above situations one by one

 

3. Asynchronous


1. Synchronous vs Asynchronous

Synchronization, my understanding is a way of linear execution, the flow of execution cannot be spanned. For example, when you are eating after talking, and you are looking at your phone after eating, you have to wait for the last thing to finish before executing the next thing.

Asynchronous is a way of parallel processing. You don't have to wait for a program to finish executing, and you can perform other tasks. For example, when a person eats, looks at the mobile phone, and talks, this is the way of asynchronous processing. Results that are processed asynchronously in a program are usually processed using callback functions.

// Synchronize
console.log(100)
alert(200);
console.log(300) //100 200 300
// async
console.log(100)
setTimeout(function(){ 
console.log(200)
}) 
console.log(300) //100 300 200

 

2. Asynchronous and single-threaded

The fundamental reason why JS needs to be asynchronous is that JS runs on a single thread, that is, it can only do one thing at the same time, and cannot "do two things at the same time". In order to take advantage of the computing power of multi-core CPUs, HTML5 proposes the Web Worker standard, which allows JavaScript scripts to create multiple threads, but the sub-threads are completely controlled by the main thread and must not operate the DOM. So, this new standard doesn't change the single-threaded nature of JavaScript.

An Ajax request takes 5 seconds due to the slow network. If it is synchronization, the page will be stuck here for 5 seconds and can't do anything. If it is asynchronous, it is much better. Waiting for 5 seconds will wait, and other things will not be delayed. As for the 5-second waiting, the network speed is too slow, not because of JS.

 

3. Front-end asynchronous scenarios

Front-end using asynchronous scenarios

 

4. Event Loop

A complete Event Loop process can be summarized into the following stages:

Next, let's look at an example to introduce the above process:

Promise.resolve().then(()=>{
  console.log('Promise1')  
  setTimeout(()=>{
    console.log('setTimeout2')
  },0)
})
setTimeout(()=>{
  console.log('setTimeout1')
  Promise.resolve().then(()=>{
    console.log('Promise2')    
  })
},0)


The final output is Promise1, setTimeout1, Promise2, setTimeout2

 

4. Prototype chain and inheritance


1. Prototypes and Prototype Chains

Prototype: In JavaScript, a prototype is a prototype object that represents the relationship between types.

Prototype chain: Everything in JavaScript is an object, and there is a relationship between objects and objects, and they do not exist in isolation. The inheritance relationship between objects, in JavaScript, points to the parent class object through the prototype object until it points to the Object object, which forms a chain pointed to by the prototype, which is called the prototype chain in professional terms.

var Person = function() {
this.age = 18
this.name = 'anonymous'
}
var Student = function() {}
//Create inheritance relationship, parent class instance as child class prototype
Student.prototype = new Person()
var s1 = new Student()
console.log(s1)


Prototype diagram:

When trying to get a property of an object, if the object itself does not have this property, it will look for it __proto__(that is, the prototype of its constructor). If the top layer is not found all the time, then it will fail and return undefined. What is the top layer -Object.prototype.__proto__ === null

 

2. Inheritance

Introduce several common inheritance methods (for more information, please click the six common inheritance methods in JavaScript ):

function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}
function Child(value) {
  Parent.call(this, value)
}
Child.prototype = new Parent()
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

The core of the above inheritance method is to Parent.call(this)inherit subclass, and then change the prototype of the new Parent()subclass to inherit the functions of the parent class.

The advantage of this inheritance method is that the constructor can pass parameters, it will not share the reference property with the parent class, and the functions of the parent class can be reused, but there is also a disadvantage that the parent class constructor is called when inheriting the parent class function, resulting in The prototype of the subclass has more unnecessary properties of the parent class, which is a waste of memory.

function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}
function Child(value) {
  Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true
  }
})
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

The core of the above inheritance implementation is to assign the prototype of the parent class to the subclass, and set the constructor to the subclass, which not only solves the problem of useless parent class attributes, but also correctly finds the constructor of the subclass.

The class keyword was introduced in ES6. Class can implement inheritance through the extends keyword, and can also define static methods of the class through the static keyword. This is much clearer and more convenient than ES5's implementation of inheritance by modifying the prototype chain. It should be noted that the class keyword is just syntactic sugar for prototypes, and JavaScript inheritance is still implemented based on prototypes .

class Parent {
  constructor(value) {
    this.val = value
  }
  getValue() {
    console.log(this.val)
  }
}
class Child extends Parent {
  constructor(value) {
    super(value)
    this.val = value
  }
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

The core of class implementation inheritance is to use extends to indicate which parent class inherits from, and super must be called in the subclass constructor, because this code can be seen as Parent.call(this, value).

 

5. DOM operation and BOM operation


1. DOM manipulation

When the web page is loaded, the browser will create the document object model (DOM) of the page. We can think that the DOM is the HTML structure recognized by JS, a common JS object or array. Next we introduce common DOM operations:

var div1 = document.getElementById('div1')
// add new node
var p1 = document.createElement('p')
p1.innerHTML = 'this is p1'
div1.appendChild(p1) // add the newly created element
// Move existing nodes. Note that this is "moving", not copying
var p2 = document.getElementById('p2')
div1.appendChild(p2)
var div1 = document.getElementById('div1')
var parent = div1.parentElement
var div1 = document.getElementById('div1')
var child = div1.childNodes
var div1 = document.getElementById('div1')
var child = div1.childNodes
div1.removeChild(child[0])

 

2. DOM event model and event flow

The DOM event model is divided into capturing and bubbling . When an event occurs, it is propagated between child and parent elements. This propagation is divided into three stages.

(1) Capture phase: the phase in which events are propagated from the window object to the target node from top to bottom;

(2) Target stage: the stage where the real target node is processing the event;

(3) Bubbling stage: the stage in which events are propagated from the target node to the window object from bottom to top.

The specific process of DOM event capture


The capture is from top to bottom, events first go from the window object, then to the document (object), then to the html tag (getting the html tag via document.documentElement), then the body tag (getting the body tag via document.body), then It is passed down layer by layer according to the ordinary html structure, and finally reaches the target element.

Next, let's look at an example of event bubbling:

<div id="outer">
    <div id="inner"></div>
</div>
......
window.onclick = function() {
    console.log('window');
};
document.onclick = function() {
    console.log('document');
};
document.documentElement.onclick = function() {
    console.log('html');
};
document.body.onclick = function() {
    console.log('body');
}
outer.onclick = function(ev) {
    console.log('outer');
};
inner.onclick = function(ev) {
    console.log('inner');
};

 

How to stop bubbling?

Prevents events from bubbling up to the parent element via the event.stopPropagation()method, preventing any parent event handlers from being executed.
We can add event.stopPropagation()this sentence to the click event of the inner element in the above example to prevent the execution of the parent event, and finally print only 'inner'.

 inner.onclick = function(ev) {
    console.log('inner')
    ev.stopPropagation()
}

 

3. Event delegation (event delegation)

Since the event will be propagated up to the parent node in the bubbling stage, the listener function of the child node can be defined on the parent node, and the listener function of the parent node can uniformly handle the events of multiple child elements. This method is called event delegation.

We set a scene, the following code, one <div> contains several <a>, and can continue to increase. So how to quickly and easily <a>bind events for all?

<div id="div1">
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4</a>
</div>
<button>Click to add an a tag</button>

<a> If you bind an event to each tag one by one, it will consume a lot of memory. With the help of event proxy, we only need to bind a method to the parent container div, so that no matter which descendant element is clicked, the click behavior of the parent container will be triggered according to the transmission mechanism of bubbling propagation, and then the corresponding method will be executed. , depending on the event source, we can know who clicked and do different things.

var div1 = document.getElementById('div1')
div1.addEventListener('click', function (e) {
// e.target can monitor which element triggers the click event
var target = e.target
if (e.nodeName === 'A') {
// clicked on an <a> element
alert(target.innerHTML)
} 
})

Finally, the advantages of using a proxy are as follows:

 

4. BOM operation

BOM (Browser Object Model) is the setting and obtaining of some information of the browser itself, such as obtaining the width and height of the browser, and setting which address to let the browser jump to.

 

Get the width and height of the screen

console.log(screen.width)
console.log(screen.height)

 

Get URL, protocol, path, parameters, hash, etc.

// For example, the current URL is https://juejin.im/timeline/frontend?a=10&b=10#some
console.log(location.href) // https://juejin.im/timeline/frontend?a=10&b=10#some
console.log(location.protocol) // https:
console.log(location.pathname) // /timeline/frontend
console.log(location.search) // ?a=10&b=10
console.log(location.hash) // #some


In addition, there are also calls to forward and backward functions of the browser, etc.

history.back()
history.forward()


Get browser characteristics (commonly known as UA) and then identify the client, such as judging whether it is a Chrome browser

var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
console.log(isChrome)

 

5. Ajax and cross domain

Ajax is a technology for asynchronously requesting data, which is very helpful for improving user experience and program performance.
Simply put, Ajax loads background data through asynchronous requests and renders it on the web page without needing to refresh the page. Common application scenarios include form verification whether the login is successful, Baidu search drop-down box prompts, and express tracking number query. The purpose of Ajax is to improve the user experience and reduce the amount of network data transmission .

How to write XMLHttpRequest without any library

var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
// The function here is executed asynchronously
if (xhr.readyState == 4) {
if (xhr.status == 200) {
alert(xhr.responseText)
} 
} 
}
xhr.open("GET", "/api", false)
xhr.send(null)

Because browsers have a same-origin policy for security reasons. That is, if there is a difference in the protocol, domain name, or port, it is cross-domain, and the Ajax request will fail.

So why is this mechanism introduced for security reasons ? In fact, it is mainly used to prevent CSRF attacks. To put it simply, CSRF attacks use the user's login state to initiate malicious requests.

Then let's consider a question, the request is cross-domain, so is the request sent ? The request must have been sent, but the browser intercepted the response.

Several common cross-domain solutions (for details, please refer to the implementation principles of nine cross-domain methods (full version) ):

 

6. Storage

Difference between sessionStorage, localStorage and cookies

Scope: localStorage can read/modify the same localStorage data under the same protocol, same hostname, and same port. sessionStorage is a bit more strict than localStorage, in addition to protocol, host name, port, it also requires the same window (that is, the browser tab)

Life cycle: localStorage is a persistent local storage, the data stored in it will never expire, and the only way to make it disappear is to delete it manually; while sessionStorage is a temporary local storage, it is a session-level storage, when When the session ends (the page is closed), the stored content is also released.

 

6. Modularization

Introduction to several common modular specifications (for details, please click Front-end Modular Explanation (full version) ):