Nodejs源代码分析之Path

时间:2022-07-01 13:04:36

今天介绍一下nodejs Path的源代码分析,Path的API文档在https://nodejs.org/dist/latest-v5.x/docs/api/path.html,使用相对简单,在API文档中,须要特别说明的是例如以下的文字:

This module contains utilities for handling and transforming file paths. Almost all these methods perform only string transformations. The file system is not consulted to check whether paths are valid.

也就是说这个类的作用是文件路径字符串的处理。而非验证文件路径的有效性,也就是说: path能够从一个文件路径中利用字符串的处理获取详细的文件夹,文件后缀。文件名称等消息,可是我们无法推断这个路径是否合法有效。

API的类型分为以下几类:

  • 获取文件路径的详细信息,如文件夹。文件后缀,文件名称等
  • 当前系统的一些属性,如分隔符,平台等
  • 文件路径的合成,相对路径的解析和标准化等。

首先查看该模块的导出:

// 当前是否是Windows 平台
var isWindows = process.platform === 'win32'; // 假设是windows,直接导出的win32,否则为posix
if (isWindows)
module.exports = win32;
else /* posix */
module.exports = posix; // 同一时候导出中含有属性win32 和posix
module.exports.posix = posix;
module.exports.win32 = win32;

从上面的源代码能够看出,其导出的为win32 或者posix,后面能够看到这是两个对象。 一个代表的是windows平台,一个是非windows的可移植性操作系统接口。一般就是指Linux。之全部两个不同的平台对象,是由于windows和linux上的文件路径,分隔符等不一样,所以分开处理,可是事实上现的API接口都基本都一样。也就是win32和posix对象里面的属性和方法基本一致。

为简单和避免反复。我们仅仅分析win32对象的API源代码与对象。

查看一下源代码中的帮助函数:

// resolves . and .. elements in a path array with directory names there
// must be no slashes or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
// 解决文件文件夹中的相对路径
// @parts 文件文件夹数组,从0- 高位分别代表一级文件夹
// @allowAboveRoot 布尔值。代表能否够超过根文件夹
// @returns, 解决掉相对路径后的数组,比方说数组
// ['/test', '/re', '..']将会返回 ['/test']
function normalizeArray(parts, allowAboveRoot) {
// 返回值
var res = [];
// 遍历数组。处理数组中的相对路径字符 '.' 或者'..'
for (var i = 0; i < parts.length; i++) {
// 取得当前的数组的字符
var p = parts[i]; // ignore empty parts
// 对空或者'.'不处理
if (!p || p === '.')
continue;
// 处理相对路径中的'..'
if (p === '..') {
if (res.length && res[res.length - 1] !== '..') {
// 直接弹出返回队列,当没有到达根文件夹时
res.pop();
} else if (allowAboveRoot) {
//allowAboveRoot 为真时。插入'..'
res.push('..');
}
} else {
// 非 '.' 和'..'直接插入返回队列。 res.push(p);
}
}
// 返回路径数组
return res;
} // returns an array with empty elements removed from either end of the input
// array or the original array if no elements need to be removed
//返回带有从头和尾空元素的队列。 假设没有空元素,直接返回原来的队列
// 比方说 ['undefined', 1, 2, 'undefined', 3, 'undefined'] 会返回
// [1, 2, 'undefined', 3]
function trimArray(arr) {
// 确定队列的最后一个索引
var lastIndex = arr.length - 1;
var start = 0;
//确定队列中从開始位置的第一个非空元素
for (; start <= lastIndex; start++) {
if (arr[start])
break;
}
//确定队列中从结束位置的第一个非空元素
var end = lastIndex;
for (; end >= 0; end--) {
if (arr[end])
break;
}
// 假设没有空元素的情况,直接返回原来的数组
if (start === 0 && end === lastIndex)
return arr;
// 处理异常情况
if (start > end)
return [];
//返回非空的数组
return arr.slice(start, end + 1);
} // Regex to split a windows path into three parts: [*, device, slash,
// tail] windows-only
// 正則表達式。将windows的路径转化成三部分。文件夹。/ 或者尾部。
// 分析这个正則表達式,能够看出,其有三部分结果会输出matchs数组中:
// part1: ^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?
// 这部分说明的是开头部分,是以字符开头("D:")。或者以"//" "\\"开头
// 加上"/abc" 通常这部分会生成两个结果,一个就是盘符,一个就是盘符后
// 注意后面加上了()?,说明是尽可能少的匹配,也就是说遇到了"D://sda/"
//这种,直接返回"D:"
// part2: [\\\/])? 返回是否有/ 或者\
// part3: ([\s\S]*?)$ 第三部分就是随意的字符结尾的匹配
// 随意的字符串或者文件夹,会分成四部分,如文档所说 [*, device, slash,
// tail]
var splitDeviceRe =
/^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)? ([\\\/])?([\s\S]*?)$/; // Regex to split the tail part of the above into [*, dir, basename, ext]
// 分析上述正則表達式一样,将分成四部分为[*, dir, basename, ext]
var splitTailRe =
/^([\s\S]*?)((? :\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(? :[\\\/]*)$/; // 这个就是win32对象,导出的对象
var win32 = {}; // Function to split a filename into [root, dir, basename, ext]
// 帮助函数,用于分离字符串中的根,文件文件夹,文件名称。和后缀名
function win32SplitPath(filename) {
// Separate device+slash from tail
// 直接利用正則表達式。分析出device。也就是盘符 和盘符后面的尾
var result = splitDeviceRe.exec(filename),
device = (result[1] || '') + (result[2] || ''),
tail = result[3] || '';
// Split the tail into dir, basename and extension
// 然后依据尾巴。来分析文件文件夹。基本名和后缀
var result2 = splitTailRe.exec(tail),
dir = result2[1],
basename = result2[2],
ext = result2[3];
//返回一个数组,含有盘符,文件夹名,基本名和后缀
//比如:'C:\\path\\dir\\index.html'
//參数。会返回
//{
// root : "C:\\",
// dir : "C:\\path\\dir",
// base : "index.html",
// ext : ".html",
// name : "index"
// }
return [device, dir, basename, ext];
}
// 获取文件路径详细信息
function win32StatPath(path) {
// 和上述的函数一样。解析路径中的信息。 var result = splitDeviceRe.exec(path),
device = result[1] || '',
// 推断是否 为UNC path
isUnc = !!device && device[1] !== ':';
// 返回详细的对象,盘符,是否为统一路径,绝对路径, 以及结尾
return {
device: device,
isUnc: isUnc,
isAbsolute: isUnc || !!result[2], // UNC paths are always absolute
tail: result[3]
};
} // 帮助函数。将路径UNC路径标准化成\\pathname\\
function normalizeUNCRoot(device) {
return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
}

先分析API的一部分内容,也就是直接从文件路径中获取文件夹,根,后缀等信息。

// 获取文件文件夹
win32.dirname = function(path) {
// 这个直接利用的的上述的帮助函数。返回了一个数组
// [device, dir, basename, ext];
var result = win32SplitPath(path),
// 获取盘符
root = result[0],
// 获取文件文件夹
dir = result[1];
// 非法參数,直接返回当前文件夹:)
if (!root && !dir) {
// No dirname whatsoever
return '.';
}
// 去掉 '/'
if (dir) {
// It has a dirname, strip trailing slash
dir = dir.substr(0, dir.length - 1);
}
// 返回结果
return root + dir;
}; // 获取名字
win32.basename = function(path, ext) {
// 获取文件基本名
var f = win32SplitPath(path)[2];
// TODO: make this comparison case-insensitive on windows? //进一步去掉扩展。如,index.js 去掉js
if (ext && f.substr(-1 * ext.length) === ext) {
f = f.substr(0, f.length - ext.length);
}
// 返回基本名
return f;
}; // 获取后缀
win32.extname = function(path) {
// 直接使用帮助函数的返回数组。
return win32SplitPath(path)[3];
};
// 从path对象中获取完整路径,这个函数和parse全然相反
// pathObject 理论上含有例如以下属性
// root, dir, base, ext, name
win32.format = function(pathObject) {
// 第一步检查參数,确觉得Object对象
if (!util.isObject(pathObject)) {
throw new TypeError(
"Parameter 'pathObject' must be an object, not " + typeof pathObject
);
}
// 确定root
var root = pathObject.root || '';
// 确保root属性为string类型
if (!util.isString(root)) {
throw new TypeError(
"'pathObject.root' must be a string or undefined, not " +
typeof pathObject.root
);
}
// 确定文件文件夹
var dir = pathObject.dir;
// 确定base,事实上也就是文件名称
var base = pathObject.base || '';
if (!dir) {
// 没有路径的情况下,返回文件名称
return base;
}
// 确认文件夹后缀有切割符的情况下,直接生成返回
if (dir[dir.length - 1] === win32.sep) {
return dir + base;
}
// 确认文件夹后缀没有切割符的情况下。加上分隔符返回
return dir + win32.sep + base;
}; // 从pathString中得到详细的对象。和上面的format左右相反
//
win32.parse = function(pathString) {
// 检查详细的參数,确保參数为string
if (!util.isString(pathString)) {
throw new TypeError(
"Parameter 'pathString' must be a string, not " + typeof pathString
);
}
// 利用帮助函数返回数组信息 [device, dir, basename, ext];
var allParts = win32SplitPath(pathString);
// 确保參数为文件路径
if (!allParts || allParts.length !== 4) {
throw new TypeError("Invalid path '" + pathString + "'");
}
// 生成pathObject对象返回
return {
root: allParts[0],
dir: allParts[0] + allParts[1].slice(0, -1),
base: allParts[2],
ext: allParts[3],
name: allParts[2].slice(0, allParts[2].length - allParts[3].length)
};
}; // 分隔符
win32.sep = '\\';
// 定界符
win32.delimiter = ';';

从上面的函数能够看出。都是正則表達式字符串的处理为基础。正如API文档中所说。没有不论什么文件路径的验证。

以下来查看一下相对路径,绝对路径。路径组合相关的API的源代码:

// path.resolve([from ...], to)
// 从函数參数中生成绝对路径返回
// 从右往左參数依次检查是否可能组成绝对路径。假设是。返回
win32.resolve = function() {
//该參数表示找到的详细的盘符
var resolvedDevice = '',
//详细找到的绝对路径的尾部
resolvedTail = '',
//是否已经生成了绝对路径
resolvedAbsolute = false;
// 分析參数,从右往左,直到生成了一个绝对路径为止
for (var i = arguments.length - 1; i >= -1; i--) {
// 候选的參数
var path;
if (i >= 0) {
// 当前的參数路径
path = arguments[i];
} else if (!resolvedDevice) {
// 运行到此,说明未找到绝对的路径,使用当前的工作文件夹
path = process.cwd();
} else {
// Windows has the concept of drive-specific current working
// directories. If we've resolved a drive letter but not yet an
// absolute path, get cwd for that drive. We're sure the device is not
// an unc path at this points, because unc paths are always absolute.
// 说明已经是參数的最后都无法找到,获取当前盘符的工作路径作为备选
path = process.env['=' + resolvedDevice];
// Verify that a drive-local cwd was found and that it actually points
// to our drive. If not, default to the drive's root.
if (!path || path.substr(0, 3).toLowerCase() !==
resolvedDevice.toLowerCase() + '\\') {
path = resolvedDevice + '\\';
}
}
// 处理參数作为候选的,假设參数包括非字符串,直接报错。
// Skip empty and invalid entries
if (!util.isString(path)) {
throw new TypeError('Arguments to path.resolve must be strings');
} else if (!path) {
continue;
}
// 直接从当前的參数中获取路径的详细信息
var result = win32StatPath(path),
// 详细盘符
device = result.device,
// 是否为UNC文件夹
isUnc = result.isUnc,
// 是否为绝对路径
isAbsolute = result.isAbsolute,
// 尾路径
tail = result.tail; if (device &&
resolvedDevice &&
device.toLowerCase() !== resolvedDevice.toLowerCase()) {
// This path points to another device so it is not applicable
continue;
} if (!resolvedDevice) {
// 假设没有找到盘符。给定盘符
resolvedDevice = device;
}
// 假设还未给定绝对路径
if (!resolvedAbsolute) {
//尝试生成一个路径,注意是 tail 和已经resolvedTail向连接
resolvedTail = tail + '\\' + resolvedTail;
// 是否已经找到绝对的路径
resolvedAbsolute = isAbsolute;
}
// 随意时刻假设找到绝对路径,都跳出循环。
//
if (resolvedDevice && resolvedAbsolute) {
break;
}
} // Convert slashes to backslashes when `resolvedDevice` points to an UNC
// root. Also squash multiple slashes into a single one where appropriate.
if (isUnc) {
resolvedDevice = normalizeUNCRoot(resolvedDevice);
} // At this point the path should be resolved to a full absolute path,
// but handle relative paths to be safe (might happen when process.cwd()
// fails) // Normalize the tail path
// 标准化尾部路径。
resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/),
!resolvedAbsolute).join('\\');
//生成绝对路径,假设没找到的,直接以'.'返回。 return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) ||
'.';
}; // 标准化文件路径,主要解决 '.'和'..'相对路径问题。 win32.normalize = function(path) {
// 利用帮助函数获取文件路径的信息
var result = win32StatPath(path),
// 盘符
device = result.device,
// 是否为windows的UNC路径
isUnc = result.isUnc,
// 是否为绝对路径
isAbsolute = result.isAbsolute,
// 文件路径结尾
tail = result.tail,
// 尾部是否为'\' 或者 '/' 结尾。 trailingSlash = /[\\\/]$/.test(tail); // Normalize the tail path
//标准化tail路径。处理掉'.' '..' 以 '\' 连接
tail = normalizeArray(tail.split(/[\\\/]+/), !isAbsolute).join('\\');
// 处理tail为空的情况
if (!tail && !isAbsolute) {
tail = '.';
}
// 当原始路径中有slash时候。须要加上
if (tail && trailingSlash) {
tail += '\\';
} // Convert slashes to backslashes when `device` points to an UNC root.
// Also squash multiple slashes into a single one where appropriate.
// 处理windows UNC的情况。 if (isUnc) {
// 获取详细的路径,假设是UNC的情况
device = normalizeUNCRoot(device);
}
// 返回详细的路径
return device + (isAbsolute ? '\\' : '') + tail;
}; // 直接利用帮助函数来确定给定的路径是否为绝对路径。
win32.isAbsolute = function(path) {
return win32StatPath(path).isAbsolute;
}; // 组合出绝对路径
win32.join = function() {
//路径数组。用于存放函数參数
var paths = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
// 确保函数參数为字符串
if (!util.isString(arg)) {
throw new TypeError('Arguments to path.join must be strings');
}
if (arg) {
// 放入參数数组
paths.push(arg);
}
}
// 生成以back slash 连接的字符组,这个就是文件文件夹
var joined = paths.join('\\'); // Make sure that the joined path doesn't start with two slashes, because
// normalize() will mistake it for an UNC path then.
// 确保候选的合并路径不是以两个slash开头(UNC路径)。
// normalize() 函数对于UNC路径的处理不好
// This step is skipped when it is very clear that the user actually
// intended to point at an UNC path. This is assumed when the first
// non-empty string arguments starts with exactly two slashes followed by
// at least one more non-slash character.
//
// Note that for normalize() to treat a path as an UNC path it needs to
// have at least 2 components, so we don't filter for that here.
// This means that the user can use join to construct UNC paths from
// a server name and a share name; for example:
// path.join('//server', 'share') -> '\\\\server\\share\')
if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) {
joined = joined.replace(/^[\\\/]{2,}/, '\\');
}
// 利用标准化接口 获取详细的文件路径
return win32.normalize(joined);
}; // path.relative(from, to)
// it will solve the relative path from 'from' to 'to', for instance:
// from = 'C:\\orandea\\test\\aaa'
// to = 'C:\\orandea\\impl\\bbb'
// The output of the function should be: '..\\..\\impl\\bbb'
//解决相对路径问题。 假设从from和to的路径中,生成相对路径。 win32.relative = function(from, to) {
// 生成from的绝对路径
from = win32.resolve(from);
// 生成to的绝对路径
to = win32.resolve(to); // windows is not case sensitive
// 直接不区分大写和小写
var lowerFrom = from.toLowerCase();
var lowerTo = to.toLowerCase();
// 生成绝对路径数组
var toParts = trimArray(to.split('\\'));
var lowerFromParts = trimArray(lowerFrom.split('\\'));
var lowerToParts = trimArray(lowerTo.split('\\'));
// 获取路径数组较小长度
var length = Math.min(lowerFromParts.length, lowerToParts.length);
var samePartsLength = length;
// 获取路径中同样的部分
for (var i = 0; i < length; i++) {
if (lowerFromParts[i] !== lowerToParts[i]) {
samePartsLength = i;
break;
}
}
假设没有不论什么路径同样。就直接返回to
if (samePartsLength == 0) {
return to;
}
// 假设有同样的部分。就须要将剩余的部分以".."连接起来
var outputParts = [];
for (var i = samePartsLength; i < lowerFromParts.length; i++) {
outputParts.push('..');
}
// 连接详细的路径
outputParts = outputParts.concat(toParts.slice(samePartsLength));
// 生成Windows的路径 以 '\' 相连
return outputParts.join('\\');
};

从上述能够看出,相对路径,合成路径等都是字符串的处理。