【JavaScript】回调地狱、Promise
文章目录
- 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)和事件来处理一些异步问题。
事件: 某个对象的属性是一个函数, 当发生一件事时, 运行该函数
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构造函数接受一个函数作为参数,这个函数的两个参数分别是resolve
和reject
。
- 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');
分析:
- 实例化一个Promise对象,调用构造函数,首先执行console.log(‘Promise’);输出“Promise”,resolve()函数将Promise对象的状态从pending改为resolved
- 使用then方法指定resolved状态回调函数
- 输出“Hi”
- 当期脚本所有同步任务执行完成后,执行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()
特点解读:
Promise.all()方法可以将多个Promise实例包装成为一个Promise对象§,接收一个数组作为参数,数组中不一定需要都是Promise对象,但一定具有Iterator接口。如果不是,调用Promise.resolve将其转化为Promise对象之后再进行处理。
使用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()
- 版本一
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)}})
}
- 版本二
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相关推荐
- JavaScript 回调函数/Promise/ async/await
并发和并行 并行和并发是两个概念,容易混淆是因为并行和并发在中文意思上相近,其实在英文中,这是完全不相同的东西,并行(parallelism).并发(concurrency). 并行(parallel ...
- 回调地狱以及解决回调地狱 - promise嵌套变链接 - 解决终极办法 - async 和 await
回调函数? 当一个函数被当做参数传递时,这个函数就叫做回调函数- callback 通常使用回调函数来处理异步代码 当异步代码执行结束后,需要执行的代码就要放在回调函数中 回调地狱? 当回调函数嵌套 ...
- js中 浅谈回调地狱 Promise之终极改造代码
Callback helll Promise 异步编程的执行顺序都是不一样的,无法保证代码的顺序: 以下是读取三个文件 const fs=require('fs');fs.readFile('a.tx ...
- javascript回调地狱
回调地狱 要了解回调地狱,我们首先需要知道回调函数是什么,在我们平时的代码中,经常出现很多将函数作为参数,传入到方法中,然后在方法中调用该方法,常见的就是定时器,各种DOM操作,各种异步请求 在使用这 ...
- Promise的真正意义--不只为回调地狱
前言 前两天在吃饭时,同事忽然抛出一个问题,Promise 的真正意义是啥? 对话场景如下: 他:Promise 的意义是啥? 我:为解决回调地狱,增强了代码可读性,改善了代码结构 他:这是大家都知道 ...
- vue页面取ajax返回值,Vue前端交互模式、Promise用法(回调地狱)
Promise 概述Promise 是异步编程的一种解决方案,从语法上讲,Promise 是一个对象,从它可以获取异步操作的消息. 优点:可以避免多层异步调用嵌套问题(回调地狱) Promise 对象 ...
- JavaScript系列之Promise的resolve、reject、then、catch
文章の目录 一.什么是Promise 二.Promise的优点 三.promise的三种状态 四.简单使用 1.Promise()构造器 1.1.概述 1.2.语法 1.3.参数 1.4.返回值 2. ...
- 什么是回调地狱?怎么解决回调地狱
前言 在正式了解"回调地狱"之前,我们先了解两个概念: 回调函数 当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函 ...
- 语言解决猜神童年龄的问题_JavaScript语言基础:Promise是如何解决回调地狱问题的?...
链接:https://juejin.im/post/5aa1fce051882555677e21aa 众所周知的,Javascript是一种单线程的语言,所有的代码必须按照所谓的"自上而下& ...
最新文章
- 非常棒的 「Sublime Text 配色/主题」与「编程字体」
- clover写入efi_2014 黑苹果折腾之clover efi全新安装
- 人人可以理解的区块链100问
- 《系统集成项目管理工程师》必背100个知识点-100信息技术服务标准
- 还在为垃圾太难分类而烦恼么?AI算法来帮您!
- xtrabackup支持的engine
- python列表添加字符串_2.python基础之—列表,元组,字典,集合,字符串的使用方法...
- 【C语言笔记结构体】
- Java synchronized到底锁住的是什么?
- golang groupcache重复抑制(singeflight)机制,防止缓存击穿
- 函数的实参 函数的形参 闭包 js 1
- 交换机二级可以分流么_「网络安全」网络设备篇(2)——交换机
- UITableViewCell 添加 checkbox 多选
- linux打包压缩命令
- 2022年信息安全工程师考试知识点:操作系统安全
- GEE批量提取站点DN值—以Landsat 8 C2 SR 产品为例
- 36种漂亮的CSS3网页按钮Button样式 - 改进版
- 计算机系统历史版本,全历史PC版
- 新款奔驰S400L改装原厂360全景影像系统,不在担心走向问题
- MCAL-GTM之时钟管理CMU
热门文章
- 【论文笔记-NER综述】A Survey on Deep Learning for Named Entity Recognition
- 前端模块化(CMJ和ESM)
- 蓝牙室内定位UWB常见场景定位分析
- mybatis-plus代码生成器,程序员偷懒神器
- 基于Visual Graph快速开发出电力系统
- linux 常用命令 ln/cat/echo/grep/sed/tar/wc/find/ssh/scp/strace/strings/dd/chrt/iostat/rotatelogs/dstat
- javascript闭包的前世今生
- 【bzoj2844】albus就是要第一个出场 高斯消元
- 《Java 2 实用教程》读书笔记(三)
- 在【Window】系统下更改 【pip install】 默认安装依赖路径