玩命加载中🤣🤣🤣

NodeJs-02-http及模块化


NodeJs-02-http及模块化

http模块

绝对路径

形式 特点 场景
https://www.baidu.com(注意是完整的 协议://域名: 端口) 直接向目标资源发送请求,容易理解。网站的外链会用到此形式 一般用于网页外部的链接,如网页最下部分
//jd.com/web 与当前页面 URL 的协议拼接形成完整 URL 再发送请求。比如当前协议是 http + 目标地址 大型网站:天猫商品的链接
/web 与当前页面 URL 的协议、主机名、端口拼接形成完整 URL 再发送请求。如 当前协议 https://saddyfire.cn/ + 目标地址 中小型网站

相对路径

形式 最终的 URL
./css/app.css 相当于当前页面所在目录的 ./css/app.css
js/app.js 同上
…/img/logo.png 相当于往上走一级
…/…/mp4/show.mp4 相当于往上走两级

但是相对路径使用较少,是参照当前页面的,所以一旦当前页面路径不正常的时候,使用相对路径获取资源就会出现异常

网页中使用 URL 的场景

  • a 标签 href
  • link 标签 href
  • script 标签 src
  • img 标签 src
  • video audio 标签 src
  • form 中的 action
  • AJAX 请求中的 URL

设置mime类型

媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型 )是一种标准,用来表示文档、文件或字节流的性质和格式。

mime 类型结构: [type]/[subType]
例如: text/html text/css image/jpeg image/png application/json

HTTP 服务可以设置响应头 Content-Type 来表明响应体的 MIME 类型,浏览器会根据该类型决定如何处理资源

下面是常见文件对应的 mime 类型

html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
json: 'application/json'

对于未知的资源类型,可以选择 application/octet-stream 类型,浏览器在遇到该类型的响应时,会对响应体内容进行独立存储,也就是我们常见的 下载 效果

可以做如下判断

// 根据文件扩展名设置正确的Content-Type
const extname = path.extname(filePath)
let contentType = ''
switch (extname) {
  case '.html':
    contentType = 'text/html; charset=utf-8'
    break
  case '.css':
    contentType = 'text/css'
    break
  case '.js':
    contentType = 'application/javascript'
    break
  case '.png':
    contentType = 'image/png'
    break
  case '.jpg':
  case '.jpeg':
    contentType = 'image/jpeg'
    break
  case '.gif':
    contentType = 'image/gif'
    break
  default:
    contentType = 'application/octet-stream'
    break
}
res.setHeader('content-type', contentType)

一些常见的网页错误及响应码

进来的时候可以校验请求方式

if (req.method !== 'GET') {
  res.statusCode = 405
  res.end('<h1>405 Method Not Allowed</h1>')
  return
}

404文件不存在,403无访问权限,500服务端错误

if (err) {
  if (err.code === 'ENOENT') {
    res.statusCode = 404
    res.end('<h1>404 Not Found</h1>')
    return
  } else if (err.code === 'EPERM') {
    res.statusCode = 403
    res.end('<h1>403 Forbidden</h1>')
  } else {
    console.error('文件读取错误:', err)
    res.statusCode = 500
    res.end('<h1>500 Internal Server Error</h1>')
    return
  }
}

完整代码示例

import http from 'http'
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'

// 在ES模块中获取__dirname和__filename
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

const server = http.createServer((req, res) => {
  if (req.method !== 'GET') {
    res.statusCode = 405
    res.end('<h1>405 Method Not Allowed</h1>')
    return
  }

  let url = new URL(req.url, 'http://127.0.0.1:9999')
  let { pathname } = url
  if (pathname === '/') {
    pathname = '/index.html'
  }

  // 修复路径拼接问题,使用path.join而不是字符串拼接
  let filePath = path.join(__dirname, 'page', pathname.slice(1)) // 移除开头的斜杠

  fs.readFile(filePath, (err, data) => {
    if (err) {
      if (err.code === 'ENOENT') {
        res.statusCode = 404
        res.end('<h1>404 Not Found</h1>')
        return
      } else if (err.code === 'EPERM') {
        res.statusCode = 403
        res.end('<h1>403 Forbidden</h1>')
      } else {
        console.error('文件读取错误:', err)
        res.statusCode = 500
        res.end('<h1>500 Internal Server Error</h1>')
        return
      }
    }
    // 根据文件扩展名设置正确的Content-Type
    const extname = path.extname(filePath)
    let contentType = ''
    switch (extname) {
      case '.html':
        contentType = 'text/html; charset=utf-8'
        break
      case '.css':
        contentType = 'text/css'
        break
      case '.js':
        contentType = 'application/javascript'
        break
      case '.png':
        contentType = 'image/png'
        break
      case '.jpg':
      case '.jpeg':
        contentType = 'image/jpeg'
        break
      case '.gif':
        contentType = 'image/gif'
        break
      default:
        contentType = 'application/octet-stream'
        break
    }
    res.setHeader('content-type', contentType)
    res.end(data)
  })

})


server.listen(9999, () => {
  console.log('静态资源服务启动成功, 请访问 http://127.0.0.1:9999')
})

模块化

暴露数据

模块暴露两种方式

  1. module.exports = value
  2. exports.name = value

如何正确使用

  1. 初始关系:在每一个 Node.js 模块文件的最开始,隐藏着这样一行关系声明:var exprots = module.exports。此时,它们指向内存中的同一个空对象 {}。因此,当使用 exports.name = 'value' 时,实际上等同于 module.exports.name = 'value',是在向同一个对象添加属性。

  2. 当执行 exports = 'value' 时,只是将变量 exports 本身重新赋值,指向了一个新的值。切断了 exportsmodule.exports 之间的引用关系。模块系统认的是 module.exports,它并没有受到影响,因此 require() 得到的仍然是 module.exports 的初始值(一个空对象 {}

  3. 结论a:需要导出一个函数、一个类、一个字符串或数字等单一实体时:务必使用 module.exports

    module.exports = function() {};
    module.exports = MyClass;
    module.exports = 'Hello, world';
    module.exports = 123;
  4. 结论b:需要到处对象添加多个属性或方法时:可以使用 exports.xxx 的简便写法

    exports.method1 = function() {};
    exports.property1 = 'some value'

导入(引入)模块

require的注意事项

  1. 对于自己创建的模块,导入时路径建议写 相对路径 ,且不能省略 ./../
  2. jsjson 文件导入时可以不用写后缀,c/c++ 编写的 node 扩展文件也可以不写后缀,但是一般用不到
  3. 如果导入其他类型的文件,会以 js 文件进行处理
  4. 如果导入的路径是个文件夹,则会首先检测该文件夹下 package.json 文件中 main 属性对应的文件,如果存在则导入,反之如果文件不存在会报错。如果 main 属性不存在,或者 package.json 不存在,则会尝试导入文件夹下的 index.jsindex.json,如果还是没找到,就会报错
  5. 导入 node.js 内置模块时,直接 require 模块的名字即可,无需加 ./../

导入模块的基本流程

require 导入自定义模块的基本流程

  1. 将相对路径转为绝对路径,定位目标文件
  2. 缓存检测
  3. 读取目标文件代码
  4. 包裹为一个函数并执行(自执行函数)。通过 arguments.callee.toString() 查看自执行函数
  5. 缓存模块的值
  6. 返回 module.exports 的值

image-20251208130119310

通过伪代码来展示 require 导入流程

/**
 * require的流程
 * 伪代码
 * 1. 检查模块是否已经被导入
 * 2. 如果没有被导入,创建一个新的模块对象
 * 3. 执行模块的代码,将导出的内容添加到模块对象中
 * 4. 返回模块对象
 */
function require(file) {
  // 1. 将相对路径转换为绝对路径
  let absolutePath = path.resolve(__dirname, file)
  // 2. 检查模块是否已经被导入
  if (require.cache[absolutePath]) {
    return require.cache[absolutePath]
  }
  // 3. 读取文件
  let code = fs.readFileSync(absolutePath).toString()
  // 4. 包裹为一个函数,然后执行
  let module = {} // 内部声明
  let exports = module.exports = {} // 内部声明

  (function (exports, require, module, __filename, __dirname) {
    eval(code) // 执行模块的代码
  })(exports, require, module, absolutePath, path.dirname(absolutePath))
  // 5. 缓存模块对象
  require.cache[absolutePath] = module.exports
}

CommonJs 规范

module.exportsexports 以及 require 这些都是 CommonJS 模块化规范中的内容。

Node.js 是实现了 CommonJS 模块化规范,二者关系有点像 JavaScriptECMAScript


文章作者: 👑Dee👑
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 👑Dee👑 !
  目录