深析filemap.js——关于JS的算法及优化的实践

项目地点:链接形貌
项目简介:https://segmentfault.com/a/1190000005968734

关于项目的用法和引见可以检察上面的两个链接,这篇文章主要内容是对filemap.js的代码举行一步一步的剖析,细致引见其运转道理和优化战略。

知识点预备:

  1. NodeJS的基础运用要领(主假如fs文件体系);

  2. ES6特征及语法(let, const, for...of, arrow function…)

  3. n叉树先序遍历算法

知识点1和2请自行查阅材料,如今对知识点3举行剖析。

N叉树先序遍历算法

起首邃晓什么是树。援用数据组织与算法JavaScript形貌

一个树组织包含一系列存在父子关系的节点。每一个节点都有一个父节点(除了顶部的第一个节点)以及零个或多个子节点:
《深析filemap.js——关于JS的算法及优化的实践》
位于树顶部的节点叫作根节点(11)。它没有父节点。树中的每一个元素都叫作节点,节点分为内部节点和外部节点。至少有一个子节点的节点称为内部节点(7、5、9、15、13和20是内部节点)。没有子元素的节点称为外部节点或恭弘=叶 恭弘节点(3、6、8、10、12、14、18和25是恭弘=叶 恭弘节点)。
一个节点可以有先人和子女。一个节点(除了根节点)的先人包含父节点、祖父节点、曾祖父节点等。一个节点的子女包含子节点、孙子节点、曾孙节点等。比方,节点5的先人有节点7和节点11,子女有节点3和节点6。
有关树的另一个术语是子树。子树由节点和它的子女组成。比方,节点13、12和14组成了上图中树的一棵子树。
节点的一个属性是深度,节点的深度取决于它的先人节点的数目。比方,节点3有3个先人节点(5、7和11),它的深度为3。
树的高度取决于一切节点深度的最大值。一棵树也可以被分解成层级。根节点在第0层,它的子节点在第1层,以此类推。上图中的树的高度为3(最大高度已在图中示意——第3层)。

关于一棵树的遍历,有先序中序后序三种遍历体式格局,在本例中运用的是先序遍历的体式格局。至于三种遍历体式格局的异同,请浏览数据组织与算法JavaScript形貌,内里有细致的引见。

起首我们建立一棵树:

let treeObj = {
    '1': [
        { '2': [{ '5': [{ '11': '11' }, { '12': '12' }, { '13': '13' }, { '14': '14' }] }] },
        { '3': [{ '6': '6' }, { '7': '7' }] },
        { '4': [{ '8': '8' }, { '9': '9' }, { '10': '10' }] }
    ]
}

为了简朴轻易,我把它的key和value都设置成了雷同的值。在例子中我们运用的都是key值。
然后剖析先序遍历的道理:
《深析filemap.js——关于JS的算法及优化的实践》
虚线为遍历递次,可以看出先序遍历可以获得整棵树的组织,这正是我们所须要的。接下来看代码怎样完成。先看完全代码:

let traverseNode = (node, deep) => {
    if (typeof node !== 'string') {
        let key = Object.keys(node)
        console.log(key, deep)
        for (let i = 0; i < node[key].length; i++) {
            traverseNode(node[key][i], deep + 1)
        }
    }
}

traverseNode(treeObj, 1)

我们建立了一个traverseNode()函数,它吸收两个对象作为参数。node参数为传入的节点,deep参数为节点的肇端深度。
起首运用Object.keys(obj)要领获得节点的key值,同时输出深度值:

let key = Object.keys(node)
console.log(key, deep)

运转,在掌握台将会输出[ '1' ] 1。接下来我们运用递返来反复这个历程,举行完全的遍历运算:

for (let i = 0; i < node[key].length; i++) {
    traverseNode(node[key][i], deep + 1)
}

这个递归就是我们前文一直在说的先序遍历。关于二叉树

先序遍历是以优先于子女节点的递次接见每一个节点的。先序遍历的一种应用是打印一个组织化的文档。
先序遍历会先接见节点自身,然后再接见它的左边子节点,末了是右边子节点。
《深析filemap.js——关于JS的算法及优化的实践》

在明白完上面这段话今后,不难把先序遍历的思绪扩大到n叉树:先接见节点自身,然后从左到右接见它的n个子节点。
每一次完全的for轮回都意味着“往下走一层”,所以只须要deep + 1即可晓得每一个节点对应的深度。

在本例子的遍历历程当中,node都是一个个的对象而非字符串。假如检测到node为字符串,证实其已到了末了一层,须要住手,不然会无穷轮回致使溢出,所以我们须要增加一个推断:

if (typeof node !== 'string')

功德圆满,如今我们尝试运转一下:

[ '1' ] 1
[ '2' ] 2
[ '5' ] 3
[ '11' ] 4
[ '12' ] 4
[ '13' ] 4
[ '14' ] 4
[ '3' ] 2
[ '6' ] 3
[ '7' ] 3
[ '4' ] 2
[ '8' ] 3
[ '9' ] 3
[ '10' ] 3

圆满。

filemap.js道理

filemap.js经由过程遍历一个文件夹内部的一切子文件和子文件夹,输出其目次组织。我们运用fs文件体系来举行。

const fs = require('fs')

然厥后组织中心部份代码:

// 推断范例。若该途径对应的是文件夹则返回true,不然返回false
let isDic = (url) => fs.statSync(url).isDirectory()

const traverseFiles = (path, deep) => {
  let files = fs.readdirSync(path)
  for (let i = 0, len = files.length; i < len; i++) {
    if (files[i] !== 'filemap.js') console.log(deep, files[i], '\n') // 疏忽filemap.js自身
    let dirPath = path + '\\' + files[i]
    // 当且仅当是文件夹时才举行下一轮遍历
    if (isDic(dirPath)) traverseFiles(dirPath, deep + 1)
  }
}

文件目次组织实在就是一棵典范的n叉树,经由过程前文的例子,不难邃晓这段代码的道理。起首经由过程fs.readdirSync(path)同步地猎取某途径对应的一切文件(夹),然后举行递归。可以把它明白为从第二层最先遍历,所以在写法上和前文例子稍有差别。

如今我们已可以猎取文件及其地点的深度了,接下来就是对这些信息举行格式化,使其输出越发直观。为了输出相似

|__folder
    |__file1
    |__file2

如许的树状组织,我们须要推断差别的深度对应的缩进,所以我们来定义一个placeHolder()函数:

const placeHolder = (num) => {
  if (placeHolder.cache[num]) return placeHolder.cache[num] + '|__'
  placeHolder.cache[num] = ''
  for (let i = 0; i < num; i++) {
    placeHolder.cache[num] += '  '
  }
  return placeHolder.cache[num] + '|__'
}
placeHolder.cache = {}

这里涉及到一个缓存函数实行效果的优化战略。因为该函数屡次被运用,假如每一次都是从头最先举行for轮回,在机能上有着庞大的糟蹋。所以我们可以把它的实行效果缓存起来,当今后碰到雷同状况时只须要掏出缓存的效果即可,无需从新运算,大大提升了机能。

如今我们把中心代码改写一下:

let isDic = (url) => fs.statSync(url).isDirectory()

const traverseFiles = (path, deep) => {
  let files = fs.readdirSync(path)
  for (let i = 0, len = files.length; i < len; i++) {
    if (files[i] !== 'filemap.js') console.log(placeHolder(deep), files[i], '\n') // 疏忽filemap.js自身
    let dirPath = path + '\\' + files[i]
    if (isDic(dirPath)) traverseFiles(dirPath, deep + 1)
  }
}

traverseFiles('./', 1)

在根目次中运转node filemap.js,我们就可以获得圆满的文件目次树状组织图了。

功用进一步扩大

如今是“无差别”地对一切文件夹举行睁开。假如想要疏忽某些文件夹,比方.git或许node_modules之类的文件夹,应当怎样做呢?参考命令行输入参数的要领,这个需求不难完成。
起首猎取须要疏忽的文件夹名:

let ignoreCase = {}
if(process.argv[2] === '-i'){
    for (let i of process.argv.slice(3)) {
      ignoreCase[i] = true
    }
}

ignoreCase保留着须要疏忽的文件夹名。这里运用对象而不是数组的原因是,当推断一个item是不是被已被保留的时刻,item.indexOf(Array)的效力并没有Object[item]来得高。运用for...of轮回可以直接获得对象。

接下来我们可以在中心代码中多加一个推断:

let isDic = (url) => fs.statSync(url).isDirectory()

const traverseFiles = (path, deep) => {
  let files = fs.readdirSync(path)
  let con = false
  for (let i = 0, len = files.length; i < len; i++) {
    if (files[i] !== 'filemap.js') console.log(placeHolder(deep), files[i], '\n')
    con = ignoreCase[files[i]] === undefined? true: false
    let dirPath = path + '\\' + files[i]
    if (isDic(dirPath) && con) traverseFiles(dirPath, deep + 1)
  }
}

被疏忽的文件夹将不会举行递归运算。
末了别忘了在退出历程:

process.exit()

至此,完全的filemap.js已完成,其一切代码以下:

/**
 * @author Jrain Lau
 * @email jrainlau@163.com
 * @date 2016-07-14
 */
 
'use strict'
const fs = require('fs')

let ignoreCase = {}
if(process.argv[2] === '-i'){
    for (let i of process.argv.slice(3)) {
      ignoreCase[i] = true
    }
}

console.log('\n\nThe files tree is:\n=================\n\n')

const placeHolder = (num) => {
  if (placeHolder.cache[num]) return placeHolder.cache[num] + '|__'
  placeHolder.cache[num] = ''
  for (let i = 0; i < num; i++) {
    placeHolder.cache[num] += '  '
  }
  return placeHolder.cache[num] + '|__'
}
placeHolder.cache = {}

let isDic = (url) => fs.statSync(url).isDirectory()

const traverseFiles = (path, deep) => {
  let files = fs.readdirSync(path)
  let con = false
  for (let i = 0, len = files.length; i < len; i++) {
    if (files[i] !== 'filemap.js') console.log(placeHolder(deep), files[i], '\n')
    con = ignoreCase[files[i]] === undefined? true: false
    let dirPath = path + '\\' + files[i]
    if (isDic(dirPath) && con) traverseFiles(dirPath, deep + 1)
  }
}

traverseFiles('./', 1)

process.exit()

运用时只须要带上参数-i 文件夹1 文件夹2 ...即可掌握文件夹的睁开与否。

跋文

在进修数据组织与算法JavaScript形貌的历程当中,有时刻真的以为迥殊困,厥后发挥本身喜好折腾的特性,想办法把死板的东西举行实践,不知不觉就会变得风趣了。在filemap.js的初期版本中有着很多bug和机能题目,比方不合理运用三元表达式,没有缓存函数实行效果,推断文件范例考虑不周等等状况。文中所涉及到的优化战略,有很多是来自别人的指导和一次次的修正才终究得出来的,在此非常谢谢赋予我协助的人。

末了谢谢你的浏览。我是Jrain,迎接关注我的专栏,将不按期分享本身的进修体验,开辟心得,搬运墙外的干货。下次见啦!

    原文作者:jrainlau
    原文地址: https://segmentfault.com/a/1190000005987714
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞