文章目录

  • 1. 回调函数
  • 2. 异步任务
  • 3. 回调地狱
  • 4. Promise
    • 4.1 Promise定义
    • 4.2 Promise基础用法
      • 4.2.1 生成Promise实例
      • 4.2.2 Promise对象的执行顺序
      • 4.2.3 Promise的链式编程 then catch方法
      • 4.2.4 Promise.all()
      • 4.2.5 Promise.race()
    • 4.3 手写一个Promise
      • 4.3.1 Promise对于状态的控制
      • 4.3.2 Promise的后续处理(then catch)
      • 4.3.3 `Promise.all()`的实现
      • 4.3.4 `Promise.race()`的实现

1. 回调函数

当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有满足一定条件后该函数才能执行。

回调函数放在另外一个函数(eg:parent)的参数列表中,作为参数传递给parent,然后在parent函数的某个位置执行。

eg:定时器,Ajax中存在回调函数


2. 异步任务

同步任务在主线程上排队,只有前一个任务执行完毕,才能执行下一个任务。

异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完毕不影响下一个任务的执行。

不阻塞后面任务执行的任务就叫做异步任务。

在es6的Promise出来之前, 我们大多数时候是在使用回调函数(callback)和事件来处理一些异步问题。

  1. 事件: 某个对象的属性是一个函数, 当发生一件事时, 运行该函数

  2. callback回调函数: 运行某个函数以实现某个功能的时候, 传入一个函数作为参数, 当某个特定的条件下的时候, 就会触发该函数

从本质上来说, 事件和回调函数也没有什么太大的区别, 仅仅就是函数放置的位置不太一样而已。

setTimeout(function() {// 这个函数就是callback回调函数
}, 10)$ajax({url: '/api/getUserInfo',success: function() {// 成功的回调函数},error: function() {// 失败的回调函数}
})var div = document.querySelector('#app');
div.addEventListener('click', function() {// 点击div以后执行该函数
}, false)
setTimeout(function() {console.log('执行了回调函数!');
},3000)console.log('1111');
分析:
按照代码编写的顺序,
应该先输出“执行了回调函数”,
再输出“111”。但实际执行结果为:
1111
执行了回调函数!

3. 回调地狱

  • 回调地狱就是为是实现代码顺序执行而出现的一种操作
  • 回调函数中嵌套回调函数的情况就叫做回调地狱。

eg:我要说一句话,语序必须是下面这样的:”武林要以和为贵,要讲武德,不要搞窝里斗。”

setTimeout(function() {console.log('武林要以和为贵');setTimeout(function() {console.log('要讲武德');setTimeout(function() {console.log('不要搞窝里斗');},3000);},2000)
},1000)


回调地狱的问题:

  • 嵌套层次很深,可读性差,难以维护
  • 无法正常使用return和throw
  • 无法正常检索堆栈信息
  • 多个回调之间难以建立联系

4. Promise

4.1 Promise定义

  • Promise是异步编程的一种解决方案,比传统的解决方案(函数回调导致回调地狱、事件)更合理,更强大。
  • ES6中将Promise写进了语言标准,统一了用法,提供原生的Promise对象,获取异步操作的消息。Promise提供统一的API,各种异步操作可以用同样的方法来处理。
  • Promise是一个容器,容器中保存着某个未来才会结束的事件,通常是异步操作。

Promise对象的两个特点:

对象状态不受外界的影响。 Promise对象代表一个异步操作,有以下三个状态:

  • pending:挂起状态(或者称之为等待状态), 未决阶段的状态, 代表事情还在未决, 结果还没出来
  • fulfilled:已处理(或者称之为处理成功), 已决阶段的状态, 代表事情已经产生结果, 而且这个结果是一个可以按照预定逻辑走下去的结果
  • reject:已拒绝(或者称之为处理失败), 已决阶段的状态, 代表事情已经产生结果, 但是这个结果跟预想的不太一样, 通常为发生错误
    只有异步操作的结果,可以决定当前是哪一种状态,任何操作都无法改变这个状态。

拿一个网络请求来说, 请求的过程中为未决阶段: 状态为pedding, 而请求到了数据状态码为OK则是resolved状态, 而请求失败500服务器错误则是rejected状态

一旦状态改变,就不会再变任何时候都可以得到这个结果。 Promise对象的状态改变只有两种可能:

  • 从pending变为fulfilled
  • 从pending变为rejected
    只要这两种情况发生,状态就凝固了,不会再变,一直保持这个结果,这时九成宫为resolved(已定型)。

4.2 Promise基础用法

打印console.dir(Promise)

说明Promise是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。


4.2.1 生成Promise实例

let promise = new Promise(function(resolve,reject){if() {  //异步操作成功resolve (value)}else {reject (error)}
})

Promise构造函数接受一个函数作为参数,这个函数的两个参数分别是resolvereject

  • resolve,reject是两个函数,是JavaScript引擎提供的,不用自己部署。
  • resolve函数的作用: 将Promise对象的状态从pending改为resolved,在异步函数操作成功的时候调用给,并将异步操作的结果作为参数传递出去。
  • reject函数的作用: 将Promise对象的状态从pending改为rejected,在异步操作失败的时候调用,并将异步操作报出的错误作为参数传递出去。
  • Promise实例生成之后,可以用then方法来分别指定resolved状态和rejected状态的回调函数
/* runAsync返回一个Promise实例,表示一段时间后才会发生的结果 */
function runAsync() {let p = new Promise((resolve,reject) => {setTimeout(function() {console.log('执行完成');resolve('随便什么数据')},2000);})return p;
}/* 过了指定ms指定的时间后,Promise实例的状态就会变为resolved */
runAsync();
// 打印结果:
// 执行完成/*   过了指定ms指定的时间后
Promise实例的状态就会变为resolved,
然后触发then方法指定的回调函数Promise实例生成之后
可以用then方法来分别指定resolved状态和rejected状态的回调函数
then方法回调函数中的参数是resolve和reject函数残敌出来的参数 */
runAsync().then(function(data){console.log(data);
})
// 打印结果:
// 执行完成
// 随便什么数据

4.2.2 Promise对象的执行顺序

let promise = new Promise((resolve, reject) => {console.log('Promise');resolve();
});
promise.then(() => {console.log('Resolved');
});
console.log('Hi');


分析:

  1. 实例化一个Promise对象,调用构造函数,首先执行console.log(‘Promise’);输出“Promise”,resolve()函数将Promise对象的状态从pending改为resolved
  2. 使用then方法指定resolved状态回调函数
  3. 输出“Hi”
  4. 当期脚本所有同步任务执行完成后,执行then方法指定的回调函数,输出“resolved"

4.2.3 Promise的链式编程 then catch方法

Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据。

then:接收两个参数, 一个thenable,意为绑定Promise成功的回调 一个catchable,意为Promise失败的回调 注册的两个函数何时执行由Promise的状态决定, 一旦Promise的状态走向已决的resolved状态则thenable函数执行, 走向rejected状态则catchabale执行

const myPromise = new Promise((resolve, reject) => {// 我在这里直接触发resolveresolve('我是成功要传递的数据');})myPromise.then((data) => {console.log(data);
}, err => {console.log(err);
})

catch:接收一个参数, 意为绑定Promise任务失败的回调, 一旦整个Promise的实例走向了rejected, catchable一定会执行


const myPromise2 = new Promise((resolve, reject) => {reject('我是失败要传递的错误');
})// then方法的第二个参数可以不传
myPromise2.then(data => {console.log(data);
})myPromise2.catch(err => {console.log(err);
})

无论是then方法还是catch方法结束一个都会返回一个新的Promise实例


4.2.4 Promise.all()

  • Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调

  • 用Promise.all来执行,all接收一个数组参数,里面的值最终都返回Promise对象。

function runAsync1() {let p = new Promise((resolve,reject) => {setTimeout(function() {console.log('异步任务1执行完成');resolve('随便什么数据1')},2000);})return p;
}function runAsync2() {let p = new Promise((resolve,reject) => {setTimeout(function() {console.log('异步任务2执行完成');resolve('随便什么数据2')},2000);})return p;
}Promise.all([runAsync1(), runAsync2()])
.then(function(results){console.log(results);
});


4.2.5 Promise.race()

  • Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2,p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
function runAsync1() {let p = new Promise((resolve,reject) => {setTimeout(function() {console.log('异步任务1执行完成');resolve('随便什么数据1')},2000);})return p;
}function runAsync2() {let p = new Promise((resolve,reject) => {setTimeout(function() {console.log('异步任务2执行完成');resolve('随便什么数据2')},2000);})return p;
}Promise.race([runAsync1(), runAsync2()])
.then(function(results){console.log(results);
});


4.3 手写一个Promise

4.3.1 Promise对于状态的控制

Promise对于状态控制上的特点:

  • Promise是一个构造函数, 且接受一个函数executor作为参数, 他构造出的实例一开始具有两个属性,一个用来表示当前状态,一个用来表示Promise结果 , 即官方所描述的[[PromiseStatus]]和 [[PromiseValue]]
  • 写在参数executor中的代码会立即执行, 同时函数参数会被传递两个参数, 一个resolve, 一个reject用于处理不同的情况
  • 用户可以在executor中通过执行两个传递进来的参数提前引导Promise走向已决, 执行的方法不同走向的状态也不同,而Promise的状态一旦改变则不可逆转
  • 在executor中如果出现错误, 则会将错误抛出, 并且将状态直接抛向已决的rejected状态

  • step1
// Promise是一个构造函数,为了更好的封闭作用域,需要用到立即执行函数
const MyPromise = (function() {// 常量变量定义const PromiseStatus = Symbol('PromiseStatus'), // 每个实例上的Promise状态PromiseValue = Symbol('PromiseValue'); // 每个实例上的Promise结果// Promise状态, pedding, resolved, rejectedconst _PEDDING = 'pedding';const _RESOLVED = 'resolved';const _REJECTED = 'rejected';// 将真正的MyPromise构造函数返回出去return class MyPromise {// excutor:用户传递进来的函数参数 constructor(excutor){// 每个Promise实例刚出生的时候状态为pedding, 结果为undefinedthis[PromiseStatus] = _PEDDING;this[PromiseValue] = undefined;}}
}());

阶段性实验代码:

const myPrimose = new MyPromise((resolve,reject) => {})
console.log(myPrimose);


  • step 2
const MyPromise = (function() {const PromiseStatus = Symbol('PromiseStatus'), PromiseValue = Symbol('PromiseValue'); const _PEDDING = 'pedding';const _RESOLVED = 'resolved';const _REJECTED = 'rejected';//  resolve和reject方法, 我们在原型上或者构造函数中是看不到的// 所以肯定resolve reject方法在闭包中const resolve = _data => {}const reject = _error => {}return class MyPromise {constructor(excutor){this[PromiseStatus] = _PEDDING;this[PromiseValue] = undefined;// excutor在一开始就会被立马执行, 所以势必是在构造函数中走了一次excutor(resolve,reject);}}
}());

阶段性测试:

const myPrimose = new MyPromise((resolve,reject) => {console.log('我会立即执行吗?');
})console.log(myPrimose);

结果在意料之中,executor中的代码被成功执行


  • step 3
const MyPromise = (function() {const PromiseStatus = Symbol('PromiseStatus'), PromiseValue = Symbol('PromiseValue'); const _PEDDING = 'pedding';const _RESOLVED = 'resolved';const _REJECTED = 'rejected';// 定义一个this指向,避免当前作用域下的this混乱let self = null;/*考虑到改变状态这个操作在resolve、reject中只有参数不同,我们可以抽离成一个方法_status: 新的状态值_result: 新的value值;我们知道用户一旦将状态推向已决, 那么PromiseValue就一定要得出一个结果*/const changePromiseStatus = (_status,_result) => {//当前阶段不是已决if(self[PromiseStatus] !== _PEDDING) return;self[PromiseStatus] = _status;self[PromiseValue] = _result;}const resolve = _data => {changePromiseStatus(_RESOLVED,_data);}const reject = _error => {changePromiseStatus(_REJECTED,_error);}return class MyPromise {constructor(excutor){// this指向selfself = this;this[PromiseStatus] = _PEDDING;this[PromiseValue] = undefined;excutor(resolve,reject);}}
}());

阶段性测试:

const myPrimose = new MyPromise((resolve,reject) => {console.log('我会立即执行吗?');resolve('hello');
})console.log(myPrimose);const mySecPromise = new MyPromise((resolve, reject) => {console.log('我是第二个Promise实例');reject('error');
})console.log(mySecPromise);

测试结果:


  • step 4
// 对状态控制的最后一个处理
const MyPromise = (function() {const PromiseStatus = Symbol('PromiseStatus'), PromiseValue = Symbol('PromiseValue'); const _PEDDING = 'pedding';const _RESOLVED = 'resolved';const _REJECTED = 'rejected';let self = null;const changePromiseStatus = (_status,_result) => {if(self[PromiseStatus] !== _PEDDING) return;self[PromiseStatus] = _status;self[PromiseValue] = _result;}const resolve = _data => {changePromiseStatus(_RESOLVED,_data);}const reject = _error => {changePromiseStatus(_REJECTED,_error);}return class MyPromise {constructor(excutor){self = this;this[PromiseStatus] = _PEDDING;this[PromiseValue] = undefined;// 如果我们在executor中报错, 则会直接触发reject从而进入rejected状态, // 并将错误信息传递给reject方便后续处理// 所以我们势必需要try catch捕捉一下错误try{excutor(resolve,reject);}catch(error){reject(error)}}}
}());

阶段性测试:

const myPrimose = new MyPromise((resolve,reject) => {console.log('我会立即执行吗?');resolve('hello');
})console.log(myPrimose);const mySecPromise = new MyPromise((resolve, reject) => {console.log('我是第二个Promise实例');console.log(abc);   // 未声明会报错
})console.log(mySecPromise);

毫无意外, 结果如下, 如果在executor中报错, 则会直接进入rejected状态


4.3.2 Promise的后续处理(then catch)

  • then和catch是属于原型上的方法, then方法接收两个参数, 第二个参数可以不传, 这两个参数分别是成功后的回调和失败后的回调,catch方法接收一个参数, 为失败后的回调。
  • Promise串联: 我们知道Promise的then和catch是可以链式调用的, 且下一次Promise的then结果是上一次Promise then或者catch的返回值,如果上一次Promise的执行过程中没有出错, 那么不管结局是resolved或者rejected,新的Promise都会走向resolved, 反之rejected。

const MyPromise = (function() {const PromiseStatus = Symbol('PromiseStatus'), PromiseValue = Symbol('PromiseValue'); const _PEDDING = 'pedding';const _RESOLVED = 'resolved';const _REJECTED = 'rejected';// 用来存储Promise.then加入的回调const fullFilledList = Symbol('fullFilledList');// 用来存储Promise.catchconst rejectedList = Symbol('rejectedList'); // 这个方法时用来处理用户调用then和catch方法时的处理函数// _status: 要判定的状态, handler: 当前处理函数, queue: 当前处理函数队列const settleHandler = (_status, handler, queue) => {if(self[PromiseStatus] === _status) {setTimeout(() => handler(self[PromiseValue]), 0);}else queue.push(handler);  }let self = null; const changePromiseStatus = (_status,_result) => {if(self[PromiseStatus] !== _PEDDING) return;self[PromiseStatus] = _status;self[PromiseValue] = _result;// 一旦用户更改了状态, 所有在then和catch中对应的方法都要执行if(self[PromiseStatus] === _RESOLVED) self[fullFilledList].forEach(ele => ele(self[PromiseValue]))else self[rejectedList].forEach(ele => ele(self[PromiseValue]))}const resolve = _data => {changePromiseStatus(_RESOLVED, _data);}const reject = _error => {changePromiseStatus(_REJECTED, _error);}return class MyPromise {constructor(executor) {self = this;this[PromiseStatus] = _PEDDING;this[PromiseValue] = undefined;this[fullFilledList] = [];this[rejectedList] = [];try {executor(resolve, reject);}catch(error) {reject(error);}}// then 和catch方法是在原型上出现的// then方法接受一个thenbale: 成功回调函数和一个catchable: 失败回调函数then = (thenable, catchable) => {// 我们知道, 如果当前状态已经为已决阶段的两种状态了, 那么回调函数// 会被立即执行, 否则才会放入相应数组// 设置相应状态settleHandler('resolved', thenable, this[fullFilledList])// 因为catchable很有可能不传递, 所以必须容错typeof catchable === 'function' &&  this.catch(catchable);}// catch方法只接受一个catchable失败回调函数catch = (catchable) => {settleHandler('rejected', catchable, this[rejectedList]) }}}())

阶段性测试:

const myPromise = new MyPromise((resolve, reject) => {console.log('我会立即执行吗?');reject('hello');
})myPromise.then((data) => {console.log(data);
}, (err) => {console.log(err);
})console.log(myPromise);

4.3.3 Promise.all()的实现

Promise.all 接收一个 promise 对象的数组作为参数,当这个数组里的所有 promise 对象全部变为resolve或 有 reject 状态出现的时候,它才会去调用 .then 方法,它们是并发执行的。

promise.all()特点解读:

  1. Promise.all()方法可以将多个Promise实例包装成为一个Promise对象§,接收一个数组作为参数,数组中不一定需要都是Promise对象,但一定具有Iterator接口。如果不是,调用Promise.resolve将其转化为Promise对象之后再进行处理。

  2. 使用Promise.all()生成的Promise对象§的状态由数组中的Promise对象(p1,p2,p3)决定:

    • 如果所有的Promise对象(p1,p2,p3)都变成fullfilled状态的话,生成的Promise对象§也会变成fullfilled状态,p1,p2,p3三个Promise对象产生的结果会组成一个数组返回给传递给p的回调函数
    • 如果p1,p2,p3中有一个Promise对象变为rejected状态的话,p也会变成rejected状态,第一个被rejected的对象的返回值会传递给p的回调函数。

手写实现自己的Promise.all()

  1. 版本一
function PromiseAll(arr) {//PromiseAll的返回值为一个Promise对象return new Promise((resolve,reject) => {//传入的PromiseAll必须是一个数组if(!Array.isArray(arr)){return reject(new TypeError('arr must be an array.'));};let resArr = [];for(let i in arr) {(function(i){Promise.resolve(arr[i]).then(res => {resArr.push(res);if(i == arr.length-1){return resolve(resArr);}},err => {return reject(err);}).catch(err => {console.log(err);})})(i)}})
}
  1. 版本二
function PromiseAll(arr) {return new Promise((resolve,reject) => { // 返回一个新的Promise对象let resArr = [];  // 定义一个空数组存放结果let i = 0;function handleData(index,data){ // 处理数据函数resArr[index] = data;i++;if(i == arr.length){  //当i等于传递的数组的长度时 resolve(resArr);  //执行resolve,并将结果放入}}for(let i=0;i<arr.length;i++) {  //循环遍历数组Promise.resolve(arr[i]).then((data) => {handleData(i,data);  //将结果和索引传入handleData函数})}})
}

测试:

// 测试
const pro1 = new Promise((res,rej) => {setTimeout(() => {res('1')},1000)
})
const pro2 = new Promise((res,rej) => {setTimeout(() => {res('2')},2000)})
const pro3 = new Promise((res,rej) => {setTimeout(() => {res('3')},3000)
})const proAll = PromiseAll([pro1,pro2,pro3])
.then(res => {console.log(res);// 3秒之后打印 ["1", "2", "3"]
}).catch((e) => {console.log(e);
})


4.3.4 Promise.race()的实现

function PromiseRace(arr) {return new Promise((resolve,reject) => {if(!Array.isArray(arr)){return reject(new TypeError('arguments must be Array'));};for(let i=0;i<arr.length;i++) {Promise.resolve(arr[i]).then(data => {resolve(data);},err => {reject(err);})}})
}

测试:

// 测试
const pro1 = new Promise((res,rej) => {setTimeout(() => {res('1')},1000)
})
const pro2 = new Promise((res,rej) => {setTimeout(() => {res('2')},2000)})
const pro3 = new Promise((res,rej) => {setTimeout(() => {res('3')},3000)
})const proAll = PromiseRace([pro1,pro2,pro3])
.then(res => {console.log(res); // 输出1
}).catch((e) => {console.log(e);
})

手写Promise参考博客

【JavaScript】回调地狱、Promise相关推荐

  1. JavaScript 回调函数/Promise/ async/await

    并发和并行 并行和并发是两个概念,容易混淆是因为并行和并发在中文意思上相近,其实在英文中,这是完全不相同的东西,并行(parallelism).并发(concurrency). 并行(parallel ...

  2. 回调地狱以及解决回调地狱 - promise嵌套变链接 - 解决终极办法 - async 和 await

    回调函数? 当一个函数被当做参数传递时,这个函数就叫做回调函数-  callback 通常使用回调函数来处理异步代码 当异步代码执行结束后,需要执行的代码就要放在回调函数中 回调地狱? 当回调函数嵌套 ...

  3. js中 浅谈回调地狱 Promise之终极改造代码

    Callback helll Promise 异步编程的执行顺序都是不一样的,无法保证代码的顺序: 以下是读取三个文件 const fs=require('fs');fs.readFile('a.tx ...

  4. javascript回调地狱

    回调地狱 要了解回调地狱,我们首先需要知道回调函数是什么,在我们平时的代码中,经常出现很多将函数作为参数,传入到方法中,然后在方法中调用该方法,常见的就是定时器,各种DOM操作,各种异步请求 在使用这 ...

  5. Promise的真正意义--不只为回调地狱

    前言 前两天在吃饭时,同事忽然抛出一个问题,Promise 的真正意义是啥? 对话场景如下: 他:Promise 的意义是啥? 我:为解决回调地狱,增强了代码可读性,改善了代码结构 他:这是大家都知道 ...

  6. vue页面取ajax返回值,Vue前端交互模式、Promise用法(回调地狱)

    Promise 概述Promise 是异步编程的一种解决方案,从语法上讲,Promise 是一个对象,从它可以获取异步操作的消息. 优点:可以避免多层异步调用嵌套问题(回调地狱) Promise 对象 ...

  7. JavaScript系列之Promise的resolve、reject、then、catch

    文章の目录 一.什么是Promise 二.Promise的优点 三.promise的三种状态 四.简单使用 1.Promise()构造器 1.1.概述 1.2.语法 1.3.参数 1.4.返回值 2. ...

  8. 什么是回调地狱?怎么解决回调地狱

    前言 在正式了解"回调地狱"之前,我们先了解两个概念: 回调函数 当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函 ...

  9. 语言解决猜神童年龄的问题_JavaScript语言基础:Promise是如何解决回调地狱问题的?...

    链接:https://juejin.im/post/5aa1fce051882555677e21aa 众所周知的,Javascript是一种单线程的语言,所有的代码必须按照所谓的"自上而下& ...

最新文章

  1. 非常棒的 「Sublime Text 配色/主题」与「编程字体」
  2. clover写入efi_2014 黑苹果折腾之clover efi全新安装
  3. 人人可以理解的区块链100问
  4. 《系统集成项目管理工程师》必背100个知识点-100信息技术服务标准
  5. 还在为垃圾太难分类而烦恼么?AI算法来帮您!
  6. xtrabackup支持的engine
  7. python列表添加字符串_2.python基础之—列表,元组,字典,集合,字符串的使用方法...
  8. 【C语言笔记结构体】
  9. Java synchronized到底锁住的是什么?
  10. golang groupcache重复抑制(singeflight)机制,防止缓存击穿
  11. 函数的实参 函数的形参 闭包 js 1
  12. 交换机二级可以分流么_「网络安全」网络设备篇(2)——交换机
  13. UITableViewCell 添加 checkbox 多选
  14. linux打包压缩命令
  15. 2022年信息安全工程师考试知识点:操作系统安全
  16. GEE批量提取站点DN值—以Landsat 8 C2 SR 产品为例
  17. 36种漂亮的CSS3网页按钮Button样式 - 改进版
  18. 计算机系统历史版本,全历史PC版
  19. 新款奔驰S400L改装原厂360全景影像系统,不在担心走向问题
  20. MCAL-GTM之时钟管理CMU

热门文章

  1. 【论文笔记-NER综述】A Survey on Deep Learning for Named Entity Recognition
  2. 前端模块化(CMJ和ESM)
  3. 蓝牙室内定位UWB常见场景定位分析
  4. mybatis-plus代码生成器,程序员偷懒神器
  5. 基于Visual Graph快速开发出电力系统
  6. linux 常用命令 ln/cat/echo/grep/sed/tar/wc/find/ssh/scp/strace/strings/dd/chrt/iostat/rotatelogs/dstat
  7. javascript闭包的前世今生
  8. 【bzoj2844】albus就是要第一个出场 高斯消元
  9. 《Java 2 实用教程》读书笔记(三)
  10. 在【Window】系统下更改 【pip install】 默认安装依赖路径