nodejs中的文件操作的基础,path模块(三)

继续之前没有说完的几个方法和属性。

path模块的方法和属性

1:dirname方法

dirname属性是用于获取一个路径中的目录名,它使用一个字符串参数,可以是相对路径,绝对就,可以是目录的路径,也可以是文件的路径,当参数值是文件路径时,返回的是该目录上册目录,当参数值是文件路径时,返回的是该文件所在的目录。

其实,也就是把传入的目录,去掉最后一个"/a"即可,这里的a是一个泛指,表示一个目录或者一个文件。

使用方法及示例如下:


path.dirname(p);

//我就把使用数量一起写在这里了,依然按照REPL模式下的方式给出示例
>var path = require("path");
undefined
>path.dirname("/fs/a/b");
"/fs/a"
>path.dirname("fs/a/b.txt");
"/fs/a"
>path.dirname()
"."
>path.dirname("");
"."
>path.dirname("//a/b");
"//a/b"
>path.dirname("/a/b");
"/a"


注意:从上面的示例中,可以看出,当路径协议是UNC模式时(即“//a/b”样式,不太知道的,请百度之),该方法直接返回输入的值,这个时候的输入值,为何是全值输出了呢?

看完上面的示例,看下源码中的逻辑,来验证,为何会有上述的一些处理:


var splitTailRe =
    /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/;
    //该正则表达式,是为了能把路径的各个部分进行分离
    //([\s\S]*?),匹配任意字符,尽量短的匹配,比如“aa”的话,则该段只匹配第一个a,
    //((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|)),可以分解为两个
    //(?:\.{1,2}|[^\\\/]+?|),该方法,匹配但是不会捕获到该子元素
    //以一个或者两个".",或者非斜杠的至少一个字符串(最短匹配),或者匹配空值
    //该方法,就会捕获最后的目录名,或者最后文件名一级的元素。
    //比如:"/a/b/c",就会匹配为c,在"a/b/c.txt"中,就会匹配"c.txt"
    //(\.[^.\/\\]*|),捕获,文件后缀,比如".txt",".png"等样子的元素
    //(?:[\\\/]*),匹配但是不会被捕获,匹配任意多个正反斜杠。

var win32 = {};

// Function to split a filename into [root, dir, basename, ext]
function win32SplitPath(filename) {
  // Separate device+slash from tail
  var result = splitDeviceRe.exec(filename),
      device = (result[1] || '') + (result[2] || ''),
      tail = result[3] || '';
  // Split the tail into dir, basename and extension
  //splitTailRe正则表达式,只是对tail的值,进行重新匹配,
  //而在"//a/b"中,splitDeviceRe匹配之后,device=//a/b,而tail为空,
  //所以,在上述的示例中,才有path.dirname("//a/b");结果为"//a/b"的情况
  var result2 = splitTailRe.exec(tail),
      dir = result2[1],
      basename = result2[2],
      ext = result2[3];
      //使用splitTailRe表达式,匹配一个路径的各元素,并把
      //并把返回的数据,组成一个数组。
  return [device, dir, basename, ext];
}

win32.dirname = function(path) {
  var result = win32SplitPath(path),
      root = result[0],
      dir = result[1];

    //root等于匹配中的divice,一般格式为:"C:","//a/b"的格式
    //dir为去掉path最后一个/[^\\\/]之后,其他的元素
    //如果root为空,并且只有一个有效目录,则返回"."
    //这也是path.dirname("");path.dirname(),path.dirname("a")返回"."的原因
  if (!root && !dir) {
    // No dirname whatsoever
    return '.';
  }

  if (dir) {
    // It has a dirname, strip trailing slash
    dir = dir.substr(0, dir.length - 1);
    //如果dir存在,则因为正则匹配之后,会保留最后的斜杠,该处是去除该斜杠
    //比如,"/a/b/c"匹配后,dir的值为"/a/b/",使用substr后,变为"a/b"
  }

  //返回值
  return root + dir;
};


2:basename方法

该方法,是用于获取一个路径中的文件名,使用两个参数,其中第一个参数为必须指定参数,第二个参数可选。

第一个参数必须是一个文件的完成路径,可以是相对路径,也可以是绝对路径

第二个参数,用于去除指定的扩展名,该方法必须以”.”开头,否则不能把扩展名去掉,而是修改了扩展名。

使用方法和示例如下:


path.basename(p,ext);

//示例依然按照REPL的格式写:
>var path = require("path");
undefined
>path.basename("/a/b/c.txt");
".txt"
>path.basename("/a/b/c.txt",".txt");
"c"
>path.basename("/a/b/c.txt","txt");
"c."


这个使用方法,也是很简单的那种,那么继续看下源码中是如何处理的


win32.basename = function(path, ext) {
  var f = win32SplitPath(path)[2];
  //请参考,win32SplitPath函数中返回数组的第三个值
  // TODO: make this comparison case-insensitive on windows?
  if (ext && f.substr(-1 * ext.length) === ext) {
    //判断,如果需要去掉扩展名,如果扩展名存在,并且与准备要返回的值的最后几位相同
	//则去掉这个扩展名。
    f = f.substr(0, f.length - ext.length);
  }
  return f;
};


3:extname方法

该方法,用于获取一个路径中的扩展名,支持一个参数,该参数必须为一个文件的完整路径,可以是相对路径,也可以是绝对路径。返回的扩展名以”.”开头,如果没有扩展名,则返回空字符串。

使用方法和源码分析,这里不做示例了:


//使用方法
path.extname(p);

//源码分析
win32.extname = function(path) {
  return win32SplitPath(path)[3];
  //没有什么好分析的,请参考前面dirname源码部分的win32SplitPath源码分析部分
};


4:parse方法

该方法,是对有一个输入的字符串,把其各个部分分离出来,比如,把上面提到的盘符(device),目录名(dirname方法的返回值),路径的文件名(basename方法的返回值),扩展名(extname的返回值)等,相关信息,保存到一个标准的对象中去。

使用方法为,及示例如下:


path.parse(p);

//REPL模式下,测试示例:
>var path = require("path");
undefined
>path.parse("C:/a/b/c.txt");
{
   root:"C:/",
   dir:"C:/a/b",//dirname的返回值
   base:"c.txt",//basename的返回值
   ext:".txt",//extname的返回值
   name:"c"
}


关于这个的示例,知道即可,现在看下源码吧,其实想想也可以想象的到的,因为只要把之前dirnamebasenameextname的源码,稍微组合一下,既可以得parse方法的源码了


win32.parse = function(pathString) {
  //首先判断,输入的伪字符串,如果不为字符串,则抛出一个类型错误
  if (!util.isString(pathString)) {
    throw new TypeError(
        "Parameter 'pathString' must be a string, not " + typeof pathString
    );
  }
  var allParts = win32SplitPath(pathString);
  //分解该路径,分解出device,div,basename,extname的数组
  if (!allParts || allParts.length !== 4) {
    //分解之后返回的是一个长度为4的数组,如果不是这个,则抛出一个类型异常
    throw new TypeError("Invalid path '" + pathString + "'");
  }
  
  //根据返回的数组,拼接出,想要的各个元素的值
  return {
    root: allParts[0],
    dir: allParts[0] + allParts[1].slice(0, -1),
    //这里,因为win32SplitPath返回的dir,不包含盘符了,所以这里拼接一起
    base: allParts[2],
    ext: allParts[3],
    name: allParts[2].slice(0, allParts[2].length - allParts[3].length)
    //去除扩展名~~类似于path.basename中,定义了第二个参数
  };
};


5:format方法

该方法返回一个标准化之后的路径,其传入一个参数,该参数是一个对象,并且该对象的格式,和parse返回的对象时基本一致的。

返回值为一个标准化之后的字符串串,个人感觉,和normalize方法的效果一样的,没有碰到具体情况,所以还不知道具体会用在哪里,因为,它输入的对象,有太多的要求,不太适合使用。

使用方法及示例:


path.format(object);

//REPL模式下的测试:
>var path = require("path");
undefined
>obj = path.parse("C:/a/b/c.txt");
{
   root:"C:/",
   dir:"C:/a/b",
   base:"c.txt",
   ext:".txt",
   name:"c"
}
>path.format(obj);
"C:a/b\\c.txt"


这也算是标准化吗?这也看来,也许这个属性,是在很早的版本中,使用的,因为这个属性,现在和normalizeresolve方法比,都很大的不如,也许只是为了兼容以前的Nodejs版本吧(猜测),因为实在是想不到该方法存在的意义。

看下源码时如何处理的:


win32.format = function(pathObject) {
  //可以认为,该方法传入的参数,其实就是parse方法中返回的对象。
  if (!util.isObject(pathObject)) {
    //传入的参数值,必须为一个对象,如果不为一个对象,则抛出一个类型错误
    throw new TypeError(
        "Parameter 'pathObject' must be an object, not " + typeof pathObject
    );
  }

  var root = pathObject.root || '';
  //该对象中,可能包含一个名为root的属性,如果有该属性,则该属性必须为字符串
  //否则,抛出一个类型错误
  if (!util.isString(root)) {
    throw new TypeError(
        "'pathObject.root' must be a string or undefined, not " +
        typeof pathObject.root
    );
  }

  var dir = pathObject.dir;
  var base = pathObject.base || '';
  
  //判断,进行输出,下面的三个判断,实质上,是相同的
  //都是为了输出一个格式的dir和base组成的字符串。
  if (!dir) {
    return base;
  }
  if (dir[dir.length - 1] === win32.sep) {
    return dir + base;
    //这时,是因为dir中的末尾,已经包含了win32.sep,所以,不需要再添加了
  }
  return dir + win32.sep + base;
};


6:sep和delimiter属性

sepdelimiter是两个属性,分别被定义为:


posix.sep = '/';
posix.delimiter = ':';


sep表示为操作系统文件分隔符

delimiter表示为操作系统的指定的路径分隔符。

其中,sep只在format方法中使用了一次,其他方法,都没有该属性的使用,而delimiter属性,在整个源码中都没有使用,所以,不明白,为什么会有着两个属性。

难道是在其他一些模块中,需要使用这两个属性?这个,我现在也只能猜测,以待以后的验证。

7:win32和posix对象

可能您也注意到了一点,上面的所有的属性,都是定义在win32对象下,那么关于win32posix有很么区别呢?

其实,主要是在window系统,和linux系统的区别而已,在window系统下的方法,是使用win32对象的属性和方法,在linux系统下,path模块的方法,会使用posix方法,既然分别写了一套方法,那么就代表着,各个方法的实现,在细节上还是有差距的,个人不懂linux系统,所以这里就不对posix对象的属性和方法进行说明。

看下在path.js中,是如何来实现在不同的系统下,加载不同的方法的


var isWindows = process.platform === 'win32';

//根据系统,分别定义一个文件对外公开的exports对象
if (isWindows)
  module.exports = win32;
else /* posix */
  module.exports = posix;

module.exports.posix = posix;
module.exports.win32 = win32;


关于对外公开的对象exports,可以参考:Nodejs中的module简介

概总

path模块在文件操作部分,是属于一个很重要且基础的模块,所以,在进入fs文件操作模块之前,先看下关于path模块的一些属性和方法,对于之后的学习,是很有帮助的,继续努力

本篇内容主要参考自:“Nodejs”源码和“Nodejs权威指南”

本文地址:http://www.zhangyunling.com/?p=414

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>