中的神器,Node仿Tree指定层级输出树形文件目录结

浅谈 JavaScript 管理树形结构的几个情景与方案

2015/06/18 · CSS · 树形结构

原作出处: 工业聚(@工业聚)   

前言

node.js下when.js 的异步编制程序实施,node.jswhen.js

若果一个事情场景:

通过rss地址,获取rss并保留于文件,rss地址保存于文件中。

完毕这一场合包车型客车作业供给做到3个职分:

1.从文件中读取rss地址。

2.获取rss。

3.保存于文件。

最后将那多个职分举行结合。

准备:

贮存rss地址的公文,address.txt。


 
任务1:

读取rss地址文件的内容并透过callback再次回到。

复制代码 代码如下:

var getRssAddress = function(path, callback) {
  fs.readFile(path, {encoding: 'utf8'}, function (err, data) {
    callback(err, data);
  });
}

任务2:

 通过rss地址get到rss,并因此callback再次回到错误或数量。

复制代码 代码如下:

var getRss = function(url, callback) {
  var data = '';
  http.get(url, function(res) {
    res.on('data', function(chrunk) {
      data = chrunk;
    });
    res.on('end', function() {
      callback(null, data);
    });
  }).on('error', function(err) {
    callback(err, null);
  });
}

 

任务3:

将rss保存于文件并经过callback重临错误。

复制代码 代码如下:

var saveRss = function(data, callback) {
  fs.writeFile('rss.txt', data, 'utf8', function(err) {
    callback(err);
  });
}

整合:

复制代码 代码如下:

getRssAddress('address.txt', function(err, data) {
  if(err) {
    console.log(err);
    return;
  }
  getRss(data, function(err, data) {
    if(err) {
      console.log(err);
      return;
    }
    saveRss(data, function(err) {
      if(err) console.log(err);
    });
  });
});

地方的代码是全异步管理,使用最普及的callback管理异步逻辑的归来,好处是职业写法,大家都能轻便采用;坏处是耦合性太强,管理特别麻烦,代码不直观,特别是拍卖事务逻辑复杂和拍卖职务多的现象,层层的callback会令人眼冒水星,代码难以保险。

Promise/A标准的贯彻之风华正茂when.js就是针对那样的难点域。

让大家来看一下改换后的代码。

任务1:

复制代码 代码如下:

var getRssAddress = function(path) {
    var deferred = when.defer();
      fs.readFile(path, {encoding: 'utf8'}, function (err, data) {
        if (err) deferred.reject(err);
        deferred.resolve(data);
      });

    return deferred.promise;
}

 
任务2:

复制代码 代码如下:

var getRss = function(url) {
  var deferred = when.defer();
    var data = '';
    http.get(url, function(res) {
      res.on('data', function(chrunk) {
        data = chrunk;
      });
      res.on('end', function() {
        deferred.resolve(data);
      });
    }).on('error', function(err) {
      deferred.reject(err);
    });

    return deferred.promise;
}

任务3:

复制代码 代码如下:

var saveRss = function(data) {
  var deferred = when.defer();
  fs.writeFile('rss.txt', data, 'utf8', function(err) {
    if(err) deferred.reject(err);
    deferred.resolve();
  });

  return deferred.promise;
}

 

整合:

复制代码 代码如下:

getRssAddress('address.txt')
  .then(getRss)
  .then(saveRss)
  .catch(function(err) {
    console.log(err);
  });

解释:

promise/A标准定义的“Deferred/Promise”模型正是“揭橥/订阅者”模型,通过Deferred对象公布事件,能够是水到渠成resolve事件,只怕是失败reject事件;通过Promise对象开展对应做到或倒闭的订阅。

在Promises/A标准中,每一种义务都有两种境况:暗许(pending)、达成(fulfilled)、失败(rejected)。

1.暗许状态能够单方面转移到变成情况,这一个历程叫resolve,对应的措施是deferred.resolve(promiseOrValue);

2.暗中认可状态还能大器晚成边转移到倒闭状态,那个历程叫reject,对应的法子是deferred.reject(reason);

3.私下认可状态时,还足以由此deferred.notify(update)来发表职分执行音信,如进行进程;

4.场所包车型大巴改变是一遍性的,大器晚成旦职务由最早的pending转为其余情况,就能够进去到下多少个职分的实施进程中。

依据地点的代码。

由此when.defer定义三个deferred对象。

var deferred = when.defer();
异步数据得到成功后,公布四个产生事件。

deferred.resolve(data);
异步数据获得败北后,发表三个功亏意气风发篑事件。

deferred.reject(err);
再正是重返Promise对象作为订阅使用。

return deferred.promise;
订阅是通过Promise对象的then方法开展达成/战败/文告的订阅。

getRssAddress('address.txt')
  .then(getRss)
then有三个参数,分别是onFulfilled、onRejected、onProgress

promise.then(onFulfilled, onRejected, onProgress)
上三个职务被resolve(data),onFulfilled函数就能够被触发,data作为它的参数.

上多个职务被reject(reason),那么onRejected就能被触发,收到reason。

其余时候,onFulfilled和onRejected都唯有其生龙活虎能够被触发,何况只触发叁回。

对于拍卖非常,when.js也提供了可是方便的不二法门,then能传递错误,七个职务串行试行时,大家得以只在最终三个then定义onRejected。也足以在最后叁个then的后面调用catch函数捕获任何四个任务的那七个。

与上述同类写法简单明了。

复制代码 代码如下:

getRssAddress('address.txt')
  .then(getRss)
  .then(saveRss)
  .catch(function(err) {
    console.log(err);
  });

Promise给异步编制程序带来了光辉的方便人民群众,能够让大家注意于单个职务的落到实处而不会沦为金字塔厄运,以上代码仅仅是主导选用,when.js提供的据守远远不仅仅本文提到的这个,具体参照官方API。

的异步编制程序实施,node.jswhen.js 借使三个业务场景: 通过rss地址,获取rss并保留于文件,rss地址保存于文件中。 实现该地方的...

姓名:岳沁

前言

前些天,Mac 下闻名软件 Homebrew 的小编,因为没解出来二叉树翻转的白板算法题,境遇 Google拒绝,继而引发Twitter热议。

在 JavaScript 中也许有无数树形结构。例如 DOM 树,省市区地方联合浮动,文件目录等; JSON 自己正是树形结构。

众多前端面试题也跟树形结构的关于,比方在浏览器端写遍历 DOM 树的函数,比如在 nodejs 运维时遍历文件目录等。

此间演示用 JavaScript 遍历树形结构的二种政策。

这段时光空余时间蛮少,后来专门腾出上午的流年来支付和睦的玩意儿。 今天讲的是为玩具所支付的八个小模块的一个效果与利益。 具体来讲它是贰个仿Tree命令能够罗列给定目录的树形结构。何况本人的做法是支付二个命令行工具,但那边先不提,大家就留心那一个目录列表效率。 须求输出相符的布局。大家先来看下要求达到的职能

学号:17101223458

场景1:遍历 DOM 树

图片 1

转载自:

方案1:递归情势

JavaScript

function walkDom(node, callback) { if (node === null) { //判别node是还是不是为null return } callback(node) //将node本人传入callback node = node.firstElementChild //退换node为其子成分节点 while (node) { walkDom(node, callback) //假若存在子成分,则递归调用walkDom node = node.nextElementSibling //自始至终遍历成分节点 } } walkDom(document, function(node) {console.count()}) //包括document节点 document.querySelectorAll('*').length //数量比地方输出的少1,因为不分包document节点

1
2
3
4
5
6
7
8
9
10
11
12
13
function walkDom(node, callback) {
    if (node === null) { //判断node是否为null
        return
    }
    callback(node) //将node自身传入callback
    node = node.firstElementChild //改变node为其子元素节点
    while (node) {
        walkDom(node, callback) //如果存在子元素,则递归调用walkDom
        node = node.nextElementSibling //从头到尾遍历元素节点
    }
}
walkDom(document, function(node) {console.count()}) //包含document节点
document.querySelectorAll('*').length //数量比上面输出的少1,因为不包含document节点

将上述代码黏贴到任意页面包车型客车决定台 console 中实践。

正文

【嵌牛导读】:ES6 原生提供了 Promise 对象。

方案2:循环方式

JavaScript

function walkDom(node, callback) { if (node === null) { return } var stack = [node] //存入数组 var target while(stack.length) { //数主任度不为0,继续循环 target = stack.shift() //抽出元素callback(target) //传入callback Array.prototype.push.apply(stack, target.children) //将其子元素一股脑推入stack,扩张长度 } } walkDom(document, function(node) {console.count()}) //富含document节点 document.querySelectorAll('*').length //数量比上边输出的少1,因为不带有document节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function walkDom(node, callback) {
    if (node === null) {
        return
    }
    var stack = [node] //存入数组
    var target
    while(stack.length) { //数组长度不为0,继续循环
        target = stack.shift() //取出元素
        callback(target) //传入callback
        Array.prototype.push.apply(stack, target.children) //将其子元素一股脑推入stack,增加长度
    }
}
walkDom(document, function(node) {console.count()}) //包含document节点
document.querySelectorAll('*').length //数量比上面输出的少1,因为不包含document节点

在循环形式中,shift方法能够换来pop,从后面部分抽出成分;push方法能够换到unshift从尾部添美金素。分裂的相继,影响了是「广度优先」照旧「深度优先」。

我们先来搜求一下如何赢得目录结构。因为我们最终需求的是给路径->获得目录结构->数据管理(输出/别的操作)

所谓 Promise,就是二个对象,用来传递异步操作的音信。它象征了某些现在才会掌握结果的风云(平时是三个异步操作卡塔 尔(阿拉伯语:قطر‎,何况这么些事件提供统生机勃勃的 API,可供进一层管理。

场景2:在 nodejs 运维时里遍历文件目录

在Node里有那样几个API

【嵌牛鼻子】:Promise

子场景1:同步模式

fs.readdir(path, callback)

【嵌牛提问】:如何加强Promise功能?

方案1:递归

JavaScript

var fs = require('fs') var Path = require('path') function readdirs(path) { var result = { //构造文件夹数据 path: path, name: Path.basename(path), type: 'directory' } var files = fs.readdirSync(path) //拿到文件目录下的兼具文件名 result.children = files.map(function(file) { var subPath = Path.resolve(path, file) //拼接为绝对路线 var stats = fs.statSync(subPath) //得到文件音讯目的 if (stats.isDirectory()) { //判别是还是不是为文件夹类型 return readdirs(subPath) //递归读取文件夹 } return { //构造文件数量 path: subPath, name: file, type: 'file' } }) return result //再次来到数据 } var cwd = process.cwd() var tree = readdirs(cwd) fs.writeFileSync(Path.join(cwd, 'tree.json'), JSON.stringify(tree)) //保存在tree.json中,去查看吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var fs = require('fs')
var Path = require('path')
 
function readdirs(path) {
    var result = { //构造文件夹数据
        path: path,
        name: Path.basename(path),
        type: 'directory'
    }
    var files = fs.readdirSync(path) //拿到文件目录下的所有文件名
    result.children = files.map(function(file) {
        var subPath = Path.resolve(path, file) //拼接为绝对路径
        var stats = fs.statSync(subPath) //拿到文件信息对象
        if (stats.isDirectory()) { //判断是否为文件夹类型
            return readdirs(subPath) //递归读取文件夹
        }
        return { //构造文件数据
            path: subPath,
            name: file,
            type: 'file'
        }
    })
    return result //返回数据
}
 
var cwd = process.cwd()
var tree = readdirs(cwd)
fs.writeFileSync(Path.join(cwd, 'tree.json'), JSON.stringify(tree)) //保存在tree.json中,去查看吧

将地方的代码保存在 tree.js 中,然后在脚下文件夹张开命令行,输入node tree.js,目录音讯保存在生成tree.json文件中。

该措施是 readdir(3) 的异步试行版本,用于读取三个索引的源委。callback 选取五个参数 (err, files),当中 files 是多少个数组,数组成员为当前目录下的文本名,不含有 . 和 ..。

【嵌牛正文】:

方案2:循环

JavaScript

var fs = require('fs') var Path = require('path') function readdirs(path) { var result = { //构造文件夹数据 path: path, name: Path.basename(path), type: 'directory' } var stack = [result] //生成一个栈数组 while (stack.length) { //假若数组不为空,读取children var target = stack.pop() //收取文件夹对象 var files = fs.readdirSync(target.path) //拿到文件名数组 target.children = files.map(function(file) { var subPath = Path.resolve(target.path, file) //转变为相对路线 var stats = fs.statSync(subPath) //得到文件新闻目的 var model = { //构造文件数据结构 path: subPath, name: file, type: stats.isDirectory() ? 'directory' : 'file' } if (model.type === 'directory') { stack.push(model) //假若是文本夹,推入栈 } return model //重临数据模型 }) } return result //重返整个数据结果 } var cwd = process.cwd() var tree = readdirs(cwd) fs.writeFileSync(Path.join(cwd, 'tree.json'), JSON.stringify(tree)) //保存在tree.json中,去查看吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var fs = require('fs')
var Path = require('path')
 
function readdirs(path) {
    var result = { //构造文件夹数据
        path: path,
        name: Path.basename(path),
        type: 'directory'
    }
    var stack = [result] //生成一个栈数组
    while (stack.length) { //如果数组不为空,读取children
        var target = stack.pop() //取出文件夹对象
        var files = fs.readdirSync(target.path) //拿到文件名数组
        target.children = files.map(function(file) {
            var subPath = Path.resolve(target.path, file) //转化为绝对路径
            var stats = fs.statSync(subPath) //拿到文件信息对象
            var model = { //构造文件数据结构
                path: subPath,
                name: file,
                type: stats.isDirectory() ? 'directory' : 'file'
            }
            if (model.type === 'directory') {
                stack.push(model) //如果是文件夹,推入栈
            }
            return model //返回数据模型
        })
    }
    return result //返回整个数据结果
}
 
var cwd = process.cwd()
var tree = readdirs(cwd)
fs.writeFileSync(Path.join(cwd, 'tree.json'), JSON.stringify(tree)) //保存在tree.json中,去查看吧

循环战术中的pop跟shift,push跟unshift也得以沟通以调动优先级,甚至用能够用splice方法更小巧的主宰stack数组。循环格局比递归格局更可控。

fs.readdirSync(path)

Promise in js

子场景2:异步形式

该格局是 readdir(3) 的联合实践版本,再次来到五个不带有 . 和 .. 的文书名数组。

回调函数真正的标题在于他剥夺了作者们利用 return 和 throw 那几个重大字的力量。而 Promise 很好地消逝了那全体。

方案1:过程式 Promise

JavaScript

var fs = require('fs') var Path = require('path') //promise包装的fs.stat方法 var stat = function(path) { return new Promise(function(resolve, reject) { fs.stat(path, function(err, stats) { err ? reject(err) : resolve(stats) }) }) } //promise包装的fs.readdir方法 var readdir = function(path) { return new Promise(function(resolve, reject) { fs.readdir(path, function(err, files) { err ? reject(err) : resolve(files) }) }) } //promise包装的fs.writeFile var writeFile = function(path, data) { return new Promise(function(resolve, reject) { fs.writeFile(path, JSON.stringify(data || ''), function(err) { err ? reject(err) : resolve }) }) } function readdirs(path) { return readdir(path) //异步读取文件夹 .then(function(files) { //拿到文件名列表 var promiseList = files.map(function(file) { //遍历列表 var subPath = 帕特h.resolve(path, file) //拼接为相对路线 return stat(subPath) //异步读取文件信息 .then(function(stats) { //获得文件消息//是文件夹类型的,继续读取目录,不然重临数据 return stats.isDirectory() ? readdirs(subPath) : { path: subPath, name: file, type: 'file' } }) }) return Promise.all(promiseList) //等待全部promise完毕 }) .then(function(children) { //获得含有全数数据的children数组 return { //再次回到结果 path: path, name: 帕特h.basename(path), type: 'directory', children: children } }) } var cwd = process.cwd() readdirs(cwd) .then(writeFile.bind(null, Path.join(cwd, 'tree.json'))) //保存在tree.json中,去查看吧 .catch(console.error.bind(console)) //出错了就输出错误日志查看原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
var fs = require('fs')
var Path = require('path')
//promise包装的fs.stat方法
var stat = function(path) {
    return new Promise(function(resolve, reject) {
        fs.stat(path, function(err, stats) {
            err ? reject(err) : resolve(stats)
        })
    })
}
//promise包装的fs.readdir方法
var readdir = function(path) {
    return new Promise(function(resolve, reject) {
        fs.readdir(path, function(err, files) {
            err ? reject(err) : resolve(files)
        })
    })
}
//promise包装的fs.writeFile
var writeFile = function(path, data) {
    return new Promise(function(resolve, reject) {
        fs.writeFile(path, JSON.stringify(data || ''), function(err) {
            err ? reject(err) : resolve
        })
    })
}
 
function readdirs(path) {
    return readdir(path) //异步读取文件夹
    .then(function(files) { //拿到文件名列表
        var promiseList = files.map(function(file) { //遍历列表
            var subPath = Path.resolve(path, file) //拼接为绝对路径
            return stat(subPath) //异步读取文件信息
            .then(function(stats) { //拿到文件信息
                //是文件夹类型的,继续读取目录,否则返回数据
                return stats.isDirectory() ?
                readdirs(subPath) : {
                    path: subPath,
                    name: file,
                    type: 'file'
                }
            })
        })
        return Promise.all(promiseList) //等待所有promise完成
    })
    .then(function(children) { //拿到包含所有数据的children数组
        return { //返回结果
            path: path,
            name: Path.basename(path),
            type: 'directory',
            children: children
        }
    })
}
 
var cwd = process.cwd()
 
readdirs(cwd)
.then(writeFile.bind(null, Path.join(cwd, 'tree.json'))) //保存在tree.json中,去查看吧
.catch(console.error.bind(console)) //出错了就输出错误日志查看原因

地点的函数都能专业,但都是七个个function的调用,显得太「进度式」;

能否用面向对象的艺术来写吗?

本来能够。

其实面向对象的写法,更显然。

为了进一层语义化,以至增显逼格。

我们用 ES6 的 class 来写这一个树形结构类。

...这里就列八个,别的的请自行去官方网站查API0-0

2015 年 6 月,ECMAScript 6 的正经版终于公布了。

方案2:ES6-class ES6-Promise

JavaScript

import fs from 'fs' import {join, resolve, isAbsolute, basename, extname, dirname, sep} from 'path' /** * 获取目录下的有所文件 * @param {string} path * @return {promise} resolve files || reject error */ let readdir = (path) => { return new Promise((resolve, reject) => { fs.readdir(path, (err, files) => { err ? reject(err) : resolve(files) }) }) } /** * 将data写入文件 * @param {string} path 路径 * @param {data} data * @return {promise} resolve path || reject error */ let writeFile = (path, data) => { return new Promise((resolve, reject) => { fs.writeFile(path, data, (err) => { err ? reject(err) : resolve(path) }) }) } /** * 获取文件属性 * @param {string} path * @return {promise} resolve stats || reject error */ let stat = (path) => { return new Promise((resolve, reject) => { fs.stat(path, (err, stats) => { err ? reject(err) : resolve(stats) }) }) } /** * 推断path是或不是存在 * @param {string} path 路径 * @return {promise} resolve exists */ let exists = (path) => { return new Promise((resolve) => fs.exists(path, resolve)) } //文档类 class Document { constructor(path) { this.path = path this.name = basename(path) } //存在性判定 exists() { return exists(this.path) } //异步获取文件消息 stat() { return stat(this.path) } //输出骨干数据 json() { return JSON.stringify(this) } //将基本数据保存在path路线的文书中 saveTo(path) { if (isAbsolute(path)) { return writeFile(path, this.json()) } return writeFile(resolve(this.path, path), this.json()) } } //文件类,世襲自文书档案类 class File extends Document { constructor(path) { super(path) //必需先调用超类构造方法 this.type = 'file' //type 为 file this.extname = extname(path) //新扩张扩大名 } //写入数据 write(data = '') { return writeFile(this.path, data) } //别的文件特有一点子如 read unlink 等 } //文件夹类,世襲自文书档案类 class Directory extends Document { constructor(path) { super(path) //必需先调用超类构造方法 this.type = 'directory' } //读取当前文件夹 readdir() { return readdir(this.path) //读取目录 .then((files) => { //获得文件名列表 let promiseList = files.map((file) => { let subPath = resolve(this.path, file) //拼接为相对路线 return stat(subPath) //获取文件新闻 .then((stats) => { //依照文件消息,归类为Directory或File类型 return stats.isDirectory() ? new Directory(subPath) : new File(subPath) }) }) return Promise.all(promiseList) }) .then((children) => { //获得children数组 this.children = children //保存children属性 return children //再次回到children }) } //深度读取文件目录 readdirs() { return this.readdir() //读取当前文件夹 .then((children) => { //获得children let promiseList = [] children.map((child) => { if (child instanceof Directory) { //是文件夹实例,继续深度读取文件目录 promiseList.push(child.readdirs()) } }) return Promise.all(promiseList) //等待全数子成分深度读取目录达成 }) .then(() => this) //重返this } //别的文件夹特有方法如 addFile removeFile addDir remveDir 等 } let cwd = process.cwd() new Directory(cwd) .readdirs() .then((tree) => { tree.saveTo('tree.json') //让它和煦保留在tree.json里 }) .catch(console.error.bind(console)) //输出荒谬日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import fs from 'fs'
import {join, resolve, isAbsolute, basename, extname, dirname, sep} from 'path'
 
/**
* 获取目录下的所有文件
* @param {string} path
* @return {promise} resolve files || reject error
*/
let readdir = (path) => {
    return new Promise((resolve, reject) => {
        fs.readdir(path, (err, files) => {
            err ? reject(err) : resolve(files)
        })
    })
}
 
/**
* 将data写入文件
* @param {string} path 路径
* @param {data} data
* @return {promise} resolve path || reject error
*/
let writeFile = (path, data) => {
    return new Promise((resolve, reject) => {
        fs.writeFile(path, data, (err) => {
            err ? reject(err) : resolve(path)
        })
    })
}
 
/**
* 获取文件属性
* @param {string} path
* @return {promise} resolve stats || reject error
*/
let stat = (path) => {
    return new Promise((resolve, reject) => {
        fs.stat(path, (err, stats) => {
            err ? reject(err) : resolve(stats)
        })
    })
}
 
/**
* 判断path是否存在
* @param {string} path 路径
* @return {promise} resolve exists
*/
let exists = (path) => {
    return new Promise((resolve) => fs.exists(path, resolve))
}
 
//文档类
class Document {
    constructor(path) {
        this.path = path
        this.name = basename(path)
    }
    //存在性判断
    exists() {
        return exists(this.path)
    }
    //异步获取文件信息
    stat() {
        return stat(this.path)
    }
    //输出基本数据
    json() {
        return JSON.stringify(this)
    }
    //将基本数据保存在path路径的文件中
    saveTo(path) {
        if (isAbsolute(path)) {
            return writeFile(path, this.json())
        }
        return writeFile(resolve(this.path, path), this.json())
    }
}
 
//文件类,继承自文档类
class File extends Document {
    constructor(path) {
        super(path) //必须先调用超类构造方法
        this.type = 'file' //type 为 file
        this.extname = extname(path) //新增扩展名
    }
    //写入数据
    write(data = '') {
        return writeFile(this.path, data)
    }
    //其他文件特有方法如 read unlink 等
}
 
//文件夹类,继承自文档类
class Directory extends Document {
    constructor(path) {
        super(path) //必须先调用超类构造方法
        this.type = 'directory'
    }
    //读取当前文件夹
    readdir() {
        return readdir(this.path) //读取目录
        .then((files) => { //拿到文件名列表
            let promiseList = files.map((file) => {
                let subPath = resolve(this.path, file) //拼接为绝对路径
                return stat(subPath) //获取文件信息
                .then((stats) => {
                    //根据文件信息,归类为Directory或File类型
                    return stats.isDirectory() ?
                    new Directory(subPath) :
                    new File(subPath)
                })
            })
            return Promise.all(promiseList)
        })
        .then((children) => { //拿到children数组
            this.children = children //保存children属性
            return children //返回children
        })
    }
    //深度读取文件目录
    readdirs() {
        return this.readdir() //读取当前文件夹
        .then((children) => { //拿到children
            let promiseList = []
            children.map((child) => {
                if (child instanceof Directory) { //是文件夹实例,继续深度读取文件目录
                    promiseList.push(child.readdirs())
                }
            })
            return Promise.all(promiseList) //等待所有子元素深度读取目录完毕
        })
        .then(() => this) //返回this
    }
    //其他文件夹特有方法如 addFile removeFile addDir remveDir 等
}
 
let cwd = process.cwd()
 
new Directory(cwd)
.readdirs()
.then((tree) => {
    tree.saveTo('tree.json') //让它自己保存在tree.json里
})
.catch(console.error.bind(console)) //输出错误日志

因为这段日子 JavaScript 引擎对 ES6 的援助度还缺乏,所以上述代码不能够平素运营。能够由此以下二种方法来证大顺码能还是不可能跑起来。

率先种,先 npm install -g bable 全局安装 babel 工具,再以 babel-node tree.js的主意替代 node tree.js 来运转上述代码。

其次种,将上述代码黏贴到  ES5 的代码,将代码保存在 tree.js文件中,以 ES5 的花样进行。

大器晚成最早自己试图用异步方法来获得,不过最终因为没管理好Promise和其它异步操作,引致最终数据还没存款和储蓄下来。因这段日子后我们讲的是用联合格局得到。异步格局也将在这里个模块写完今后再去写大器晚成版本。

ECMAScript 是 JavaScript 语言的国际规范,JavaScript 是 ECMAScript 的兑现。ES6 的目标,是驱动 JavaScript 语言能够用来编排大型的复杂性的应用程序,成为公司级开辟语言。

结语

以上正是自个儿晓得的片段用 JavaScript 管理树形结构的两种办法,希望看现在对你有救助。

1 赞 4 收藏 评论

图片 2

大概网络的章程都以递归调用方法来博取整个目录结构。

概念

这里也不例外。但是我们必要注意大家必要的将总体目录关系都缓存在叁个Object里。何况因为本身供给内定层级目录输出。因而每种文件/目录还将有壹特性质便是deep,它作为一个报告调用者该目录层级的吃水。如若是0,说明是点名目录的根目录下同级的文本/目录。依次类推。

ES6 原生提供了 Promise 对象。

自然这里就一贯贴出代码

所谓 Promise,正是一个目的,用来传递异步操作的消息。它意味着了有个别以往才会清楚结果的事件(平常是二个异步操作卡塔 尔(阿拉伯语:قطر‎,何况那几个事件提供联合的 API,可供进一步管理。

_getAllNames: function(level, dir) {

Promise 对象有以下多个特点。

        var filesNameArr = []

(1卡塔 尔(英语:State of Qatar)对象的情况不受外部影响。Promise 对象表示贰个异步操作,有三种情景:Pending(进行中卡塔 尔(阿拉伯语:قطر‎、Resolved(已到位,又称 Fulfilled卡塔尔国和 Rejected(已破产)。唯有异步操作的结果,能够决定当前是哪生机勃勃种景况,任何别的操作都力无法支转移那一个场地。那也是 Promise 这么些名字的来由,它的意大利共和国语意思就是「承诺」,表示别的手腕不能够改造。

        let cur = 0

(2卡塔尔生龙活虎旦状态改造,就不会再变,任曾几何时候都足以获得那么些结果。Promise 对象的景观更改,独有三种或者:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要那三种状态时有爆发,状态就死死了,不会再变了,会直接维持那个结果。就算退换风流洒脱度发出了,你再对 Promise 对象加多回调函数,也会立时获得这一个结果。这与事件(伊夫nt卡塔尔国完全两样,事件的性状是,要是你失去了它,再去监听,是得不到结果的。

            // 用个hash队列保存每种目录的吃水

有了 Promise 对象,就足以将异步操作以同步操作的流程表达出来,防止了稀缺嵌套的回调函数。别的,Promise 对象提供联合的接口,使得调控异步操作更是轻便。

        var mapDeep = {}

Promise 也会有豆蔻梢头对久治不愈的疾病。首先,不可能废除Promise,黄金时代旦新建它就能够立马实践,不能中途撤销。其次,借使不安装回调函数,Promise 内部抛出的失实,不会影响到表面。第三,当处于 Pending 状态时,不能获知如今拓宽到哪叁个品级(刚刚带头照旧将在实现卡塔尔。

        mapDeep[dir] = 0

var promise = new Promise(function(resolve, reject) {

            // 先遍历三回给其创造深度索引

if (/* 异步操作成功 */){

        function getMap(dir, curIndex) {

resolve(value);

            var files = fs.readdirSync(dir) //同步得到文件目录下的全体文件名

} else {

            files.map(function(file) {

reject(error);

                //var subPath = path.resolve(dir, file) //拼接为相对路线

}

                var subPath = path.join(dir, file) //拼接为相对路线

});

                var stats = fs.statSync(subPath) //拿到文件讯息指标

promise.then(function(value) {

                    // 必须过滤掉node_modules文件夹

// success

                if (file != 'node_modules') {

}, function(value) {

                    mapDeep[file] = curIndex 1

// failure

                    if (stats.isDirectory()) { //剖断是还是不是为文件夹类型

});

                        return getMap(subPath, mapDeep[file]) //递归读取文件夹

Promise 构造函数选取一个函数作为参数,该函数的七个参数分别是 resolve 方法和 reject 方法。

                    }

生机勃勃旦异步操作成功,则用 resolve 方法将 Promise 对象的事态,从「未产生」变为「成功」(即从 pending 变为 resolved卡塔 尔(阿拉伯语:قطر‎;

                }

假设异步操作退步,则用 reject 方法将 Promise 对象的动静,从「未产生」变为「退步」(即从 pending 变为 rejected卡塔尔国。

                //console.log(subPath)

基本的 api

            })

Promise.resolve()

        }

Promise.reject()

        getMap(dir, mapDeep[dir])

Promise.prototype.then()

            //console.log(mapDeep)

Promise.prototype.catch()

        function readdirs(dir, folderName,myroot) {

Promise.all()    // 全部的产生

            var result = { //构造文件夹数据

var p = Promise.all([p1,p2,p3]);

                path: dir,

Promise.race()      // 竞速,完成四个就可以

                name: path.basename(path),

进阶

                type: 'directory',

promises 的奇特在于赋予大家原先的 return 与 throw,各个 Promise 都会提供多个 then() 函数,和一个 catch(),实际上是 then(null, ...) 函数,

                deep: mapDeep[folderName]

somePromise().then(functoin(){

            }

// do something

            var files = fs.readdirSync(dir) //同步得到文件目录下的保有文件名

});

            result.children = files.map(function(file) {

大家能够做三件事,

                //var subPath = path.resolve(dir, file) //拼接为相对路线

  1. return 另一个 promise

  2. return 二个联机的值 (或然 undefined)

  3. throw 一个一同极度throw new Eror('');

  4. 装进同步与异步代码

                var subPath = path.join(dir, file) //拼接为绝对路线

```

                var stats = fs.statSync(sub帕特h) //得到文件音信目的

new Promise(function (resolve, reject) {

                    //console.log(subPath)

resolve(someValue);

                if (stats.isDirectory()) { //判定是不是为文件夹类型

});

                    // console.log(mapDeep[file])

```

                    return readdirs(subPath, file,file) //递归读取文件夹

写成

                }

```

                return { //构造文件数量

Promise.resolve(someValue);

                    path: subPath,

```

                    name: file,

  1. 破获同步非凡

                    type: 'file'

new Promise(function (resolve, reject) {

                }

throw new Error('悲剧了,又出 bug 了');

            })

}).catch(function(err){

            return result //重返数据

console.log(err);

        }

});

        filesNameArr.push(readdirs(dir, dir))

风姿罗曼蒂克经是一路代码,能够写成

        return filesNameArr

Promise.reject(new Error("什么鬼"));

    },

  1. 三个可怜捕获,越来越精准的破获

过滤掉node_modules是必得的,因为开辟中那些目录一贯存在。。。是个宏大苦闷源。

somePromise.then(function() {

那边本身代码都加了批注应该很好掌握。

return a.b.c.d();

咱俩来输出下缓存的目录结构

}).catch(TypeError, function(e) {

{ path: './',

//If a is defined, will end up here because

  name: '[object Object]',

//it is a type error to reference property of undefined

  type: 'directory',

}).catch(ReferenceError, function(e) {

  deep: 0,

//Will end up here if a wasn't defined at all

  children:

}).catch(function(e) {

  [ { path: '.DS_Store', name: '.DS_Store', type: 'file' },

//Generic catch-the rest, error wasn't TypeError nor

    { path: '.babelrc', name: '.babelrc', type: 'file' },

//ReferenceError

    { path: '.gitignore', name: '.gitignore', type: 'file' },

});

    { path: 'README.MD', name: 'README.MD', type: 'file' },

  1. 获得多个 Promise 的再次回到值

  2. .then 格局顺序调用

  3. 设定更加高层的作用域

  4. spread

  5. finally

    { path: 'bin',

别的意况下都会实践的,通常写在 catch 之后

      name: '[object Object]',

  1. bind

      type: 'directory',

somethingAsync().bind({})

      deep: 1,

.spread(function (aValue, bValue) {

      children: [Object] },

this.aValue = aValue;

    { path: 'lib',

this.bValue = bValue;

      name: '[object Object]',

return somethingElseAsync(aValue, bValue);

      type: 'directory',

})

      deep: 1,

.then(function (cValue) {

      children: [Object] },

return this.aValue this.bValue cValue;

    { path: 'package.json', name: 'package.json', type: 'file' },

});

    { path: 'test',

也许 你也得以这么

      name: '[object Object]',

var scope = {};

      type: 'directory',

somethingAsync()

      deep: 1,

.spread(function (aValue, bValue) {

      children: [Object] } ] }

scope.aValue = aValue;

[ { path: './',

scope.bValue = bValue;

    name: '[object Object]',

return somethingElseAsync(aValue, bValue);

    type: 'directory',

})

    deep: 0,

.then(function (cValue) {

    children:

return scope.aValue scope.bValue cValue;

    [ [Object],

});

      [Object],

可是,那有丰盛多的差距,

      [Object],

您不得不先申明,有浪费能源和内部存储器走漏的高风险

      [Object],

不可能用来放在二个表达式的前后文中

      [Object],

频率更低

      [Object],

  1. all。极其用于于管理多少个动态大小均匀的 Promise 列表

  2. join。极度适用于处理多个分别的 Promise

      [Object],

```

      [Object] ] } ]

var join = Promise.join;

针对./这几个当前目录的门道,程序重临上边的构造。 那样大家就拿走了第一步最幼功的数目。

join(getPictures(), getComments(), getTweets(),

后日首先个步骤是为了参照他事他说加以考察Tree工具输出命令。

function(pictures, comments, tweets) {

此间给个例子参照他事他说加以考查:

console.log("in total: " pictures.length comments.length tweets.length);

├── .DS_Store

});

├── .babelrc

```

├── .gitignore

  1. props。管理二个 promise 的 map 群集。唯有有八个诉讼失败,全体的实行都终止

├── README.MD

```

├── bin

Promise.props({

│   ├── .DS_Store

pictures: getPictures(),

│   ├── folderTree.js

comments: getComments(),

│   ├── lib2

tweets: getTweets()

│   │   ├── .DS_Store

}).then(function(result) {

│   │   └── testa

console.log(result.tweets, result.pictures, result.comments);

│   └── testb

});

│      └── .DS_Store

```

├── lib

  1. any 、some、race

│   ├── .DS_Store

```

│   ├── folderFactory.js

Promise.some([

│   └── testlib

ping("ns1.example.com"),

│      └── testlibfile.js

ping("ns2.example.com"),

├── package.json

ping("ns3.example.com"),

└── test

ping("ns4.example.com")

   ├── .DS_Store

], 2).spread(function(first, second) {

   ├── index.js

console.log(first, second);

    └── testFolder

}).catch(AggregateError, function(err) {

       ├── .DS_Store

err.forEach(function(e) {

       ├── a

console.error(e.stack);

       ├── b

});

       └── c

});;

它有多少个注意点,一个是每当你的文本只怕目录是近来层级最终一个,那么输出└──前缀假设是经常见到的出口├── 前缀。 要是是多层级,那么最前方照旧须要│ 来实行上下层级的联络。

```

再者还恐怕有好多小细节须要管理。

有十分的大希望,战败的 promise 比较多,导致,Promsie 永恒不会 fulfilled

一同初最佳的消除措施其实是递归。不过自己感到有一点点复杂,笔者想试下迭代方法。最终结果如开首的效用。基本上只要根目录中最终贰个file是目录项目。那么它是例行的。可是假如根目录中最后多个文书是文件类型类型。作者这段迭代并未进展管理。

  1. .map(Function mapper [, Object options])

咱俩先来看下那个有顽固的病魔的代码:

用以拍卖三个数组,大概 promise 数组,

    var stack = [obj[0]]

Option: concurrency 并发现

        var isFinal = false

map(..., {concurrency: 1});

        function printFolder(arr, folderName, isLastFolder) {

以下为不限制并发数量,读书文件音讯

            if (arr.deep <= level) {

var Promise = require("bluebird");

                for (var i = 0; i < arr.children.length; i ) {

var join = Promise.join;

                    var isLastFile = i == arr.children.length - 1 ? true : false

var fs = Promise.promisifyAll(require("fs"));

                    var isRootLast = i == arr.children.length - 1

var concurrency = parseFloat(process.argv[2] || "Infinity");

                    isFinal = isFinal == true ? true :  arr.deep == 0 && arr.children[i].type == 'directory' && i == arr.children.length-1

var fileNames = ["file1.json", "file2.json"];

                    printFile(arr.children[i], folderName, arr.deep, isLastFile, isLastFolder,isFinal)

Promise.map(fileNames, function(fileName) {

                    if (arr.children[i].type == 'directory') {

return fs.readFileAsync(fileName)

                        //console.log('directory')

.then(JSON.parse)

                        var t = arr.children[i].path

.catch(SyntaxError, function(e) {

                        if (i == arr.children.length - 1) {

e.fileName = fileName;

                            printFolder(arr.children[i], t, true)

throw e;

                        } else {

})

                            printFolder(arr.children[i], t, false)

}, {concurrency: concurrency}).then(function(parsedJSONs) {

                        }

console.log(parsedJSONs);

                    }

}).catch(SyntaxError, function(e) {

                }

console.log("Invalid JSON in file " e.fileName ": " e.message);

            }

});

        }

结果

        function printFile(file, folderName, deep, isLastFile, isLastFolder) {

$ sync && echo 3 > /proc/sys/vm/drop_caches

            if (file[0] != '.') {

$ node test.js 1

                // console.log("Folder:" folderName)

reading files 35ms

                // console.log(deep)

$ sync && echo 3 > /proc/sys/vm/drop_caches

                //console.log(isLastFile)

$ node test.js Infinity

                //console.log(isLastFolder)

reading files: 9ms

                var name = file.path.replace(folderName '/', '')

  1. .reduce(Function reducer [, dynamic initialValue]) -> Promise

                //console.log(isFinal)

Promise.reduce(["file1.txt", "file2.txt", "file3.txt"], function(total, fileName) {

                if (deep == 0) {

return fs.readFileAsync(fileName, "utf8").then(function(contents) {

                    if (isLastFile) {

return total parseInt(contents, 10);

                        console.log('└── ' name)

});

                    } else {

}, 0).then(function(total) {

                        console.log('├── ' name)

//Total is 30

                    }

});

                }

  1. Time

                if (deep > 0) {

.delay(int ms) -> Promise

                    if (!isLastFolder) {

.timeout(int ms [, String message]) -> Promise

                        if (!isLastFile) {

Promise 的实现

                            console.log('│   '.repeat(deep) '├── ' name)

q

                        } else {

bluebird

                            console.log('│   '.repeat(deep) '└── ' name)

co

                        }

when

                    } else {

ASYNC

                        if (!isLastFile) {

async 函数与 Promise、Generator 函数一样,是用来顶替回调函数、消除异步操作的生龙活虎种方法。它实质上是 Generator 函数的语法糖。async 函数并不归属 ES6,而是被列入了 ES7。

                            console.log('    '.repeat(deep) '├── ' name)

                        } else {

                        if(isFinal){

                            console.log('    '.repeat(deep) '└── ' name)

                        }else{

                          var str = '    '.repeat(deep) '└── ' name

                          var temp = str.split('')

                          temp[0] = '│'

                            console.log(temp.join(''))

                        }

                        }

                    }

                }

            }

        }

        printFolder(obj[0], '', false)

        console.log('目录及文件罗列实现')

自己做了众多决断,但照旧低估了叁个索引它也许的纷纭(目录中有空目录,目录中有空文件,文件和目录的多个嵌套等等)。因为本身付出的时候是依靠多少个example来进行调节和测量试验判定。当自个儿最后写完,重新再去测量试验几个目录的时候发掘并发上述的file缺欠。 如图: 

图片 3

故而要求改写为递归方式。我们不大概基于其余属性来进行判断。由此大家必要依靠的或许事先的这份目录结构缓存。

此间也直接贴源码:

_showList(obj, level) {

        var sourceStruct = obj[0]

        var dirCount = 0

        var fileCount = 0

            // 字符集

        var charSet = {

            'node': '├── ', //节点

            'pipe': '│  ', // 上下链接

            'last': '└── ', // 最终的file或folder必要回勾

            'indent': '    ' // 缩进

        };

        function log(file, depth, parentHasNextSibling) {

          // console.log("log:")

            if (!parentHasNextSibling && depth.length > 1) {

                // Replace a pipe with an indent if the parent does not have a next sibling.

                depth[depth.length - 2] = charSet.indent;

            }

            if(file.lastIndexOf('/') > -1){

                file = file.substring(file.lastIndexOf('/') 1)

            }

            console.log(depth.join('') file);

        }

        // 由于已经有缓存数据了,由此对数据开展遍历找寻

        // 要是type 是file 就无需后续

        // 假诺type 是directory 对一时数组

        function walk(path, depth, parentHasNextSibling) {

          //  console.log(path)

            var childrenLen = path.length - 1

          // console.log(childrenLen)

            var loop = true

            if (depth.length >= level) {

                loop = false

            }

            if (loop) {

                path.forEach(function walkChildren(child, index) {

                    var newdepth = !!depth ? depth.slice(0) : []

                    var isLast = (index >= childrenLen)

                    if (isLast) {

                        newdepth.push(charSet.last)

                    } else {

                        newdepth.push(charSet.node)

                    }

                    if(child.type == "file"){

                      log(child.name, newdepth, parentHasNextSibling)

                    }else{

                      log(child.path, newdepth, parentHasNextSibling)

                    }

                    if (child.type == "directory") {

                        var childPath = child.children

                        if (!isLast) {

                            newdepth.pop()

                            newdepth.push(charSet.pipe)

                        }

                        walk(childPath, newdepth, !isLast)

                    }

                })

                loop = !loop

            }

        }

        walk(sourceStruct.children, [])

        //console.log(sourceStruct)

        console.log('level:' level)

        console.log('目录及文件罗列实现')

    },

此地只需求看清文件类型,利用递归的性状将每种文件的路线存于newpath,然后推断长度,要是超过level就跳过。具体注释都在源码里了。

图片 4

结尾

率先个模块总算实现,10月先是篇小说居然在中旬也算对得起拖延症了~

本文由星彩网app下载发布于前端技术,转载请注明出处:中的神器,Node仿Tree指定层级输出树形文件目录结

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。