Promise
1.什么是Promise
- Promise是异步编程解决方案,承诺它过一段时间会给你一个结果,为了解决多个异步请求的回调地狱问题。
- 有三种状态: 1.成功态:resolved 2.失败态:rejected 3.等待态:pending pending => resolved; pending => rejected; resolved和rejected不能相互转化
- 用法
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 是单线程的 (主线程是单线程的) 一个进程里 只有一个主线程;
- process.cwd() current working directory code runner 当前执行的文件夹
- __dirname 是一个绝对路径代表的当前文件所在的目录
- __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 来写入到文件中;
- 读取 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);
- JSON.stringify() 实现 zhuanlan.zhihu.com/p/70361133