日常笔记

162 阅读15分钟

Promise

1.什么是Promise

  • Promise是异步编程解决方案,承诺它过一段时间会给你一个结果,为了解决多个异步请求的回调地狱问题。
  • 有三种状态: 1.成功态:resolved 2.失败态:rejected 3.等待态:pending pending => resolved; pending => rejected; resolved和rejected不能相互转化
  1. 用法
let p = new Promise(function(reslove,reject){
    //reslove('成功')  //状态由等待变为成功,传的参数作为then函数中成功函数的实参
    reject('失败')  //状态由等待变为失败,传的参数作为then函数中失败函数的实参
})
//then中有2个参数,第一个参数是状态变为成功后应该执行的回调函数,第二个参数是状态变为失败后应该执行的回调函数。
p.then((data)=>{
    console.log('成功'+data)
},(err)=>{
    console.log('失败'+err)
})
setTimeout(function () {
  console.log('setTimeout')
}, 0);
let p =  new Promise(function (resolve,reject) {
  // 如果在这里调用了resolve 就会变成成功态
  // 同时调用resolve 和reject 只会执行一个
  reject();
  resolve();
});
// 每个promise(实例) 都拥有一个then方法
// then方法是一个异步方法,默认不会再当前的上下文中执行 setTimeout
// 再异步编程中 会给异步方法 编造两个序号 宏任务 setTimeout 微任务promise
p.then(function () { // 成功
  console.log('成功')
},function () { // 失败
  console.log('失败')
});
console.log('xxx');

结果: xxx => 失败 => setTimeout


setTimeout(function () {
  console.log(1);
}, 0)
new Promise(function (resolve) {
  console.log(2);
  for (var i = 0; i < 100; i++) {
    i == 99 && resolve();
  }
  console.log(3);
}).then(function () {
  console.log(4);
})
console.log(5);
//2,3,5,4,1

首先Promise内部直接执行,2=>3 异步 then 所以先执行 5 然后微任务 4 ,最后 1 简易版promise实现

// 1.promise需要有三个状态

function Promise(executor){
  let self = this;
  self.status = 'pending'
  self.value = undefined;
  self.reason = undefined;
  self.onResolvedCallbacks = [];
  self.onRejectedCallbacks = [];
  // 只有状态是pending 参能进行状态的转化
  function resolve(value) {
    if(self.status === 'pending'){
      self.value = value;
      self.status = 'fulfilled';
      self.onResolvedCallbacks.forEach(function (fn) {
        fn();
      });
    }
  }
  function reject(reason) {
    if(self.status === 'pending'){
      self.reason = reason;
      self.status = 'rejected';
      self.onRejectedCallbacks.forEach(function (fn) {
        fn();
      })
    }
  }
  try{
    executor(resolve, reject); // 如果执行这个executor执行时候抛出异常 应该走下一个then的失败
  }catch(e){
    reject(e);// 出错了 reason就是错误
  }
}
Promise.prototype.then = function (onFulfilled, onRejected) {
  let self = this;
  if (self.status === 'fulfilled'){
    onFulfilled(self.value);
  }
  if (self.status === 'rejected'){
    onRejected(self.reason);
  }
  if( self.status === 'pending'){
    // 默认当前 new Promise  executor中是有异步的
    self.onResolvedCallbacks.push(function () {
      onFulfilled(self.value);
    });
    self.onRejectedCallbacks.push(function () {
      onRejected(self.reason);
    })
  }
}

module.exports = Promise

完整版

// 1.promise需要有三个状态

function Promise(executor){
  let self = this;
  self.status = 'pending'
  self.value = undefined;
  self.reason = undefined;
  self.onResolvedCallbacks = [];
  self.onRejectedCallbacks = [];
  // 只有状态是pending 参能进行状态的转化
  function resolve(value) {
    if(self.status === 'pending'){
      self.value = value;
      self.status = 'fulfilled';
      self.onResolvedCallbacks.forEach(function (fn) {
        fn();
      });
    }
  }
  function reject(reason) {
    if(self.status === 'pending'){
      self.reason = reason;
      self.status = 'rejected';
      self.onRejectedCallbacks.forEach(function (fn) {
        fn();
      })
    }
  }
  try{
    executor(resolve, reject); // 如果执行这个executor执行时候抛出异常 应该走下一个then的失败
  }catch(e){
    reject(e);// 出错了 reason就是错误
  }
}
// 核心方法 处理 成功或者失败执行的返回值 和promise2的关系
function resolvePromise(promise2,x,resolve,reject) {
  // 这个处理函数 需要处理的逻辑韩式很复杂的
  // 有可能这个x 是一个promise  但是这个promise并不是我自己的
  if(promise2 === x){
   throw new TypeError('TypeError: Chaining cycle detected for promise #<Promise>')
  }
  // 不单单需要考虑自己 还要考虑 有可能是别人的promise
  let called; // 文档要求 一旦成功了 不能调用失败
  if((x!=null&&typeof x=== 'object') || typeof x === 'function'){
    // 这样只能说 x 可能是一个promise
    try{
      // x = {then:function(){}}
      let then = x.then; // 取then方法
      if(typeof then === 'function'){
        then.call(x,function (y) { // resolve(new Promise)
          if(!called){called = true;} else{ return;}
          resolvePromise(x,y,resolve,reject); //  递归检查promise
        },function (r) {
          if (!called) { called = true; } else { return; }
          reject(r);
        });
      }else{ // then方法不存在
        resolve(x); // 普通值
      }
    }catch(e){ // 如果取then方法出错了,就走失败
      if (!called) { called = true; } else { return; }
      reject(e);
    }
  }else{
    resolve(x);
  }
}
Promise.prototype.then = function (onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled:function (data) {
    return data
  }
  onRejected = typeof onRejected === 'function' ? onRejected:function (err) {
    throw err;
  }
  let self = this;
  let promise2; // 这个promise2 就是我们每次调用then后返回的新的promise
  // 实现链式调用主要的靠的就是这个promise
  promise2 = new Promise(function (resolve,reject) {
    if (self.status === 'fulfilled') {
      // 这个返回值是成功函数的执行结果
      setTimeout(() => {
        try{
          let x = onFulfilled(self.value);
          // 判断promise2 和 x 也是then函数返回的结果和promise2的关系 如果x 是普通值 那就让promise2成功 如果 是一个失败的promise那就让promise2 失败
          resolvePromise(promise2, x, resolve, reject);
        }catch(e){
          reject(e);
        }
      }, 0);
    }
    if (self.status === 'rejected') {
      setTimeout(() => {
        try{
          let x = onRejected(self.reason);
          resolvePromise(promise2, x, resolve, reject);
        }catch(e){
          reject(e)
        }
      },0)
    }
    if (self.status === 'pending') {
      // 默认当前 new Promise  executor中是有异步的
      self.onResolvedCallbacks.push(function () {
        setTimeout(() => {
          try{
            let x = onFulfilled(self.value);
            resolvePromise(promise2, x, resolve, reject);
          }catch(e){
            reject(e)
          }
        }, 0);
      });
      self.onRejectedCallbacks.push(function () {
        setTimeout(() => {
          try{
            let x = onRejected(self.reason);
            resolvePromise(promise2, x, resolve, reject);
          }catch(e){
            reject(e);
          }
        }, 0);
      })
    }
  });
  return promise2;
  
}
// npm install promises-aplus-tests -g
Promise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected);
}
// finally 也是then的一个简写
Promise.prototype.finally = function (cb) {
  // 无论成功还是失败 都要执行cb 并且把成功或者失败的值向下传递
  return this.then(function (data) {
    cb();
    return data;
  }, function (err) {
    cb();
    throw err;
  });
}
// 类调用的都叫静态方法
Promise.reject = function (reason) {
  return new Promise(function (resolve, reject) {
    reject(reason);
  })
}
Promise.resolve = function (value) {
  return new Promise(function (resolve, reject) {
    resolve(value);
  })
}
Promise.deferred = Promise.defer = function () {
  let dfd = {};
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve;
    dfd.reject = reject;
  })
  return dfd
}
Promise.all = function (promises) {
  return new Promise(function (resolve, reject) {
    let arr = [];
    // 处理数据的方法
    let i = 0;
    function processData(index, data) {
      arr[index] = data; //数组的索引和长度的关系
      if (++i === promises.length) { // 当数组的长度 和promise的个数相等时 说明所有的promise都执行完成了
        resolve(arr);
      }
    }
    for (let i = 0; i < promises.length; i++) {
      let promise = promises[i];
      if (typeof promise.then == 'function') {
        promise.then(function (data) {
          processData(i, data); // 把索引和数据 对应起来 方便使用
        }, reject)
      } else {
        processData(i, promise);
      }
    }
  });
}
Promise.race = function (promises) {
  return new Promise(function (resolve, reject) {
    for (let i = 0; i < promises.length; i++) {
      let promise = promises[i];
      if (typeof promise.then == 'function') {
        promise.then(resolve, reject)
      } else {
        resolve(promise);
      }
    }
  })
}
module.exports = Promise;

ES6

ES2015之后的版本统称为ES6

  • 展开运算符
// 剩余运算符号 只能放在最后一项
function sum(...args) { 
  let arr = [1, 6];
  return [...arr, ...args]
}
let r = sum(1, 2, 3, 4, 5);
console.log(r); => [ 1, 6, 1, 2, 3, 4, 5 ]
  • 浅拷贝与深拷贝 JSON.parse(JSON.stringify());//缺点:正则,函数等就不适用了
let school = {
  name: 'jeffywin', 
  age: 1, 
  a: null, 
  d: new Date(), 
  reg: /reg/, 
  fn: function () {
  }
};
function deepClone(obj){
  if (obj == null) return obj; // null == undefined
  if(obj instanceof Date) return new Date(obj);
  if(obj instanceof RegExp) return new RegExp(obj);
  if (typeof obj !='object') return obj;
  //Object.prototype.toString.call()==[] ? [] : {}
  let newObj = new obj.constructor;
  for (let key in obj){
    newObj[key] = deepClone(obj[key]);
  }
  return newObj
}
let d = deepClone(school); // null  undefined
console.log( d);

function A() {}
let a = new A()
console.log(A.prototype.constructor === a.constructor) //true
  • 箭头函数
let a = x => y => ({ sum: x + y });
let r = a(1)(2);
console.log(r);
let a = 'jeffywin'
let obj = {
  a:'jw',
  fn(){
      console.log(this.a)
  }
}
obj.fn(); //jw obj调用,this指向obj 
let a = 'jeffywin'
let obj = {
  a:'jw',
  fn(){
    setTimeout(function(){
      console.log(this.a); 
    }, 0);
  }
}
obj.fn(); //undefined window.setTimeout,window上没有属性a,所以undefined,let声明不会到window

let a = 'jeffywin'
let obj = {
  a:'jw',
  fn(){
    setTimeout(function(){
      console.log(this.a); 
    }.bind(this), 0);
  }
}
obj.fn(); //jw bind改变this指向,相当于加了一句 let that = obj

let a = 'jeffywin'
let obj = {
  a:'jw',
  fn(){
    setTimeout(() => {
      console.log(this.a); 
    }, 0);
  }
}
obj.fn(); //jw setTimeout加了箭头函数,this找不到,往上找,发现obj
//如果是对象 不是作用域 如果是箭头函数this 会继续向上级作用域查找
let a = 'jeffywin'
let obj = {
  a:'jw',
  fn:() => {
    setTimeout(() => {
      console.log(this.a); 
    }, 0);
  }
}
obj.fn(); //undefined 箭头函数找上一个作用域,obj={}是对象,不是作用域,所以继续往上window.a
  • Object.defineProperty
let obj = {};
let temp = ''
Object.defineProperty(obj,'name',{ // 属性描述器
  enumerable:true, // 非隐藏属性 可以列举出来这个属性
  configurable:true, // 是否可配置 默认不可配置,true可以删除对象属性
  //writable:true, // 是否可以被改写,或者设置get,和set
  get(){
    console.log('get')
    return temp
  },
  set(value){
    console.log('set')
    temp = value;
  }
});
obj.name ='jeffywin';
console.log(obj.name);

简写版

let o = { // 属性访问器 setter 和getter 上面写法的简写
  temp:'',
  get name(){
    return this.temp;
  },
  set name(val){
    this.temp = val;
  }
}
o.name = 'hello'
console.log(o.name);

简易版MVVM


// 数据劫持

function update(){
  console.log('数据改变了 刷新视图')
}

function observer(o) { // 把当前对象上的所有属性 都改写成 Object.defineProperty的形式
  if(typeof o !== 'object'){return o;}
  for(let key in o){
    defineReactive(o,key,o[key]);
  }
}
function defineReactive(obj,key,value) {
  observer(value); // 只要是对象 就要不停的去监控
  Object.defineProperty(obj,key,{
    get(){return value;},
    set(val){
      observer(val)//监控
      if(val !== value){ // 保证设置的属性 和以前的值不一样才更新
        update();
        value = val;
      }
    }
  })
}

let obj = {
  name:'jeffywin',
  age:{age:9}
}

observer(obj);
obj.age = {name:'jeffywin'};

Object.defineProperty 他不支持数组; proxy + reflect 兼容性不好

  • proxy 代理
let obj = ['zfpx']
let proxy = new Proxy(obj,{ // 代理的属性 13种 
  set(target,key,value){
    if(key === 'length') return true; //数组长度变化
    console.log('数据更新了')
    return Reflect.set(target, key, value);
  },
  get(target,key){ // 可以使用reflect
    return Reflect.get(target, key) 
  }
});
proxy.push('1'); // 深度监控 (可以递归 也可以具体拿到某个对象实现)
console.log(obj)

let obj = {name: 'jeffywin'}
let p = new Proxy(obj,{
  set(target,key,value) {
    console.log('set',target,key,value)
    return Reflect.set(target,key,value)
  },
  get(target,key) {
    console.log('get',target,key)
    return Reflect.get(target,key)
  }
})

//p.name = '11'
console.log(p.name)
  • 类 构造函数 模拟类的 实例都是通过new 类产生的 实例上的属性 类上的属性 (静态属性) 公共属性
    类(构造函数)才会有原型prototype;所有类型会有__proto__ 相当于一个链

function Animal(name){
  this.name = name;
}
// 公有属性
Animal.prototype.info = { time: '100' }
let animal1 = new Animal('哺乳类');
let animal2 = new Animal('哺乳类');
console.log(animal1.info === animal2.info) //true
console.log(animal1.__proto__ == Animal.prototype); //true
console.log(animal1.__proto__.constructor === Animal); //true
console.log(Animal.prototype.__proto__ == Object.prototype); //true
console.log(Object.prototype.__proto__); //null 

  • 继承
function Animal(name) {
  this.name = name;
}
Animal.prototype.info = { time: '100' }
function Cat(name) {}
- 继承实例上的属性 Parent.call()
function Cat(name) {
  Animal.call(this,name);
}
let cat = new Cat('小花')
console.log(cat.name); 

- 继承公共属性
1. Object.setPrototypeOf(Cat.prototype, Animal.prototype); 
2. Cat.prototype = Object.create(Animal.prototype, {constructor:{value:Cat}});
let cat = new Cat('小花花');
console.log(cat.info);//{ time: '100' }

es6写法
class Cat extend Animal{}

-es6继承

class Parent {
  a = 'hello'; //实例上的属性
  constructor(age){
    this.age = age; //实例上的属性
  }
  drink = () =>{ // es7 语法 保证this指向的  子类实例调用drink()就不报错了
    console.log(this)//原型上的属性 prototype,
  }
}
// extends 默认会继承实例上的属性 和 原型上的属性
// Child.__proto__ = Parent
class Child extends Parent{
  constructor(val){
    super(val);
  }
}
let child = new Child(5);//把5传递父类
let drink =  child.drink;  // 不能把原型上的方法拿出来调用否则this无指向
drink();
// static 属性都是es7 提供的
// babel 把es6 -> es5  @babel/core @babel/cli @babel/preset-env @babel/plugin-proposal-class-properties就是把es高级转化成低级语法, 自己安装其他插件
// 运行  npx babel 文件名 -o 2.es5class.js(编译后的名字)
  • reduce 收敛
function sum(a, b) {//1
  return a + b;
}
function toUpper(str) {//2
  return str.toUpperCase();
}
function add(str) {//3
  return 'jeffywin' + str
}
let compose = (...fns) => (...args)=>{//fns:(add,toUpper,sum) args:(a,b)
  let fn = fns.pop(); //sum
  let r = fn(...args);//sum(a,b)
  return fns.reduceRight((a,b)=>{//a:toUpper  b:add
    return b(a)
  },r);
}
let composeFn = compose(add,toUpper,sum)
console.log(composeFn('a','b'))

let compose1 = (...fns) => {
  return fns.reduce((a,b) =>(...args) => a(b(...args)))
}

let compose = (...fns) => fns.reduce(function (a, b) {
  return function (...args) {
    return a(b(...args))
  }
});

// add(toUpper(args))
let composeFn = compose1(add,toUpper, sum);
let result = composeFn('a', 'b');
console.log(result);


Array.prototype.reduce = function (callback, prev) {
  for (let i = 0; i < this.length; i++) {
    if (prev != null) {
      prev = callback(prev, this[i], i, this);
    } else {
      prev = callback(this[i], this[i + 1], i + 1, this);
      i++; // 如果没有prev 内部会迭代两次 所以迭代后i++
    }
  }
  return prev
}
let r = [1, 2, 3].reduce((a, b, currentIndex, arr) => {
  console.log(currentIndex)
  return (
    a + b
  )
});
  • EventLoop 浏览器
setTimeout(() => {
  console.log('0');
  setTimeout(() => {
    console.log('哈哈');
  }, 0);
}, 0);
setTimeout(() => {
  console.log('1')
}, 0);//0=>1=>哈哈

首先两个定时器放到callback queue中,等栈清空后开始执行定时器, //执行第一个定时器的时候,发现又有定时器,循环,再排队在队列中,排到第三个等待执行

Promise.resolve().then(function () {
  console.log('then1');
  setTimeout(() => {
    console.log('timer1')
  }, 0);
})
setTimeout1(() => {
  console.log('timer2')
  Promise.resolve().then(function () {
      console.log('then2')
  })
}, 0);//then1 => timer2 =>then2 => timer1

//首先promise放进微任务队列,下面的settimeout1放进宏任务队列,then1微任务执行,检测到发现内有settimeout,排队到settimeout1之后,宏任务队列中 //接着微任务就执行完了,销毁,开始执行宏任务,timer2,发现有promise,排队到微任务队列中,then2,然后timer1

  • node

process 进程:计算机分配资源的一个基本单位;js 是单线程的 (主线程是单线程的) 一个进程里 只有一个主线程;

  1. process.cwd() current working directory code runner 当前执行的文件夹
  2. __dirname 是一个绝对路径代表的当前文件所在的目录
  3. __filename 代表当前文件的文件名 绝对路径
set NODE_ENV=dev 
process.env.NODE_ENV 环境变量
// node 2.node.js --port 3000 --dir home 设置变量
// {} '--port'
// { port: '3000' } '3000'
// { port: '3000' } '--dir'
// { port: '3000', dir: 'home' } 'home'
let r = process.argv.slice(2).reduce((memo,b,index,arr)=>{
  //index: 0,1,2,3 arr:[ '--port', '3000', '--dir', 'home' ] 
  //obj={},obj['port'] = 3000 => //obj['dir'] = home=>{port: 3000, dir: home}
  if(b.includes('--')){
    memo[b.slice(2)] = arr[index+1];
  }
  return memo;
},{});
  • EventLoop node环境

Promise.resolve().then(()=>{
  console.log('then1');
  setTimeout(() => {
    console.log('timer1');
  }, 0);
})
setTimeout(() => {
  console.log('timer2');
  Promise.resolve().then(() => {
    console.log('then2');
  })
}, 0);

// 首先微任务队列先执行then1,下面的settime放进定时器队列,执行timer2,当promise执行完发现有定时器,
// 这时候会等待时间到了才会把time1放到time2后面,这时候可能时间还没到,又接着切换队列轮询执行微任务,then2,然后time1
// 所以time1时间没到  then1 timer2 then2 timer1,时间到了then1 timer2 timer1 then2

  • module

为什么要模块化:防止变量 污染全局 防止代码重名 闭包实现 一个不销毁的作用域

let r = function(){
    module.exports = 'xx'
    return module.exports
}()

commonjs node模块化规范(require) es6通用模块化方案(import) node 里面有三种模块 require('fs'); 内置模块 核心模块 require('bluebird') 下载 第三方模块 require('./promise.js') 文件模块 模块化简易实现

// require方法 
// Module._load 加载模块
// Module._resolveFilename 解析文件名 把一个相对路径转化成绝对路径 加一个.js后缀
// Module._cache 存放缓存的
// 如果没有缓存 new Module 就创建一个模块
// Module 中 1) id 路径 2) exports ={}
// 把模块缓存起来 绝对路径
// tryModuleLoad 尝试加载模块 load();     
// 如果是json 按照json 来处理 如果js 按照js的方式来处理
// Module.extensions[];
let path = require('path');
let fs = require('fs');
let vm = require('vm');
function Module(id) {
  this.id = id;
  this.exports = {}
}
Module.wrap = function (script) {
  return `(function (exports, require, module, __filename, __dirname) {
      ${script}
  })`
}
Module._extensions = {
  '.js'(module){
    let content = fs.readFileSync(module.id, 'utf8');
    let fnStr = Module.wrap(content);
    let fn = vm.runInThisContext(fnStr);
    fn.call(module.exports, module.exports, req, module); // module.exports = 'hello';
  },
  '.json'(module){
    let content = fs.readFileSync(module.id,'utf8');
    module.exports = JSON.parse(content);
  }
}
function req(p) {
  let readlPath = path.resolve(__dirname,p); // 把相对路径转化成绝对路径
  let module = new Module(readlPath); // {id:'xxxx/a.json',exports = {}}
  let extName = path.extname(module.id);
  Module._extensions[extName](module);
  return module.exports;
}
let r = req('./a.js');
 req('./a.js');
console.log(r);

  • fs file system文件系统

chmod -R 777 * r(read) w(write) a(append) 2:读的权限 4:写的权限 1:执行 0o666 可读可写权限; fs 文件操作一般 都有同步 (模块加载)和 异步两种情况; 采用的都是异步方式 (不会阻塞主线程); 读取出来的文件内容 默认编码为null null 表示是buffer类型; 写的时候 默认会以utf8 来写入到文件中;

  1. 读取 fs.readFileSync同步 fs.readFile异步
 fs.readFile('./2.txt',{encoding:'utf8',flag:'r'},function (data) {
    console.log(data);
 })

// 1) 编码问题1:我们读取的文件可能会有编码问题
// 爬虫 爬别人的网页 人家的网页就是gbk     yarn add iconv-lite
let path = require('path');
let iconvLite = require('iconv-lite');
let result = fs.readFileSync(path.resolve(__dirname,'./test.txt'));
// gbk表示当前编码是gbk的格式
let r = iconvLite.decode(result,'gbk'); // gbk -> utf8编码
console.log(r.toString())
// 2) 编码问题2: gbk-> utf8会出现一个 BOM头
let path = require('path');
let result = fs.readFileSync(path.resolve(__dirname, './test.txt'));
console.log(stripBOM(result).toString());
// ef bb bf e7 8f a0 e5 b3 b0  去掉前三个BOM头
function stripBOM(content) {
  if(Buffer.isBuffer(content)){
    if(content[0] === 0xef && content[1] === 0xbb && content[2] === 0xbf ){
      return content.slice(3);
    }
  }else{
    if (content.charCodeAt(0) === 0xFEFF) {
      content = content.slice(1);
    }
  }
  return content;
}

实现同步copy方法

let {promisify} = require('util');
let read = promisify(fs.readFile);
let write = promisify(fs.writeFile);
// 不能读取较大的文件  流 读取一点写一点 手写流的实现
// fs.writeFile('./1.txt','1',{flag:'a'})
// fs.writeFile('./1.txt','2',{flag:'a'})
// 把异步方法进行排队处理
async function copy(source,target) {
  let chunk = await read(source);
  await write(target,chunk);
}
copy('./1.txt','./2.txt').then(data=>{
  console.log(data);
});

mkdir 创建目录 fs.mkdir fs.mkdirsync(同步)

// fs.mkdirSync('a/b/c/d'); // mkdir 会帮你转义 不能直接创建,要先有目录才可以多级创建
// 目录的创建 只能一层层创建
// 查看目录是否存在 fs.exists废弃了 
// 现在用fs.accessSync()是否能被访问 如果不能找到那就不正常 会抛出错误

同步

function mkdirp(url) {
  let arr = url.split('/');
  // for循环是同步代码
  for(let i = 0; i< arr.length;i++){
    let currentDir = arr.slice(0,i+1).join('/');
    try{
      fs.accessSync(currentDir)//存在就什么都不做,接着循环
    }catch(e){
      fs.mkdirSync(currentDir);//不存在就创建目录
    }
  }
}
mkdirp('b/e/q');

异步 //思路:一层层mkdir目录,判断点:path.length和循环的次数 next()函数,fs.access判断目录是否存在,split('/') slice(0,index).join('/')

function mkdirp(url,cb) {
  let arr = url.split('/');
  let index = 0;
  function next() { // 如果想实现异步的迭代 必须要用一个next函数
    if (index === arr.length) return cb();
    // 要截取目录 ,当第一层创建后 再去创建第二层
    let currentDir = arr.slice(0, ++index).join('/');
    fs.access(currentDir,function (err) {
      if(err){
        fs.mkdir(currentDir,function () {
          next();
        })
      }else{
        next();
      }
    })
  }
  next();
}

mkdirp('b/e/q',function () {
  console.log('创建成功')
});

rmdir rmdirSync删除目录

// 1)先序 深度 同步
//思路:递归删除
fs.readdirSync读取子目录(儿子辈)
let statObj = fs.statSync() 判断文件是否存在 会返回一个状态对象 
statObj.isFile 文件 statObj.isDirectory 文件夹
unlinkSync删除文件 rmdirSync 删除文件夹
function removeDirSync(p) {
  let statObj = fs.statSync(p);
  if (statObj.isDirectory()){
    // todo...
    let dirs = fs.readdirSync(p);
    // 拿到儿子后 删除儿子 先写 父子关系 
    dirs.forEach(dir => { // 循环儿子路径 拼上父亲路径 如果是文件夹就要递归删除 
      let currentPath = path.join(p,dir);
      removeDirSync(currentPath);//循环把拼接后的路径放进去
    });
    // 儿子删除后 删除自己
    fs.rmdirSync(p);
    // a/e/q/f
    // a/e/q
    // a/e
    // a
  }else{
    fs.unlinkSync(p); // 如果是文件 删除后就完事了
  }
}
removeDirSync('a');
// 2) 先序 深度 异步 串行
function removeDir(p,callback) {
   fs.stat(p,function (err,statObj) {
     console.log(p);
     if (statObj.isDirectory()){
       // 当前是目录 读取目录
       fs.readdir(p,function (err,dirs) {
         // 读取目录 如果没有儿子 就把自己删除掉
         // 把目录映射成相对路径
         dirs = dirs.map(dir=>path.join(p,dir));
         let index = 0; // 先删除b 目录 b 目录删除后 删除q
         function next(index) { // 拿第一个删除,删除调用删除下一个
           if (dirs.length == index) return fs.rmdir(p, callback);
           // 先删除b目录 ,后面放一个回调 当自己删除后 再调用回调继续删除
           removeDir(dirs[index],()=>next(index+1));
         }
         next(index);
       });
     }else{
       fs.unlink(p,callback)
     }
   });
}
removeDir('a',function () {
  console.log('删除成功');
});
let fs = require('fs');
let path = require('path')
// 改造先序 深度 并行
function removeDir(p,callback) {
   fs.stat(p,function (err,statObj) {
     if (statObj.isDirectory()){
       fs.readdir(p,function (err,dirs) {
         dirs = dirs.map(dir=>path.join(p,dir));
         // 我们希望 可以同时删除这些目录
         if(dirs.length == 0) return fs.rmdir(p,callback);
         // 先预定一个函数 所有儿子都删除了的函数回调
         let index = 0;
         function all() {
           index++;
           if (index === dirs.length) fs.rmdir(p, callback);
          //  if (index === dirs.length) fs.rmdir(p, ()=>callback());
         }
         dirs.forEach(dir=>{
           removeDir(dir, all);
         });
       });
     }else{
       fs.unlink(p,callback)
     }
   });
}
removeDir('a',function () {
  console.log('删除成功');
});


let fs = require('fs');
let path = require('path')
// 改造 removeDir async + await 版本
let {promisify} = require('util'); // node的核心模块 inherits 继承 promisify  转化成promise
let stat = promisify(fs.stat);
let readdir = promisify(fs.readdir);
let unlink = promisify(fs.unlink);
let rmdir = promisify(fs.rmdir);
async function removeDir(p) {
  let statObj = await stat(p);
  if(statObj.isDirectory()){
    let dirs = await readdir(p);
    dirs = dirs.map(dir => removeDir(path.join(p, dir))); 
    await Promise.all(dirs);
    await rmdir(p);
  }else{
    await unlink(p);
  }
}
removeDir('a').then(data=>{
  console.log('删除成功');
});
  • stream
// stream 流 有方向  有开始位置 和结束位置

// socket tcp 
// http req res 都是基于流的
// 压缩 流
// 文件的读取 也是流
// 流的类型 1)可读流 2)可写 3) 可读可写 双工流duplex 4) 转化流 transform流
// 操作文件 先基于文件 来看看流的使用 
let fs = require('fs');
// 创建一个可读流 基于文件操作的,默认创建流不会把内容读取出来
let rs = fs.createReadStream('./1.txt',{
  flags:'r', // 操作文件的方式 ,再在内部  fs.open(flags,mode)
  encoding:null, // 读取出的编码格式 默认就是buffer
  mode:0o666, // 
  autoClose:true, // 读取完毕后是否调用fs.close方法
  start:0, // fs.read(position)
  end:3, // 0-3 是 4个 包前又包后
  highWaterMark:4 // 每次读取多少个,默认64k
  //比如读取012345,一次读4个,从0读到3就是4个,结果0123 - 45 两次读完
});
// 默认我们创建的流 叫非流动模式 => 流动模式
// 流是基于事件的 EventEmitter 内置 , 内部会监控用户是否监听了on('data')事件
// newListener 可以监控用户是否监听了data事件,再内部可以发射这个事件,内部调用fs.read()把读取的结果 通过rs.emit('结果'),不停的读取直到文件读完为止
let arr = [];
// 前三个被占用了 标准输入 0  标准输出 1  错误输出 2  进程

rs.on('open',function (fd) { // 默认从3开始
  console.log(fd);
})
rs.on('data',function (data) {
  rs.pause();
  arr.push(data);
});
rs.on('end',function () { // 文件读取完毕后会触发end方法
  console.log(Buffer.concat(arr).toString())
});
rs.on('close',function () {
  console.log('close');
});
rs.on('error',function (err) {
  console.log(err)
})
setTimeout(() => {
  rs.resume();
}, 1000);
// 可读流流可以实现 暂停(暂停触发on('data'))和恢复(恢复触发on('data'))

// rs.on('data') rs.on('end') rs.on('error');  重要
// open close pause resume;

-iframe流 通过在Html页面嵌入一个隐藏的iframe,然后将这个iframe的src属性设为一个长连接的请求,服务器就源源不断的往客户的推送数据

  • http Http Header里Content-Type一般有三种 1.application/x-www-form-urlencoded(默认):数据被编码为名称/值对。这是标准的编码格式 2.multipart/form-data:数据被编码为一条消息,页上的每个控件对应消息中的一个部分 3.text/plain:数据以纯文本形式进行编码(text/json/xml/html)
let http = require('http');
let client = http.request({
  method:'POST',
  hostname:'localhost',
  path:'/a=1#top',
  headers:{
    "Content-Type":"application/json"
  },
  port:3000
},function (res) {
  console.log(res.headers);
  res.on('data',function (chunk) {
    console.log(chunk.toString());//f服务端返回的
  })
});
// 把请求真正的发出
// end方法中可以写入请求体
client.end('{"name":"jeffywin"}');
server
// node为了实现服务器
let http = require('http'); // http内置模块
let querystring = require('querystring');
//req 指的是客户端的请求  可读流 on('data')
//res 指的是 服务端的响应 可写流 write end
let server = http.createServer((req,res)=>{ // 监听函数,客户端请求到来后会执行此回调
  // 当请求到来时会执行此方法
  let arr = [];
  let method = req.method; // 这个方法是大写的
  console.log(method); // POST
  let url = req.url;
  console.log(url);
  let version = req.httpVersion;
  let headers = req.headers; // 获取请求头 对象所有的key都是小写的
  console.log(headers)
  req.on('data',function (chunk) {
    arr.push(chunk);
  });
  req.on('end', function () {
    // 查询字符串 希望把a=b&c=d 格式转化成对象格式  {a:b,c:d}
    let str = Buffer.concat(arr).toString();
    let obj
    if (headers['content-type'] === 'application/x-www-form-urlencoded'){
      obj= querystring.parse(str); //表单格式
    }else{
      obj = JSON.parse(str);//json格式
    }
    res.statusCode = 200;//响应行
    res.setHeader('Content-Type', 'application/json');//响应头
    res.setHeader('a', '1');
    res.end(JSON.stringify(obj)); // 在给客户端写入响应体 end只能string或者buffer
  });
});
// listen EADDRINUSE :::3000 端口被占用
let port = 3000;
server.listen(port,function () {
  console.log(`server start ${port}`)
});
server.on('error',function (err) {
  if (err.errno === 'EADDRINUSE'){
    port++;
    server.listen(port);
  }
})
```js
let http = require('http');
let server = http.createServer();
server.on('request', function (req, re) {
  http.get({
    host: 'news.baidu.com',
  }, function (res) {
    let arr = [];
    res.on('data', function (data) {
      arr.push(data);
    });
    res.on('end', function () {
      let r = Buffer.concat(arr).toString();
      let arrs = r.match(/<li (?:[\s\S]*?)<\/li>/img); // 匹配百度页面的所有的li 拼接成页面返回
      re.setHeader('Content-Type', 'text/html;charset=utf8');
      re.end(arrs.join(''));
    })
  });
});

// let obj = {}; // str.replace(/([^&=])=([^&=])/g, function () { // obj[arguments[1]] = arguments[2]; // }); // console.log(obj);

// let str = 'name==jeffywin&age==9' // let querystring = require('querystring'); // let obj = querystring.parse(str, '&', '=='); // let s = querystring.stringify(obj) // console.log(obj, s);

请求有请求行,请求头,请求体等组成
POST /www.baidu.com HTTP/1.1 方法,URI, 协议版本
协议,域名,端口号不同会引起跨域
206 分段请求 Range:bytes=0-5
304走浏览器的缓存
缓存有两种:强制缓存和协商缓存(对比缓存)
--- 强制:1.res.setHeader('Cache-Control','max-age=10') 告诉浏览器10秒内别找我
  1.res.setHeader('Exipres', new Date(Date.now() + 10 * 1000).toLocaleString()); // 绝对时间(已经废弃)
  ```js
  http.createServer(async function (req,res) {
  console.log(req.url);
  // 告诉浏览器十秒内别再找我了 (index.html) 不会的
  res.setHeader('Cache-Control','max-age=10'); // http 1.1
  // 废弃了
  res.setHeader('Exipres', new Date(Date.now() + 10 * 1000).toLocaleString()); // 绝对时间
  let { pathname } = url.parse(req.url);
  let realPath = path.join(__dirname, pathname); // 拼接真实文件的路径
  try {
    let statObj = await stat(realPath); // 判断文件是否存在
    if (statObj.isFile()) { // 是文件 返回文件
      res.setHeader('Content-Type', mime.getType(realPath) + ';charset=utf-8');
      fs.createReadStream(realPath).pipe(res);
    } else {
      let url = path.join(realPath, 'index.html'); // 目录找html
      res.setHeader('Content-Type', 'text/html;charset=utf-8');
      fs.createReadStream(url).pipe(res);
    }
  } catch (e) { // 不存在返回404
    res.statusCode = 404;
    res.end('Not found');
  }
}).listen(4000);

--- 协商缓存


http.createServer(async function (req,res) {
// 告诉浏览器十秒内别再找我了 (index.html) 不会的
res.setHeader('Cache-Control','no-cache'); // http 1.1
// 废弃了
res.setHeader('Exipres', new Date(Date.now() + 10 * 1000).toLocaleString()); // 绝对时间

// 第一次访问的时候 要给浏览器加一个头 last-modified
// 第二请求的时候 会自动带一个头 if-modified-since 
// 如果当前带过来的头和文件当前的状态有出入 说明文件被更改了(时间变了但是内容没更改 会出现再次访问文件的问题)
let { pathname } = url.parse(req.url);
let realPath = path.join(__dirname, pathname); // 拼接真实文件的路径
try {
  let statObj = await stat(realPath); // 判断文件是否存在
  if (statObj.isFile()) { // 是文件 返回文件'
    let prev = req.headers['if-modified-since'];
    let current = statObj.ctime.toGMTString();
    if (prev === current){ // 当前文件没有更改 去找缓存把
      res.statusCode = 304;
      res.end();
    }else{
      res.setHeader('Last-Modified', statObj.ctime.toGMTString());
      res.setHeader('Content-Type', mime.getType(realPath) + ';charset=utf-8');
      fs.createReadStream(realPath).pipe(res);
    }
  } else {
    let url = path.join(realPath, 'index.html'); // 目录找html
    res.setHeader('Content-Type', 'text/html;charset=utf-8');
    fs.createReadStream(url).pipe(res);
  }
} catch (e) { // 不存在返回404
  res.statusCode = 404;
  res.end('Not found');
}
}).listen(4000);

http.createServer(async function (req,res) {
res.setHeader('Cache-Control','no-cache'); // http 1.1
res.setHeader('Exipres', new Date(Date.now() + 10 * 1000).toLocaleString()); // 绝对时间

// 第一次访问 给你一个文件的签名 Etag:各种
// 下次你再来访问 会带上这个标签 if-none-match
// 我在去拿文件当前的内容 在生成一个标签 如果相等 返回304即可(读文件)

let { pathname } = url.parse(req.url);
let realPath = path.join(__dirname, pathname); // 拼接真实文件的路径
try {
  let statObj = await stat(realPath); // 判断文件是否存在
  if (statObj.isFile()) { // 是文件 返回文件'
    let content = await readFile(realPath);
    let sign = crypto.createHash('md5').update(content).digest('base64');
    let ifNoneMatch = req.headers['if-none-match'];
    if(ifNoneMatch === sign){
      res.statusCode = 304;
      res.end();
    }else{
      res.setHeader('Etag', sign);
      res.setHeader('Content-Type', mime.getType(realPath) + ';charset=utf-8');
      res.end(content);
    }
  } else {
    let url = path.join(realPath, 'index.html'); // 目录找html
    res.setHeader('Content-Type', 'text/html;charset=utf-8');
    fs.createReadStream(url).pipe(res);
  }
} catch (e) { // 不存在返回404
  res.statusCode = 404;
  res.end('Not found');
}
}).listen(4000);


// 缓存 一般分为 强制缓存 和 对比缓存
// 第一次来先来个强制缓存 Cache-Control + expires
// 过了10s 在刷新 此时会再次发送请求 启用对比缓存 
//   1) Last-Modified:ctime  2) if-modified-since
//   1) Etag                 2) if-none-match

  • crypto 加密
// MD5的特点 不可逆
// 不同的内容加密长度是一样的
// 如果内容不相同 那么摘要的结果肯定也是不相同的
let crypto = require('crypto'); 
// let r = crypto.createHash('md5').update('123456777777').digest('base64');
// console.log(r);
// r = crypto.createHash('md5').update('4QrcOUm6Wau+VuBX8g+IPg==').digest('base64');
// 加盐算法
// 弄一个密码 根据我的密码进行加密 加密cookie
let fs = require('fs');
let s = fs.readFileSync('./rsa_private_key.pem','utf8');
let r = crypto.createHmac('sha1', s).update('123456').digest('base64');
  • cookie
// cookie 存储 4k (每次发请求的时候都会带上cookie) 鉴权  jwt模式
// localStorage 5m (只存在浏览器上)
// cookie (不安全 签名算法) session(基于cookie的,服务器的)localStorage sessionStorage(浏览器关掉就没有了)
// 设置cookie 有个方案 浏览器设置  服务端设置

let http = require('http');
let querystring = require('querystring');
let crypto = require('crypto');
// 1)默认cookie只对当前域名生效 (cookie不能给不同的域设置cookie)
// 2)一级和二级域名可以共用cookie (配置)
// 3) expires 绝对的时间 
// 4) maxAge 过多长时间失效
// 5) domain
http.createServer(function (req, res) {
  // 设置cookie 和 读取cookie的方法
  let cookies = req.headers['cookie']
  req.cookies = querystring.parse(cookies, '; ', '='); //name=123; age=9
  req.signedCookie = function (key) {
    // jeffywin.asdasfghmfs
    let [k, sign] = (req.cookies[key] || '').split('.');//key value
    // 当取值的时候 将内容在次签名比对 发现有篡改就把这个cookie抛弃掉
    let newSign = crypto.createHmac('sha256', 'jeffywin').update(k).digest('base64').replace(/\+/g, '')
    if (newSign == sign) {
      return k;
    } else {
      return '';
    }
  }
  let arr = []
  res.setCookie = function (key, value, opts = {}) {
    if (opts.signed) { // 要把这个值进行签名
      value = value + '.' + crypto.createHmac('sha256', 'jeffywin').update(value).digest('base64').replace(/\+/g, '');
    }
    let str = `${key}=${value}`;
    if (opts.maxAge) {
      str += `; max-age=${opts.maxAge}`
    }
    if (opts.httpOnly) {
      str += `; httpOnly`
    }
    arr.push(str);
    res.setHeader('Set-Cookie', arr)
  }
  if (req.url === '/read') { // 读取cookie
    res.end(req.signedCookie('name'));
  }
  if (req.url === '/write') { // 像浏览器写入cookie
    // 多个cookie设置 用数组的方式
    // res.setHeader('Set-Cookie', ['name=jeffywin; max-age=10, 'age=9']);10秒后过期
    // res.setHeader('Set-Cookie',['name=jeffywin; max-age=10; domain=.jeffywin.cn','age=9']);
    // res.setHeader('Set-Cookie', ['name=jeffywin; path=/write', 'age=9']);
    // res.setHeader('Set-Cookie','name=jeffywin; httpOnly=true'); // 前端不能获取服务端设置的cookie
    res.setCookie('name', 'jeffywin', { httpOnly: true, signed: true });
    res.setCookie('age', '9', { httpOnly: true, maxAge: 3 });
    res.end('write Ok');
  }
}).listen(3000);
  • Koa
let Koa = require('koa');
let app = new Koa();
//ctx包含了 原生的req和res 又扩展了request,response
// 进来不要调用原生req 和 res
// 洋葱模型
app.use((ctx,next)=>{
  console.log(1);
  next();
  //这里的next相当于下面use中的函数
  console.log(2);
})
app.use((ctx, next) => {
  console.log(3);
  //ctx.body = { name: 'jeffywin'};
  console.log(4);
})
app.listen(4000);
//打印 1 3 4 2
  console.log(ctx.req.path); // 原生的url 
  console.log(ctx.request.req.path);
  
  //console.log(ctx.response.req.url);
  console.log(ctx.request.path);
  console.log(ctx.path);
 // ctx.req = ctx.request.req = req;
 // ctx.res = ctx.response.res = res;
 // ctx.path 代理 ctx.request.path 属性

Koa中间件 body-parser

let Koa = require('koa');
let fs = require('fs');
let path = require('path');
// let bodyParser = require('koa-bodyparser');

function bodyParser() { // 自己实现bodyParser
  return async (ctx,next)=>{
    await new Promise((resolve,reject)=>{
      let arr = [];
      ctx.req.on('data', function (chunk) {
        arr.push(chunk);
      });
      ctx.req.on('end', function () {
        if (ctx.get('Content-Type') === 'application/x-www-form-urlencoded'){
          // a=1&b=2;
          ctx.request.body = require("querystring").parse(Buffer.concat(arr).toString());
        }
        resolve();
      });
    });
    await next();
  }
}
let app = new Koa();
app.use(bodyParser()); // 在内部会把解析后的结果 放到 ctx.request.body
app.use(async (ctx,next)=>{
  if((ctx.path === '/') && (ctx.method === 'GET')){
    ctx.set('Content-Type','text/html;charset=utf8');
    ctx.body = fs.createReadStream(path.resolve(__dirname,'index.html'));
  }else{
    return next();
  }
});
app.use(async (ctx, next) => {
  if((ctx.path === '/login')&&(ctx.method === 'POST')){
    // 登录后获取用户填写的信息
    ctx.body = ctx.request.body;
  }
});
app.on('error',function (err) {
  console.log(err);
})
app.listen(8080);

Koa 路由

let Koa = require('koa');
let Router = require('./koa-router');
let app = new Koa();
let router = new Router(); //  配置路由  装载路由
// 会把路径 相同的回调函数过滤出来 进行组合 compose
router.get('/', async (ctx,next) => {
  ctx.body = '首页';
  next();
})
router.get('/reg', async (ctx, next) => {
  ctx.body = '注册'
});
router.get('/', async (ctx, next) => {
  ctx.body = '登录页';
  next();
})

app.use(router.routes()); // 装载路由
app.use((ctx,next)=>{
  console.log('body end');
  ctx.body = 'body end'
})
// app.use(router.allowedMethods()); // 允许的方法 不写没有提示而已
app.listen(3000);

  • Koa-static
// koa-views  访问/ 的时候 我要返回index.html


let Koa = require('koa');
// let static = require('koa-static');
let app = new Koa();
let path = require('path');
let Router = require('koa-router');
let mime = require('mime');
let fs = require('mz/fs');
let router = new Router();
router.get('/', async (ctx, next) => {
  console.log('start');
  ctx.set('Content-Type', 'text/html;charset=utf8')
  ctx.body = fs.createReadStream(path.resolve(__dirname, 'index.html'))
})

function static(dir) {
  return async (ctx, next) => {
    // 先找当前目录下是否有这个文件,如果没有向下执行
    let p = path.join(dir, ctx.path);
    console.log(p);
    try{
      let statObj = await fs.stat(p);
      if (statObj.isFile()){
        ctx.set('Content-Type', `${mime.getType(p)};charset=utf8`);
        ctx.body = fs.createReadStream(p);
      }else{
        p = path.join(p,'index.html');
        await fs.access(p); // 判断有没有index.html 没有的话 会抛出异常
        ctx.set('Content-Type', `text/html;charset=utf8`);
        ctx.body = fs.createReadStream(p);
      }
    }catch(e){
      await next();
    }
  }
}
app.use(static(__dirname)); // static中间件 如果有这个文件则不会继续
app.use(router.routes()); // 路由
app.listen(3000);

  • express

let express = require('./express');
// koa 里面 new app 是一个实例 
// express() 这个返回的app他是一个监听函数
// express 没有处理异步逻辑
let app =  express();
app.get('/',function (req,res) {
  res.end('hello');
});
app.get('/add', function (req, res) {
  res.end('add');
});
app.post('/add', function (req, res) {
  res.end('post add');
});
// 这个路由不能放在最上面否则 就一直执行这个路由
app.all('*', function (req, res) {
  res.end('xxxxxxxxxxxxxx');
})

app.listen(3000);