node

236 阅读12分钟

宏任务: 执行js脚本是一个宏任务, ui渲染  ajax, 定时器,事件, MessageChannel, setImmediate 宏任务队列 “队列只用有一个”
微任务: promise mutationObserver


const fs = require('fs').promises; // 将fs中的所有对象都转变成promise
fs.readFile('./a.txt','utf8').then((data)=>{
    console.log(data);
})

function promisify(fn){ // 将node异步api转换成 promise写法
    return function (...args){
        return new Promise((resolve,reject)=>{
            fn(...args,function(err,data){
                if(err) return reject(err);
                resolve(data);
            })
        })
    }
}


// 最早异步 回调 -> promise (基于回调的) -> generator 生成器

// 生成器就是生成迭代器的  迭代器的含义就是 可以拥有迭代方法的

// Array.from 将类数组 转化成数组 拓展运算符必须要求对象是可以被迭代的

// function* read() { // 这就表示他是一个generator函数, 配合yield使用
//     yield 'react'
//     yield 'vue'
//     yield 'node'
// }


// let it = read();
// let done = false
// do{
//    let {value,done:flag} = it.next();
//    console.log(value);
//    done = flag
// }while (!done)
// console.log(it.next()); // 特点就是碰到yield 就停止

console.log([...{
    0:1,
    1:2,
    2:3,
    lenght:3,
    [Symbol.iterator]: function* () {
        
    }
}]);


// console.log([
//   ...{
//     0: 1,
//     1: 2,
//     2: 3,
//     length: 3,
//     [Symbol.iterator]: function* () {
//         let i = 0;
//         while (i !== this.length) { // 怎么看this 谁调用的方法this 就是谁
//             yield this[i++]
//         }
//       // 迭代器函数返回会一个对象 对象中有next方法  方法中返回value和done
//       //   let i = 0;
//       //   return {
//       //     next: () => {
//       //       // 迭代函数 每次迭代都会调用next方法
//       //       return {
//       //         // 对象里有value和done
//       //         value: this[i],
//       //         done: i++ == this.length,
//       //       };
//       //     },
//       //   };
//     },
//   },
// ]);



// function* read(){
//     let a = yield 'node';
//     console.log(a,52)
//     let b = yield 'react';
//     console.log(b,53)
//     let c = yield 'vue'
//     console.log(c)
// }
// let it = read();
// console.log(it.next()) // 第一次传递参数是没有任何意义的
// console.log(it.next('xxx')); // next传递的参数, 是可以赋予给上一次yield的返回值
// console.log(it.next('ooo')); // next传递的参数, 是可以赋予给上一次yield的返回值
// console.log(it.next('qqq')); // next传递的参数, 是可以赋予给上一次yield的返回值
这是co的写法
const fs = require("fs").promises;
function* read() { // 生成器  固定写法 加个*
  // 很像同步的
  let out1 = yield fs.readFile("a.txt", "utf8");
  let out2 = yield fs.readFile(out1, "utf-8");
  return out2;
}

function co(it){
    return new Promise((resolve, reject)=>{
        function next(data) {
            let { value, done } = it.next(data);
            if(done) {
                return resolve(value);
            }
            Promise.resolve(value).then(data=>{
                next(data)
            });
        }
        next();
    });
}


1636812630(1).png


![1636812630(1).png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7dc5a0de67f5460f8aee1cde50024fb0~tplv-k3u1fbpfcp-watermark.image?)
![1636812630(1).png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/56d99fa4b11b4307ae75a6b1c9ee89ba~tplv-k3u1fbpfcp-watermark.image?)
请看一道题

console.log(1);
async function async() {
    console.log(2);

    await console.log(3); // Promise.resolve(console.log(3)).then(()=>{console.log(4)})
    console.log(4)
}
setTimeout(() => {
    console.log(5);
}, 0);
const promise = new Promise((resolve, reject) => {
    console.log(6);
    resolve(7) // 立刻执行 status = fulfilled
})
promise.then(res => {
    console.log(res)
})
async();
console.log(8)
      function fn1() {
            console.log('listener1');
            Promise.resolve().then(() => console.log('micro task1'))
        }
        function fn2() {
            console.log('listener2');
            Promise.resolve().then(() => console.log('micro task2'))
        }
       //  事件是宏任务  宏任务的执行顺序是 1个个执行
    //    button.click(); // 没有基于宏任务  fn1() fn2()  直接就执行了
    fn1()
    fn2()
    
浏览器中的全局对象 window
服务端可以直接访问global
// let r = fs.readFileSync('./note1.md','utf8');
// let exists = fs.existsSync('./note1.md') ;// 这个方法的异步方法被废弃了 (异步的回调中 存在不存在,node中要求 error-first)
// console.log(exists)
console.log(path.join(__dirname,'a','b','c','/')); // a\b\c\ 拼接路径的
console.log(path.resolve(__dirname,'a','b','c')); // 解析出绝对路 也具备拼接的功能

console.log(path.dirname(__filename)); // __filename __dirname  取父路径的
console.log(path.extname('a.min.js')); // 取扩展名 都是最后一个
console.log(path.basename('a.min.js','.js'))

// eval  会引用上级作用域的变量   适合简单的js执行  不依赖上下文变量
// new Function () 会创造一个和全局平级的执行环境  也会引用上级变量

1636897173(1).png

const vm = require('vm'); // 虚拟机模块  a b 两个模块没有关系

vm.runInThisContext('console.log(a)'); // 实现一个 安全的执行,但是挂载全局上已经可以获取到
// 我期望执行代码的时候 在一个干净的环境下执行代码 而不依赖当前的上下文

我们看看e.js的实现原理

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <%=name%> <%=age%>
    <%arr.forEach(item=>{%>
        <a href=""><%=item%></a>
    <%})%>
</body>
</html>


// const ejs = require('ejs');
const fs = require('fs').promises
const path = require('path');

const ejs = {
    async renderFile(filePath, data) {
        let template =  await fs.readFile(filePath,'utf8');
        let head = 'let str = ``;\r\n';
        head += 'with(obj){\r\nstr += `'
        let content = template.replace(/<%=(.+?)%>/g,function(){  
            // <%=xxx%> ->  ${xxx}
            return '${' + arguments[1] +  '}'
        })
        content =  content.replace(/<%(.+?)%>/g,function(){
            return '`\r\n'+arguments[1] + '\r\nstr+=`'
        });
       
        let tail = '`}\r\n return str'
        return new Function('obj',head + content + tail)(data);
    }
}
ejs.renderFile(path.resolve(__dirname, 'template.html'), { name: 'zf', age: 12,arr:[1,2,3] }).then(data => {
    console.log(data)
})
// 模板引擎的实现原理 就是 with + new Function  (掌握模板引擎的实现原理 都是这个套路 )
// commonjs 实现原理   

// 1.with解决的是在模板中取值的问题
// 2.new Function 就是让代码传参 ,返回最终的结果

比如// let obj = {
//     a:1
// }

// setTimeout(()=>{
//     obj.a++;
// },1000);

var a = 100;

setTimeout(()=>{
   a++;
},1000);
module.exports = a;
这种情况如果a是常量,那么导入的话不会发生变化,如果a是基本数据类型,那么在另一个文件require的值不会发生变化,如果a是一个对象就会发生变化,


默认是exports = module.exports = 'b';
= {};
如果exports = {a:1}
相当于module.exports还是{},给如果exports换了一个新房子为{a:1}

同理
 exports = 'a'最终也是导入的{},因为module.exports还是{}


那么问题就来了一般导出都是module.exports,那么要exports有啥用呢?
如果这个时候导出的是对象,那么作用就来了

exports.a = 1;exports.b = 1;
相当于和module.exports时一个房子,所以这样导出很有效,就不用
 module.exports.a = 1; module.exports.b = 1
common.js的实现原理

const path = require('path');
const fs = require('fs')
const vm = require('vm')
function Module(id) {
    this.id = id;
    this.exports = {}
}
Module._extensions = {
    '.js'(module){
        let source =  fs.readFileSync(module.id); // js中的源代码
        let wrapper = ['(function(exports,module,require,__dirname,__filename){','})']
        const script = wrapper[0] + source + wrapper[1]; // 字符串 
      
        let fn = vm.runInThisContext(script); // 字符串转成的函数

        let exports = module.exports  
        let filename = module.id
        let dirname = path.dirname(filename)
        // 执行此函数 用户会自动的给module.exports 赋值
        // 此函数执行的时候 this 发生了变化
        fn.call(exports,exports,module,req,dirname,filename); // 用户module.exports = 'hello'
    },
    '.json'(module){
       let content =  fs.readFileSync(module.id); // content 就是json中的内容
       module.exports = JSON.parse(content)
    }
}
Module._resolveFilename = function (id) {
    let filepath = path.resolve(__dirname,id);
    if(fs.existsSync(filepath)) return filepath

    let exts = Object.keys(Module._extensions);
    for(let i = 0; i < exts.length;i++){
        let p = filepath + exts[i];
        if(fs.existsSync(p)) return p
    }
    // 不存在尝试添加后缀 , .json ,json
    throw new Error('模块不存在')
}
Module.prototype.load = function(){ // 加载模块 
    let extension = path.extname(this.id);
    Module._extensions[extension](this)
}
Module._cache = {}; // 全局的缓存区 用来缓存模块的
function req(id) {
    // 将路径变成绝对路径 进行操作, 可能id 没有后缀 需要依次尝试追加后缀 .js  .json
    let filepath = Module._resolveFilename(id); // 根据相对路径获取一个绝对路径
    if(Module._cache[filepath]){ // 如果缓存中有 直接使用上一次缓存的结果
        return Module._cache[filepath].exports
    }
    let module = new Module(filepath);
    Module._cache[filepath] = module; // 将模块进行缓存
    module.load(); // 加载模块 
    return module.exports; // 最终返回的是module.exports 
}

let r = req('./a.js');
r = req('./a.js');
r = req('./a.js');
const EventEmitter = require('./events');
// const {inherits} = require('util')

// on 订阅  emit 发布  once 订阅一次  off 移除监听  removeAllListeners 删除所有监听
class Girl extends EventEmitter {

}

let girl = new Girl();
let pending = false
girl.on('newListener', function (eventName) { // 当触发这个回调的时候 ,绑定的事件还未绑定成功 
    if (!pending) { // vue的nextTick  flushSqudualerQueue 异步更新原理
        Promise.resolve().then(() => {
            girl.emit(eventName); // []  ['哭']  [库,吃]  ['哭',’吃‘,’购物‘]
            pending = false;
        })
        pending = true;
     }
});

girl.on('我失恋了', function () { // {'我失恋了':[fn1,fn2,fn3],结婚了:[fn]}
    console.log('哭')
})
girl.on('我失恋了', function () {
    console.log('吃')
})
function shopping () {
    console.log('购物')
}
// girl.once('我失恋了',shopping )
girl.off('我失恋了',shopping )
// [fn1,fn2,fn3]
// 只要你绑定了这个事件 我就让你自动触发

girl.emit('我失恋了')
girl.emit('我失恋了')

const fs = require('fs');
// process

// 写命令行工具的时候  找运行的配置文件  webpack (会去当前目录下查找这个webpack.config.js 文件)
// vite  vite.config,js

// 环境变量的设置是在当前命令中设置的,切换到其他地方执行就不一样了
const path = require('path');
console.log(process.cwd(), path.resolve());  // 如果为了方便 完全可以只使用process.cwd 获取路径
// global上的属性 可以直接访问process    pwd print working directory

如果写一个.env的文件,需要怎么加到环境变量里(process.env里)?
const res = fs.readFileSync('.env', 'utf-8');
process.env[res.split('=')[0]] = res.split('=')[1];

console.log(process.env.VUE_APP_TITLEA, 'ee');
// env  webpack 当前的是环境变量 enveriorment

// axios baseURL  npm install cross-env  webpack vite rollup
let baseURL = ''

if (process.env.VUE_APP_TITLEA === 'dev') {
    baseURL = 'http://localhost:3000'; // 服务器在本地市话是3000
} else {
    baseURL = 'http://39.106.175.189'
}
console.log(baseURL); // 先设置环境变量在使用  在windows下可以使用 set 命令 对于mac export

// set xxx=1
// export xxx=1
// corss-env 

// argv  通过argv属性来获取用户传递的参数 webpack --config a 1 --port 3000 ...

// 默认argv是一个数组 1.可执行文件 2.执行的文件  other 所有用户的参数。。。。


// let args = process.argv.slice(2);

71744974a9425de685a03d2e02bd2be.png


// webpack --watch 
// let r = args.reduce((memo,current,index,arr)=>{
//     if(current.startsWith('--')){
//         memo[current.slice(2)] = arr[index+1];
//     }
//     return memo
// },{})
// console.log(r); // commander 可以实现命令行工具
// 组成一个用户参数列表


const { program } = require('commander'); // 用别人包来解析参数
const pkg = require('./package.json')
program.name('zf');
program.usage('xxx') //这个name与usage是链式调用 只能设置一个name与usage
program.version(pkg.version) --Version和--Help是内置的 
直接node 文件 --Version 就可以查到

program.option('-c|--config <x>','config file') 相当于 在终端执行-c
program.option('-p|--port <c>','sever port')
program.command('create [projectName]').action((args)=>{
    console.log(args)
})
// --config --port
program.on('--help',function(){
    console.log('\r\n my help')
})

program.parse(process.argv)
console.log(program.opts()); // commander 内部就是解析process.argv的


// process.env
// process.argv
// process.cwd
// process.nextTick


接下来看一看进制的讲解
// --------------------------------
Buffer.prototype.split = function (sep) { // sep 做分隔符
    // buffer 和字符串的长度不一样
    sep = Buffer.isBuffer(sep) ? sep : Buffer.from(sep);
    let offset = 0; // 不停的修改
    let position = 0; // 找到的位置
    const arr = [];
    while (-1 !== (position = this.indexOf(sep, offset))) {
        arr.push(this.slice(offset,position));
        offset = position + sep.length
    }
    arr.push(this.slice(offset));
    return arr;
}

let buffer10 = Buffer.from('**|**|**');  // 分割buffer  -> 分割成buffer[]
// console.log(buffer10.indexOf('我',7)); // 返回的是字节索引
console.log(buffer10.split('|')) // 实现一个buffer的split方法


// buffer.length !== 字符串长度 
// buffer.slice  可以截取buffer, buffer在截取的时候 截取的是内存
// concat 这个用的是最大
// Buffer.isBuffer() 判断是不是buffer
// buffer.indexOf('x',从哪里开始找)  类似于字符串的indexOf

// buffer的使用  没有数组中filter forEach   buffer 用法很像数组 也很像 字符串 (字符串具备不可变性)



Buffer.concat = function (list, len = list.reduce((a, b) => a + b.length, 0)) {
    let bigBuffer = Buffer.alloc(len);
    let offset = 0;
    for (let i = 0; i < list.length; i++) {
        list[i].copy(bigBuffer, offset);
        offset += list[i].length
    }
    return bigBuffer

}
let bigBuffer2 = Buffer.concat([buf5, buf6]);
console.log(bigBuffer2)



let bigBuffer = Buffer.alloc(6);
buf5.copy(bigBuffer, 0, 0, 2); // 第一次把两个字节拷贝过去
buf6.copy(bigBuffer, 2, 0, 4) // 第二次是把4个字节拷贝过去
console.log(bigBuffer.toString('utf8'));  // 拼接后一起消费


// 文件不能太大, 如果文件很大超过了内存大小 就无法读取了
// 一般认为 小于64k的都可以采用这种方式
fs.readFile(path.resolve(__dirname, 'package.json'), (err, data) => {
    if (err) return console.log(err);
    fs.writeFile(path.resolve(__dirname, 'a.txt'), data, function (err) {
        if (err) return console.log(err);
        console.log('写入成功')
    })
})

一般来说我们先会读流

// 返回的是一个可读流对象
const rs = fs.createReadStream(path.resolve(__dirname,'a.txt'),{ // 可读流一次可以读取64k
    flags: 'r',// fs.open
    encoding:null, // 默认就是buffer
    mode:0o666, // fs.open(mode)
    autoClose: true, // 读取后是否自动关闭
    emitClose:true, // 是否内部触发一个事件 emit('close')
    start:0, // 从哪到哪
    end:5, // 0 - 5 6个字节 包前又包后
    highWaterMark:2,// 64k  每次读取多少个 默认64k
});

rs.on('open',function(fd){
    console.log(fd);
})
// 默认会在内部递归的将数据读取完毕
rs.on('data',function(data){ // fs.read() -> rs.emit('data',读取到的数据)
    console.log(data)
    // 我们希望能控制读取的速率 ,读完后 等我写入到文件中了 在去读取
    rs.pause(); // 不会触发data事件   配合可写流 
})
rs.on('end',function(){ // 当指定的内容读取完毕后 会触发end事件   rs.emit('end')
    console.log('读取完毕')
})
rs.on('close',function(){ //  rs.emit('close')
    console.log('close');
    clearTimeout(timer)
})
rs.on('error',function(){  //  rs.emit('error')
    console.log('error')
}) 
let timer = setInterval(()=>{
    rs.resume(); // 1s 后恢复读取 再触发data事件
},1000)

来看下WriteStream

const ws =  new WriteStream(path.resolve(__dirname,'b.txt'),{
    //  可读流的 highWaterMark 每次读取多少个   可写流: 我期望每次能写入多少
    highWaterMark: 3, // 默认值是16k
    flags:'w', //  写入的标识
    encoding:'utf8',
    start:0, // 开始写入的位置 没有end
    emitClose:true // 是否触发close
});

// 我期望 用3个内存大小的字节 来进行整个写入的操作
let i = 0; // 0-9
function write(){
    let flag = true;
    while(flag && i<=9){
            flag = ws.write(i++ + '','utf8',()=>{
                console.log( '写入了')
            }); // 当写入的时候达到了预期的时候 此时flag 就会变为false
      
    }
    if(i > 9){
         ws.end('ok');
    }
}
write();
ws.on('drain',()=>{ // 等待内存全部写入到文件中后,会触发drain. drain事件只有当达到预期值 (并且消耗掉)的时候才能触发
    console.log('drain')
    write();
})

其实在工作中 我们用的最多的就是

const ReadStream = require('./readstream');
const WriteStream = require('./writeStream');
const path = require('path');


const rs = new ReadStream(path.resolve(__dirname,'a.txt'),{
    highWaterMark:4 // 64k
});

const ws = new WriteStream(path.resolve(__dirname,'b.txt'),{
    highWaterMark:1 // 16k
})

// pipe 可以实现 从 a->b  效果是读一点写一点 是异步的
rs.pipe(ws);

当然还有其它写法,了解一下就行

const { Readable, Writable } = require('stream');

class MyReadStream extends Readable {
    _read() {
        this.push('123');
        this.push(null); // push null 后会调用end方法
    }
}
let mrs = new MyReadStream();
mrs.on('data', function (chunk) { // 内部默认调用的是Readable中的.read , 会去调用自己写的 _read
    console.log(chunk)
})

mrs.on('end', function () {
    console.log('读取完毕了')
})


// 可读流内部实现原理 就是 内部创建后 当用监听了data事件 就会触发Readable 对应的read方法
// 默认会调用子类的_read 方法 (具体实现你可以自己实现 只是fs中用了fs.read方法)
// 将自己造的数据放到Readable.push方法中, 内部会自动触发data事件 将结果抛出来

class MyWriteStream extends Writable {
    _write(chunk, encoding, withClearBufferCallback) {
        console.log(chunk,'---'); // 第一次真的调用的是 _write 后续都放到缓存中了
        withClearBufferCallback();
    }
}
let mws = new MyWriteStream();
mws.write('ok')
mws.write('ok')
mws.write('ok')
mws.write('ok')
mws.end('123')

这个就是链表

// 队列 栈 链表 线性结构
class Node {
    constructor(element, next) {
        this.element = element; // 存放当前节点的内容
        this.next = next; // 下一个指针
    }
}

class LinkedList {
    constructor() {
        this.head = null;
        this.size = 0; // 当前链表存放数据的个数
    }
    getNode(index) {
        let current = this.head; // 根据头部一直向下查找
        for (let i = 0; i < index; i++) {
            current = current.next;
        }
        return current; // 找到所引处返回
    }
    add(element, index = this.size) {
        if (index === 0) {
            let head = this.head; // 先把老的头部拿到 
            this.head = new Node(element, head); // 创造新节点替换掉老节点
        } else {
            // 通过索引添加 要循环找到对应的元素在添加 
            let prevNode = this.getNode(index - 1);// 获取前一个节点
            prevNode.next = new Node(element, prevNode.next);

        }
        this.size++;
    }
    remove(index) {
        let node;
        if (index == 0) {
            if (this.head) {
                node = this.head;
                this.head = this.head.next; // 将头部的内容指向下一个节点 , 第一个就删掉了
                this.size--;
            }
        } else {
            let preveNode = this.getNode(index - 1);
            node = preveNode.next; // 要删除的节点
            preveNode.next = node.next;
            this.size--;

        }
        return node;
        // 删除返回删除后的节点
    }
    reverse1() {
        let head = this.head;
        if (head == null || head.next == null) return head;    // 一个节点没有, 或者就一个节点
        let newHead = null;
        while (head) { // 把链表的内容 整个搬运一遍,什么时候没有了就没了
            let temp = head.next; // 先把除了对一个节点的节点保存一下
            head.next = newHead; // 将第一个头 指向新接的节点
            newHead = head; // 让新链表的头指向这个节点
            head = temp; // 将原来的链表更新
        }
        return newHead
    }

}

来看下二叉树

1638798702(1).png

class Node {
    constructor(element, parent) {
        this.element = element;
        this.parent = parent;
        this.left = null;
        this.right = null;
    }
}
class BST {
    constructor(compare) {
        this.size = 0;
        this.root = null;

        let defaultCompare = this.compare;
        this.compare = compare || defaultCompare;
    }
    compare(e1, e2) {
        return e1 - e2
    }
    add(element) {
        if (this.root === null) {
            this.root = new Node(element, null);
        } else {
            // this.insertNode(this.root, element)
            let current = this.root;
            let parent;
            let v;
            while (current !== null) {
                // v 决定是放左边还是放右边
                v = this.compare(element, current.element); // 用当前的插入元素 和 根元素比较
                parent = current; // parent决定了把数据放在谁的下面
                if (v < 0) {
                    current = current.left; // 如果比当前节点小的 放在左边
                } else if (v > 0) {
                    current = current.right
                } else {
                    current.element = element; // 直接用新的覆盖掉老的
                }
            }
            // current = null
            let node = new Node(element, parent)
            if (v < 0) {
                parent.left = node
            } else {
                parent.right = node
            }
        }
        this.size++;
    }
    // 先序遍历
    prevOrderTraversal(visitor) { // 自己实现以下 非递归的 先序中序 后续
        let flag = false
        const traversal = (node) => {
            if (node == null || flag) return;
            flag = visitor.visit(node.element);  // 当前 左边 右边
            traversal(node.left);
            traversal(node.right)
        }
        traversal(this.root);
    }
    inorderTraversal(visitor) {
        let flag = false
        const traversal = (node) => {
            if (node == null || flag) return;
            traversal(node.left); // 左边已经有节点是6 了 就不要在继续访问左边了   8   6
            if (flag) return
            flag = visitor.visit(node.element); // 左边 当前  右边
            traversal(node.right)
        }
        traversal(this.root);
    }
    postorderTranversal(visitor) {
        let flag = false
        const traversal = (node) => {
            if (node == null || flag) return;
            traversal(node.left); // 右边已经有节点是6 了 就不要在继续访问左边了
            traversal(node.right) // 左边 右边中间
            if (flag) return
            flag = visitor.visit(node.element);
        }
        traversal(this.root);
    }
    levelOrderTraversal(visitor) {
        if (this.root == null) return;
        let stack = [this.root];
        let index = 0;
        let currentNode;
        while (currentNode = stack[index++]) {
            if (visitor.visit(currentNode.element)) return;
            if (currentNode.left) {
                stack.push(currentNode.left)
            }
            if (currentNode.right) {
                stack.push(currentNode.right)
            }
        }
    }
    invert() {
        const traversal = (node) => {
            if (node == null ) return;
            const temp = node.left; // 左右交换
            node.left = node.right;
            node.right = temp;
            traversal(node.left); // 左右交换了 , 接着交换
            traversal(node.right)
        }
        traversal(this.root);
    }
}
let bst = new BST((e1, e2) => {
    return e1.age - e2.age
});
// 二叉搜索树的特点是存储的数据 必须有课比较性
// 数据存储的类型 必须是数字吗?  sort Api  支持数字的排序  传入排序器
[{ age: 10 }, { age: 8 }, { age: 6 }, { age: 19 }, { age: 15 }, { age: 22 }, { age: 20 }].forEach(item => {
    bst.add(item);
})
bst.levelOrderTraversal({
    visit(element) { // 后面我们使用babel的时候 遍历语法树的时候 写法就是这样的
        console.log(element)
        return element.age == 6; // 返回true 则停止遍历
    }
});
// diff算法 目录操作  路由配置  权限配置 ---- 树  常用的就是遍历树 和 格式化数据
// 如何反转二叉树 ?  homebrew 

bst.invert();
console.dir(bst,{depth:20})

第二种写法

class Node {  
    constructor(element, parent) {
        this.element = element;
        this.parent = parent;
        this.left = null;
        this.right = null;
    }
}
class BST {
    constructor(compare) {
        this.size = 0;
        this.root = null;

        let defaultCompare = this.compare;
        this.compare = compare || defaultCompare;
    }
    compare(e1, e2) {
        return e1 - e2
    }
    add(element) {
        if (this.root === null) {
            this.root = new Node(element, null);
        } else {
            // this.insertNode(this.root, element)
            let current = this.root;
            let parent;
            let v;
            while (current !== null) {
                // v 决定是放左边还是放右边
                v = this.compare(element, current.element); // 用当前的插入元素 和 根元素比较
                parent = current; // parent决定了把数据放在谁的下面
                if (v < 0) {
                    current = current.left; // 如果比当前节点小的 放在左边
                } else if (v > 0) {
                    current = current.right
                } else {
                    current.element = element; // 直接用新的覆盖掉老的
                }
            }
            // current = null
            let node = new Node(element, parent)
            if (v < 0) {
                parent.left = node
            } else {
                parent.right = node
            }
        }
        this.size++;
    }
    // 这里想用非递归的方式 遍历树 可以采用栈的形式来实现 , 前序遍历 将左右都存放起来 ,弹出左边,最后弹出右边
    prevOrderTraversal(visitor) {
        if (this.root == null) return;
        let stack = [this.root]; // 先进入的后出
        // 按照存放的顺序 去入栈,之后倒叙出栈  
        // 队列 先进的先出  层序遍历
        // 栈 是先进的后出 后进的先出
        while (stack.length > 0) { // 栈中有内容 我们就不停的循环 
            let node = stack.pop();
            if (visitor.visit(node.element)) return;
            if (node.right !== null) {
                stack.push(node.right)
            }
            if (node.left !== null) {
                stack.push(node.left)
            }
        }
    }
    // 中序遍历 就是将左边不停的入栈 , 弹出后访问在看有没有右边
    inorderTraversal(visitor) {
        if (this.root == null) return;
        let stack = [];
        let node = this.root;// 第一个节点 
        while (true) {
            if (node !== null) {
                stack.push(node);
                node = node.left; // 不停的找左边 
            } else if (stack.length === 0) {
                return;
            } else {
                node = stack.pop(); // 弹出来一个就访问一个
                if (visitor.visit(node.element)) return; // 交给用户访问节点
                node = node.right
            }
        }
    }
    // 后续遍历 就是左右都放入 ,先弹出左边,在弹出右边 ,最后访问自己
    postorderTranversal(visitor) {
        if (this.root == null) return;
        let stack = [this.root];
        let node;
        while (stack.length > 0) {
            let current = stack[stack.length - 1]; // 取出最后一个 
            if ((current.left == null && current.right == null) || (node && node.parent == current)) { // 1永远有左右
                node = stack.pop(); // 左边出来
                if (visitor.visit(node.element)) return
            } else {
                if (current.right !== null) { // 有右边 我就先扔到栈中
                    stack.push(current.right); // 先将右边放入  先放右在放左  出来的就是左 
                }
                if (current.left !== null) {
                    stack.push(current.left); // 先将右边放入  先放右在放左  出来的就是左 
                }
            }
        }
    }
}
let bst = new BST((e1, e2) => {
    return e1.age - e2.age
});

[{ age: 11 }, { age: 8 }, { age: 6 },{ age: 7 }, { age: 19 },{age:22},{age:20},{ age: 9 }].forEach(item => {
    bst.add(item);
})
bst.inorderTraversal({
    visit(element) {
        console.log(element)
    }
});

接下来来看操作文件目录



// 目录操作 
const fs = require('fs');
const path = require('path')
// fs.mkdir (fs.existsSync -> fs.access -> fs.stat  都可以判断文件的存在性 stat用的比较多)
// fs.rmdir (默认支持递归删除)
// 异步api  
// mkdir('a/b/c',function(err){ // mkdir -p a/b/c
//     console.log(err)
// })


// 删除的过程中需要先删除子目录 在删除 父目录
// dirs = fs.readdirSync('a'); // 只能读取子目录
// dirs = dirs.map((item)=>path.join('a',item)); // a/b  a/e  a/f a/index.js
// dirs.forEach(item=>{
//     let statObj = fs.statSync(item);
//     if(statObj.isDirectory()){
//         fs.rmdirSync(item)
//     }else{
//         fs.unlinkSync(item);
//     }
// });
// fs.rmdirSync('a')

// fs.readdir 读取子目录
// fs.stat 判断状态 isDirectory isFile
// fs.rmdir 删除目录 
// fs.unlink
// fs.acess


// fs.writeFileSync('a/b/c/index.js','aaa')


// (tapble)  目前现在写的是异步串行


这个是异步并行
let { stat, unlink, readdir, rmdir } = require('fs').promises;
async function rmdir4(dir) {
    let statObj = await stat(dir)  // 如果是文件 删除即可
    if (statObj.isFile()) {
        await unlink(dir)
    } else {
        let dirs = await readdir(dir);
        // 删除完毕儿子后 删除自己
        await Promise.all(dirs.map(item => rmdir4(path.join(dir, item))))
        await rmdir(dir);
    }
}
// rmdir4('a').then(() => {
//     console.log('ok 删除成功')
// }).catch(err=>{
//     console.log(err)
// })

// 异步串行 异步并行 这个就是异步串行

1638800939(1).png

function rmdir5(dir) {
    let stack = [dir];;
    let index = 0;
    let current;
    while (current = stack[index++]) {
        let stats = fs.statSync(current);
        if (stats.isDirectory()) {
            stack = [...stack, ...fs.readdirSync(current).map(item => path.join(current, item))]
        }
    }
    index = stack.length - 1
    while (index >= 0) {
        let current = stack[index--];
        let stats = fs.statSync(current);
        if (stats.isDirectory()) {
            fs.rmdirSync(current);
        } else {
            fs.unlinkSync(current)
        }
    }
}
rmdir5('a');

说到底如果真的想删除文件夹直接
fs.rmdir('a',{recursive:true})