0%

检查目录是否存在,创建目录

// node -v v12.*
const fs = require('fs');

// 查找目录 并判断是否存在目标文件夹
async function findDir(dirPath = '.', dirName) {
  try {
    const files = await fs.promises.readdir(dirPath, {
      encoding: 'utf8',
      withFileTypes: true,
    })
    return (
      files.length > 0 &&
      files.findIndex((item) => item.name === dirName && item.isDirectory()) > -1
    )
  } catch (err) {
    throw err
  }
}

// 创建目录
async function createDir(dirPath) {
  try {
    await fs.promises.mkdir(dirPath)
  } catch (err) {
    throw err
  }
}

// 检查是否存在目录 不存在则创建
async function findOrCreateDir() {
  await findDir('.', dir)
    .then((res) => {
      if (!res) {
        return createDir(dir)
      } else {
      }
    })
    .catch((e) => {
      console.error('error', e)
    })
}

// 检查是否存在目录 存在则清空
function delDir(path) {
  let files = []
  // if (fs.existsSync(path)) {
    files = fs.readdirSync(path, {
      withFileTypes: true
    })
    files.forEach((file) => {
      let curPath = `${path}/${file.name}`
      if (file.isDirectory()) {
        delDir(curPath) //递归删除文件夹
      } else {
        fs.unlinkSync(curPath) //删除文件
      }
    })
    fs.rmdirSync(path)
  // }
}
// 方法2
async function findOrCreateDir(path) {
    if (fs.existsSync(path)) {
        delDir(path)
    }
    await createDir(path)
}

循环删除空文件夹

// node -v v14.*
const fs = require('fs');
const path = require('path');


const originPath = path.resolve(__dirname, './source/_posts')

function resolveOriginPath(url) {
    return path.resolve(originPath, url)
}

function rmEmptyDir() {
    // 同步读取目录 过滤非文件夹目录及非空文件夹
    const files = fs.readdirSync(originPath, { withFileTypes: true })
                    .filter(file => {
                        if (file.isDirectory(resolveOriginPath(file.name))) {
                            return fs.readdirSync().length === 0
                        }
                        return false
                    })
    console.log(files)
    
    function rmDir(url) {
        fs.rmdir(url, err => {
            err && console.log('rm err: ', url, err)
        })
    }
    files.forEach(item => rmDir(resolveOriginPath(item.name)))
}

《JavaScript高级程序设计(第3版) 24章》

可维护性

特征

可理解性、直观性、可适应性、可扩展性、可调试性。

可读性
阅读全文 »

函数

函数声明与函数表达式的区别:

  1. 写法不同;

  2. 名称标识符将会绑定在何处,函数表达式是可以匿名的,而函数声明则不可以省略函数名。

  3. 变量提升。函数声明会被整体提升,而函数表达式只会提升变量,预设为undefined,然后在等待执行阶段被赋值。所以函数表达式不能在定义前被使用,而函数声明可以。

    只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。

    在执行代码块之前,解析器会先处理函数声明,并使其在执行任何代码之前可用。所以函数声明在其被声明的代码块内的任何位置都是可见的。

    函数表达式必须等到解析器执行到所在的代码行,才会真正被节是执行,即在执行流程到达时创建。

更多阅读:https://zh.javascript.info/function-expressions

常见函数概念

阅读全文 »

《学习JavaScript数据结构与算法(第3版)》

集合是由一组无序且唯一(即不能重复)的项组成的。JavaScript的对象不允许一个键指向两个不同的属性,也保证了集合里的元素都是唯一的。所以使用对象来实现集合。

数据结构

class Set {
  constructor() {
    this.items = {};
  }
  // 向集合添加一个新元素
  add(element) {
    // 检查元素是否已存在
    if (!this.has(element)) {
      // 同时作为键和值保存,有利于查找该元素
      this.items[element] = element;
      return true;
    }
    return false;
  }
  // 从集合移除一个元素
  delete(element) {
    // 检查元素是否已存在
    if (this.has(element)) {
      delete this.items[element];
      return true;
    }
    return false;
  }
  // 如果元素在集合中,返回true,否则返回false
  has(element) {
    // 该方法返回一个表明对象是否具有特定属性的布尔值,使用in运算符则返回对象在原型链上是否有特定属性的布尔值
    return Object.prototype.hasOwnProperty.call(this.items, element);
  }
  // 返回一个包含集合中所有值(元素)的数组
  values() {
    return Object.values(this.items);
  }
  // 并集,对于给定的两个集合,返回一个包含两个集合中所有元素的新集合
  union(otherSet) {
    const unionSet = new Set();
    this.values().forEach(value => unionSet.add(value));
    otherSet.values().forEach(value => unionSet.add(value));
    return unionSet;
  }
  // 交集,对于给定的两个集合,返回一个包含两个集合中共有元素的新集合
  intersection(otherSet) {
    const intersectionSet = new Set();
    const values = this.values();
    const otherValues = otherSet.values();
    let biggerSet = values; // size较大的集合
    let smallerSet = otherValues; // size较小的集合
    if (otherValues.length - values.length > 0) {
      biggerSet = otherValues;
      smallerSet = values;
    }
    smallerSet.forEach(value => { // 循环元素较小的集合
      if (biggerSet.includes(value)) { // 元素较大的集合中判断是否有该元素
        intersectionSet.add(value);
      }
    });
    return intersectionSet;
  }
  // 差集,对于给定的两个集合,返回一个包含所有存在于第一个集合且不存在于第二个集合的元素的新集合
  difference(otherSet) {
    const differenceSet = new Set();
    this.values().forEach(value => {
      if (!otherSet.has(value)) {
        differenceSet.add(value);
      }
    });
    return differenceSet;
  }
  // 子集,验证一个给定集合是否是另一集合的子集
  isSubsetOf(otherSet) {
    if (this.size() > otherSet.size()) {
      return false;
    }
    let isSubset = true;
    this.values().every(value => {
      if (!otherSet.has(value)) {
        isSubset = false;
        return false;
      }
      return true;
    });
    return isSubset;
  }
  // 集合是否为空
  isEmpty() {
    return this.size() === 0;
  }
  // 返回集合所包含元素的数量。它与数组的length属性类似
  size() {
    // 也可以定义一个count变量进行维护
    return Object.keys(this.items).length;
  }
  // 移除集合中的所有元素
  clear() {
    this.items = {};
  }
  toString() {
    if (this.isEmpty()) {
      return '';
    }
    const values = this.values();
    let objString = `${values[0]}`;
    for (let i = 1; i < values.length; i++) {
      objString = `${objString},${values[i].toString()}`;
    }
    return objString;
  }
}

使用Object.prototype.hasOwnProperty.call(obj, element)而不使用obj.hasOwnPeoperty(element)的原因是因为不是所有的对象都继承了Object.prototype,甚至继承了Object.prototype的对象上的hasOwnProperty方法也有可能被覆盖,导致代码不能正常工作,因此使用Object.prototype.hasOwnProperty.call(obj, element)更安全。

阅读全文 »

《学习JavaScript数据结构与算法(第3版)》

数组

内存中的一段连续地址。

对象中的属性,是一段不连续的地址。对象属性插入或删除不需要移动其他元素。

要存储多个元素,数组(或列表)可能是最常用的数据结构。虽然存取方便,然而,这种数据结构有一个缺点:(在大多数语言中)数组的大小是固定的,从数组的起点或中间插入或移除项的成本很高,因为需要移动元素。

阅读全文 »

《学习JavaScript数据结构与算法(第3版)》

链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。

链表数据结构(单向链表)

链表的第一个节点为头部,链表最后一个节点的下一个元素始终是undefined或null。

// 比较链表中的元素是否相等
function defaultEquals(a, b) {
  return a === b;
}

// 节点类
class Node {
  constructor(element, next) {
    this.element = element; // 表示要加入链表元素的值
    this.next = next; // 指向链表中下一个元素的指针
  }
}

class LinkedList {
  constructor(equalsFn = defaultEquals) {
    this.equalsFn = equalsFn;
    this.count = 0; // 存储链表中的元素数量
    this.head = undefined; // 保存引用
  }
  // 向链表尾部添加一个新元素
  push(element) {
    const node = new Node(element); // 创建节点类
    let current; // 插入位
    if (this.head == null) {
      // catches null && undefined
      this.head = node;
    } else {
      current = this.head; // 默认初始插入位为头部
      while (current.next != null) { // 一直找到链表的最后一项
        current = current.next;
      }
      // 将插入位的next赋为新元素,建立链接
      current.next = node;
    }
    this.count++; // 增加链表长度
  }
  // 返回链表中特定位置的元素。如果链表中不存在这样的元素,则返回undefined
  getElementAt(index) {
    // 检查越界值
    if (index >= 0 && index <= this.count) {
      // 从头开始循环
      let node = this.head;
      for (let i = 0; i < index && node != null; i++) {
        node = node.next;
      }
      return node;
    }
    return undefined;
  }
  // 向链表的特定位置插入一个新元素
  insert(element, index) {
    // 检查越界值
    if (index >= 0 && index <= this.count) {
      const node = new Node(element);
      if (index === 0) {
        // 在头部添加
        const current = this.head;
        // 将新插入的元素下一个节点指向原链表的head
        node.next = current;
        // 替换链表的head
        this.head = node;
      } else {
        // 查找到目标位的前一个元素
        const previous = this.getElementAt(index - 1);
        // 替换前一个元素的下一个引用 从而插入元素 形成链接
        node.next = previous.next;
        previous.next = node;
      }
      // 链表长度增加
      this.count++;
      return true;
    }
    // 越界了就无法添加到链表中
    return false;
  }
  // 从链表的特定位置移除一个元素
  removeAt(index) {
    // 检查越界值
    if (index >= 0 && index < this.count) {
      let current = this.head; // 默认移除点为头部
      if (index === 0) { // 移除第一项(头部)
        this.head = current.next;
      } else {
        // 查找到需要移除的位置前一项 解除链接关系
        const previous = this.getElementAt(index - 1);
        // 循环列表的当前元素引用 移除点
        current = previous.next;
        // 将previous与current的下一项链接起来:跳过current,从而实现移除
        previous.next = current.next;
      }
      // 链表长度减少
      this.count--;
      // 返回被移除项的值
      return current.element;
    }
    // 未有移除项
    return undefined;
  }
  // 从链表中移除一个元素 先找到索引值后根据索引值删除元素
  remove(element) {
    const index = this.indexOf(element);
    return this.removeAt(index);
  }
  // 能够在链表中找到一个特定的元素
  indexOf(element) {
    let current = this.head;
    // 为了运行不会发生错误,需要验证一下current的有效性
    for (let i = 0; i < this.size() && current != null; i++) {
      if (this.equalsFn(element, current.element)) {
        // 返回查找的索引值
        return i;
      }
      current = current.next;
    }
    // 没有找到返回-1
    return -1;
  }
  // 如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false
  isEmpty() {
    return this.size() === 0;
  }
  // 返回链表包含的元素个数
  size() {
    return this.count;
  }
  // 返回链表第一个元素
  getHead() {
    return this.head;
  }
  // 清空链表
  clear() {
    this.head = undefined;
    this.count = 0;
  }
  // 返回表示整个链表的字符串。由于列表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值
  toString() {
    if (this.head == null) {
      return '';
    }
    let objString = `${this.head.element}`;
    let current = this.head.next;
    for (let i = 1; i < this.size() && current != null; i++) {
      objString = `${objString},${current.element}`;
      current = current.next;
    }
    return objString;
  }
}
阅读全文 »

《学习JavaScript数据结构与算法(第3版)》

栈是一种遵从后进先出(LIFO)原则的有序集合。新添加或待删除的元素都保存在栈的同一端,称作栈顶,另一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。

栈也被用在编程语言的编译器和内存中保存变量、方法调用等,也被用于浏览器历史记录(浏览器的返回按钮)。

数据结构

创建一个Stack类最简单的方式是使用一个数组来存储其元素。在使用数组时,大部分方法的时间复杂度是O(n)。如果数组有更多元素的话,所需的时间会更长。另外,数组是元素的一个有序集合,为了保证元素排列有序,它会占用更多的内存空间。我们也可以使用一个JavaScript对象来存储所有的栈元素,保证它们的顺序并且遵循LIFO原则。

阅读全文 »

webpack是什么

静态资源模块打包工具

当 webpack 处理应用程序时,它会在内部构建一个依赖图(dependency graph),此依赖图对应映射到项目所需的每个模块,并生成一个或多个包(bundle)。

webpack打包是前端工程化必不可少的环节,可以基于webpack 解决前端一系列问题,逐渐形成一套前端工程化解决方案。

webpack解决了什么

阅读全文 »

1

执行环境:执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。全局执行环境被认为是 window 对象。

执行栈:每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。

作用域:

作用域链:本质上是一个指向变量对象的指针列表,它只引用但不实际包含对象。

阅读全文 »