Nodejs源代码分析之Path
今天介绍一下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('\\');
};
从上述能够看出,相对路径,合成路径等都是字符串的处理。
 
                     
                    
                 
                    
                
 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号