前端面试必问知识点总结

前端面试必问知识点总结

前言

大家好。面对严峻的找工作大环境。

通过网友分享的面试题集锦中,我整理出了基本上是必考的知识点。

大概花了一周的时间,完成这篇基础面试题知识点的总结与分析。

希望对大家有所帮助。跟大家一起学习。

知识点总结

数据类型

JavaScript有几种数据类型
  • 基础类型

    • string
    • number
    • boolean
    • null
    • undefined
    • symbol
    • bigint
  • 引用类型

    • object
基础类型和引用类型的区别

基础类型:

  • key,value都存放在栈中。每个基础类型都会单独存放一份
  • 修改基础类型的变量值,都会生成一份新的数据。所以声明好的基础类型数据的值是不会改变的。
  • 能用typeof运算来判断,注意typeof null //-> object
  • 值类型在计算的时候会进行隐式类型转换,不能用==来判断

引用类型

  • key,value也是存放在栈中。但是在栈中的value存放的是一个地址,而非值本身。这个地址指向了在堆中的真正的值。

数据类型转换规则:

  • 字符串类型

    • Null 和 Undefined 类型 ,null 转换为 “null”,undefined 转换为 “undefined”,

    • Boolean 类型,true 转换为 “true”,false 转换为 “false”。

    • Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。

    • Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。

    • 对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。

  • 数字类型

    • Undefined 类型的值转换为 NaN。

    • Null 类型的值转换为 0。

    • Boolean 类型的值,true 转换为 1,false 转换为 0。

    • String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。

    • Symbol 类型的值不能转换为数字,会报错。

    • 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。

typeof运算符

用于检测数据的类型。通过下面的代码来演示。

typeof ''				//string
typeof 1 //nunber
typeof true //boolean
typeof Symbol('s') //symbol
typeof undefined //undefined

typeof null //object
typeof function(){} //function
typeof [1,2] //object
typeof {} //object

typeof new String('s') //object
typeof new Number(1) //object
typeof new Function() //function

注意:

  1. null 和 function 比较特殊。
  2. 所有使用 new调用的构造函数都将返回非基本类型(“object” 或 “function”)。
=的区别

两者都是比较运算符。这两个运算符之间的区别在于“”用于比较值,而“=”用于比较值和类型。

前面说过,基础类型在进行计算、比较的时候会进行类型转换,比如:

0==false
false==0
''==false;
[]==0

变量提升(Hosting)

变量提升在JavaScript编程中、是一个比较重要的概念。每个开发者应该都要了解的。具体原理涉及到JavaScript引擎的执行机制。这里先简单的介绍

JavaScript引擎在编译解释代码的阶段会将变量声明提升到代码块的最开始部分。注意,这里只提升变量声明,变量赋值并不会提升。这个时候所有的变量声明的值都是undefined;

Tips:

  • JavaScript中的变量提升,只限于var关键字声明的变量,let,const并不会。
  • 严格模式(‘use strict’) ,也不会提升,并且会报错

变量声明提升:

console.log(name);
var name='jojo';

//等价于
var name=undefied;
console.log(name);
name='jojo';

//所以代码输出:undefined

函数声明提升:

play();
function play(){
  console.log('jojo')
}

//等价于
function play(){
  console.log('jojo')
}
play();

//注意:函数声明的提升是整个函数的提升,这也是为什么函数调用可以在函数声明之前,
// 这里只限于函数的声明方式

严格模式:

"use strict";
x = 'jojo'; // 'x' is not declared
var x; 

var,let,const的区别

JavaScript中有三种声明变量的方式;

  • var
  • let (es6+)
  • const (es6+)

现在我们从以下几个方面来解释它们之间的区别

  • 声明

    • 都可以用于变量的声明
    • var,let 声明的变量可以不初始化,const必须要初始化
    • let,const 变量都必须要先声明,才能使用。var 则没有此限制。
  • 赋值

    • var,let 声明的变量可以不初始化,const必须要初始化
    • const 声明变量,不能够重新赋值。let,var则没有此限制
  • 作用域

    • var 声明的变量,可以在当前作用域的任何位置调用
    • let,const声明的变量,在当前作用域中会存在暂时性死区。必须先声明在调用
  • 变量提升

    • var 有变量提升
    • const,let 不存在变量提升

函数call,bind ,apply的区别

都是用于改变函数的this指向。

call、apply与bind的区别:

call和apply改变了函数的this上下文后,同时执行该函数。

bind则是返回改变了this上下文后的一个函数。用于this的绑定,方便后续的调用。

Promise

(本部分内源来源网络)

Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和更强大。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise的实例有三个状态:

  • Pending(进行中)
  • Resolved(已完成)
  • Rejected(已拒绝)

当把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected。

Promise的实例有两个过程

  • pending -> fulfilled : Resolved(已完成)
  • pending -> rejected:Rejected(已拒绝)

注意:一旦从进行状态变成为其他状态就永远不能更改状态了。

Promise的特点:

  • 对象的状态不受外界影响。promise对象代表一个异步操作,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是promise这个名字的由来——“承诺”;
  • 一旦状态改变就不会再变,任何时候都可以得到这个结果。promise对象的状态改变,只有两种可能:从pending变为fulfilled,从pending变为rejected。这时就称为resolved(已定型)。如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果。这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。

Promise的缺点:

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

总结:

Promise 对象是异步编程的一种解决方案,最早由社区提出。Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。

状态的改变是通过 resolve() 和 reject() 函数来实现的,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。

注意:

在构造 Promise 的时候,构造函数内部的代码是立即执行的

Promise只执行一次状态变化,多次执行将不会起作用

隐式类型转换

JavaScript中有隐式类型转换发生在俩种情况:

  • 数学运算符中
  • 逻辑运算

在数学运算符中主要分为:加,减,乘,除。其中加法比较特殊,我们先来讨论减,乘,除(-,*,/):

在减,乘,除(-,*,/)运算中,会把非Number类型数据转成Number类型

1 - true; // 0  true 转换为数字是1, 然后执行1 - 1
1 * null; // 0 null 转换为数字是0, 然后执行1 * 0
1 * undefined;// NaN undefined 转换为数字是 NaN
1 - []; //[]==0, 1
1 - [3]; //[3]==3, -2

​ 注意

[3]为什么会等于3?这里具体涉及到拆箱操作。也就是调用引用类型的toString,valueOf方法。[3].toString() //3

作用域,作用域链

什么是作用域?

变量,函数能访被访问的位置范围

在JavaScript中,有俩种作用域

  • 全局作用域(Global)

  • 局域作用域(Local)

    • 函数作用域
    • 块级作用域(ES6+才有的)
全局作用域

当我们说道全局作用域的时候,指的是没有被包含在{}的代码。能被任何位置的代码访问。

const name='jojo'
if(true){
  console.log(name); //这里可以访问name
}
function say(){
  console.log(name); //这里也可以访问name
}
//say 函数也能被任何地方的代码调用

注意

在if,for语句中使用var声明的变量,也能被任何位置的代码访问。因为var声明的变量存在提升(# Hoisting)

这也是为什么在es6以前,没有块级作用域。

if(true){
  var name='jojo';
}
console.log(name) //jojo
局部作用域

局部作用域、在es6以前指的就是函数内部的变量声明,只能在函数内部被访问。

在es6之后,由于新加了const,let变量声明,所以有了块级作用域的作用。在 ES6 之前,我们只有一个关键字来定义变量“var”。所以那时我们有一种类型的局部作用域,它是由函数创建的。在 ES6 中,他们想让代码更加模块化,所以他们增加了两个关键字来声明一个变量“let”和“const”。用“let”和“const”声明的变量遵循所有括号并且不能从外部访问。通过这种方式,使用“let”和“const”我们现在可以拥有块作用域。同样对于 ES6,使用“let”和“const”是最好的声明变量的方式。

let 语句声明一个块级作用域的局部变量,并可以初始化为一个值(可选)。

let 跟 const一样。

关于let更多知识,点击访问

作用域链Scope Chain

通过上面的代码

const name='jojo'
if(true){
  console.log(name); //这里可以访问name
}
function say(){
  console.log(name); //这里也可以访问name
}
say();

当我们运行say函数时,say函数内部需要使用一个name变量。显然,在say函数的作用域内并没有发现name变量。这个时候呢,将尝试从父作用域中寻找个变量,对应这里的就是全局作用域。

还没看明白?下面通过一个列子来解释,解释之前呢,我们先了解几个概念。

  • 静态作用域
  • 动态作用域

在代码执行的时候,如何确定作用域中变量的值?一般情况分静态作用域,和动态作用。静态作用域关心的是代码声明的位置。动态作用域关心的是函数的调用者的位置。在JavaScript编程中,使用的是静态作用域,作用域的范围跟函数的声明位置有关。

const name='jojo'

function word(){
  console.log(name); //jojo
}

function say(){
  const name='wwbb'
  word();
}
say();

上面代码可以看出,word确定变量name的值的时候,选择的是全局作用域的name变量,而在调用word函数的say作用域中声明的name变量,没有被使用。

总结
  • JavaScript有俩种作用域,全局作用域和局部作用域。局部作用域分为函数作用域,和块级作用域
  • JavaScript是静态作用域,作用域链的关系跟函数,变量的声明位置有关。

JavaScript中的DOM API

现在前端框架很流行,但是不代表我们可以不用知道原生dom编码。

学习原生dom开发将有助你更好的理解前端框架。初级开发者,应该要掌握基础的dom操作api;

学习之前、先了解一个Node的概念。

Node是一个接口,中文叫节点,很多类型的DOM元素都是继承于它,都共享着相同的基本属性和方法。常见的Node有 element,text,attribute,comment,document 等(所以要注意 节点 和 元素 的区别,元素属于节点的一种)

  • 创建API
    • createElement 创建element节点
    • createTextNode 创建text节点
    • cloneNode 复制一个节点
    • createDocumentFragment 创建一个html片段,用于批量操作
  • 修改API
    • appendChild
    • insertBefore
    • removeChild
    • replaceChild
  • 查询API
    • getElementById
    • getElementsByTagName
    • getElementsByName
    • getElementsByClassName
    • querySelector 通过选择器查询元素,只返回第一个匹配的
    • querySelectorAll 通过选择器查询元素,只返回所有匹配的
  • 节点属性
    • parentNode 父节点
    • parentElement 父元素节点
    • previousSibling 前一个兄弟节点
    • previousElementSibling 前一个兄弟元素节点
    • nextSibling 后一个兄弟节点
    • nextElementSibling 后一个兄弟元素节点
    • childNodes 返回元素的所有类型节点的子节点列表
    • children 只返回的element类型节点的子节点列表
    • firstChild 第一个子节点
    • lastChild 最后一个子节点
    • hasChildNodes 用来判断是否包含子节点

箭头函数

箭头函数特点:

  • 没有自己的this,this在箭头函数声明的时候就已经确定,this指向声明时的作用域的this
  • 如果函数体的返回值只有一句,可以省略大括号
  • 如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字
  • call()、apply()、bind()等方法不能改变箭头函数中this的指向
    • this在箭头函数声明的时候就已经确定,this指向声明时的作用域的this因为箭头函数中的this不是自己的,
  • 箭头函数不能作为构造函数使用
    • 因为箭头函数中的this不是自己的
  • 箭头函数没有自己的arguments
    • 箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值
  • 箭头函数没有prototype
  • 箭头函数不能用作Generator函数,不能使用yeild关键字

JavaScript中的this

在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)。this 不能在执行期间被赋值,并且在每次函数被调用时 this 的值也可能会不同。ES5 引入了 bind 方法来设置函数的 this 值,而不用考虑函数如何被调用的。ES2015 引入了箭头函数,箭头函数不提供自身的 this 绑定(this 的值将保持为闭合词法上下文的值)。

在实际开发中,this 有以下几种情况

  • 直接调用一个函数时,this 指向全局对象
  • 作为一个对象的方法来调用,this 指向这个对象。
  • 函数通过 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
  • 第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。

题目:

(function(){
   var x = y = 1;
})();
var z;

console.log(y); // 1
console.log(z); // undefined
console.log(x); // Uncaught ReferenceError: x is not defined

解析:

这段代码的关键在于:var x = y = 1; 实际上这里是从右往左执行的,首先执行y = 1, 因为y没有使用var声明,所以它是一个全局变量,然后第二步是将y赋值给x,讲一个全局变量赋值给了一个局部变量,最终,x是一个局部变量,y是一个全局变量,所以打印x是报错。

题目:

// a
function Foo () {
 getName = function () {
   console.log(1);
 }
 return this;
}
// b
Foo.getName = function () {
 console.log(2);
}
// c
Foo.prototype.getName = function () {
 console.log(3);
}
// d
var getName = function () {
 console.log(4);
}
// e
function getName () {
 console.log(5);
}

Foo.getName();           // 2
getName();               // 4
Foo().getName();         // 1
getName();               // 1 
new Foo.getName();       // 2
new Foo().getName();     // 3
new new Foo().getName(); // 3

解析:

  1. **Foo.getName(),**Foo为一个函数对象,对象都可以有属性,b 处定义Foo的getName属性为函数,输出2;
  2. **getName(),**这里看d、e处,d为函数表达式,e为函数声明,两者区别在于变量提升,函数声明的 5 会被后边函数表达式的 4 覆盖;
  3. **Foo().getName(),**这里要看a处,在Foo内部将全局的getName重新赋值为 console.log(1) 的函数,执行Foo()返回 this,这个this指向window,Foo().getName() 即为window.getName(),输出 1;
  4. **getName(),**上面3中,全局的getName已经被重新赋值,所以这里依然输出 1;
  5. **new Foo.getName(),**这里等价于 new (Foo.getName()),先执行 Foo.getName(),输出 2,然后new一个实例;
  6. **new Foo().getName(),**这里等价于 (new Foo()).getName(), 先new一个Foo的实例,再执行这个实例的getName方法,但是这个实例本身没有这个方法,所以去原型链__protot__上边找,实例.protot === Foo.prototype,所以输出 3;
  7. **new new Foo().getName(),**这里等价于new (new Foo().getName()),如上述6,先输出 3,然后new 一个 new Foo().getName() 的实例。

客户端存储(浏览器存储)

cookie

理论上来说,cookie并非用于存储的,而是用于保持客户端与服务端的连接状态。因为http是无状态的,需要借助cookie来实现多次连接的关系判定。比如典型的购物车应用;

特性:

  • cookie由服务端生成,并且通过response header, set-cookie写入客户端。

  • 在客户处维护。客户端可以通过api操作cookie;

  • 每个http连接都会带上cookie;

缺点:

  • 存储大小比较小,一般来说,只有4kb;

  • 由于每次http请求都会带上cookie,所以会造成http请求数据变大

  • cookie是明文传递,不安全;当然了,我们可以https来使的连接更安全

sessionStorage/localStorage

其实这俩个storage才是被设计出来用于客户端存储的。

  • sessionStorage
    • 会话级别存储
    • 浏览器关闭,存储被清除
    • 存储内容不共享,只限当前浏览器标签页(同一窗口)
    • 仅在客户端使用,不和服务端进行通信
  • localStorage
    • 永久存储,当前域名,随时访问。
    • 通过api手动操作存储
    • 仅在客户端使用,不和服务端进行通信
    • 一般用在网页存储,提升网页加载速度。

客户端存储使用场景:

  • 保留登录状态
  • 购物车,代办事项
  • 页面的个性化设置,主题,配色
  • 缓存部分数据,提高页面的加载速度

Ajax

AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。

创建AJAX请求的步骤:

  • 创建XMLHttpRequest 对象
  • 通过open方法来创建请求
  • 添加一个状态监听函数
  • 调用send可以传入参数作为发送的数据体。

常见使用:

let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", 'http://www.baidu.com', true);
// 设置状态监听函数
xhr.onreadystatechange = function() {
  if (this.readyState !== 4) return;
  // 当请求成功时
  if (this.status === 200) {
    handle(this.response);
  }
};
// 设置请求失败时的监听函数
xhr.onerror = function() {
  console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);

原型,原型链

JavaScript中的原型、和原型链是必须得会的。也是面试的时候必会出现的问题。

这里给大家梳理一下基础知识。其实原型总结下来就三点:

  1. 函数都有prototype,称之为显示原型
  2. 对象实例都有 __proto__,称之为隐式原型
  3. 隐式原型指向对象实例的构造函数的显示原型

注意:

class也有原型,因为class本质也是一个函数

function Person(name){
  this.name=name
}
// 第一点 函数有prototype
Person.prototype.getName=function(){
  return this.name;
}
var p=new Person('web');
//p 是Person的实例,是一个对象。有__proto__
//__proto__指向构造函数的显示原型
console.log(p.__proto__ === p.constructor.prototype) //true

记住这三点。就可以弄明白所有的原型知识点了。

然后再来说说原型链。

原型链:

当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。

还是通过上面的代码来分析:

function Person(name){
  this.name=name
}
Person.prototype.getName=function(){
  return this.name;
}
var p=new Person('web');
console.log(p.toString()) //[object Object]

//p的隐式原型指向构造函数的显示原型Person.prototype
p.__proto__ == p.constructor.prototype (Person.prototype)
//prototype是一个对象实例,构造函数是Object,所以
p.__proto__.__proto__===Object.prototype
//Object.prototype的隐式原型,指向null,这个要记住。所以
p.__proto__.__proto__.__proto__=null;

上面代码最后一行的通过原型一直往上访问,直到找到需要的属性,方法;

题目:

function Parent() {
    this.a = 1;
    this.b = [1, 2, this.a];
    this.c = { demo: 5 };
    this.show = function () {
        console.log(this.a , this.b , this.c.demo );
    }
}

function Child() {
    this.a = 2;
    this.change = function () {
        this.b.push(this.a);
        this.a = this.b.length;
        this.c.demo = this.a++;
    }
}

Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;

parent.show(); // 1  [1,2,1] 5
child1.show(); // 11 [1,2,1] 5
child2.show(); // 12 [1,2,1] 5
child1.change();
child2.change(); 
parent.show(); // 1 [1,2,1] 5
child1.show(); // 5 [1,2,1,11,12] 5
child2.show(); // 6 [1,2,1,11,12] 5

解析:

  1. parent.show(),可以直接获得所需的值,没啥好说的;
  2. child1.show(),Child的构造函数原本是指向Child的,题目显式将Child类的原型对象指向了Parent类的一个实例,需要注意Child.prototype指向的是Parent的实例parent,而不是指向Parent这个类。
  3. child2.show(),这个也没啥好说的;
  4. parent.show(),parent是一个Parent类的实例,Child.prorotype指向的是Parent类的另一个实例,两者在堆内存中互不影响,所以上述操作不影响parent实例,所以输出结果不变;
  5. child1.show(),child1执行了change()方法后,发生了怎样的变化呢?
  • this.b.push(this.a),由于this的动态指向特性,this.b会指向Child.prototype上的b数组,this.a会指向child1a属性,所以Child.prototype.b变成了**[1,2,1,11]**;
  • this.a = this.b.length,这条语句中this.athis.b的指向与上一句一致,故结果为child1.a变为4;
  • this.c.demo = this.a++,由于child1自身属性并没有c这个属性,所以此处的this.c会指向Child.prototype.cthis.a值为4,为原始类型,故赋值操作时会直接赋值,Child.prototype.c.demo的结果为4,而this.a随后自增为5(4 + 1 = 5)。
  1. child2执行了change()方法, 而child2child1均是Child类的实例,所以他们的原型链指向同一个原型对象Child.prototype,也就是同一个parent实例,所以child2.change()中所有影响到原型对象的语句都会影响child1的最终输出结果。
  • this.b.push(this.a),由于this的动态指向特性,this.b会指向Child.prototype上的b数组,this.a会指向child2a属性,所以Child.prototype.b变成了**[1,2,1,11,12]**;
  • this.a = this.b.length,这条语句中this.athis.b的指向与上一句一致,故结果为child2.a变为5;
  • this.c.demo = this.a++,由于child2自身属性并没有c这个属性,所以此处的this.c会指向Child.prototype.c,故执行结果为Child.prototype.c.demo的值变为child2.a的值5,而child2.a最终自增为6(5 + 1 = 6)。

闭包

闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。

特点;

  • 函数外部能够访问到函数内部的变量
  • 缓存变量

异步编程

JavaScript编程中异步编程是一个核心的概念。

由于JavaScript是单线程序。也就是说只能按顺序执行代码,遇到耗时任务就得等待它完成之后在继续执行。

为了能更高效的执行代码。于是利用事件循环(后面会专门介绍这个)来实现异步机制。

让程序可以在执行一个可能长期运行的任务的同时继续对其他事件做出反应而不必等待任务完成。与此同时,你的程序也将在任务完成后显示结果。

以下技术都属于异步编程

  • ajax
  • 回调函数
  • dom事件
  • promise,async/await,setTimeout

事件循环(Event Loop)

JavaScript代码的执行过程中,利用执行栈来解决函数的执行顺序,同时还有维护一个任务队列(task queue)来解决异步任务的执行。整个执行过程,我们称为事件循环过程。

任务队列又分为macro-task(宏任务)与micro-task(微任务)。在最新标准中,它们被分别称为task与jobs。

  • macro-task(宏任务)
    • script(整体代码)
    • setTimeout
    • setInterval
    • setImmediate
    • I/O
    • UI render
  • micro-task(微任务)
    • process.nextTick
    • Promise.then
    • Async/Await(实际就是promise)
    • MutationObserver(html5新特性)

事件循环机制:

首先,执行宏任务,也就是按顺序执行主代码块,遇到微任务,就把微任务放入事件队列,直到执行完当前的宏任务。

然后,执行事件队列中的微任务,事件队列中所有的微任务执行完毕后,开始循环下一轮的宏任务。

通过一个例子来解释:

console.log('script start')
setTimeout(function() {
    console.log('setTimeout')
}, 0)
new Promise(resolve => {
    console.log('Promise')
    resolve()
})
.then(function() {
    console.log('promise1')
})
.then(function() {
    console.log('promise2')
})
console.log('script end')

先思考一下上面代码的输出?

答案:script start,Promise,script end,promise1,promise2,setTimout

分析:

  • 代码运行,输出script start
  • 遇到setTimout ,宏任务,放到宏任务队列
  • 执行Promise,输出Promise
    • Promise函数是个立即执行函数,属于宏任务,只有then才是微任务
  • 遇到then方法是个微任务,放到微任务队列
  • 第二个then方法暂时不动
  • 继续执行console.log 输出 script end
  • 此时,宏任务执行完毕
  • 执行微任务队列,第一个then方法,输出promise1
  • 执行第二个then方法,then方法是微任务,放到微任务队列
  • 微任务队列还有方法,继续执行then,输出promise2

结尾

如果大家在面试中遇到其他的知识点,可以一起分享,

谢谢大家

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端3K小哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值