聊聊Node.js path模組中的常用工具函數
本篇文章带大家聊聊Node中的path模块,介绍一下path的常见使用场景、执行机制,以及常用工具函数,希望对大家有所帮助!
在开发过程中,会经常用到 Node.js ,它利用 V8 提供的能力,拓展了 JS 的能力。而在 Node.js 中,我们可以使用 JS 中本来不存在的 path 模块,为了我们更加熟悉的运用,让我们一起来了解一下吧~
本文 Node.js 版本为 16.14.0,本文的源码来自于此版本。希望大家阅读本文后,会对大家阅读源码有所帮助。
path 的常见使用场景
Path 用于处理文件和目录的路径,这个模块中提供了一些便于开发者开发的工具函数,来协助我们进行复杂的路径判断,提高开发效率。例如:
在项目中配置别名,别名的配置方便我们对文件更简便的引用,避免深层级逐级向上查找。
reslove: { alias: { // __dirname 当前文件所在的目录路径 'src': path.resolve(__dirname, './src'), // process.cwd 当前工作目录 '@': path.join(process.cwd(), 'src'), }, }
在 webpack 中,文件的输出路径也可以通过我们自行配置生成到指定的位置。
module.exports = { entry: './path/to/my/entry/file.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'my-first-webpack.bundle.js', }, };
又或者对于文件夹的操作
let fs = require("fs"); let path = require("path"); // 删除文件夹 let deleDir = (src) => { // 读取文件夹 let children = fs.readdirSync(src); children.forEach(item => { let childpath = path.join(src, item); // 检查文件是否存在 let file = fs.statSync(childpath).isFile(); if (file) { // 文件存在就删除 fs.unlinkSync(childpath) } else { // 继续检测文件夹 deleDir(childpath) } }) // 删除空文件夹 fs.rmdirSync(src) } deleDir("../floor")
简单的了解了一下 path 的使用场景,接下来我们根据使用来研究一下它的执行机制,以及是怎么实现的。
path 的执行机制
引入 path 模块,调用 path 的工具函数的时候,会进入原生模块的处理逻辑。
使用
_load
函数根据你引入的模块名作为 ID,判断要加载的模块是原生 JS 模块后,会通过loadNativeModule
函数,利用 id 从_source
(保存原生JS模块的源码字符串转成的 ASCII 码)中找到对应的数据加载原生 JS 模块。执行 lib/path.js 文件,利用 process 判断操作系统,根据操作系统的不同,在其文件处理上可能会存在操作字符的差异化处理,但方法大致一样,处理完后返回给调用方。
常用工具函数简析
resolve 返回当前路径的绝对路径
resolve 将多个参数,依次进行拼接,生成新的绝对路径。
resolve(...args) { let resolvedDevice = ''; let resolvedTail = ''; let resolvedAbsolute = false; // 从右到左检测参数 for (let i = args.length - 1; i >= -1; i--) { ...... } // 规范化路径 resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isPathSeparator); return resolvedAbsolute ? `${resolvedDevice}\\${resolvedTail}` : `${resolvedDevice}${resolvedTail}` || '.'; }
根据参数获取路径,对接收到的参数进行遍历,参数的长度大于等于 0 时都会开始进行拼接,对拼接好的 path 进行非字符串校验,有不符合的参数则抛出 throw new ERR_INVALID_ARG_TYPE(name, 'string', value)
, 符合要求则会对 path 进行长度判断,有值则 +=path 做下一步操作。
let path; if (i >= 0) { path = args[i]; // internal/validators validateString(path, 'path'); // path 长度为 0 的话,会直接跳出上述代码块的 for 循环 if (path.length === 0) { continue; } } else if (resolvedDevice.length === 0) { // resolvedDevice 的长度为 0,给 path 赋值为当前工作目录 path = process.cwd(); } else { // 赋值为环境对象或者当前工作目录 path = process.env[`=${resolvedDevice}`] || process.cwd(); if (path === undefined || (StringPrototypeToLowerCase(StringPrototypeSlice(path, 0, 2)) !== StringPrototypeToLowerCase(resolvedDevice) && StringPrototypeCharCodeAt(path, 2) === CHAR_BACKWARD_SLASH)) { // 对 path 进行非空与绝对路径判断得出 path 路径 path = `${resolvedDevice}\\`; } }
尝试匹配根路径,判断是否是只有一个路径分隔符 ('\') 或者 path 为绝对路径,然后给绝对路径打标,并把 rootEnd
截取标识设为 1 (下标)。第二项若还是路径分隔符 ('\') ,就定义截取值为 2 (下标),并用 last
保存截取值,以便后续判断使用。
继续判断第三项是否是路径分隔符 ('\'),如果是,那么为绝对路径,rootEnd
截取标识为 1 (下标),但也有可能是 UNC 路径 ( \servername\sharename,servername 服务器名。sharename 共享资源名称)。如果有其他值,截取值会继续进行自增读取后面的值,并用 firstPart
保存第三位的值,以便拼接目录时取值,并把 last 和截取值保持一致,以便结束判断。
const len = path.length; let rootEnd = 0; // 路径截取结束下标 let device = ''; // 磁盘根 D:\、C:\ let isAbsolute = false; // 是否是磁盘根路径 const code = StringPrototypeCharCodeAt(path, 0); // path 长度为 1 if (len === 1) { // 只有一个路径分隔符 \ 为绝对路径 if (isPathSeparator(code)) { rootEnd = 1; isAbsolute = true; } } else if (isPathSeparator(code)) { // 可能是 UNC 根,从一个分隔符 \ 开始,至少有一个它就是某种绝对路径(UNC或其他) isAbsolute = true; // 开始匹配双路径分隔符 if (isPathSeparator(StringPrototypeCharCodeAt(path, 1))) { let j = 2; let last = j; // 匹配一个或多个非路径分隔符 while (j < len && !isPathSeparator(StringPrototypeCharCodeAt(path, j))) { j++; } if (j < len && j !== last) { const firstPart = StringPrototypeSlice(path, last, j); last = j; // 匹配一个或多个路径分隔符 while (j < len && isPathSeparator(StringPrototypeCharCodeAt(path, j))) { j++; } if (j < len && j !== last) { last = j; while (j < len && !isPathSeparator(StringPrototypeCharCodeAt(path, j))) { j++; } if (j === len || j !== last) { device = `\\\\${firstPart}\\${StringPrototypeSlice(path, last, j)}`; rootEnd = j; } } } } else { rootEnd = 1; } // 检测磁盘根目录匹配 例:D:,C:\ } else if (isWindowsDeviceRoot(code) && StringPrototypeCharCodeAt(path, 1) === CHAR_COLON) { device = StringPrototypeSlice(path, 0, 2); rootEnd = 2; if (len > 2 && isPathSeparator(StringPrototypeCharCodeAt(path, 2))) { isAbsolute = true; rootEnd = 3; } }
检测路径并生成,检测磁盘根目录是否存在或解析 resolvedAbsolute
是否为绝对路径。
// 检测磁盘根目录 if (device.length > 0) { // resolvedDevice 有值 if (resolvedDevice.length > 0) { if (StringPrototypeToLowerCase(device) !== StringPrototypeToLowerCase(resolvedDevice)) continue; } else { // resolvedDevice 无值并赋值为磁盘根目录 resolvedDevice = device; } } // 绝对路径 if (resolvedAbsolute) { // 磁盘根目录存在结束循环 if (resolvedDevice.length > 0) break; } else { // 获取路径前缀进行拼接 resolvedTail = `${StringPrototypeSlice(path, rootEnd)}\\${resolvedTail}`; resolvedAbsolute = isAbsolute; if (isAbsolute && resolvedDevice.length > 0) { // 磁盘根存在便结束循环 break; } }
join 根据传入的 path 片段进行路径拼接
接收多个参数,利用特定分隔符作为定界符将所有的 path 参数连接在一起,生成新的规范化路径。
接收参数后进行校验,如果没有参数的话,会直接返回 '.' ,反之进行遍历,通过内置
validateString
方法校验每个参数,如有一项不合规则直接throw new ERR_INVALID_ARG_TYPE(name, 'string', value);
window 下为反斜杠 ('\') , 而 linux 下为正斜杠 ('/'),这里是
join
方法区分操作系统的一个不同点,而反斜杠 ('\') 有转义符的作用,单独使用会被认为是要转义斜杠后面的字符串,故此使用双反斜杠转义出反斜杠 ('\') 使用。最后进行拼接后的字符串校验并格式化返回。
if (args.length === 0) return '.'; let joined; let firstPart; // 从左到右检测参数 for (let i = 0; i < args.length; ++i) { const arg = args[i]; // internal/validators validateString(arg, 'path'); if (arg.length > 0) { if (joined === undefined) // 把第一个字符串赋值给 joined,并用 firstPart 变量保存第一个字符串以待后面使用 joined = firstPart = arg; else // joined 有值,进行 += 拼接操作 joined += `\\${arg}`; } } if (joined === undefined) return '.';
在 window 系统下,因为使用反斜杠 ('\') 和 UNC (主要指局域网上资源的完整 Windows 2000 名称)路径的缘故,需要进行网络路径处理,('\') 代表的是网络路径格式,因此在 win32 下挂载的join
方法默认会进行截取操作。
如果匹配得到反斜杠 ('\'),slashCount
就会进行自增操作,只要匹配反斜杠 ('\') 大于两个就会对拼接好的路径进行截取操作,并手动拼接转义后的反斜杠 ('\')。
let needsReplace = true; let slashCount = 0; // 根据 StringPrototypeCharCodeAt 对首个字符串依次进行 code 码提取,并通过 isPathSeparator 方法与定义好的 code 码进行匹配 if (isPathSeparator(StringPrototypeCharCodeAt(firstPart, 0))) { ++slashCount; const firstLen = firstPart.length; if (firstLen > 1 && isPathSeparator(StringPrototypeCharCodeAt(firstPart, 1))) { ++slashCount; if (firstLen > 2) { if (isPathSeparator(StringPrototypeCharCodeAt(firstPart, 2))) ++slashCount; else { needsReplace = false; } } } } if (needsReplace) { while (slashCount < joined.length && isPathSeparator(StringPrototypeCharCodeAt(joined, slashCount))) { slashCount++; } if (slashCount >= 2) joined = `\\${StringPrototypeSlice(joined, slashCount)}`; }
执行结果梳理
resolve | join | |
---|---|---|
无参数 | 当前文件的绝对路径 | . |
参数无绝对路径 | 当前文件的绝对路径按顺序拼接参数 | 拼接成的路径 |
首个参数为绝对路径 | 参数路径覆盖当前文件绝对路径并拼接后续非绝对路径 | 拼接成的绝对路径 |
后置参数为绝对路径 | 参数路径覆盖当前文件绝对路径并覆盖前置参数 | 拼接成的路径 |
首个参数为(./) | 有后续参数,当前文件的绝对路径拼接参数 无后续参数,当前文件的绝对路径 |
有后续参数,后续参数拼接成的路径 无后续参数,(./) |
后置参数有(./) | 解析后的绝对路径拼接参数 | 有后续参数,拼接成的路径拼接后续参数 无后续参数,拼接(/) |
首个参数为(../) | 有后续参数,覆盖当前文件的绝对路径的最后一级目录后拼接参数 无后续参数,覆盖当前文件的绝对路径的最后一级目录 |
有后续参数,拼接后续参数 无后续参数,(../) |
后置参数有(../) | 出现(../)的上层目录会被覆盖,后置出现多少个,就会覆盖多少层,上层目录被覆盖完后,返回(/),后续参数会拼接 | 出现(../)的上层目录会被覆盖,后置出现多少个,就会覆盖多少层,上层目录被覆盖完后,会进行参数拼接 |
總結
閱讀了原始碼之後,resolve
方法會對參數進行處理,考慮路徑的形式,在最後拋出絕對路徑。在使用的時候,如果是進行檔案之類的操作,建議使用resolve
方法,相較來看, resolve
方法就算沒有參數也會回傳一個路徑,供使用者操作,在執行過程中會進行路徑的處理。而 join
方法只是將傳入的參數進行規範化拼接,對於產生一個新的路徑比較實用,可以依照使用者意願建立。不過每個方法都有優點,要依照自己的使用場景以及專案需求,去選擇合適的方法。
更多node相關知識,請造訪:nodejs 教學!
以上是聊聊Node.js path模組中的常用工具函數的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

基於無阻塞、事件驅動建立的Node服務,具有記憶體消耗低的優點,非常適合處理海量的網路請求。在海量請求的前提下,就需要考慮「記憶體控制」的相關問題了。 1. V8的垃圾回收機制與記憶體限制 Js由垃圾回收機

怎麼處理文件上傳?以下這篇文章為大家介紹一下node專案中如何使用express來處理文件的上傳,希望對大家有幫助!

文件模組是對底層文件操作的封裝,例如文件讀寫/打開關閉/刪除添加等等文件模組最大的特點就是所有的方法都提供的**同步**和**異步**兩個版本,具有sync 字尾的方法都是同步方法,沒有的都是異

這篇文章跟大家分享Node的進程管理工具“pm2”,聊聊為什麼需要pm2、安裝和使用pm2的方法,希望對大家有幫助!

PiNetwork節點詳解及安裝指南本文將詳細介紹PiNetwork生態系統中的關鍵角色——Pi節點,並提供安裝和配置的完整步驟。 Pi節點在PiNetwork區塊鏈測試網推出後,成為眾多先鋒積極參與測試的重要環節,為即將到來的主網發布做準備。如果您還不了解PiNetwork,請參考Pi幣是什麼?上市價格多少? Pi用途、挖礦及安全性分析。什麼是PiNetwork? PiNetwork項目始於2019年,擁有其專屬加密貨幣Pi幣。該項目旨在創建一個人人可參與

事件循環是 Node.js 的基本組成部分,透過確保主執行緒不被阻塞來實現非同步編程,了解事件循環對建立高效應用程式至關重要。以下這篇文章就來帶大家深入了解Node中的事件循環 ,希望對大家有幫助!

node無法用npm指令是因為沒有正確配置環境變量,其解決方法是:1、開啟“系統屬性”;2、找到“環境變數”->“系統變數”,然後編輯環境變數;3、找到nodejs所在的資料夾;4、點選「確定」即可。
