【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(二、JavaScript 异步编程)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(课前准备)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(一、函数式编程范式)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(二、JavaScript 异步编程)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(三、手写Promise源码)
【Part1作业】https://gitee.com/zgp-qz/part01-task
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(一、ECMAScript 新特性)
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(二、TypeScript 语言)
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(三、JavaScript 性能优化1)
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(四、JavaScript 性能优化2)
【Part2作业】https://gitee.com/zgp-qz/part02-homework
JavaScript 异步编程
- 异步编程
异步编程
单线程 javascript 异步方案
目前主流的 javascript 环境,都是以 单线程 模式去执行的 javascript 代码,javascript 采用单线程模式工作的原因与它最早的设计初衷有关。
最早 javascript 这门语言就是运行在浏览器端的脚本语言,它的目的是为了用来去实现页面上的动态交互, 而实现页面交互的核心就是 DOM 操作,这也就决定了它必须使用单线程模型,否则就会出现很复杂的线程同步问题。
为了避免这种线程同步问题,javascript 从一开始就被设计成了这种单线程的工作模式。这也就成为了这门语言最为核心的特性之一。
这里所说的单线程指的是:js 执行环境中,负责执行代码的线程只有一个。
如果出现了一个特别耗时的任务,就会导致程序会被拖延,出现假死的情况。
为了解决这种耗时任务阻塞程序执行的这种问题,javascript 语言将任务的执行模式,分成了两种,分别是:同步模式(Synchronous) 和 异步模式(Asynchronous)
这里主要讲解的是:
同步模式与异步模式
- 同步模式和异步模式表象上的差异以及存在的意义事件循环与消息队列
- javascript 单线程如何实现的异步模式异步编程的几种方式
Promise 异步方案、宏任务 / 微任务队列
- ES2015 当中提供的 Promise 异步方案
- 牵扯到的 宏任务 / 微任务 相关概念Generator 异步方案、Async / Await 语法糖
- ES2015 提供的 Generator 异步方案
- ES2017 提供的 Async / Await 语法糖
同步模式(Synchronous)
代码当中的任务依次执行,后一个任务必须要等待前一个任务结束,才能够开始执行。
程序的执行顺序和代码的编写顺序完全一致。
注意:这里的同步不是同时执行,而是排队执行。
异步模式(Asynchronous)
异步模式是不回去等待这个任务的结束才开始执行下一个任务,对于耗时操作是开启过后就立即往后执行下一个任务。耗时任务的后续逻辑一般会通过回调函数的方式定义。
在内部这个耗时任务完成之后,就会自动执行我们这里传入的回调函数。
如果没有这种异步模式的话,javascript 就无法同时处理大量的耗时任务。
对于开发者而言,难点在于 代码执行顺序混乱 ,更多的时候是需要理解和习惯。最好的办法就是多看多练多思考。
这里的不管同步也好,异步也好,肯定不是说的我们写代码的方式,而是说我们运行环境所提供的 API 到底是以同步或异步模式的方式去工作回调函数 – 所有异步编程方案的根基
回调函数可以理解为一件你想要做的事情,明确的知道这件事情该怎么做,但是你并不知道这件事情所依赖的任务什么时候完成,最好的办法就是把你这件事情的步骤写到一个函数当中,交给异步任务的执行者,这个异步任务的执行者知道这个任务什么时候结束,它就可以在任务结束过后,帮你执行你想要做的事情。那么这件想要做的事情我们就可以理解成 回调函数 。
这种由调用者定义,交给执行者执行的函数,就被称之为回调函数。
Promise – 一种更优的异步编程统一方案
直接使用传统的回调方式去完成复杂的异步流程,就无法避免大量的回调函数嵌套,这也就会导致我们常说的 回调地狱
为了避免 回调地狱的问题,CommonJS 社区 率先提出了 Promise 的规范。目的就是为异步编程去提供一种更合理,更强大的统一解决方案。
在 ES2015 中被标准化,成为语言规范。
Promise – 基本用法
// Promise 基本示例const promise = new Promise(function (resolve, reject) {// 这里用于“兑现”承诺// resolve(100) // 承诺达成reject(new Error('promise rejected')) // 承诺失败 })promise.then(function (value) {// 即便没有异步操作,then 方法中传入的回调仍然会被放入队列,等待下一轮执行console.log('resolved', value) }, function (error) {console.log('rejected', error) })console.log('end')
Promise – 使用案例
// Promise 方式的 AJAX function ajax (url) {return new Promise(function (resolve, reject) {var xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.responseType = 'json'xhr.onload = function () {if (this.status === 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send()}) }ajax('/api/foo.json').then(function (res) {console.log(res) }, function (error) {console.log(error) })
Promise – 常见误区
// Promise 常见误区function ajax (url) {return new Promise(function (resolve, reject) {var xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.responseType = 'json'xhr.onload = function () {if (this.status === 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send()}) }// 嵌套使用 Promise 是最常见的误区 // ajax('/api/urls.json').then(function (urls) {// ajax(urls.users).then(function (users) {// ajax(urls.users).then(function (users) {// ajax(urls.users).then(function (users) {// ajax(urls.users).then(function (users) {// }) // }) // }) // }) // })
Promise – 链式调用
相比于传统回调的方式,Promise 最大的优势就是可以链式调用。这样就可以最大程度的避免回调嵌套。
· Promise 对象的 then 方法会返回一个全新的 Promise 对象。
· 后面的 then 方法就是在为上一个 then 返回的 Promise 注册回调。
· 前面 then 方法中回调函数的返回值会作为后面 then 方法回调的参数。
· 如果回调中返回的是 Promise ,那后面 then 方法的回调会等待它的结束。// Promise 链式调用function ajax (url) {return new Promise(function (resolve, reject) {var xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.responseType = 'json'xhr.onload = function () {if (this.status === 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send()}) }// var promise = ajax('/api/users.json')// var promise2 = promise.then( // function onFulfilled (value) {// console.log('onFulfilled', value) // }, // function onRejected (error) {// console.log('onRejected', error) // } // )// console.log(promise2 === promise)ajax('/api/users.json').then(function (value) {console.log(1111)return ajax('/api/urls.json')}) // => Promise.then(function (value) {console.log(2222)console.log(value)return ajax('/api/urls.json')}) // => Promise.then(function (value) {console.log(3333)return ajax('/api/urls.json')}) // => Promise.then(function (value) {console.log(4444)return 'foo'}) // => Promise.then(function (value) {console.log(5555)console.log(value)})
Promise – 异常处理
catch 方法其实就是 then 方法的别名
then 方法的第二个参数只是给当前的 then 方法指定的 失败回调
最后的 catch 方法是给前面的 then 方法指定的 catch (错误回调)
只不过是前面的 then 方法捕获到的错误会通过链条一直往后传递,传递到了最后的 catch 方法
// Promise 异常处理function ajax (url) {return new Promise(function (resolve, reject) {// foo()// throw new Error()var xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.responseType = 'json'xhr.onload = function () {if (this.status === 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send()}) }// ajax('/api/users11.json') // .then(function onFulfilled (value) {// console.log('onFulfilled', value) // }, function onRejected (error) {// console.log('onRejected', error) // })// 使用 catch 注册失败回调是更常见的// ajax('/api/users11.json') // .then(function onFulfilled (value) {// console.log('onFulfilled', value) // }) // .catch(function onRejected (error) {// console.log('onRejected', error) // })// then(onRejected) 实际上就相当于 then(undefined, onRejected)// ajax('/api/users11.json') // .then(function onFulfilled (value) {// console.log('onFulfilled', value) // }) // .then(undefined, function onRejected (error) {// console.log('onRejected', error) // })// 同时注册的 onRejected 只是给当前 Promise 对象注册的失败回调 // 它只能捕获到当前 Promise 对象的异常// ajax('/api/users.json') // .then(function onFulfilled (value) {// console.log('onFulfilled', value) // return ajax('/error-url') // }, function onRejected (error) {// console.log('onRejected', error) // })// 因为 Promise 链条上的任何一个异常都会被一直向后传递,直至被捕获 // 分开注册的 onRejected 相当于给整个 Promise 链条注册失败回调ajax('/api/users.json').then(function onFulfilled (value) {console.log('onFulfilled', value)return ajax('/error-url')}) // => Promise {}// .catch(function onRejected (error) {// console.log('onRejected', error)// })// 全局捕获 Promise 异常,类似于 window.onerror window.addEventListener('unhandledrejection', event => {const { reason, promise } = eventconsole.log(reason, promise)// reason => Promise 失败原因,一般是一个错误对象// promise => 出现异常的 Promise 对象event.preventDefault() }, false)// Node.js 中使用以下方式 // process.on('unhandledRejection', (reason, promise) => {// console.log(reason, promise) // // reason => Promise 失败原因,一般是一个错误对象 // // promise => 出现异常的 Promise 对象 // })
下方的这种全局捕获的方式不推荐使用:
更合适的办法是在代码中明确的捕获每一个可能发生的异常,而不是丢给全局统一处理
除此之外,我们还可以在全局对象上注册一个 unhandleredrejection 事件,去处理那些我们代码当中没有被手动捕获的 promise 异常
在浏览器环境中:
在 node 环境中:
注意:node 环境中事件的名称是驼峰命名的
Promise 静态方法
Promise.resolve()
作用:快速的把一个值转换为一个 Promise 对象,如果接收到的是另外一个 Promise 对象,那这个 Promise 对象会被原样返回
特殊情况:如果传入的是一个 对象,对象包含一个 同 Promise 一样的 then 方法,那么在这个方法当中可以接收到 onFulfilled 和 onRejected 两个回调,调用 onFulfilled 传入一个值,这样的一个对象也可以作为 Promise 对象被执行,在后面的 then 方法中也能够拿到对应的被传入的值。
带有这种 then 方法的对象,称之为 thenable 的接口。也就是说它是一个可以被 then 的对象。
Promise.reject()
作用:快速去创建一个一定是失败作用的对象。
// 常用 Promise 静态方法function ajax (url) {return new Promise(function (resolve, reject) {// foo()// throw new Error()var xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.responseType = 'json'xhr.onload = function () {if (this.status === 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send()}) }// Promise.resolve('foo') // .then(function (value) {// console.log(value) // })// new Promise(function (resolve, reject) {// resolve('foo') // })// 如果传入的是一个 Promise 对象,Promise.resolve 方法原样返回// var promise = ajax('/api/users.json') // var promise2 = Promise.resolve(promise) // console.log(promise === promise2)// 如果传入的是带有一个跟 Promise 一样的 then 方法的对象, // Promise.resolve 会将这个对象作为 Promise 执行// Promise.resolve({// then: function (onFulfilled, onRejected) {// onFulfilled('foo') // } // }) // .then(function (value) {// console.log(value) // })// Promise.reject 传入任何值,都会作为这个 Promise 失败的理由// Promise.reject(new Error('rejected')) // .catch(function (error) {// console.log(error) // })Promise.reject('anything').catch(function (error) {console.log(error)})
Promise 并行执行
相比于传统回调的方式,Promise 提供了更扁平的异步编程体验。如果我们需要同时并行执行多个异步任务,Promise 也可以提供更为完善的体验。
Promise.all() // 同时执行多个异步任务,可以把多个 Promise 对象组合为一个全新的 promise 对象
Promise.race() // 同样是同时执行多个异步任务,可以把多个 Promise 对象组合为一个全新的 promise 对象
两者不同的是:
Promise.all() 等待所有任务结束后才会结束,执行后面的 then 方法
Promise.race() 是跟着所有的任务当中第一个完成的任务一起结束,执行后面的 then 方法,也就是说只要有任何一个任务完成了,这个所返回的新的 promise 对象也就会完成// Promise 并行执行function ajax (url) {return new Promise(function (resolve, reject) {// foo()// throw new Error()var xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.responseType = 'json'xhr.onload = function () {if (this.status === 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send()}) }// ajax('/api/users.json') // ajax('/api/posts.json')// var promise = Promise.all([ // ajax('/api/users.json'), // ajax('/api/posts.json') // ])// promise.then(function (values) {// console.log(values) // }).catch(function (error) {// console.log(error) // })// ajax('/api/urls.json') // .then(value => {// const urls = Object.values(value) // const tasks = urls.map(url => ajax(url)) // return Promise.all(tasks) // }) // .then(values => {// console.log(values) // })// Promise.race 实现超时控制const request = ajax('/api/posts.json') const timeout = new Promise((resolve, reject) => {setTimeout(() => reject(new Error('timeout')), 500) })Promise.race([request,timeout ]) .then(value => {console.log(value) }) .catch(error => {console.log(error) })
Promise 执行时序
即便是我们的 Promise 当中并没有任何的异步操作,它的回调函数仍然会进入到回调队列当中去排队。也就是说我们必须要等待当前所有的同步代码执行完了过后才会去执行 promise 当中的回调。
回调队列中的任务称之为 [ 宏任务 ]
宏任务执行过程当中会临时加上一些额外的需求,这时对于这些额外的临时需求可以选择作为一个新的宏任务进到队列中排队。
也可以作为当前任务的 [ 微任务 ]
直接在当前任务结束过后立即执行,而不是到整个队伍的末尾进行排队。
这就是 宏任务 和 微任务之间的一个差异。
Promise 的回调,就是作为微任务执行的。所以他会在本轮调用结束的末尾自动执行,这也就是为什么会先打印的 promise 然后再打印的 setTimeout 。
微任务:提高整体的响应能力。
目前,绝大多数异步调用都是作为宏任务执行(进入到回调队列),而 Promise 和 MutationObserver 还有 node 当中的一个 process.nextTick 都会作为 微任务,直接在本轮调用的末尾,直接执行。
// 微任务console.log('global start')// setTimeout 的回调是 宏任务,进入回调队列排队 setTimeout(() => {console.log('setTimeout') }, 0)// Promise 的回调是 微任务,本轮调用末尾直接执行 Promise.resolve().then(() => {console.log('promise')}).then(() => {console.log('promise 2')}).then(() => {console.log('promise 3')})console.log('global end')
Generator 异步方案(上)
Promise 会形成一个任务的链条,从而实现所有任务的串联执行,但是这样写仍然会有大量的回调函数,虽然它们相互之间没有嵌套,但是他们还是没有办法达到传统,同步代码的那种可读性。
如果是传统同步代码的方式,我们的代码可能会是这个样子的:
很明显,这种方式去写异步代码,它是最简洁,也是最容易阅读和理解的。ES2015 提供的 Generator (生成器函数)
// 生成器函数回顾function * foo () {console.log('start')try {const res = yield 'foo'console.log(res)} catch (e) {console.log(e)} }const generator = foo()const result = generator.next() console.log(result)// generator.next('bar')generator.throw(new Error('Generator error'))
Generator 异步方案(中)
体验 Generator 函数异步方案
例子见 15. Generator 异步方案(下) – 递归执行 Generator 函数
Generator 异步方案(下) – 递归执行 Generator 函数
2015 年之前是很流行的,但是后来随着技术的发展,出现的 Async & Await 之后,这种方案相对来讲就没有那么普及了,不过使用 Generator 它最明显的一个变化就是让我们的异步调用再次回归到扁平化。
这也是 javascript 异步编程发展过程当中很重要的一步。
// Generator 配合 Promise 的异步方案function ajax (url) {return new Promise((resolve, reject) => {var xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.responseType = 'json'xhr.onload = () => {if (xhr.status === 200) {resolve(xhr.response)} else {reject(new Error(xhr.statusText))}}xhr.send()}) }function * main () {try {const users = yield ajax('/api/users.json')console.log(users)const posts = yield ajax('/api/posts.json')console.log(posts)const urls = yield ajax('/api/urls11.json')console.log(urls)} catch (e) {console.log(e)} }function co (generator) {const g = generator()function handleResult (result) {if (result.done) return // 生成器函数结束result.value.then(data => {handleResult(g.next(data))}, error => {g.throw(error)})}handleResult(g.next()) }co(main)// const result = g.next()// result.value.then(data => {// const result2 = g.next(data)// if (result2.done) return// result2.value.then(data => {// const result3 = g.next(data)// if (result3.done) return// result3.value.then(data => {// g.next(data) // }) // }) // })
Async / Await 语法糖 – 语言层面的异步编程标准
有了 Generator 过后,javascript 中的异步编程,基本上就已经与同步代码有类似的体验了,但是使用 Generator 这种异步编程方案,我们还需要自己手动编写一个类似于 co 的执行器函数,所以说会比较麻烦。
在 ES2017 当中提供了一个 Async 的函数,同样提供了这种扁平化的异步编程体验。而且它是语言层面标准的异步编程 语法。所以使用起来就会更加的方便一点。其实 Async 函数就是 Generator 函数的一种更方便的语法糖。所以语法上是非常类似的。
Async 函数会给我们返回一个 promise 对象,这样就利于我们对整体代码进行控制。
注意:await 关键词只能出现在 Async 函数内部,不能直接在外部(顶层作用域)直接使用。
不过关于在最外层直接使用 await 的功能已经在开发了。不久之后可能出现在标准当中。到时候使用 Async / Await 就更加方便一些。
// Async / Await 语法糖function ajax (url) {return new Promise((resolve, reject) => {var xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.responseType = 'json'xhr.onload = () => {if (xhr.status === 200) {resolve(xhr.response)} else {reject(new Error(xhr.statusText))}}xhr.send()}) }function co (generator) {const g = generator()function handleResult (result) {if (result.done) return // 生成器函数结束result.value.then(data => {handleResult(g.next(data))}, error => {g.throw(error)})}handleResult(g.next()) }async function main () {try {const users = await ajax('/api/users.json')console.log(users)const posts = await ajax('/api/posts.json')console.log(posts)const urls = await ajax('/api/urls.json')console.log(urls)} catch (e) {console.log(e)} }// co(main) const promise = main()promise.then(() => {console.log('all completed') })
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(课前准备)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(一、函数式编程范式)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(二、JavaScript 异步编程)
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(三、手写Promise源码)
【Part1作业】https://gitee.com/zgp-qz/part01-task
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(一、ECMAScript 新特性)
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(二、TypeScript 语言)
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(三、JavaScript 性能优化1)
【学习笔记】Part1·JavaScript·深度剖析-ES 新特性与 TypeScript、JS 性能优化(四、JavaScript 性能优化2)
【Part2作业】https://gitee.com/zgp-qz/part02-homework
【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(二、JavaScript 异步编程)相关推荐
- [学习笔记]C语言深度剖析
近期正在看<C语言深度剖析>,里面有一个讲解自加运算符(++),有一程序如下: void main() {int i;for(i=0, printf("First i = %d\ ...
- Java8学习笔记(1) -- 从函数式接口说起
转载自 Java8学习笔记(1) -- 从函数式接口说起 希望本文能够成为Java8 Lambda表达式的快速入门指南. 函数式接口 理解Functional Interface(函数式接口,以下 ...
- 游戏黑客圣经GHB1学习笔记 part1(1-5)
游戏黑客圣经(Game Hacking Bible1) 我在这里记录我所有课程的学习笔记,包括一些小技巧以及源码,俗话说好记性不如烂笔头,写在这里,用于温故而知新. 前言 学习游戏黑客的必备条件 智力 ...
- 深度学习笔记4:深度神经网络的正则化
出处:数据科学家养成记 深度学习笔记4:深度神经网络的正则化 恍恍惚惚,又20天没写了.今天笔者要写的是关于机器学习和深度学习中的一项关键技术:正则化.相信在机器学习领域摸爬滚打多年的你一定知道正则化 ...
- 学习笔记之——基于深度学习的图像超分辨率重建
最近开展图像超分辨率( Image Super Resolution)方面的研究,做了一些列的调研,并结合本人的理解总结成本博文~(本博文仅用于本人的学习笔记,不做商业用途) 本博文涉及的paper已 ...
- 计算机网络学习笔记Part1
计算机网络学习笔记Part1 1. 概念 计算机网络:是一个将分散的.具有独立功能的计算机系统,通过通信设备与线路连接起来,由功能完善的软件实现资源共享和信息传递的系统. 2.功能 1.数据通信.2. ...
- CS230(DeepLearning)Leture2的学习笔记(2)之深度学习
CS230_Leture2的学习笔记2之深度学习 我们先来回顾下第二章的学习目标: 而对于第二章Week1的学习目标已经完成,具体学习内容参考博主另一篇博客,站内搜索 CS230(Dee ...
- 【学习笔记】Pytorch深度学习—Batch Normalization
[学习笔记]Pytorch深度学习-Batch Normalization Batch Normalization概念 `Batch Normalization ` `Batch Normalizat ...
- 关键字深度剖析,集齐所有关键字可召唤神龙?【二】
关键字深度剖析,集齐所有关键字可召唤神龙?[二] 1. if.else 组合 1.1 if 和 else 1.1.1 结论1 1.1.2 结论2 1.1.3 结论3 1.2 bool 变量与" ...
最新文章
- 关于华为的E180 3G 无线网卡在windows 7 上的驱动问题
- 大数据学习——sparkRDD
- boost::sub_range相关的测试程序
- html网络,HTML—构建网络
- 长见识:你真的知道C语言里extern quot;Cquot; 的作用吗?
- 云原生数据库风起云涌,华为云GaussDB破浪前行
- pxe安装linux dhcp失败,利用PXE自动化安装Centos时启动DHCP服务时遇到错误,请求大佬指教...
- 网店如何提高顾客的回购率?
- 处理数据集python脚本(处理自己制作的数据集)
- 理财子公司成长的烦恼
- robocopy 备份_使用Robocopy复制、备份文件夹
- android dtb文件位置_确定msm8937+android7.1采用的dtb文件
- 请求 Provisional headers are shown 问题
- 【后端】--process information unavailable解决办法[详细版]
- lae界面开发工具入门介绍之一新建工程篇
- How to do Mathematics
- 手工卡纸做机器人_怎么用卡纸手工制作可拨动的时钟玩教具(步骤图解)
- MBR15200FAC-ASEMI塑封肖特基二极管MBR15200FAC
- java小练习---记账软件
- 傻子都能看懂的SVM
热门文章
- 解决spring+c3p0数据库连接一直增加的问题
- Scala:用 Scala 进行 OO 教学
- python判断输入的内容是否为一个数字(整数、负数)
- 蓝桥杯-迷宫(17年)-python
- 堆排序(最小堆为例)
- markdown 链接跳转到标题_markdown中锚链接实现目录跳转以及注意事项
- 图片处理Photoshop给广告模特专业润肤及磨皮
- hibernate mysql 例子_Hibernate的基本功能:对数据库的增删改查(创建对象实例)...
- 人工智能项目取得成功,主要有哪几种方法?
- freertos 怎么做超时处理_新公司开办费会计处理怎么做?如何进行税务处理?