UzumakiItachi
首页
  • JavaSript
  • Vue

    • Vue2
    • Vue3
  • React

    • React_18
  • WebPack
  • 浏览器相关
  • 工程化相关
  • 工作中遇到的问题以及解决方案
  • Git
  • 面试
  • 学习
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 个人产出
  • 实用工具
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

UzumakiItachi

起风了,唯有努力生存。
首页
  • JavaSript
  • Vue

    • Vue2
    • Vue3
  • React

    • React_18
  • WebPack
  • 浏览器相关
  • 工程化相关
  • 工作中遇到的问题以及解决方案
  • Git
  • 面试
  • 学习
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 个人产出
  • 实用工具
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaScript

    • 手撕Promise
    • 手撕JS八股文系列
      • 一、事件循环
      • 二、作用域,作用域链,执行上下文
      • 三、js垃圾回收机制
      • 四、模块化相关
    • 手撕观察者模式和发布订阅模式
    • 页面可见性——visibilitychange
    • JS数据类型那点事儿
    • 作用域、执行上下文和this
    • 防抖和节流
    • 装饰器
    • JS设计模式小结
  • CSS

  • 前端
  • JavaScript
hanhanbuku
2023-03-01
目录

手撕JS八股文系列

# 一、事件循环

在了解事件循环之前 先要了解什么是同步任务什么是异步任务。我们都知道js代码在执行的时候是按顺序执行,在上一行代码没执行完毕是不能去执行下一行代码的。这也就意味着js在无法同时做多件事情。而异步任务就是为了解决这一问题。

异步任务又分为宏任务和微任务。而事件循环指的就是宏任务和微任务的执行所产生的一个循环机制。

常见的宏任务有script(整体代码)、setTimeout、setInterval、setImmediate(nodejs独有)、requestAnimationFrame(浏览器独有)、IO、UIrender(浏览器独有)

常见的微任务有:process.nextTick(nodejs独有)、Promise.then()、Object.observe、MutationObserver

当一段js代码在执行的时候 会维护宏任务和微任务两个队列。遇到了宏任务或者微任务就会将他压入对应的对列中。等当前代码执行完毕后就会将微任务队列里得任务拿出来执行。如果此过程中产生了新的微任务 则又维护一个新的微任务队列,等当前微任务队列清空后再去执行新的微任务队列里得任务,当所有得微任务都执行完之后就会开始执行宏任务队列里得任务。如果此过程中产生了微任务则又会开始执行微任务,以此循环直到两个队列都被清空。这就是事件循环得全过程。ps:微任务会在dom渲染之前执行,因为js引擎线程和GUI线程互斥的原因,导致js在执行,就不会去渲染页面。 下面让我们看一道练习题来巩固一下知识

setTimeout(function () {
    console.log("setTimeout1");

    new Promise(function (resolve) {
        resolve();
    }).then(function () {
        new Promise(function (resolve) {
            resolve();
        }).then(function () {
            console.log("then4");
        });
        console.log("then2");
    });
});

new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("then1");
});

setTimeout(function () {
    console.log("setTimeout2");
});

console.log(2);

new Promise(function (resolve) {
    resolve();
}).then(function () {
    console.log("then3");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

首先代码遇到setTimeout 将他加入宏任务队列,然后执行道第一个promise 输出其中得同步代码 promise1 然后将.then加入微任务队列。 随后又遇到了setTimeout 将其加入宏任务队列 紧接着输出2 最后又是一个promise 而这个promise里没有同步代码 所以直接将then加入微任务队列。 此时已经输出了promise1,2。 而当前得宏任务队列里有两个定时器 微任务队列里有两个promise.then 现在开始执行微任务,先输出then1 紧接着输出then3. 当前微任务队列已经空了 开始执行宏任务队列。 进入第一个定时器 输出setTimeout1,紧接着又遇到了微任务,所以又开始执行微任务。 输出then2 而这个微任务里又有一个微任务 所以接着执行他 输出then4 微任务清空了 接着去执行宏任务 输出setTimeout2.

所以最终得输出顺序应该是 promise1,2,then1,then3,setTimeout1,then2,then4,setTimeout2

# 二、作用域,作用域链,执行上下文

作用域指的就是一个变量被定义得区域,他决定了如何查找这个变量以及变量可被访问的权限。 作用域分为全局作用域 函数作用域 块级作用域。 在es6之前js是没有块级作用域得 所以除函数作用域外var声明得变量都会提升到全局,变成全局变量。es6引进了let和const两个关键字来声明变量。从而就有了块级作用域的概念 。在一个花括号内使用let和const声明的变量只能在这个块内被访问,外部是无法访问的。const用来声明一个不可改变的常量

js采用的是静态作用域,也就是变量得作用域在他被定义得时候就已经决定了。而与之对应得动态作用域则是在哪儿被调用 作用域就在哪儿。

执行上下文 当一个函数被调用得时候,js就会进行准备工作, 这个准备工作指的就是创建执行上下文。 一个执行上下文包括 变量对象(vo) 作用域链 this 执行上下文也分为全局执行上下文和函数执行上下文。

js有一个专门维护执行上下文得栈 全局执行上下文始终在栈得最底部 每个函数被调用时 他得执行上下文就会被压入栈中 等他执行完毕后 这个执行上下文又会被弹出。

变量对象包括函数得形参 以及函数内部所声明得所有变量。当在函数中访问一个变量时 js会现在当前函数得执行上下文得变量对象中查找,如果没有找到 则会去查找父级作用域得变量对象。一直到全局上下文得变量对象为止。这个查找得过程就是作用域链。

# 三、js垃圾回收机制

js大概有两种垃圾回收机制

  • 1、标记清除 js引擎会给所有的对象打上一个标记,当这个对象被引用时 就会把标记清除掉,最后再把所有还保留这标记的变量回收 释放内存。这种方法会导致有很多内存碎片。

  • 2、引用计数 采用计数的方式记录一个变量是否可以被回收,初始赋值的时候数值+1当变量被赋值给别人的时候 数量+1 当给变量赋值的时候数量-1,如果变量的计数为0则说明没有人引用这个变量了。则可以回收此变量。直接给变量赋值null此变量就会被回收。

# 四、模块化相关

模块化有利于代码的提高代码复用性和可维护性,防止命名冲突以及全局污染。

js的模块化方案大致有以下几种:

  • 1、AMD:异步加载模块,所有的模块代码都写在回调函数中,只有当模块加载完毕才会去执行回调函数里的代码,此模式适用于浏览器端
  • 2、CMD:可异步也可同步的模块加载方案,他和AMD不同的是执行了require就会去加载模块,支持同步也支持异步,支持浏览器也支持node
  • 3、UMD:AMD和CMD的整合
  • 4、Commonjs:这是nodejs的模块化方案他的每个js都是一个模块,每个js中它提供了几个核心变量:exports、module.exports、require。前两个用来暴露出变量,最后一个用来引入。
  • -4.1、require的加载流程:首先被加载的内容分为 node核心模块,自定义模块,文件模块。 像我们通过路劲引入的则是文件模块,而直接通过包名引入的话 commonjs首先会去查找是否是node自带的模块,如果不是则去node_modules里查找这个包,如果还没有则往父级的依赖文件夹里找 直到根目录为止。 那commonjs是如何避免循环引用和重复加载的呢? 要搞清楚这个问题得直到module和Module这两个东西,上文说了每个js文件都是一个module,这个module身上不仅有导入导出的方法,还有一个loaded属性,表示这个模块是否被加载过。Module则是nodejs在运行的时候创建的一个保存所有模块值得变量,每个模块在被加载之前都会去Module身上查找是否有缓存,没有的话就会存下。

有了这两个东西之后commonjs就可以做到避免重复加载和循环引用的问题。

  • 5、ESM:Es Module是JS的es6规范才提出的模块化方案,是一个真正属于js自己的模块化规范

他的诞生有很多优势: 借助 Es Module 的静态导入导出的优势,实现了 tree shaking。 Es Module 还可以 import() 懒加载方式实现代码分割。

Es Module 中用 export 用来导出模块,import 用来导入模块。但是 export 配合 import 会有很多种组合情况,接下来我们逐一分析一下。

ESM的导入导出都是静态的,import会自动提升到代码顶部,并且他的语法都不能写在函数作用域和块级作用域内,也不能写在条件判断内。

正因为这种静态的语法,所有他会在代码编译阶段就确定依赖关系,也是借助这个特性,可以做摇树优化。

因为import会提升到最顶部,所以esm的执行顺序是先子后父。

下面总结一下ESM和Commonjs:

Commonjs:

  • 1、代码运行时加载
  • 2、导出的是值得浅拷贝,值一旦输出,在模块内到处方法是无法修改已导出的值的
  • 3、可以动态加载,对每个导出的值都有缓存,能有效解决循环引用的问题
  • 4、加载的时候才执行

ESM

  • 1、代码编译时加载
  • 2、导出的值可以被模块内的方法修改,但不支持外界主动修改
  • 3、写法灵活,可以单个导入导出混合导入导出
  • 4、因为import会提升,所以esm的模块会提前加载并执行
编辑 (opens new window)
上次更新: 2023/03/01, 17:49:47
手撕Promise
手撕观察者模式和发布订阅模式

← 手撕Promise 手撕观察者模式和发布订阅模式→

最近更新
01
小程序实现全局状态管理
07-09
02
前端检测更新,自动刷新网页
06-09
03
swiper渲染大量数据的优化方案
06-06
更多文章>
Theme by Vdoing | Copyright © 2023-2025 UzumakiItachi | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式