找回密码
 立即注册
首页 资源区 代码 JavaScript

JavaScript

坏级尹 5 小时前
事件循环机制

事件循环练习
由于js是单线程,为防止代码阻塞,将代码分为同步和异步,同步代码会直接放入执行栈中执行,异步代码(如setTimeout)放入宿主环境(浏览器,Node)中,时机到了(点击事件即点击后,setTimeout即时间结束后)以后将回调函数放入任务队列中,执行栈中的代码执行完后就会去任务队列中查看有无异步代码要执行。反复循环查看执行,这个过程就是事件循环。
1.png

js又把异步任务分为宏任务(由宿主环境发起,如script,事件,网络请求Ajax/Fetch,setTimeout()/setInterval())和微任务(由JS引擎发起,如Promise,Promise本身同步,then/catch回调函数异步)
2.png

注意⚠️


  • 排前面的 script 先执行,执行其内部的【同】,再执行其【微】,接着就轮到下一个大的宏,也就是执行下一个 script,【同】、【微】......顺序执行完后,再从头开始,看第一个 script 是否有需要执行的【宏】,再去下一个 script 中找 【宏】,等大家宏结束后,进入下一轮循环。
3.png


  • async函数里面的属于同步代码,await后的代码属于异步微任务
4.png

Practice

1️⃣
  1. console.log('A');·
  2. setTimeout(() => console.log('B'), 0);
  3. Promise.resolve().then(() => console.log('C'));
  4. Promise.resolve().then(() => setTimeout(() => console.log('D'), 0));
  5. console.log('E');
复制代码
点击查看答案
  1. A → E → C → B → D
复制代码
2️⃣
  1. const promise = new Promise((resolve, reject) => {
  2. console.log(1);
  3. console.log(2);
  4. });
  5. promise.then(() => {
  6. console.log(3);
  7. });
  8. console.log(4);
复制代码
点击查看答案
  1. 1 → 2 → 4
复制代码
3️⃣
  1. async function async1() {
  2.   console.log('1');
  3.   await async2();
  4.   console.log('2');
  5. }
  6. async function async2() { console.log('3'); }
  7. setTimeout(() => console.log('4'), 0);
  8. async1();
  9. new Promise(resolve => {
  10.   console.log('5');
  11.   resolve();
  12. }).then(() => console.log('6'));
  13. console.log('7');
复制代码
点击查看答案
  1. 1 → 3 → 5 → 7 → 2 → 6 → 4
复制代码
4️⃣
  1. new Promise((resolve) => {
  2.   console.log(1);
  3.   resolve(3);
  4.   Promise.resolve().then(() => {
  5.     console.log(4);
  6.   });
  7. }).then((num) => {
  8.   console.log(num);
  9. });
  10. setTimeout(() => {
  11.   console.log(6);
  12. });
  13. Promise.resolve().then(() => {
  14.   console.log(5);
  15. });
  16. console.log(2);
复制代码
点击查看答案
  1. 1 → 2 → 4 → 3 → 5 → 6
复制代码
5️⃣
  1. console.log('start');
  2. setTimeout(() => {
  3.   console.log('Timeout1');
  4. }, 1000);
  5. Promise.resolve().then(() => {
  6.   console.log('Promise1');
  7. });
  8. Promise.resolve().then(() => {
  9.   console.log('Promise2');
  10.   setTimeout(() => {
  11.     Promise.resolve().then(() => {
  12.       console.log('Promise3');
  13.     })
  14.     console.log('Timeout2');
  15.   }, 0);
  16. });
  17. console.log('end');
复制代码
点击查看答案
  1. start → end → Promise1 → Promise2 → Timeout2 → Promise3 → Timeout1
复制代码
  1. function app() {
  2.   setTimeout(() => {
  3.     console.log("1-1");3
  4.     Promise.resolve().then(() => {
  5.       console.log("2-1");5
  6.     });
  7.   });
  8.   console.log("1-2"); 1
  9.   Promise.resolve().then(() => {
  10.     console.log("1-3"); 2
  11.     setTimeout(() => {
  12.       console.log("3-1"); 4
  13.     });
  14.   });
  15. }
复制代码
点击查看答案
  1. 1-2 → 1-3 → 1-1 → 3-1 → 2-1
复制代码
内存管理

JS有如下数据类型:
原始数据类型:String, Number, Boolean, Null, Undefined, Symbol
引用数据类型:Object
而存放这些数据的内存又可以分为两部分:栈内存(Stack)和堆内存(Heap)。原始数据类型存在栈中,引用类型存在堆中。
栈内存

栈是一种只能一端进出的数据结构,先进后出,后进先出。
5.png

堆内存

JS中原始数据类型的内存大小是固定的,由系统自动分配内存。但是引用数据类型,比如Object, Array,他们的大小不是固定的,所以是存在堆内存的。JS不允许直接操作堆内存,我们在操作对象时,操作的实际是对象的引用,而不是实际的对象。可以理解为对象在栈里面存了一个内存地址,这个地址指向了堆里面实际的对象。所以引用类型的值是一个指向堆内存的引用地址。
6.png

函数也是引用类型,当我们定义一个函数时,会在堆内存中开辟一块内存空间,将函数体代码以字符串的形式存进去。然后将这块内存的地址赋值给函数名,函数名和引用地址会存在栈上。
7.png

垃圾回收

垃圾回收就是找出那些不再继续使用的变量,然后释放其占用的内存,垃圾回收器会按照固定的时间间隔周期性执行这一操作。JS使用垃圾回收机制来自动管理内存,但是他是一把双刃剑:
优势: 可以大幅简化程序的内存管理代码,降低程序员负担,减少因为长时间运行而带来的内存泄漏问题。
劣势:程序员无法掌控内存,JS没有暴露任何关于内存的API,我们无法进行强制垃圾回收,更无法干预内存管理。
引用计数

引用计数是一种回收策略,它跟踪记录每个值被引用的次数,每次引用的时候加一,被释放时减一,如果一个值的引用次数变成0了,就可以将其内存空间回收。
使用引用计数会有一个很严重的问题:循环引用。循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。
  1. function problem(){
  2.     var objectA = {};
  3.     var objectB = {};
  4.     objectA.a = objectB;
  5.     objectB.b = objectA;
  6. }
复制代码
在这个例子中,objectA 和 objectB 通过各自的属性相互引用;也就是说,这两个对象的引用次数都是 2。当函数执行完毕后,objectA 和 objectB 还将继续存在,因为它们的引用次数永远不会是 0。
标记—清除算法

算法分为 “标记”和“清除” 两个阶段,最终目的是识别并释放 “不再需要” 的内存。

  • 标记阶段:区分 “有用” 和 “无用” 的变量
    标记规则:
    垃圾回收器会从根对象(Roots) 开始遍历所有变量(根对象通常是全局对象,如浏览器中的window、Node.js 中的global)。


  • 所有能被根对象直接或间接访问到的变量,标记为 “有用”(处于 “进入环境” 状态,即仍在执行环境中被使用)。
  • 无法被根对象访问到的变量,标记为 “无用”(处于 “离开环境” 状态,即已脱离执行环境,不再被使用)。

  • 清除逻辑:
    垃圾回收器会遍历内存中所有变量,将标记为 “无用” 的变量占用的内存释放,并将这些内存空间归还给操作系统,供后续使用。
闭包

当一个内部函数引用了外部函数的变量时,就形成了闭包。
闭包的优点/特点


  • 通过闭包可以让外部环境访问到函数内部的局部变量
  • 通过闭包可以让全局变量持续保存下来,不随着它的上下文一起销毁
通过此特性,我们可以解决一个全局变量污染的问题, 早期在 JavaScript 还无法进行模块化的时候,在多人协作时,如果定义过多的全局变量有可能造成全局变量命名冲突,使用闭包来解决功能对变量的调用将变量写到一个独立的空间里面,从而能够一定程度上解决全局变量污染的问题。
闭包经典面试题一

[code]for (var i = 1; i  3)。
因此所有回调函数打印的都是 4。</ul>解决方法

[code]for (var i = 1; i
您需要登录后才可以回帖 登录 | 立即注册