1. 程式人生 > >Nodejs源代碼分析之Path

Nodejs源代碼分析之Path

can roo 字符串 spl ret -c eth oev +=

今天介紹一下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(‘\\‘); };

從上述能夠看出,相對路徑,合成路徑等都是字符串的處理。

Nodejs源代碼分析之Path