node源码学习笔记--第二篇(最简单的模块path)

这篇是正式阅读的第一篇,从最简单的path开始阅读,我记得之前在掘金收藏过node源码阅读的文章,想找来看看它们的写作思路,发现早就已经取消收藏了(大概是因为质量不高)。不过这样也好,不受到其他人思路的干扰,我的文章输出我自己的阅读收获,先尽量保持清晰。

path功能回顾

这个系列我希望能做到格式统一,第一部分先过一下API。我觉得这样做很有必要,对于不熟悉的功能可以先熟悉用法,对于熟悉的功能也可以做一个知识梳理,知道怎么用才能有机会去理解为什么这样用。

path是node中非常常用的一个模块,用来处理各种文件路径相关操作,path的API可以根据所在平台不同展现对应的行为,屏蔽了win和*nix系统之间差异。

path常见API及功能如下:

具体的功能链接里面都有,下面分析源码时候会逐个讲解,这部分就不再重复。

path API 源码阅读

path源码的最后一行如下,导出的内容就是我们require时引入的内容,这里利用process模块识别当前操作系统是否为win32,根据操作系统导出不同内容。process也是node提供的一个模块,内部肯定是要调用原生API来处理,后面还会读到这里,所以在此不作具体分析。

win32很熟悉,就是windows环境。posix是一种线程标准(详见wiki),最早为unix系统使用,后来unix-like系统也开始遵守此标准,现在Windows其实也有兼容,但是通常还是用来作为 nix 系统的代指,个人理解这里可以理解为 nix 系统。具体二者之间的平台差异会在具体API下对比分析。

1
module.exports = process.platform === 'win32' ? win32 : posix;

之后来看win32和posix两个对象,其实很简单,里面只有一些属性和方法,这些就是暴露出的path相关API,我们可以在程序中使用path加点调用的,上面已经对其进行了一一列举,下面就依次逐个分析。(因为文档上的排列顺序是按照字母表的,这里为了对应沿用下来,具体在源码中的位置可以搜索查看)

path.basename(path[, ext])

这个方法用于获取path的最后一部分,类似于Unix的basename命令,第二个参数为扩展名,如果传入会返回去除扩展名之后的结果。直接引用官方示例:

1
2
path.basename('/foo/bar/baz/asdf/quux.html'); // 返回: 'quux.html'
path.basename('/foo/bar/baz/asdf/quux.html', '.html'); // 返回: 'quux'

实现方式其实两端差异并不是很大,先看posix平台下的逻辑,最后再来看差异的部分。

这个方法的逻辑想想也知道,匹配出最后一个分隔符(posix平台为/符号),截取后面的内容,如果传入了扩展名再截下扩展名部分,返回最终结果。

进入方法首先通过validateString校验参数,如果传入的不是string会抛出错误。之后这里首先处理有扩展名的情况(扩展名长度要小于路径长度,否则视为无效,按照无扩展处理),这里从后向前遍历path,通过charCodeAt获取code,判断是否为分隔符(posix平台为/符号),最终截取首次匹配到分隔符的位置的下一个位置开始,到扩展名前一个位置结束的内容,即为返回结果。对于没传入扩展名的情况,只要截取到结尾位置即可。

对于Windows系统这里有两处不同,首先是盘符判断,Windows系统可以有多个磁盘分区,对于从根目录开始的情况,路径前面会携带盘符信息如C:,这种情况需要排除前两个字符,从第三个字符开始处理。另外,Windows的分隔符为/或\两种都可以,所以这里需要判断两种情况,在源码中的isPathSeparator方法处理了win32的两种分隔符。

特别的,在js等很多编程语言里,\为转义字符,所以想表示\字符本身需要写作\\

path.delimiter

这是一个常量,返回对应操作系统的路径定界符,路径定界符就是指并列写多个路径时候用来分隔的符号,一个常见的场景就是配置环境变量时候,对于多个值中间的分隔,Windows上为;,而在posix上则是:。在源码中也就是两个导出的属性值常量。

在程序中,我们可以这样使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// posix

console.log(process.env.PATH);
// 打印: '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin'

process.env.PATH.split(path.delimiter);
// 返回: ['/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/bin']


// win32

console.log(process.env.PATH);
// 打印: 'C:\Windows\system32;C:\Windows;C:\Program Files\node\'

process.env.PATH.split(path.delimiter);
// 返回: ['C:\\Windows\\system32', 'C:\\Windows', 'C:\\Program Files\\node\\']

path.dirname(path)

这个方法用于获取path的目录名,类似于Unix的dirname命令,示例:

1
path.dirname('/foo/bar/baz/asdf/quux'); // 返回: '/foo/bar/baz/asdf'

这个方法在win32和posix上的实现方式有很大区别,首先来看比较简单的posix平台。

posix平台下的实现很简单,依旧是从后向前遍历,找到第一个分隔符截取前面内容即可,特别的,对于没匹配到的情况,绝对路径返回/相对路径返回.

在win32平台下核心处理逻辑和posix是相同的,但是关于开头的盘符相关处理有一段特有的逻辑,通过多次遍历处理最终截取正确的dirname。

path.extname(path)

返回扩展名,包括.符号,这里就是一个字符串匹配,找到最后一次出现.的位置截取后面内容,如果.出现在文件首部则返回空。

1
2
3
4
5
6
7
8
9
10
11
path.extname('index.html'); // 返回: '.html'

path.extname('index.coffee.md'); // 返回: '.md'

path.extname('index.'); // 返回: '.'

path.extname('index'); // 返回: ''

path.extname('.index'); // 返回: ''

path.extname('.index.md'); // 返回: '.md'

具体的匹配思路也没有什么特别之处,依旧是从后向前遍历,win32下需要处理首部的盘符信息。

path.format(pathObject)

path.isAbsolute(path)

判断是否为绝对路径,在posix上很简单,判断是否以/开头即可,win32上复杂一些,以盘符开头的也符合绝对路径条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// posix

path.isAbsolute('/foo/bar'); // true
path.isAbsolute('/baz/..'); // true
path.isAbsolute('qux/'); // false
path.isAbsolute('.'); // false


// win32

path.isAbsolute('//server'); // true
path.isAbsolute('\\\\server'); // true
path.isAbsolute('C:/foo/..'); // true
path.isAbsolute('C:\\foo\\..'); // true
path.isAbsolute('bar\\baz'); // false
path.isAbsolute('bar/baz'); // false
path.isAbsolute('.'); // false

path.join([…paths])

path.normalize(path)

path.parse(path)

path.posix

返回path方法中的posix部分。在源码的结尾处有这样两行:

1
2
posix.win32 = win32.win32 = win32;
posix.posix = win32.posix = posix;

这里在导出的内容上都挂载了win32和posix两个对象的引用,无论在什么平台上,都可以通过path获取想要的对象,通过这个对象可以访问对应平台下的属性和方法。

path.relative(from, to)

path.resolve([…paths])

path.sep

一个常量,系统分隔符属性,在win32返回\,在posix返回/ ,实际上win32两种都支持,这里只返回\,同样由于转义字符的原因,源码里为\\。常见用法:

1
2
3
4
5
6
7
8
// posix

'foo/bar/baz'.split(path.sep); // 返回: ['foo', 'bar', 'baz']


// win32

'foo\\bar\\baz'.split(path.sep); // 返回: ['foo', 'bar', 'baz']

path.toNamespacedPath(path)

这个方法只在win32生效,posix环境是一个空方法,直接返回path。关于namespace相关的内容参见这个链接,相关的东西我没使用过,源码也只是根据格式对path做了匹配。

path.win32

见path.posix。

总结

这是第一篇node源码解读文章,写的是最简单的path模块,但是真正开始写起来,要比想象的困难很多,可能也是很长时间没写作的原因吧,完成最简单的一篇用了好多天,后面我可能会适当调整方法了。

-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!