细聊 JavaScript 的事件执行机制

我使用的 Node.js 版本是 v12.13.0 的 , 11 的版本前会与11 版本后的事件循环有些区别。

线程?

都说 JavaScript 是单线程的 ,那么什么是单线程呢?

单线程就是进程只有一个线程。

那这又扯到进程这个概念了。

进程

进程 (Process): 进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。

简而言之,就是存在内存没有执行的程序叫程序,执行起来叫进程

简单说了一下进程,那么就得看看什么是线程了。

线程

线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

线程是进程的一个实体,一个进程可以有多个线程。它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

一看更不好理解 ,我的理解是去类比一个活动的执行 ,必然需要一个执行任务的人,或者多个执行任务的人,那么这些人就是线程。也就是活动再细分成任务,那么人就是执行这些任务的基本单位 ,当所有任务完成,也就是活动的完成。

单线程和多线程

那么些微了解了什么是线程,那么就容易理解什么是单线程什么是多线程了。

单线程和多线程都是基于一个进程而言的 。一个进程必须有一个或以上的线程。

单线程进程在执行任务时,只有一个线程可以去执行,并且在同一时刻只能执行一个任务。

而多线程进程在执行任务时是可以同时执行多个任务的。

同步?异步?

同步(sync):也就是同一时间只能做一件事,就拿上面的单线程任务执行过程来看 ,前一个任务完成之前,第二个任务是不会被执行的 。

异步(async) :异步是执行一个任务时,即使这个任务没有结束,也可以继续下去,执行其他任务。

那么根据这两个概念,就可以把任务分成同步任务和异步任务。

同步任务:在任务没有结果前,线程不会继续执行,而是等到当前任务完成后再执行。

异步任务: 执行到此任务时,会把此任务交给其他机制管理 ,而线程去执行其他任务,等到有结果了再返回给线程来继续处理。

对于异步任务的调用,调用的返回并不受调用者控制。

对于通知调用者的三种方式,具体如下:

  • 状态:即监听异步任务的状态(轮询),调用者需要每隔一定时间检查一次,效率会很低。

  • 通知:当被调用者执行完成后,发出通知告知调用者,无需消耗太多性能。

  • 回调:与通知类似,当被调用者执行完成后,会调用调用者提供的回调函数。

同步和异步的区别

同步与异步的区别不在于任务执行的时间。也就是同步任务可能比异步任务执行的时间要长。

主要区别是线程在执行这些任务时,是否需要立即得到结果。需要的是同步,得到结果了才继续执行,而异步是不需要立即得到结果,那么线程可以继续执行也可以等待。

JavaScript 如何做到异步

那么既然 JavaScript 是单线程的,按理说应该是同步的。但同步会遇到一个问题。

就是如果一个JS执行时间太长。就会造成页面不连贯,卡顿。比如我发送了一个异步请求,而请求回来前,我即不能点击其他按钮,也不能滚动页面(也就是我做了这些动作,但是没有响应)。因此体验十分不好。

那么 JS 需要异步,但是执行异步任务,就需要把异步任务交给其他线程。那么如何实现异步呢?

别忘了 JavaScript 代码是在一个 JavaScript 运行时的环境里执行的 。如游览器 ,或者 Node.js 。

他们可以为 JavaScript 提供其他线程 。具体在 JS 执行机制一节会介绍。

堵塞?非堵塞?

堵塞: 是指线程执行任务时,在没有得到结果前,线程会被挂起,不会去执行其他任务了。等得到结果才会继续执行

非堵塞: 是指线程执行任务时,即使不能立即得到结果,该任务也不会影响线程的继续执行其他任务。

同步,异步 与 堵塞,非堵塞的区别

这么看起来 ,堵塞和同步 ,非堵塞和异步似乎很像,有些一模一样了 。但是它们是不一样的。

同步和异步是关于任务的 ,而堵塞和非堵塞是关于线程的。它们有一定的关系,但并不相等。

如何理解呢?

  • 同步和异步是关于任务的 : 比如我有一个快递 ,同步是我自己去做 。由我亲自完成。而异步就是我让我同学帮我拿快递,我不用自己去。
  • 堵塞和非堵塞 : 非堵塞就是 比如我去拿快递,路上我也可以顺便买些东西,只不过会先停止去拿快递。但拿快递并没有堵塞我想做什么 。 而堵塞就比如 我让舍友去拿快递,我有空,但是我什么都不想做,就等快递回来。

如此理解,就是说同步异步的任务不会直接决定线程的堵塞和非堵塞 。

因此我们就有以下4中情况 :

  • 同步堵塞 : 执行一个同步任务,没有结果,不执行其他,线程被挂起。等有结果了再继续执行其他任务

我去买东西,发现没钱了,叫同学发红包,同学还没回,我就付不了。

  • 同步非堵塞 : 任务是同步的 ,但我过程中,暂时停止的这个同步任务,去执行了其他任务,同时又可能时不时回来继续执行。

我不能同时抄作业,又同时打游戏,但我可以打一会,又玩一会。只要我的速度快,你就可能以为我是同时进行的

  • 异步堵塞 : 执行到异步任务时 ,任务不占用当前线程,但是线程的设置机制就是等到任务有结果后再进行执行其他任务

我在打游戏,外卖到了,让舍友拿,但是我好饿,不打了,等外卖

  • 异步非堵塞 : 执行到异步任务时 ,任务交给其他线程执行,当前线程继续执行其他任务。

我在打游戏,让舍友去给我拿外卖,我继续打游戏!

显然异步堵塞几乎没什么用 ,而同步非堵塞也不是很符合实际需求或解决大部分的问题。

JavaScript 的执行机制

先体验一波 JavaScript 的同异步任务的执行结果吧!

console.log(1)setTimeout(() => {console.log(2)
}, 0);console.log(3);
// 1 3 2

在了解同异步前,我会认为答案 是 1 2 3 ,因为定时器是 0 秒的 。应该会被立即执行的吧 。

但实际不是如此的 。这就得说到一种运行机制叫做事件循环机制

事件循环 (Event Loop)

事件循环机制是计算机系统的一种运行机制 。而 JavaScript 是单线程的 ,因此采用了这种机制来解决运行中存在的一些问题。

**先粗浅的看看什么是事件循环 **

单线程无法同一时间执行多个事件 ,必须一件一件的完成 。但如果遇到异步的任务,就比方说点击事件 ,

but.onclick = (){}

当线程执行到这段代码时 给节点添加点击事件后,发现 but 节点的点击还没发生 ,也是就等着。那么页面就会出现 “假死” 状态 ,页面操作不了 ,接下来的代码也不执行了 。

而如果使用事件循环机制 ,把异步任务交给其他线程完成 ,那么当执行完成后有结果了再返回主线程。那么在这段本来应该等待的时间里就可以做其他的事情了 。

那么这于多线程有什么区别呢 ? 输入事件回调也会使用到其他线程 ,但其他线程是相当于辅助作用的。主线程遇到异步了就交给它,其他情况下它并不活跃 ,不会占据太多的资源。

而相比多线程 ,多条线程是同地位的 ,这意味着占用的资源更多 ,并且遇到异步任务需要等待时 ,都没有相互帮忙,而是等着 。这显然不合理。

JavaScript 中的事件循环

先看一张图 :

如何理解呢 ? 我们来看下面一段代码 :

console.log('a')  // 函数 1setTimeout(() => {  // 函数2 , 回调函数3console.log('b')  // 函数4
}, 1000);console.log('c');  // 函数5//   a c b

过程 : JavaScript 执行线程按照顺序执行 ,执行函数以时 ,把函数1 压入函数调用栈 。输出 a 。执行完毕弹出。

然后执行到函数2 ,也会被压入函数执行栈,但它是计时器 ,它为异步函数 ,会立即弹出,被放入其他线程中处理,也就是异步处理模块那。

执行函数5 ,同函数1一样,执行完后弹出 。输出 c 。

异步处理模块于主执行线程互不影响,异步函数在进入异步处理模块后根据 FIFO (也就是先进先出,先来后到)的顺序执行 ,一秒后执行完成,将回调函数放入任务队列中 。

此时如果函数执行栈有其他函数 ,则任务队列不会被理睬 ,等到函数执行栈空了,就会监听任务队列,如果有任务就会压入执行栈执行。执行完了就会继续监听任务队列,是否有任务需要执行。如果执行到异步任务,则又放到异步处理模块

那这个重复循环的过程就是事件循环

任务队列(Event Queue)

任务队列有的也叫(消息队列) ,里面的任务 可分为 宏任务(Macro-task)微任务 ( Micro-task ) 。

游览器和 Node.js 在任务队列的不同

游览器和 Node 在宏任务和微任务上是有区别的。主要原因是使用场景不同,提供的 API 也不同 。

游览器 :

macro-task大概包括:

  • setTimeout
  • setInterval
  • I/O
  • UI render

micro-task大概包括:

  • Promise.then(回调)
  • Async/Await(实际就是promise)
  • MutationObserver(html5新特性)

**Node.js **

macro-task 大概包括:

  • setTimeout
  • setInterval
  • setImmediate
  • I/O

micro-task 大概包括:

  • process.nextTick(与普通微任务有区别,在微任务队列执行之前执行)
  • Promise().then(回调)等。

ES6 作业队列

ECMAScript 2015 引入了作业队列的概念,Promise 使用了该队列(也在 ES6/ES2015 中引入)。 这种方式会尽快地执行异步函数的结果,而不是放在调用堆栈的末尾

也就是异步任务也需要排队的 ,也就是如果前面有一个宏任务,就必须等前面一个先执行完。微任务也需要等它前面的微任务先执行完才能被执行。

而 作业队列我们可以理解成微队列 ,因为作业队列必须在同步任务执行完后,但它无需等待宏任务 。因为无论在游览器还是 Node.js ,都会先执行完微任务再执行宏任务 。

console.log(1)setTimeout(() => {  console.log(2); // 宏任务
}, 0);new Promise((resolve, reject) =>{console.log(3)resolve(4)
}).then(resolve => console.log(resolve))  // 微任务console.log(5);
// 1 3 4 2

注意 , 传入Promise的回调不是异步的,是同步执行的!

游览器和 Node.js 在事件循环中的不同

游览器和 Node.js 中都有事件循环 。但它们的机制是有区别的 。

但整体上是一样的, 也是同步先执行 ,异步分开执行 ,然后将回调放入任务队列 ,并且任务队列也区分宏任务和微任务 。等函数执行栈空了再监听任务队列 。

但不同的是在对任务队列里的任务执行上的区别 。

游览器

游览器会先执行微任务队列里的任务 ,然后再按顺序执行宏任务,执行过程中,如果有异步任务会被移到异步任务处理器中, 如果是有微任务最后会被添加到微任务处理器中 。执行结束后 ,会查看微任务列表,如果有会把任务依次全部执行完。再进行下一个宏任务,如果没有就直接下一个宏任务。

console.log('1');setTimeout(() => {console.log('2');Promise.resolve().then(() => {console.log('3');})new Promise((resolve) => {console.log('4');resolve();}).then(() => {console.log('5')})
})new Promise((resolve) => {console.log('7');resolve();
}).then(() => {console.log('8')
})setTimeout(() => {console.log('9');Promise.resolve().then(() => {console.log('10');})new Promise((resolve,reject) => {console.log('11');reject();}).then(() => {console.log('12')})
})new Promise((resolve) => {console.log('13');resolve();
}).then(() => {console.log('14')
})// 1 7 13 8 14 2 4 3 5 9 11 10
// 没有 12 , 因为 reject 了。

Node.js

当 Node.js 启动后,它会初始化事件循环,处理已提供的输入脚本。当所有同步任务完成后,会先开始执行微任务 。然后开始处理事件循环。

在大部分情况下 , Node.js 的事件循环结果与 游览器结果相同 ,但细化后 ,是不同的 。

先看看 Node.js 事件循环的 循环图

这看起来很复杂 ,在游览器里是微任务-宏任务的切换着执行的机制。而 Node.js 就根据回调的类型,设置了不同的阶段 ,不同阶段执行当前可以执行的,且对应的回调。

也就是 回调还没可以执行不会执行 ,回调不是这个阶段该执行的也不会执行。

那接下来看看这些阶段分别是什么 :

  • timers 阶段 ( 定时器 ) : 这个阶段执行setTimeout(callback) and setInterval(callback)预定的callback;
  • pending callbacks 阶段 (待定回调): 执行延迟到下一个循环迭代的 I/O 回调。
  • idle, prepare 阶段 : 仅node内部使用;
  • poll 阶段 (轮询): 检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。
  • check 阶段 (检测): 执行setImmediate() 设定的callbacks;
  • close callbacks 阶段 (关闭的回调): 比如socket.on(‘close’, callback)的callback会在这个阶段执行.

注意:

注意上面六个阶段都不包括 process.nextTick()

每个阶段的回调执行完毕或者达到执行的限度后才会进入下一阶段。

但其实我看到这里,还不是很理解 。在多方搜索后 ,我才明白了。于是我再形象的补充些图形 。

也就是我们可以理解为 Node.js 的循环过程中 ,在一个循环里是划分了不同的阶段的 ,每个阶段都有对应的一个任务队列。不同节点执行的回调的类型是不同的。每个阶段都必须等当前需要执行的回调执行完或者达到回调执行的限制时才会进行下一阶段。

但每次执行宏任务后也会检查是否有微任务需要执行。这点与游览器是一样的。

通俗的讲,就是游览器不管什么时候,你按照规矩来,早上吃饭也好,半夜吃饭也好,必须付钱。

而 node.js 就是,白天吃饭,必须按规矩来,必须付钱,晚上不是吃饭的时间,晚上饿了也不能吃,等第二天。

案例
const fs = require('fs')// timers阶段
const startTime = Date.now();
setTimeout(() => {Promise.resolve('pro1').then(res => {console.log(res);})setImmediate(() => {console.log('check1')})setTimeout(() => {console.log('timer1');}, 0);const endTime = Date.now()console.log(`timer2: ${endTime - startTime}ms`)
}, 1000)// poll阶段(等待新的事件出现)
const readFileStart = Date.now();
fs.readFile('./index.html', (err, data) => {if (err) throw errlet endTime = Date.now()// 获取文件读取的时间console.log(`read time: ${endTime - readFileStart}ms`)// 通过while循环将fs回调强制阻塞5000mswhile (endTime - readFileStart < 5000) {endTime = Date.now()}setImmediate(() => {console.log('check2')})setTimeout(() => {console.log('timer3');}, 0)
})// check阶段
setImmediate(() => {console.log('check3')
})Promise.resolve('pro2').then(res => {console.log(res);
})// 结果 :
// pro2
// check3
// read time: 30ms
// check2
// timer2: 5004ms
// pro1
// timer3
// check1
// timer1
分析 :

第一次循环

在 Node 执行完代码中的同步任务后 ,会先执行微任务队列 ,所以先输出 pro2

然后进入事件循环 ,先进入 timers阶段 此时其他线程执行的定时器还没有结束 ,不会进入任务队列 ,因此进入下一阶段。

一直到 poll 阶段 ,此时会监听 I/O 事件 ,也就是文件读取。但此时文件读取还没有结果 ,也就是 I/O 回调列队为空 ,因此进入下一阶段

此时进入 check 阶段 , setImmediate 的回调会再此执行,于是输出 check3

然后下一阶段 ,没有关闭的回调,因此此次循环结束,进入下一次的循环。

此时我们删除已经执行完的代码。得到如下代码:

// timers阶段
setTimeout(() => {Promise.resolve('pro1').then(res => {console.log(res);})setImmediate(() => {console.log('check1')})setTimeout(() => {console.log('timer1');}, 0);const endTime = Date.now()console.log(`timer2: ${endTime - startTime}ms`)
}, 1000)// poll阶段(等待新的事件出现)
fs.readFile('./index.html', (err, data) => {if (err) throw errlet endTime = Date.now()// 获取文件读取的时间console.log(`read time: ${endTime - readFileStart}ms`)// 通过while循环将fs回调强制阻塞5000mswhile (endTime - readFileStart < 5000) {endTime = Date.now()}setImmediate(() => {console.log('check2')})setTimeout(() => {console.log('timer3');}, 0)
})

第二次循环

此时一个循环结束后,第一个定时器的一秒还没有结束 ,因此没有任务可执行,就进入下一个节点,一直到

poll 阶段 ,文件读取已经成功 ,于是执行 I/O 的回调。输出进入循环到读取成功大概耗时 read time: 30ms

然后 执行 while 语句 ,耗时 5s 。这段时间里不会进入下一阶段 。然后跳出 while 语句 ,执行 setImmediate 和 setTimeout 。它们是异步的 ,会移出执行栈 。(交给其他线程执行。执行完成后放入任务队列。)然后此时 I/O 的回调任务队列为空,进入下一个阶段 。

进入 check 阶段 ,上一阶段的 setImmediate 的回调已经可以执行了,于是输出 check2

第三次循环

Promise.resolve('pro1').then(res => {console.log(res);
})setImmediate(() => {console.log('check1')
})setTimeout(() => {console.log('timer1');
}, 0);const endTime = Date.now()
console.log(`timer2: ${endTime - startTime}ms`)

进入新循环的 timers 阶段 此时已经消耗了 5s 以上,第一次的定时器的回调已经加入任务队列 ,不过只有在 timers 阶段才能执行 ,于是执行Promise.resolve(‘pro1’).then() , setImmediate() ,setTimeout() , 输出 timer2: 5004ms 。 然后检查到有微任务 ,就执行输出 pro1 。 然后执行下一个宏任务 ,也就是上一循环输出 timer3 的回调 ,于是输出 timer3

一直到 check 阶段 输出 check1

第四次循环

输出 timer1

process.nextTick

process.nextTick 是一个异步方法 ,但独立于eventLoop 的任务队列 。

于 node11 版本后 ,它成为的微任务的一员 ,但它会优先于微任务先执行 。

setImmediate(() => {console.log('timeout1')Promise.resolve().then(() => console.log('promise resolve'))process.nextTick(() => console.log('next tick1'))
});
setImmediate(() => {console.log('timeout2')process.nextTick(() => console.log('next tick2'))
});
setImmediate(() => console.log('timeout3'));
setImmediate(() => console.log('timeout4'));// timeout1=>next tick1=>promise resolve=>timeout2=>next tick2=>timeout3=>timeout4

node 11 版本前的事件循环

Node11 版本前的事件循环与 11 版本后的有些不一样 。主要体现在微任务处理上 。

11 前的微任务不是在宏任务执行后执行的 ,而是在阶段切换前执行。

用代码来说话就是 :

console.log(1)
setTimeout(() => {console.log(2)Promise.resolve().then(function () {console.log('3')})
}, 0)
setTimeout(() => {console.log(4)Promise.resolve().then(function () {console.log(5)})process.nextTick(() => console.log('tick'))
}, 0)// 输出 : 1 2 4 tick 3 5

输出 1 2 4 tick 3 5 , 可见微任务都是 timers 阶段 的宏任务都执行完后再执行的 。

process.nextTick 也是在循环阶段切换时执行,并且在微任务前执行。

练习题

1. node.js

async function async1(){console.log('async1 start')  //2await async2()               console.log('async1 end')   // 8}
async function async2(){console.log('async2')   // 3
}
console.log('script start')   // 1
setTimeout(function(){console.log('setTimeout0')  // 10
},0)
setTimeout(function(){console.log('setTimeout3')   // 11
},3)
setImmediate(() => console.log('setImmediate'));  // 12
process.nextTick(() => console.log('nextTick'));  // 7
async1();
new Promise(function(resolve){console.log('promise1')  // 4resolve();console.log('promise2')  // 5
}).then(function(){console.log('promise3')  // 9
})
console.log('script end')   // 6// script start
// async1 start
// async2
// promise1
// promise2
// script end
// nextTick
// async1 end
// promise3
// setTimeout0
// setImmediate
// setTimeout3

2 .游览器

    async function async1() {console.log('async1 start')await async2()console.log('async1 end') }async function async2() {console.log('async2') // 3console.log('script start') setTimeout(function () {console.log('setTimeout0') }, 0)setTimeout(function () {console.log('setTimeout3') }, 3)async1();new Promise(function (resolve) {console.log('promise1') resolve();console.log('promise2') }).then(function () {console.log('promise3') })console.log('script end') // script start
// async1 start
// async2
// promise1
// promise2
// script end
// async1 end
// promise3
// setTimeout0
// setTimeout3

同步异步,堵塞非堵塞

同步异步,堵塞非堵塞

进程-百度百科

线程-百度百科

Event-loop

Node官网-事件循环

深入浅出浏览器事件循环

什么是 Event Loop?

再谈Event Loop- 阮一峰

Node.js Event Loop 的理解 Timers,process.nextTick()

说说事件循环机制

浏览器与Node的事件循环(Event Loop)有何区别?

深入理解NodeJS事件循环机制

细聊 JavaScript 的事件执行机制相关推荐

  1. JavaScript的事件执行机制及异步

    由于javascript是单线程的,只能在JS引擎的主线程上运行的,所以js代码只能一行一行的执行,不能在同一时间执行多个js代码任务,这就导致如果有一段耗时较长的计算,或者是一个ajax请求等IO操 ...

  2. JavaScript:异步执行机制

    使用JavaScript的开发者都知道,JS的异步执行机制在JS中占据着重要的地位,主要就是体现在回调函数以及事件方面,最近看了很多文章,将自己的一些感受和理解跟各位分享一下. 前面的博客中也有提到, ...

  3. 【JavaScript】JS执行机制微任务和宏任务

    文章目录 一.了解JS执行机制 二. 异步任务(宏任务.微任务) 1.宏任务 2.微任务 三.实操演练 解析: 一.了解JS执行机制 在学习 promise(期约) 之前,我们需要了解JS的执行机制, ...

  4. 【JavaScript】JS执行机制--同步与异步

    目录 单线程 同步与异步 事件循环 单线程 JavaScript语言具有单线程的特点,同一个时间只能做一件事情.这是因为JavaScript脚本语言是为了处理页面中用户的交互,以及操作DOM而诞生的. ...

  5. 记录JS event Loop机制及Node v8事件执行机制

    转载于:https://www.cnblogs.com/liujiekun/p/11295536.html

  6. 理解JavaScript的执行机制

    一直没有深入了解过JavaScript的事件执行机制,直到看到了这篇文章:<这一次,彻底弄懂JavaScript执行机制> 才发觉熟悉JavaScript的执行机制非常重要. 毕竟在跟进项 ...

  7. Javascript事件模型系列(二)事件的捕获-冒泡机制及事件委托机制

    一.事件的捕获与冒泡 由W3C规定的DOM2标准中,一次事件的完整过程包括三步:捕获→执行目标元素的监听函数→冒泡,在捕获和冒泡阶段,会依次检查途径的每个节点,如果该节点注册了相应的监听函数,则执行监 ...

  8. 宏事件、微事件、 JavaScript 执行机制

    案例预热: setTimeout(function(){console.log('定时器开始啦')});new Promise(function(resolve){console.log('马上执行f ...

  9. JavaScript执行机制-node事件循环

    node环境下的事件循环机制 和浏览器有什么不同? 在node中,事件循环表现出来的状态和浏览器大致相同,但是node有一套自己的模型. node事件循环依靠libuv引擎,node选择chrome ...

最新文章

  1. mips 内存 linux,MIPS 在linux中的内存映射
  2. 滴滴顺风车GM:大多数产品经理定义是狭隘的
  3. Git学习笔记;Git bash 库同步问题
  4. AspectJ学习笔记
  5. java实现验证码3秒刷新一次
  6. java登录界面命令_Java命令行界面(第10部分):picocli
  7. pcb板材的tg是什么_做到这6点,PCB过回焊炉不会出现板弯及板翘!
  8. html中输出 u263c,二级C语言笔试必过399题
  9. jQuery之美,第一次...
  10. Flink CDC 2.2 正式发布,新增四种数据源,支持动态加表,提供增量快照框架
  11. 【深度】谈谈我对于5G的理解
  12. 网站灰色代码|哀悼日专用
  13. bsoj 1512 金明的预算方案(树型DP)
  14. XLA编译器用于JIT加速
  15. java对word文档的操作
  16. 【渝粤题库】国家开放大学2021春2196社会调查研究与方法题目
  17. 数论-快速幂、矩阵快速幂、慢速乘
  18. WebSphere MQ应急预案
  19. Quillbot:英语到英语的屠龙剑
  20. 一张图读懂PBN飞越转弯衔接TF/CF航段计算

热门文章

  1. 从零在FPG上实现OFDM(一)
  2. [半监督学习] Deep Co-Training for Semi-Supervised Image Recognition
  3. Python获取股票历史数据和收盘数据的代码实现
  4. pytorch转onnx踩坑日记
  5. 自学前端建立知识体系【全新web前端开发视频教程】
  6. Codeforces Round #749 (Div. 1 + Div. 2, based on Technocup 2022 Elimination Round 1)
  7. 易观分析:2022年Q2中国网络零售B2C市场交易规模达23444.7亿元
  8. smtp gmail_如何使用Gmail SMTP服务器在WordPress中发送电子邮件
  9. 如何下载 qq群课堂 回放视频
  10. Could not load file or assembly ‘NPOI.OOXML, Version=2.5.5.0, Culture=neutral, PublicKeyToken=0df73e