最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

如何使用Node寫靜態(tài)文件服務(wù)器

 更新時(shí)間:2022年09月19日 08:41:38   作者:進(jìn)擊的大蔥  
這篇文章主要介紹了如何使用Node寫靜態(tài)文件服務(wù)器,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下

背景

作為前端工程師,我想大家一定對靜態(tài)文件服務(wù)器不會(huì)陌生。所謂的靜態(tài)文件服務(wù)器做的工作就是將我們的前端靜態(tài)文件(.js/.css/.html)傳輸給瀏覽器,然后瀏覽器再將我們的頁面渲染出來。我們常用的webpack-dev-server就是本地開發(fā)用的靜態(tài)文件服務(wù)器,而一般線上環(huán)境我們會(huì)使用nginx,因?yàn)樗臃€(wěn)定和高效。既然靜態(tài)文件服務(wù)器無處不在,那么它們又是如何實(shí)現(xiàn)的呢?本篇文章將帶你手把手實(shí)現(xiàn)一個(gè)高效的靜態(tài)文件服務(wù)器。

功能介紹

我們的靜態(tài)服務(wù)器包括下面兩個(gè)功能:

  • 當(dāng)用戶請求的內(nèi)容是文件夾時(shí),展示當(dāng)前文件夾的結(jié)構(gòu)信息
  • 當(dāng)用戶請求的內(nèi)容是文件時(shí),返回文件的內(nèi)容

我們來看一下實(shí)際效果,服務(wù)端的靜態(tài)文件目錄是這樣的:

static
└── index.html

訪問localhost:8080可以獲取根目錄的信息:

請求根文件夾

在根目錄下只有一個(gè)index.html文件。我們點(diǎn)擊index.html文件可以獲取這個(gè)文件的具體內(nèi)容:

index.html

代碼實(shí)現(xiàn)

根據(jù)上面的需求描述,我們先用流程圖來設(shè)計(jì)一下我們的邏輯如何實(shí)現(xiàn):

邏輯設(shè)計(jì)流程圖

其實(shí)靜態(tài)文件服務(wù)器的實(shí)現(xiàn)思路還是很簡單的:先判斷資源存不存在,不存在就直接報(bào)錯(cuò),資源存在的話根據(jù)資源的類型返回對應(yīng)的結(jié)果給客戶端就可以了。

基礎(chǔ)代碼實(shí)現(xiàn)

看完上面的流程圖,我相信大家的思路基本清晰了,接著我們看一下具體的代碼實(shí)現(xiàn):

const http = require('http')
const url = require('url')
const fs = require('fs')
const path = require('path')
const process = require('process')

// 獲取服務(wù)端的工作目錄,也就是代碼運(yùn)行的目錄
const ROOT_DIR = process.cwd()

const server = http.createServer(async (req, resp) => {
  const parsedUrl = url.parse(req.url)
  // 刪除開頭的'/'來獲取資源的相對路徑,e.g: `/static`變?yōu)閌static`
  const parsedPathname = parsedUrl.pathname.slice(1)
  // 獲取資源在服務(wù)端的絕對路徑
  const pathname = path.resolve(ROOT_DIR, parsedPathname)

  try {
    // 讀取資源的信息, fs.Stats對象
    const stat = await fs.promises.stat(pathname)

    if (stat.isFile()) {
      // 如果請求的資源是文件就交給sendFile函數(shù)處理
      sendFile(resp, pathname)
    } else {
      // 如果請求的資源是文件夾就交給sendDirectory函數(shù)處理
      sendDirectory(resp, pathname)
    }
  } catch (error) {
    // 訪問的資源不存在
    if (error.code === 'ENOENT') {
      resp.statusCode = 404
      resp.end('file/directory does not exist')
    } else {
      resp.statusCode = 500
      resp.end('something wrong with the server')
    }
  }
})

server.listen(8080, () => {
  console.log('server is up and running')
})

在上面的代碼中我使用http模塊創(chuàng)建了一個(gè)server實(shí)例,這個(gè)實(shí)例里面定義了處理所有HTTP請求的handler函數(shù)。handler函數(shù)實(shí)現(xiàn)比較簡單,讀者根據(jù)上面的代碼注釋就可以看明白了,這里想要說明一下我為什么使用fs.promises.stat來獲取資源的元信息(fs.Stats類,包括資源的類型和更改時(shí)間等)而不使用可以實(shí)現(xiàn)同一個(gè)功能的fs.statfs.statSync:

  • fs.promises.stat vs fs.statfs.promises.statpromise-style的,可以使用asyncawait來實(shí)現(xiàn)異步的邏輯,代碼很干凈。而fs.statcallback-style的,這種API寫異步邏輯最后可能會(huì)變成意大利面條,后期維護(hù)困難。
  • fs.promises.stat vs fs.statSyncfs.promises.stat讀取文件的信息是一個(gè)異步操作,不會(huì)阻塞主線程的執(zhí)行。而fs.statSync是同步的,這也就意味著當(dāng)這個(gè)API執(zhí)行的時(shí)候,JS主線程會(huì)卡死,其它的資源請求是處理不了的。這里我也建議當(dāng)大家需要在服務(wù)端進(jìn)行文件系統(tǒng)的讀寫的時(shí)候,一定要優(yōu)先使用異步API避免使用同步式的API

接著我們來看一下sendFilesendDirectory這兩個(gè)函數(shù)的具體實(shí)現(xiàn):

const sendFile = async (resp, pathname) => {
  // 使用promise-style的readFile API異步讀取文件的數(shù)據(jù),然后返回給客戶端
  const data = await fs.promises.readFile(pathname)
  resp.end(data)
}
const sendDirectory = async (resp, pathname) => {
  // 使用promise-style的readdir API異步讀取文件夾的目錄信息,然后返回給客戶端
  const fileList = await fs.promises.readdir(pathname, { withFileTypes: true })
  // 這里保存一下子資源相對于根目錄的相對路徑,用于后面客戶端繼續(xù)訪問子資源
  const relativePath = path.relative(ROOT_DIR, pathname)

  // 構(gòu)造返回的html結(jié)構(gòu)體
  let content = '<ul>'
  fileList.forEach(file => {
    content += `
    <li>
      <a href=${
        relativePath
      }/${file.name}>${file.name}${file.isDirectory() ? '/' : ''}
      </a>
    </li>` 
  })

  content += '</ul>'
  // 返回當(dāng)前的目錄結(jié)構(gòu)給客戶端
  resp.end(`<h1>Content of ${relativePath || 'root directory'}:</h1>${content}`)
}

sendDirectory通過fs.promises.readdir來獲取其底下的目錄信息,然后以列表的形式返回一個(gè)html結(jié)構(gòu)給客戶端。這里值得一提的是:由于客戶端需要按照返回的子資源信息進(jìn)一步訪問子資源,所以我們需要記錄子資源相對于根目錄的相對路徑。sendFile函數(shù)的實(shí)現(xiàn)相對于sendDirectory會(huì)簡單一點(diǎn),它只需要讀取文件的內(nèi)容然后返回給客戶端就可以了。

上面的代碼寫完后,我們其實(shí)已經(jīng)實(shí)現(xiàn)了上面說的需求了,可是這個(gè)服務(wù)端是生產(chǎn)不可用的,因?yàn)樗泻芏酀撛诘膯栴}沒有解決,接著就讓我們看一下如何解決這些問題來優(yōu)化我們的服務(wù)端代碼。

大文件優(yōu)化

我們先來看看在現(xiàn)在的實(shí)現(xiàn)下,客戶端請求一個(gè)大文件會(huì)發(fā)生什么。首先我們在static文件夾下準(zhǔn)備一個(gè)大文件test.txt,這個(gè)文件里面有1000萬行Hello World!,文件的大小為124M:

124M文件

然后我們啟動(dòng)服務(wù)器,查看服務(wù)器啟動(dòng)完成后Node的內(nèi)存占用情況:

Node內(nèi)存使用狀況

可以看到Node服務(wù)只占用了8.5M的內(nèi)存,我們在瀏覽器訪問一下test.txt:

瀏覽器訪問test.tx

瀏覽器在瘋狂輸出Hello World!,這個(gè)時(shí)候再看一眼Node的內(nèi)存占用情況:

內(nèi)存激增

內(nèi)存使用一下子由8.5M激增到了132.9M,而增加的資源差不多就是文件的大小124M,這到底是為什么呢?我們再來看一下sendFile文件的實(shí)現(xiàn):

const sendFile = async (resp, pathname) => {
  // readFile會(huì)讀取文件的數(shù)據(jù)然后存在data變量里面
  const data = await fs.promises.readFile(pathname)
  resp.end(data)
}

上面的代碼中,其實(shí)我們會(huì)一次性讀取文件的內(nèi)容然后保存在data變量里面,也就是說我們會(huì)將124M的文本信息保存在內(nèi)存里面!你試想一下,如果有多個(gè)用戶同時(shí)訪問大資源,我們的程序肯定會(huì)因?yàn)閮?nèi)存爆炸而OOM(Out of Memory)的。那么這個(gè)問題如何解決呢?其實(shí)node提供的stream模塊可以很好地解決我們的問題。

Stream

我們先來看一下stream官方介紹:

A stream is an abstract interface for working with streaming data in Node.js. There are many stream objects provided by Node.js. For instance, a request to an HTTP server and process.stdoutare both stream instances.Streams can be readable, writable, or both. All streams are instances of EventEmitter

簡單來說,stream就是給我們流式處理數(shù)據(jù)用的,那么什么是流式處理呢?用最簡單的話來說就是:不是一下子處理完數(shù)據(jù)而是一點(diǎn)一點(diǎn)地處理它們。使用stream, 我們要處理的數(shù)據(jù)就會(huì)一點(diǎn)一點(diǎn)地加載到內(nèi)存的某一個(gè)固定大小的區(qū)域(buffer)以給其它消費(fèi)者消費(fèi)。由于保存數(shù)據(jù)的buffer大小一般是固定的,當(dāng)舊的數(shù)據(jù)處理完才會(huì)加載新的數(shù)據(jù),因此它可以避免內(nèi)存的崩潰。話不多說,我們馬上使用stream來重構(gòu)一下上面的sendFile函數(shù):

const sendFile = async (resp, pathname) => {
  // 為需要讀取的文件創(chuàng)建一個(gè)可讀流readableStream
  const fileStream = fs.createReadStream(pathname)
  fileStream.pipe(resp)
}

上面的代碼中,我們?yōu)樾枰x取的文件創(chuàng)建了一個(gè)可讀流(ReadableStream),然后將這個(gè)流和resp對象連接(pipe)在一起,這樣文件的數(shù)據(jù)就會(huì)源源不斷發(fā)送給客戶端了??吹竭@里你可能會(huì)問,為什么resp對象可以和fileStream連接在一起呢?原因就是這個(gè)resp對象底層是一個(gè)可寫流(WritableStream),而可讀流的pipe函數(shù)接收的就是可寫流。優(yōu)化完后我們再來請求一下test.txt大文件,同樣瀏覽器一頓瘋狂輸出,不過這個(gè)時(shí)候Node服務(wù)的內(nèi)存用量是這樣的:

Stream優(yōu)化后的內(nèi)存使用

Node的內(nèi)存基本穩(wěn)定在9.0M,比服務(wù)剛啟動(dòng)時(shí)只多了0.5M!從這個(gè)可以看出我們通過stream來優(yōu)化確實(shí)達(dá)到了很好的效果。由于文章篇幅的限制,這里沒有詳細(xì)介紹stream的API如何使用,需要了解的同學(xué)可以自行查看官方文檔。

減少文件傳輸帶寬

使用stream的確可以減少服務(wù)端的內(nèi)存占用問題,可是它沒有減少服務(wù)端和客戶端傳輸?shù)臄?shù)據(jù)大小。換句話來說,假如我們的文件大小是2M我們就實(shí)打?qū)崅鬏斶@2M的數(shù)據(jù)給客戶端。如果客戶端是手機(jī)或者其它移動(dòng)設(shè)備的話,這么大的帶寬消耗肯定是不可取的。這個(gè)時(shí)候我們需要對被傳輸?shù)臄?shù)據(jù)進(jìn)行壓縮然后再在客戶端進(jìn)行解壓,這樣傳輸?shù)臄?shù)據(jù)量才能大幅度減少。服務(wù)端數(shù)據(jù)壓縮的算法有很多,這里我使用了一個(gè)比較常用的gzip算法,我們來看一下如何更改sendFile以支持?jǐn)?shù)據(jù)壓縮:

// 引入zlib包
const zlib = require('zlib')

const sendFile = async (resp, pathname) => {
  // 通過header告訴客戶端:服務(wù)端使用的是gzip壓縮算法
  resp.setHeader('Content-Encoding', 'gzip')
  // 創(chuàng)建一個(gè)可讀流
  const fileStream = fs.createReadStream(pathname)
  // 文件流首先通過zip處理再發(fā)送給resp對象
  fileStream.pipe(zlib.createGzip()).pipe(resp)
}

在上面的代碼中,我使用Node原生的zlib模塊創(chuàng)建了一個(gè)轉(zhuǎn)換流(Transform Stream),這種流是既可讀又可寫的(Readable and Writable Stream),所以它像是一個(gè)轉(zhuǎn)換器將輸入的數(shù)據(jù)進(jìn)行加工然后輸出到下游的可寫流。我們請求index.html文件來看一下優(yōu)化后的效果:

zlib壓縮效果

上圖中,第一行的請求是沒有經(jīng)過gzip壓縮的請求大小,大概是2.6kB,而經(jīng)過gzip壓縮后傳輸數(shù)據(jù)一下子變成373B,優(yōu)化效果十分顯著!

使用瀏覽器緩存

數(shù)據(jù)壓縮雖然解決了服務(wù)端客戶端傳輸數(shù)據(jù)的帶寬問題,可是沒有解決重復(fù)數(shù)據(jù)傳輸?shù)膯栴}。我們知道一般來說服務(wù)器的靜態(tài)文件是很少會(huì)改變的,在服務(wù)端資源沒有發(fā)生改變的前提下,同一個(gè)客戶端多次訪問同一個(gè)資源,服務(wù)端會(huì)傳輸一樣的數(shù)據(jù),而這種情況下更有效的方式是:服務(wù)器告訴客戶端資源沒有變化,你直接使用緩存就可以了。瀏覽器緩存的方式有很多種,有協(xié)商緩存強(qiáng)緩存。關(guān)于這兩種緩存的區(qū)別我想網(wǎng)絡(luò)上已經(jīng)有很多文章說得很清晰了,我在這里也不再多說,本篇文章主要想說一下強(qiáng)緩存Etag機(jī)制如何實(shí)現(xiàn)。

什么是Etag

其實(shí)Etag(Entity-Tag)可以理解為文件內(nèi)容的指紋,如果文件內(nèi)容發(fā)生了改變那么這個(gè)指紋是大概率是會(huì)變的。這里注意的是我用了大概率而不是絕對,這是因?yàn)?code>HTTP1.1協(xié)議里面并沒有規(guī)定etag具體生成算法是什么,這完全是由開發(fā)者自己決定的。通常對于文件來說,etag是由文件的長度 + 更改時(shí)間生成的,這種做法其實(shí)是會(huì)存在瀏覽器讀取不到最新文件內(nèi)容的情況的,不過這不是本文的重點(diǎn),有興趣的同學(xué)可以參考網(wǎng)上的其它資料。

接著讓我們圖解一下基于etag協(xié)商緩存過程:

Etag交互過程

具體的過程如下:

  • 瀏覽器第一次請求服務(wù)端的資源時(shí),服務(wù)端會(huì)在Response里面設(shè)置當(dāng)前資源的etag信息,例如Etag: 5d-1834e3b6ea2
  • 瀏覽器第二次請求服務(wù)端資源時(shí),會(huì)在請求頭部的If-None-Match字段帶上最新的etag信息5d-1834e3b6ea2。服務(wù)端收到請求解析出If-None-Match字段并將其和最新的服務(wù)端etag進(jìn)行對比,如果是一樣的就會(huì)返回304給瀏覽器表示資源無更新,如果資源發(fā)生了更改則將最新的etag設(shè)置到頭部并且將最新的資源返回給瀏覽器。

接著我們來看一下sendFile函數(shù)如何支持etag:

// 這個(gè)函數(shù)會(huì)根據(jù)文件的fs.Stats信息計(jì)算出etag
const calculateEtag = (stat) => {
  // 文件的大小
  const fileLength = stat.size
  // 文件的最后更改時(shí)間
  const fileLastModifiedTime = stat.mtime.getTime()
  // 數(shù)字都用16進(jìn)制表示
  return `${fileLength.toString(16)}-${fileLastModifiedTime.toString(16)}`
}

const sendFile = async (req, resp, stat, pathname) => {
  // 文件的最新etag
  const latestEtag = calculateEtag(stat)
  // 客戶端的etag
  const clientEtag = req.headers['if-none-match']
  
  // 客戶端可以使用緩存
  if (latestEtag == clientEtag) {
    resp.statusCode = 304
    resp.end()
    return
  }
  resp.statusCode = 200
  resp.setHeader('etag', latestEtag)
  resp.setHeader('Content-Encoding', 'gzip')
  const fileStream = fs.createReadStream(pathname)
  fileStream.pipe(zlib.createGzip()).pipe(resp)
 }

在上面的代碼中我新增了一個(gè)計(jì)算etag的函數(shù)calculateEtag,這個(gè)函數(shù)會(huì)根據(jù)文件的大小和最后更改時(shí)間算出文件最新的etag信息。接著我還修改了sendFile的函數(shù)簽名,接收了req(HTTP請求體)和stat(文件的信息,fs.Stats類)兩個(gè)新參數(shù)。sendFile會(huì)先判斷客戶端的etag和服務(wù)端的etag是不是一樣的,如果相同就返回304給客戶端否則返回文件的最新內(nèi)容并且在header設(shè)置最新的etag信息。同樣我們再次訪問index.html文件來驗(yàn)證優(yōu)化效果:

etag優(yōu)化效果

上圖可以看到第一次請求資源時(shí)瀏覽器沒有緩存,服務(wù)端返回了文件的最新內(nèi)容和200狀態(tài)碼,這個(gè)請求的實(shí)際帶寬是396B,第二次請求時(shí),由于瀏覽器有緩存并且服務(wù)端資源沒有更新,所以服務(wù)端返回304狀態(tài)碼而沒有返回實(shí)際的文件內(nèi)容,這個(gè)時(shí)候的文件實(shí)際帶寬是113B!可以看出優(yōu)化效果是很明顯的,我們稍微更改一下index.html的內(nèi)容來驗(yàn)證一下客戶端會(huì)不會(huì)拉到最新的數(shù)據(jù):

客戶端獲取最新內(nèi)容

從上圖可以看出當(dāng)index.html更新后,舊的etag失效,瀏覽器可以獲取最新的數(shù)據(jù)。我們最后再來看一下這三個(gè)請求的詳細(xì)信息,下面是第一次請求時(shí),服務(wù)端給瀏覽器返回etag信息:

服務(wù)端設(shè)置etag

接著是第二次請求時(shí),客戶端請求服務(wù)端資源時(shí)帶上etag信息:

第二次請求

第三次請求,etag失效,拿到新的數(shù)據(jù):

etag失效

值得一提的是,這里我們只通過etag實(shí)現(xiàn)了瀏覽器的緩存,這是不完備的,實(shí)際的靜態(tài)服務(wù)器可能會(huì)加上基于Expires/Cache-Control強(qiáng)緩存和基于Last-Modified/Last-Modified-Since協(xié)商緩存來優(yōu)化。

總結(jié)

本篇文章我先實(shí)現(xiàn)了一個(gè)最簡單能用的靜態(tài)文件服務(wù)器,然后通過解決三個(gè)實(shí)際使用時(shí)會(huì)遇到的問題優(yōu)化了我們的代碼,最后完成了一個(gè)簡單高效的靜態(tài)文件服務(wù)器。

如上文所說,由于篇幅的限制,我們的實(shí)現(xiàn)上還是漏了很多東西的,例如MIME類型的設(shè)置,支持更多的壓縮算法如deflate以及支持更多的緩存方式如Last-Modified/Last-Modified-Since等。這些內(nèi)容其實(shí)在掌握了上面的方法后很容易就可以實(shí)現(xiàn)了,所以就留給大家在需要真正用到的時(shí)候自己實(shí)現(xiàn)了。

到此這篇關(guān)于如何使用Node寫靜態(tài)文件服務(wù)器的文章就介紹到這了,更多相關(guān)Node靜態(tài)文件服務(wù)器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Node.js中文件操作模塊File System的詳細(xì)介紹

    Node.js中文件操作模塊File System的詳細(xì)介紹

    FileSystem模塊是類似UNIX(POSIX)標(biāo)準(zhǔn)的文件操作API,用于操作文件系統(tǒng)——讀寫目錄、讀寫文件——Node.js底層使用C程序來實(shí)現(xiàn),這些功能是客戶端JS所不具備的。下面這篇文章就給大家詳細(xì)介紹了Node.js中的文件操作模塊File System,有需要的朋友們可以參考借鑒。
    2017-01-01
  • 獨(dú)立部署小程序基于nodejs的服務(wù)器過程詳解

    獨(dú)立部署小程序基于nodejs的服務(wù)器過程詳解

    這篇文章主要介紹了獨(dú)立部署小程序基于nodejs的服務(wù)器過程詳解,完全自定義的部署小程序服務(wù)器, 不依托于騰訊云服務(wù)器體系. 以阿里云服務(wù)器為基礎(chǔ)建立.服務(wù)器語言選用nodejs.,需要的朋友可以參考下
    2019-06-06
  • NestJS實(shí)現(xiàn)接口的多版本管理

    NestJS實(shí)現(xiàn)接口的多版本管理

    在軟件開發(fā)中,隨著應(yīng)用的迭代更新,經(jīng)常需要引入新的接口版本同時(shí)保留舊版本以保證向后兼容,本教程將通過 NestJS 框架演示如何實(shí)現(xiàn)接口的多版本管理,感興趣的小伙伴跟著小編一起來看看吧
    2024-06-06
  • 深入理解Commonjs規(guī)范及Node模塊實(shí)現(xiàn)

    深入理解Commonjs規(guī)范及Node模塊實(shí)現(xiàn)

    本篇文章主要介紹了深入理解Commonjs規(guī)范及Node模塊實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-05-05
  • 詳解Node.js包的工程目錄與NPM包管理器的使用

    詳解Node.js包的工程目錄與NPM包管理器的使用

    這篇文章主要介紹了Node.js包的工程目錄與NPM包管理器的使用,可以幫助編程者更好地組織Node.js代碼文件,需要的朋友可以參考下
    2016-02-02
  • 在Linux系統(tǒng)上升級(jí)Node.js遇到GLIBC依賴問題的多種解決方案

    在Linux系統(tǒng)上升級(jí)Node.js遇到GLIBC依賴問題的多種解決方案

    在現(xiàn)代 Web 開發(fā)和 DevOps 實(shí)踐中,Node.js 是一個(gè)不可或缺的工具,在升級(jí) Node.js 版本時(shí),尤其是在較舊的 Linux 系統(tǒng)上,可能會(huì)遇到一些依賴庫不兼容的問題,特別是與 GLIBC 和 GLIBCXX 相關(guān)的錯(cuò)誤,本文將詳細(xì)介紹如何解決這個(gè)依賴問題,需要的朋友可以參考下
    2025-01-01
  • 詳解node.js中的npm和webpack配置方法

    詳解node.js中的npm和webpack配置方法

    Node.js用c++語言編寫而成的,是一個(gè)基于chrome V8引擎的javascript運(yùn)行環(huán)境。這篇文章主要介紹了詳解node.js中的npm和webpack配置,需要的朋友可以參考下
    2018-01-01
  • node結(jié)合swig渲染摸板的方法

    node結(jié)合swig渲染摸板的方法

    本篇文章主要介紹了node結(jié)合swig渲染摸板的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-04-04
  • 在Node.js中實(shí)現(xiàn)關(guān)注列表和粉絲列表的方法示例

    在Node.js中實(shí)現(xiàn)關(guān)注列表和粉絲列表的方法示例

    在社交網(wǎng)絡(luò)或者任何需要用戶交互的應(yīng)用中,實(shí)現(xiàn)關(guān)注和被關(guān)注的功能是非常常見的需求,本文將通過一個(gè)簡單的例子,展示如何在Node.js環(huán)境下實(shí)現(xiàn)用戶的關(guān)注列表和粉絲列表,需要的朋友可以參考下
    2024-04-04
  • 深入探究node之Transform

    深入探究node之Transform

    這篇文章主要介紹了深入探究node之Transform,本文深入內(nèi)部闡述Transform的運(yùn)行機(jī)理及使用技巧,有興趣的可以了解一下
    2017-07-07

最新評論

枝江市| 巴中市| 仙居县| 正蓝旗| 柘城县| 崇信县| 长海县| 博客| 湖州市| 邻水| 重庆市| 康保县| 贵定县| 治多县| 平度市| 新余市| 青海省| 南部县| 新河县| 龙海市| 白水县| 寿宁县| 成武县| 银川市| 广饶县| 长沙市| 黔西县| 虎林市| 永城市| 临泽县| 边坝县| 玛沁县| 海宁市| 克山县| 南陵县| 辰溪县| 南郑县| 岗巴县| 呈贡县| 南靖县| 长武县|