이 글은 Node의 버퍼를 이해하고 브라우저 이벤트 루프에 대해 이야기하는 데 도움이 될 것입니다.
Buffer
바이너리 데이터
컴퓨터의 모든 콘텐츠: 텍스트, 숫자, 그림, 오디오 및 비디오는 결국 바이너리 시스템을 사용하여 표현하게 됩니다.
JS
는 우리가 일반적으로 사용자에게 표시하는 문자열과 같은 매우 직관적인 데이터를 직접 처리할 수 있습니다.JS
可以直接去处理非常直观的数据:比如字符串,我们通常展示给用户的也是这些内容-
但你可能会以为JS也能够处理图片
- 事实上在网页端,图片一直是交给浏览器来处理的
-
JS
或者HTML
,只是负责告诉浏览器图片的地址 - 浏览器负责发送请求获取这个图片,并且最终将这个图片给渲染出来
-
但是对于服务端来说是不一样的
- 服务端要处理的本地文件类型相对较多
- 比如某一个保存文本的文件并不是使用
utf-8
进行编码的,而是用GBK
,那么我们必须读取到他们的二进制数据,再通过GKB转换成对应的文字 - 比如我们需要读取的是一张图片数据(二进制),再通过某些手段对图片数据进行二次的处理(裁剪、格式转换、旋转、添加滤镜),Node中有一个名为
sharp
的库,就是负责读取图片或者传入图片的Buffer
对其再进行处理的 - 比如在
Node
中通过TCP
建立长连接,TCP传输的是字节流,我们需要将数据转成字节再进行传入,并且需要知道传输字节的大小(客户端需要根据大小来判断读取多少内容)
Buffer和二进制
我们会发现,对于前端开发来说,通常很少会和二进制打交道,但是对于服务器端来说,为了实现很多功能,我们必须直接去操作其二进制的数据
所以
Node
为了可以方便开发者完成更多功能,提供给了我们一个名为Buffer
的类,并且他是全局的-
我们前面说过,Buffer中存储的是二进制数据,那么到底是如何存储的呢?
- 我们可以将Buffer看成是一个存储二进制的数组
- 这个数组中的每一项,可以保存
8
位二进制:00000000
,刚好是一个字节
-
为什么是8位呢?
- 在计算机中,很少的情况我们会直接操作一位二进制,因为一位二进制存储的数据是非常有限的
- 所以通常会将8位合在一起作为一个单元,这个单元称之为一个字节(
byte
) - 也就是说
1 byte = 8 bit
,1kb = 1024 byte
,1M = 1024kb
,1 G = 1024 M
- 比如很多编程语言中的
int
类型是4
个字节,long
类型是8
个字节 - 比如
TCP
传输的是字节流,在写入和读取时都需要说明字节的个数 - 比如
RGB
的值分别都是255
,所以本质上在计算机中都是用一个字节存储的
Buffer和字符串
Buffer
相当于是一个字节的数组,数组中的每一项对于一个字节的大小-
如果我们希望将一个字符串放入到Buffer中,是怎么样的过程呢?
- 将字符串直接传入Buffer类中,然后再创建
buffer
实例 - 英文字符串有个特点,每一个字符对应一个字节的二进制编码
- 将字符串直接传入Buffer类中,然后再创建
const message = 'Hello' // 使用new关键字创建buffer实例,但这种创建方法已经过期了 const buffer = new Buffer(message) console.log(buffer); // <Buffer 48 65 6c 6c 6f> console.log(buffer.toString()); // Hello
中文字符串的编解码
-
buffer
的默认编码是utf-8
,所以在下列代码中,Buffer
类是使用了utf-8编码对我们的字符串进行编码,使用的也是utf-8对我们的字符串进行解码 - 中文字符串有个特点,在utf-8编码中,一个文字对应
3
个字节的二进制编码
const message = '你好啊' // 使用Buffer.from对我们的字符串进行解码 const buffer = Buffer.from(message) console.log(buffer); // <Buffer e4 bd a0 e5 a5 bd e5 95 8a> // buffer实例中有个toString方法可以对编码进行解码 console.log(buffer.toString()); // '你好啊'
-
那如果编码和解码用的是不同形式的编码结果会怎么样呢?
- 毫无疑问,解码出来的东西并不是我们原先编码的字符串
const message = '你好啊' const buffer = Buffer.from(message, 'utf16le') console.log(buffer); // <Buffer 60 4f 7d 59 4a 55> console.log(buffer.toString()); // `O}YJU
Buffer的其他创建方式
创建buffer
的方式有很多,我们这里可以通过alloc
的方式创建Buffer
JS
또는 HTML
은 브라우저에 이미지 주소를 알려주는 역할만 담당합니다🎜🎜브라우저가 담당합니다 이 이미지를 얻기 위해 요청을 보내고 최종적으로 이 이미지를 렌더링합니다🎜🎜🎜🎜하지만 서버측에서는 다릅니다🎜🎜🎜서버측에서는 상대적으로 많은 로컬 파일 형식을 처리해야 합니다🎜🎜예를 들어 A 텍스트를 저장하는 파일은 utf-8
를 사용하여 인코딩되지 않고 GBK
를 사용하여 인코딩된 다음 바이너리 데이터를 읽은 다음 해당 텍스트로 변환해야 합니다 🎜🎜예를 들어, 우리는 그림 데이터(바이너리)를 읽어야 하며 그런 다음 몇 가지 방법(자르기, 형식 변환, 회전, 필터 추가)을 통해 그림 데이터에 대한 2차 처리를 수행해야 합니다. 노드에는 sharp
라는 라이브러리가 있습니다. 이미지를 읽거나 이미지의 버퍼
를 전달한 다음 이를 처리하는 일을 담당합니다. 예를 들어 노드
에서 TCP
를 통해 긴 연결을 설정합니다. TCP는 바이트 스트림을 전송합니다. 데이터를 전달하기 전에 바이트로 변환해야 하며 전송된 바이트의 크기를 알아야 합니다(클라이언트는 읽을 내용의 크기를 기준으로 판단해야 합니다)🎜🎜🎜🎜🎜버퍼와 바이너리 🎜🎜🎜🎜🎜🎜프론트엔드 개발에서는 보통 바이너리를 거의 다루지 않지만, 서버측에서는 많은 기능을 구현하기 위해 바이너리 데이터를 직접 조작해야 합니다 🎜🎜🎜🎜그래서 개발자가 더 많은 기능을 완성할 수 있도록 Node
는 Buffer
의 클래스라는 파일을 제공하는데, global🎜🎜🎜🎜 앞서 바이너리 데이터가 Buffer에 저장된다고 했는데 어떻게 저장되나요? 🎜🎜🎜버퍼는 바이너리를 저장하는 배열로 생각할 수 있습니다. 🎜🎜이 배열의 각 항목은 8
비트의 바이너리인 00000000
를 저장할 수 있으며 이는 정확히 1바이트입니다🎜 🎜🎜🎜🎜왜 8비트인가요? 🎜🎜🎜컴퓨터에서는 바이너리 비트에 저장되는 데이터가 매우 제한되어 있기 때문에 바이너리 비트를 직접 연산하는 경우는 거의 없습니다🎜🎜그래서 일반적으로 8비트를 하나의 단위로 묶어서 이 단위를 1바이트라고 합니다. (바이트
) 🎜🎜즉, 1바이트 = 8비트
, 1kb = 1024바이트
, 1M = 1024kb
입니다. code>, 1 G = 1024 M
🎜🎜예를 들어 많은 프로그래밍 언어에서 int
유형은 4
바이트, long
유형은 8
바이트🎜🎜예를 들어 TCP
는 바이트 스트림을 전송하며 쓰기 및 읽기 시 바이트 수를 지정해야 합니다. 🎜예를 들어 RGB
의 값은 각각 255
이므로 컴퓨터에는 기본적으로 1바이트로 저장됩니다🎜🎜🎜 🎜🎜버퍼와 문자열🎜🎜🎜🎜🎜🎜버퍼
는 바이트 배열과 동일하며 배열의 각 항목 크기는 1바이트입니다🎜🎜🎜🎜문자를 변환하려는 경우 문자열을 버퍼에 넣는 과정은 무엇입니까? 🎜🎜🎜문자열을 Buffer 클래스에 직접 전달한 다음 버퍼
인스턴스를 만듭니다🎜🎜영어 문자열에는 특성이 있으며 각 문자는 이진 인코딩의 바이트에 해당합니다🎜🎜 ul>// 其可以指定我们buffer的位数,比如这里传递进去的是8,那么创建出来的buffer就有8个元素,且每个元素对应的二进制数都是0 const buffer = Buffer.alloc(8) console.log(buffer); // <Buffer 00 00 00 00 00 00 00 00> // 赋值为十进制数字的话,buffer会帮我们转化为16进制数字再写入到对应的位置 buffer[0] = 88 // 在js中,以0x开头的就表示为16进制的数字 buffer[1] = 0x88 console.log(buffer); // <Buffer 58 88 00 00 00 00 00 00>🎜🎜🎜중국어 문자열 인코딩 및 디코딩🎜🎜🎜🎜🎜
버퍼
의 기본 인코딩은 utf-8
이므로 다음 코드에서는 Buffer
클래스는 utf-8 인코딩을 사용하여 문자열을 인코딩하고 utf-8은 문자열을 디코딩하는 데도 사용됩니다. 중국어 문자열에는 utf-8 인코딩에서 하나의 텍스트가 3바이트의 바이너리 인코딩🎜<pre class='brush:php;toolbar:false;'>const fs = require(&#39;fs&#39;)
fs.readFile(&#39;./a.txt&#39;, (err, data) => {
console.log(data); // <Buffer e5 93 88 e5 93 88>
})</pre>🎜🎜🎜인코딩과 디코딩이 서로 다른 인코딩 형식을 사용하면 어떻게 될까요? 🎜🎜🎜디코딩된 것이 원래 인코딩한 문자열이 아니라는 것은 의심의 여지가 없습니다.🎜🎜<pre class='brush:php;toolbar:false;'>const fs = require(&#39;fs&#39;)
// encoding表示解码所用的字符编码,编码默认为utf-8
fs.readFile(&#39;./a.txt&#39;, { encoding: &#39;utf-8&#39; }, (err, data) => {
console.log(data); // 哈哈
})</pre>🎜🎜🎜버퍼를 만드는 다른 방법🎜🎜🎜🎜<code>버퍼
만들기 여러 가지 방법이 있습니다. 여기서는 alloc
🎜을 통해 버퍼
를 생성할 수 있습니다.-
我们可以直接对buffer实例以数组的形式对每一位进行修改
- 如果修改的是一个十进制数字,那它会自动帮助我们转化成16进制的数字
- 如果修改的是一个十六进制数字,那么就会直接写入
// 其可以指定我们buffer的位数,比如这里传递进去的是8,那么创建出来的buffer就有8个元素,且每个元素对应的二进制数都是0 const buffer = Buffer.alloc(8) console.log(buffer); // <Buffer 00 00 00 00 00 00 00 00> // 赋值为十进制数字的话,buffer会帮我们转化为16进制数字再写入到对应的位置 buffer[0] = 88 // 在js中,以0x开头的就表示为16进制的数字 buffer[1] = 0x88 console.log(buffer); // <Buffer 58 88 00 00 00 00 00 00>
Buffer和文件操作
1、文本文件
- 如果未指定字符编码,则不进行解码,直接返回原始的
buffer
,也就是文件内容结果utf-8
编码后的二进制数
const fs = require('fs') fs.readFile('./a.txt', (err, data) => { console.log(data); // <Buffer e5 93 88 e5 93 88> })
- 编码和解码用的都是utf-8,则可以得到文件中正确的内容
const fs = require('fs') // encoding表示解码所用的字符编码,编码默认为utf-8 fs.readFile('./a.txt', { encoding: 'utf-8' }, (err, data) => { console.log(data); // 哈哈 })
- 如果编码和解码所用的字符编码不同,则最终读取出来的内容会乱码
const fs = require('fs') // 编码用的是utf16le字符编码,解码使用的是utf-8格式,肯定是解不是正确的内容的 fs.readFile('./a.txt', { encoding: 'utf16le' }, (err, data) => { console.log(data); // 鏥袓 }) // 以上代码和下面代码类似 const msg = '哈哈' const buffer = Buffer.from(msg, 'utf-8') console.log(buffer.toString('utf16le')); // 鏥袓
2、图片文件
-
对图片编码进行拷贝,达到复制图片的目的
- 读取图片的时候不要指定
encoding
属性,因为字符编码只有在读取文本文件的时候才有用
- 读取图片的时候不要指定
const fs = require('fs') fs.readFile('./logo.png', (err, data) => { console.log(data); // 打印出来的是图片文件对应的二进制编码 // 我们还可以将图片编码写入到另一个文件当中,相当于我们将该图片拷贝了一份 fs.writeFile('./bar.png', data, err => { console.log(err); }) })
- 对图片进行翻转、裁剪等操作,可以使用
sharp
这个库
const sharp = require('sharp') // 将logo.png这张图片裁剪成200x300后拷贝到文件bax.png中 sharp('./logo.png') .resize(200, 300) .toFile('./bax.png', (err, info) => { console.log(err); }) // 还可以将图片文件先转为buffer,然后在写入到文件中,也可以实现拷贝图片的目的 sharp('./logo.png') .resize(300, 300) .toBuffer() .then(data => { fs.writeFile('./baa.png', data, err => { console.log(err); }) })
Buffer的创建过程
- 事实上我们创建
Buffer
时,并不会频繁的向操作系统申请内存,它会默认先申请一个8 * 1024
个字节大小的内存,也就是8kb
- 等到内存不够或者快用完的时候才会去申请新的内存
事件循环和异步IO
什么是事件循环?
-
事件循环是什么?
- 事实上我把事件循环理解成我们编写的
JS
和浏览器或者Node
之间的一个桥梁
- 事实上我把事件循环理解成我们编写的
- 浏览器的事件循环是一个我们编写的
JS
代码和浏览器API调用(setTimeout
、AJAX
、监听事件
等)的一个桥梁,桥梁之间通过回调函数进行沟通 - Node的事件循环是一个我们编写的JS代码和系统调用(
file system
、networ
等)之间的一个桥梁,,桥梁之间也是通过回调函数进行沟通的
进程和线程
进程和线程是操作系统中的两个概念:
- 进程(
process
):计算机已经运行的程序 - 线程(
thread
):操作系统能够运行运算调度的最小单位,所以CPU
能够直接操作线程
听起来很抽象,我们直观一点解释:
- 进程:我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程)
- 线程:每一个进程中,都会启动一个线程用来执行程序中的代码,这个线程被称之为主线程
- 所以我们也可以说进程是线程的容器
再用一个形象的例子解释
- 操作系统类似于一个工厂
- 工厂中有很多车间,这个车间就是进程
- 每个车间可能有一个以上的工人在工厂,这个工人就是线程
多进程多线程开发
操作系统是如何做到同时让多个进程(边听歌、边写代码、边查阅资料)同时工作呢?
- 这是因为
CPU
的运算速度非常快,他可以快速的在多个进程之间迅速的切换 - 当我们的进程中的线程获取到时间片时,就可以快速执行我们编写的代码
- 对于用户来说是感受不到这种快速的切换的
浏览器和JavaScript
我们经常会说
JavaScript
是单线程的,但是JS的线程应该有自己的容器进程:浏览器或者Node
-
浏览器是一个进程吗,它里面只有一个线程吗?
- 目前多数的浏览器其实都是多进程的,当我们打开一个
tab
页面时就会开启一个新的进程,这是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出 - 每个进程中又有很多的线程,其中包括执行JavaScript代码的线程
- 目前多数的浏览器其实都是多进程的,当我们打开一个
-
但是JavaScript的代码执行是在一个单独的线程中执行的
- 这就意味着
JS
的代码,在同一时刻只能做一件事 - 如果这件事是非常耗时的,就以为这当前的线程就会被阻塞
- 这就意味着
JavaScript的执行过程
函数要被压入函数调用栈中后才会被执行,下面我们来分析下代码的执行过程
const message = 'Hello World' console.log(message); function sum(num1, num2) { return num1 + num2 } function foo() { const result = sum(20, 30) console.log(result); } foo()
- 我们JS的代码其实也可以像其它编程语言一样可以看成是在
main
函数中执行的 - 那么首先我们要将main函数压入函数调用栈中
- 定义变量
message
- 执行
log
函数,log函数会被放入到函数调用栈中,执行完后出栈 - 调用
foo
函数,foo函数被压入函数调用栈中,但是执行过程中又需要调用sum
函数 - 所以sum函数会被压入到函数调用栈中,sum函数执行完毕后出栈
- 此时foo函数也得到了sum函数返回的值,并执行了赋值操作,但又遇到了log函数
- 所以又要将log函数压入到调用栈,log函数被执行完毕,出栈后foo函数也执行完毕,foo函数出栈
- foo函数执行完后,整个
js
代码执行完毕,main函数出栈
浏览器的事件循环
如果在执行JS
代码的过程中,有异步操作呢?
- 比如中间我们插入了一个
setTimeout
的函数调用 - 那么setTimeout这个函数被放入到调用栈中,执行会立即结束,并不会阻塞后续代码的执行
那么,往setTimeout函数里面传入的函数(我们称之为timer
函数),会在什么时候被执行呢?
- 事实上,setTimeout是调用了
web api
,浏览器会提前会将回调函数存储起来,在合适的时机,会将timer函数加入到一个事件队列中 - 事件队列中的函数,会被放入到函数调用栈中,在调用栈中被执行
为什么setTimeout不会阻塞代码的执行呢?就是因为浏览器里面维护了一个非常非常重要的东西——事件循环
浏览器中会通过某种方式帮助我们保存setTimeout中的回调函数的,比较常用的方法就是保存到一个红黑树里面
等到setTimeout定时器时间到达的时候,它就会将我们的timer回调函数从保存的地方取出来并放入到事件队列里面
事件循环一旦发现我们的队列中有东西了,并且当前函数调用栈是空的,其它同步代码也执行完之后,就会将我们队列中的回调函数依次出列放入到函数调用栈中执行(队列中前一个函数出栈后,下一个函数才会入栈)
当然事件队列中不一定只有一个事件,比如说在某个过程中用户点击了浏览器当中的某个按钮,我们可能对这个按钮的点击做了一个监听,对应了一个回调函数,那个回调函数也会被加入到我们的队列里面的,执行顺序按照它们在事件队列中的顺序执行。还有我们发送ajax
请求的回调,也是加入到事件队列里面的
总结:其实事件循环是一个很简单的东西,它就是在某一个特殊的情况下,需要去执行某一个回调的时候,它就把提前保存好的回调塞入事件队列里面,事件循环再给它取出来放入到函数调用栈中
宏任务与微任务
但是事件循环中并非只维护一个队列,事实上是有两个队列,而且队列中的任务执行一定会等到所有的script都执行完毕后
- 宏任务队列(
macrotask queue
):ajax
、setTimeout
、setInterval
、DOM
监听、UI Rendering
等 - 微任务队列(
microtask queue
):Promise
的then
回调、Mutation Observer API
、queueMicrotask()
等
那么事件循环对于两个队列的优先级是怎么样的呢?
-
main script
中的代码优先执行(编写的顶层script代码) - 在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
- 也就是宏任务执行之前,必须保证微任务队列是空的
- 如果不为空,那么就优先执行微任务队列中的任务(回调)
面试题
考点:main stcipt
、setTimeout
、Promise
、then
、queueMicrotask
setTimeout(() => { console.log('set1');4 new Promise(resolve => { resolve() }).then(resolve => { new Promise(resolve => { resolve() }).then(() => { console.log('then4'); }) console.log('then2'); }) }) new Promise(resolve => { console.log('pr1'); resolve() }).then(() => { console.log('then1'); }) setTimeout(() => { console.log('set2'); }) console.log(2); queueMicrotask(() => { console.log('queueMicrotask'); }) new Promise(resolve => { resolve() }).then(() => { console.log('then3'); }) // pr1 // 2 // then1 // queueMicrotask // then3 // set1 // then2 // then4 // set2
setTimeout
会立即压入函数调用栈,执行完毕后立即出栈,其timer
函数被放入到宏任务队列中传入
Promise
类的函数会被立即执行,其并不是回调函数,所以会打印出pr1
,并且由于执行了resolve
方法,所以该Promise的状态会立即变为fulfilled
,这样then
函数执行的时候其对应的回调函数就会被放入到微任务队列中又遇到了一个setTimeout函数,压栈出栈,其timer函数会被放入到宏任务队列中
遇到
console.log
语句,函数压栈后执行打印出了2
,然后出栈这里通过
queueMicrotask
绑定了个函数,该函数会被放入到微任务队列中又遇到了new Promise语句,但是其立即就将promise的状态改为了fulfilled,所以then函数对应的回调也被放入到了微任务队列中
由于同步脚本代码已经执行完毕,现在事件循环开始要去把微任务队列和宏任务对垒的任务按照优先级顺序放入到函数调用栈中执行了,注意:微任务的优先级比宏任务高,每次想要执行宏任务之前都要看看微任务队列里面是否为空,不为空则需要先执行微任务队列的任务
第一个微任务是打印
then1
,第二个微任务是打印queueMicrotask,第三个微任务是打印then3
,执行完毕后,就开始去执行宏任务第一个宏任务比较复杂,首先会打印
set1
,然后执行了一个立即变换状态的new promise
语句,其then回调会被放入到微任务队列中,注意现在微任务队列可不是空的,所以需要执行优先级较高的微任务队列,相当于该then回调被立即执行了,又是相同的new Promise语句,其对应的then对调被放入到微任务队列中,注意new Promise语句后面还有一个console
函数,该函数会在执行完new Promise语句后立即执行,也就是打印then2
,现在微任务对垒还是有一项任务,所以接下来就是打印then4
。目前为止,微任务队列已经为空了,可以继续执行宏任务队列了所以接下里的宏任务
set2
会被打印,宏任务执行完毕整个代码的打印结果是:
pr1 -> 2 -> then1 -> queueMicrotask -> then3 -> set1 -> then2 -> then4 -> set2
面试题
考点:main script
、setTimeout
、Promise
、then
、queueMicrotask
、await
、async
知识补充:async、await是Promise
的一个语法糖,在处理事件循环问题时
- 我们可以将await关键字后面执行的代码,看做是包裹在
new Promise((resolve,rejcet) => { 函数执行 })
中的代码 - await语句后面的代码,可以看做是上一个Promise中的
then(res => {函数执行})
中的代码
async function async1() { console.log('async1 start'); await async2() console.log('async1 end'); } async function async2() { console.log('async2'); } console.log('script start'); setTimeout(() => { console.log('setTimeout'); }, 0) async1() new Promise(resolve => { console.log('promise1'); resolve() }).then(() => { console.log('promise2'); }) console.log('script end'); // script start // async1 start // async2 // promise1 // script end // async1 end // promise2 // setTimeout
一开始都是函数的定义,不需要压入函数调用栈中执行,直到遇到第一个
console
语句,压栈后执行打印script start
后出栈遇到第一个
setTimeout
函数,其对应的timer
会被放入到宏任务队列中async1函数被执行,首先打印出
async1 start
,然后又去执行await
语句后面的async2
函数,因为前面也说了,将await关键字后面的函数看成是new Promise
里面的语句,这个函数是会被立即执行的,所以async2会被打印出来,但该await语句后面的代码相当于是放入到then回调中的,也就是说console.log('async1 end')
这行代码被放入到了微任务队列里코드가 계속 실행되어 새로운 Promise 문을 만나므로
promise1
이 즉시 인쇄되고 then 콜백의 함수가 마이크로태스크 대기열에 들어갑니다promise1
,then回调中的函数被放入到了微任务队列里面去最后一个console函数执行打印
script end
,同步代码也就执行完毕了,事件循环要去宏任务和微任务队列里面执行任务了首先是去微任务队列,第一个微任务对应的打印语句会被执行,也就是说
async1 end
会被打印,然后就是promise2
被打印,此时微任务队列已经为空,开始去执行宏任务队列中的任务了timer函数对应的setTimeout会被打印,此时宏任务也执行完毕,最终的打印顺序是:
script start -> async1 start -> async2 -> promise1 -> script end -> async1 end -> promise2 -> setTimeout
script end
를 인쇄하고 동기화 코드가 완료됩니다. 이벤트 루프는 작업을 실행하기 위해 매크로 작업 및 마이크로 작업 대기열로 이동합니다.첫 번째 마이크로 작업 대기열로 이동합니다. task 작업에 해당하는 print 문이 실행됩니다. 즉, async1 end
가 인쇄되고, 이어서 promise2
가 인쇄됩니다. 이때 마이크로태스크가 인쇄됩니다. queue는 비어 있고 실행이 시작됩니다. 🎜🎜🎜🎜timer 함수에 해당하는 setTimeout이 인쇄됩니다. 이때 매크로 작업도 실행됩니다. script start -> ; async1 start -> async2 -> ; script end -> async1 end -> setTimeout
🎜🎜🎜🎜 더 많은 노드 관련 지식을 확인하세요. 🎜! 🎜위 내용은 Node.js의 Buffer에 대한 간략한 분석 및 이벤트 루프에 대한 이야기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

Vercel是什么?本篇文章带大家了解一下Vercel,并介绍一下在Vercel中部署 Node 服务的方法,希望对大家有所帮助!

gm是基于node.js的图片处理插件,它封装了图片处理工具GraphicsMagick(GM)和ImageMagick(IM),可使用spawn的方式调用。gm插件不是node默认安装的,需执行“npm install gm -S”进行安装才可使用。

如何用pkg打包nodejs可执行文件?下面本篇文章给大家介绍一下使用pkg将Node.js项目打包为可执行文件的方法,希望对大家有所帮助!

本篇文章带大家详解package.json和package-lock.json文件,希望对大家有所帮助!

本篇文章给大家分享一个Nodejs web框架:Fastify,简单介绍一下Fastify支持的特性、Fastify支持的插件以及Fastify的使用方法,希望对大家有所帮助!

node怎么爬取数据?下面本篇文章给大家分享一个node爬虫实例,聊聊利用node抓取小说章节的方法,希望对大家有所帮助!

本篇文章给大家分享一个Node实战,介绍一下使用Node.js和adb怎么开发一个手机备份小工具,希望对大家有所帮助!

先介绍node.js的安装,再介绍使用node.js构建一个简单的web服务器,最后通过一个简单的示例,演示网页与服务器之间的数据交互的实现。


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

Eclipse용 SAP NetWeaver 서버 어댑터
Eclipse를 SAP NetWeaver 애플리케이션 서버와 통합합니다.

MinGW - Windows용 미니멀리스트 GNU
이 프로젝트는 osdn.net/projects/mingw로 마이그레이션되는 중입니다. 계속해서 그곳에서 우리를 팔로우할 수 있습니다. MinGW: GCC(GNU Compiler Collection)의 기본 Windows 포트로, 기본 Windows 애플리케이션을 구축하기 위한 무료 배포 가능 가져오기 라이브러리 및 헤더 파일로 C99 기능을 지원하는 MSVC 런타임에 대한 확장이 포함되어 있습니다. 모든 MinGW 소프트웨어는 64비트 Windows 플랫폼에서 실행될 수 있습니다.

VSCode Windows 64비트 다운로드
Microsoft에서 출시한 강력한 무료 IDE 편집기

맨티스BT
Mantis는 제품 결함 추적을 돕기 위해 설계된 배포하기 쉬운 웹 기반 결함 추적 도구입니다. PHP, MySQL 및 웹 서버가 필요합니다. 데모 및 호스팅 서비스를 확인해 보세요.

mPDF
mPDF는 UTF-8로 인코딩된 HTML에서 PDF 파일을 생성할 수 있는 PHP 라이브러리입니다. 원저자인 Ian Back은 자신의 웹 사이트에서 "즉시" PDF 파일을 출력하고 다양한 언어를 처리하기 위해 mPDF를 작성했습니다. HTML2FPDF와 같은 원본 스크립트보다 유니코드 글꼴을 사용할 때 속도가 느리고 더 큰 파일을 생성하지만 CSS 스타일 등을 지원하고 많은 개선 사항이 있습니다. RTL(아랍어, 히브리어), CJK(중국어, 일본어, 한국어)를 포함한 거의 모든 언어를 지원합니다. 중첩된 블록 수준 요소(예: P, DIV)를 지원합니다.
