4

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

 3 years ago
source link: https://fengxu.ink/2019/08/22/node%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-%E7%AC%AC%E4%BA%8C%E7%AF%87%EF%BC%88%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84%E6%A8%A1%E5%9D%97path%EF%BC%89/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

这篇是正式阅读的第一篇,从最简单的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下对比分析。

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

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

path.basename(path[, ext])

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

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上则是:。在源码中也就是两个导出的属性值常量。

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

// 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命令,示例:

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

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

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

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

path.extname(path)

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

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上复杂一些,以盘符开头的也符合绝对路径条件。

// 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部分。在源码的结尾处有这样两行:

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两种都支持,这里只返回\,同样由于转义字符的原因,源码里为\\。常见用法:

// 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模块,但是真正开始写起来,要比想象的困难很多,可能也是很长时间没写作的原因吧,完成最简单的一篇用了好多天,后面我可能会适当调整方法了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK