掌握JavaScript中的迭代器和生成器,顺便了解一下async、await的原理

Python微信订餐小程序课程视频

https://edu.csdn.net/course/detail/36074

Python实战量化交易理财系统

https://edu.csdn.net/course/detail/35475

掌握JavaScript中的迭代器和生成器,顺便了解一下async、await的原理

前言

相信很多人对迭代器和生成器都不陌生,当提到async和await的原理时,大部分人可能都知道async、await是Promise+生成器的语法糖,其原理具体是怎么做的呢?下面通过这篇文章带你详细了解一下迭代器和生成器,以及带你从生成器一步步推导到async和await。

1.迭代器(Iterator)

1.1.什么是迭代器?

迭代器是确使用户在容器对象(链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。

  • 迭代器的定义可能比较抽象,简单来说迭代器就是一个对象,可用于帮助我们对某个数据结构(链表、数组)进行遍历

  • 在JavaScript中,迭代器也是一个具体的对象,并且这个对象必须符合迭代器协议(iterator protocol);

  • 什么是迭代器协议?

    • 在JavaScript中就是指这个对象必须实现一个特定的next方法,并且next方法有如下要求;
    • next方法可接收0个或者1个参数(在生成器中next可以接收1个参数),并且需返回一个对象,对象包含以下两个属性:
      • done:值为Boolean,如果迭代器可以迭代产生下一个值,就为false,如果已经迭代完毕,就为true;
      • value:迭代器返回的值,如果done为false,value一般为undefined;
  • 编写一个最简单的迭代器:

const iterator = {next: function() {return { done: false, value: 123 }}
}

1.2.迭代器的基本使用

明白了迭代器的基本定义,下面就来实现一下符合迭代器协议的对象吧,并且看看其它的一些基本用法。比如,需要通过迭代器访问一个数组:

(1)创建一个迭代器对象

const names = ['curry', 'kobe', 'klay']let index = 0 // 通过一个index来记录当前访问的位置
const iterator = {next() {if (index < names.length) {return { done: false, value: names[index++] }} else {return { done: true, value: undefined }}}
}console.log(iterator.next()) // { done: false, value: 'curry' }
console.log(iterator.next()) // { done: false, value: 'kobe' }
console.log(iterator.next()) // { done: false, value: 'klay' }
console.log(iterator.next()) // { done: true, value: undefined }

(2)实现生成迭代器的函数

  • 如果每次需要去访问一个数组就去编写一个对应的迭代器对象肯定是很麻烦的;
  • 可以封装一个函数,用于生成一个访问数组的迭代器;
function createIterator(arr) {let index = 0return {next() {if (index < arr.length) {return { done: false, value: arr[index++] }} else {return { done: true, value: undefined }}}}
}
const names = ['curry', 'kobe', 'klay']
// 调用createIterator函数,生成一个访问names数组的迭代器
const namesIterator = createIterator(names)console.log(namesIterator.next()) // { done: false, value: 'curry' }
console.log(namesIterator.next()) // { done: false, value: 'kobe' }
console.log(namesIterator.next()) // { done: false, value: 'klay' }
console.log(namesIterator.next()) // { done: true, value: undefined }

1.3.可迭代对象

1.3.1.什么是可迭代对象?

上面提到了迭代器是一个对象,并且符合迭代器协议,那么什么是可迭代对象呢?它与迭代器又有什么区别?

  • 迭代器是一个符合**迭代器协议(iterator protocol)**的对象,对象内实现了一个特定的next方法;
  • 而可迭代对象是一个符合**可迭代协议(iterable protocol)**的对象,对象内实现了一个Symbol.iterator方法,并且该方法返回一个迭代器对象;
  • 所以,可以说可迭代对象包含了迭代器对象,可迭代对象中实现了一个特定方法用于返回迭代器对象;

如下,iteratorObj就是一个可迭代对象:

const iteratorObj = {names: ['curry', 'kobe', 'klay'],[Symbol.iterator]: function() {let index = 0return {// 注意:这里的next需要使用箭头函数,否则this访问不到iteratorObjnext: () => {if (index < this.names.length) {return { done: false, value: this.names[index++] }} else {return { done: true, value: undefined }}}}}
}
// 调用iteratorObj中的Symbol.iterator得到一个迭代器
const iterator = iteratorObj[Symbol.iterator]()console.log(iterator.next()) // { done: false, value: 'curry' }
console.log(iterator.next()) // { done: false, value: 'kobe' }
console.log(iterator.next()) // { done: false, value: 'klay' }
console.log(iterator.next()) // { done: true, value: undefined }
1.3.2.JS内置的可迭代对象

上面的可迭代对象都是由自己实现的,其实在JavaScript中为我们提供了很多可迭代对象,如:String、Array、Map、Set、arguments对象、NodeList(DOM集合)等。

// 1.String
const str = 'abc'const strIterator = str[Symbol.iterator]()
console.log(strIterator.next()) // { value: 'a', done: false }
console.log(strIterator.next()) // { value: 'b', done: false }
console.log(strIterator.next()) // { value: 'c', done: false }
console.log(strIterator.next()) // { value: undefined, done: true }// 2.Array
const names = ['curry', 'kobe', 'klay']
console.log(names[Symbol.iterator])const namesIterator = names[Symbol.iterator]()
console.log(namesIterator.next()) // { value: 'curry', done: false }
console.log(namesIterator.next()) // { value: 'kobe', done: false }
console.log(namesIterator.next()) // { value: 'klay', done: false }
console.log(namesIterator.next()) // { value: undefined, done: true }// 3.Map/Set
const set = new Set
set.add(10)
set.add(20)
set.add(30)const setIterator = set[Symbol.iterator]()
console.log(setIterator.next()) // { value: 10, done: false }
console.log(setIterator.next()) // { value: 20, done: false }
console.log(setIterator.next()) // { value: 30, done: false }
console.log(setIterator.next()) // { value: undefined, done: true }
1.3.3.可迭代对象应用场景

可迭代对象在实际应用中特别常见,像一些语法的使用、创建一些对象和方法调用都用到了可迭代对象。

  • JS中的语法:for…of、展开语法、解构等。

    • for…of可用于遍历一个可迭代对象,其原理就是利用迭代器的next函数,如果done为false,就从返回的对象中拿到value返回给我们,而对象不是一个可迭代对象,所以对象不能使用for…of遍历;
    const num = [1, 2, 3]
    for (const item of num) {console.log(item) // 1 2 3
    }
    // 遍历上面自己定义的可迭代对象iteratorObj也是可以的
    for (const item of iteratorObj) {console.log(item) // curry kobe klay
    }
    const obj = { name: 'curry', name: 30 }
    for (const key of obj) {console.log(key)
    }

    • 为什么数组能使用展开语法,其原理也是用到了迭代器,在使用...对数组进行展开时,也是通过迭代器的next去获取数组的每一项值,然后存放到新数组中;
    const names = ['james', 'green']
    // 将数组和iteratorObj通过扩展进行合并
    const newNames = [...names, ...iteratorObj]
    console.log(newNames) // [ 'james', 'green', 'curry', 'kobe', 'klay' ]
    • 可迭代对象都是可以使用解构语法的,像数组、字符串为什么可以使用解构,其原因就在这,原理也是通过迭代器一个个取值然后再赋值给对应变量;
    const str = 'abc'
    const nums = [1, 2, 3]const [str1, str2, str3] = str
    console.log(str1, str2, str3) // a b cconst [num1, num2, num3] = nums
    console.log(num1, num2, num3) // 1 2 3const [name1, name2, name3] = iteratorObj
    console.log(name1, name2, name3) // curry kobe klay
    • 注意:在扩展语法和解构语法中,我们知道数组可以使用,但是对象也可以使用呀,为什么没有提到对象呢?因为对象的扩展和解构是在ES9中新增的特性,其原理并不是使用迭代器实现的,只是ECMA提供给我们的一种操作对象的新语法而已;
  • JS创建对象:new Map([Iterable])、new WeakMap([Iterable])、new Set([Iterable])、new WeakSet([Iterable])等。

// 1.Set
const set = new Set(iteratorObj)
console.log(set) // Set(3) { 'curry', 'kobe', 'klay' }// 2.Array.from
const names = Array.from(iteratorObj)
console.log(names) // [ 'curry', 'kobe', 'klay' ]
  • JS方法调用:Promise.all(Iterable)、Promise.race(Iterable)、Array.from(Iterable)等。
// 传入的可迭代对象中的每个值,会使用Promise.resolve进行包裹
Promise.all(iteratorObj).then(res => {console.log(res) // [ 'curry', 'kobe', 'klay' ]
})

扩展:现在我们都知道了for…of可用于遍历一个可迭代对象,如果在遍历过程中终端了呢?因为使用break、continue、return、throw都是可以中断遍历的,既然for…of遍历的原理是基于迭代器的,那么在for…of中进行中断操作,一定是可以被迭代器监听到的,上面说了,在迭代器中有一个next方法,其实还可以指定一个return方法,如果遍历过程中断了,就会去调用return方法,注意return方法也要返回和next方法一样的对象。这种情况就称之为迭代器的中断

const iteratorObj = {names: ['curry', 'kobe', 'klay'],[Symbol.iterator]: function() {let index = 0return {next: () => {if (index < this.names.length) {return { done: false, value: this.names[index++] }} else {return { done: true, value: undefined }}},return() {console.log('哎呀,我被中断了!')return { done: true, value: undefined }}}}
}for (const item of iteratorObj) {console.log(item)if (item === 'kobe') break
}

1.3.4.自定义可迭代类

上面提到了对象不是一个可迭代对象,所以对象不能使用for…of遍历,如果我们想要实现通过for…of遍历对象呢?那么可以自己实现一个类,这个类的实例化对象是可迭代对象。

  • 实现一个Person类,并且Person类中实现了Symbol.iterator方法用于返回一个迭代器;
  • Person类的实例化对象p中包含一个friends数组,通过for…of遍历p对象时,可以将friends数组的每一项遍历出来;
class Person {constructor(name, age, friends) {this.name = namethis.age = agethis.friends = friends}[Symbol.iterator]() {let index = 0return {next: () => {if (index < this.friends.length) {return { done: false, value: this.friends[index++] }} else {return { done: true, value: undefined }}}}}
}

简单看一下效果:

const p = new Person('curry', 30, ['kobe', 'klay', 'green'])
for (const name of p) {console.log(name) // kobe klay green
}

2.生成器(Generator)

2.1.什么是生成器?

生成器是ES6中新增的一种控制函数执行的方案,它可以帮助我们控制函数的暂停和执行。生成器是一种特殊的迭代器,所以生成器也是一个对象,并且可以调用next方法。那么怎么创建一个生成器对象呢?

创建生成器对象需要使用生成器函数,生成器函数和普通函数不一样,主要有以下特点:

  • 生成器函数的声明需要在function后加上一个符号*
  • 在生成器函数中可以使用yield关键字来分割函数体代码,控制函数的执行;
  • 生成器函数调用的返回值就是生成器对象了;

2.2.生成器的基本使用

实现一个生成器函数,该函数的执行可以通过返回的生成器对象进行控制。

function* generatorFn() {console.log('函数开始执行~')console.log('函数第一段代码执行...')yieldconsole.log('函数第二段代码执行...')yieldconsole.log('函数第三段代码执行...')yieldconsole.log('函数第四段代码执行...')console.log('函数执行结束~')
}// 调用generatorFn获取生成器
const generator = generatorFn()generator.next()
console.log('------------------------')
generator.next()
console.log('------------------------')
generator.next()
console.log('------------------------')
generator.next()

2.3.生成器next方法的返回值

上面说到了生成器是一种特殊的迭代器,那么调用next方法肯定也是有返回值的,并且返回值是一个包含done、value属性的对象。

function* generatorFn() {console.log('函数第一段代码执行...')yieldconsole.log('函数第二段代码执行...')yieldconsole.log('函数第三段代码执行...')yieldconsole.log('函数第四段代码执行...')
}// 调用generatorFn获取生成器
const generator = generatorFn()console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())

从打印结果可以看出来,next返回的对象中value是没有值的,当执行到最后一段代码后,done的值就为true了:

如果需要指定next返回值中的value,那么可以通过在yield后面跟上一个值或者表达式,就可以将对应的值传递到next返回对象value中了。

function* generatorFn() {console.log('函数第一段代码执行...')yield 10console.log('函数第二段代码执行...')yield 20console.log('函数第三段代码执行...')yield 30console.log('函数第四段代码执行...')
}// 调用generatorFn获取生成器
const generator = generatorFn()console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())

观察以上打印结果,在执行完第四段代码后,调用的next返回值为{ value: undefined, done: true },原因是后面已经没有yield了,而且当函数没有指定返回值时,最后会默认执行return undefined

2.4.生成器next方法的参数传递

在前面介绍迭代器定义时,提到迭代器的next可以传递0个或1个参数,而可以传递1个参数的情况就是生成器的next可以传递一个参数,而给每一段代码传递过去的参数可以通过yield来接收。

function* generatorFn(value) {console.log('函数第一段代码执行...', value)const value1 = yield 10console.log('函数第二段代码执行...', value1)const value2 = yield 20console.log('函数第三段代码执行...', value2)const value3 = yield 30console.log('函数第四段代码执行...', value3)
}// 调用generatorFn获取生成器
const generator = generatorFn('参数0')console.log(generator.next())
console.log('------------------------')
console.log(generator.next('参数1'))
console.log('------------------------')
console.log(generator.next('参数2'))
console.log('------------------------')
console.log(generator.next('参数3'))

next参数传递解释

  • next中传递的参数是会被上一个yield接收的,这样可以方便下面代码使用这个参数,所以给next传递参数,需要从第二个next开始传递;
  • 如果第一段代码需要使用参数呢?可以在调用生成器函数时传递参数过去,供第一段代码使用;

2.5.生成器的return方法

return方法也可以给生成器函数传递参数,但是调用return后,生成器函数就会中断,之后再调用next就不会再继续生成值了。

function* generatorFn() {console.log('函数第一段代码执行...')yieldconsole.log('函数第二段代码执行...')yieldconsole.log('函数第三段代码执行...')yieldconsole.log('函数第四段代码执行...')
}// 调用generatorFn获取生成器
const generator = generatorFn()console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.return(123))
console.log('------------------------')
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

上面的执行return方法,相当于函数内部执行了return:

function* generatorFn() {console.log('函数第一段代码执行...')yieldconsole.log('函数第二段代码执行...')const value = yieldreturn valueconsole.log('函数第三段代码执行...')yieldconsole.log('函数第四段代码执行...')
}// 调用generatorFn获取生成器
const generator = generatorFn()console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next(123))
console.log('------------------------')
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

2.6.生成器的throw方法

throw方法可以给生成器函数内部抛出异常。

  • 生成器调用throw方法抛出异常后,可以在生成器函数中进行捕获;
  • 通过try catch捕获异常后,后续的代码还是可以正常执行的;
function* generatorFn() {console.log('函数第一段代码执行...')yield 10console.log('函数第二段代码执行...')try {yield 20} catch (err) {console.log(err)}console.log('函数第三段代码执行...')yield 30console.log('函数第四段代码执行...')
}// 调用generatorFn获取生成器
const generator = generatorFn()console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.throw('err message'))
console.log('------------------------')
console.log(generator.next())

2.7.生成器替换迭代器

在前面实现了一个生成迭代器的函数,实现过程还需要进行判断,并自己返回对应的对象,下面就用生成器来实现一个生成迭代器的函数。

  • 方式一:根据数组元素的个数,执行yield;
function* createIterator(arr) {let index = 0yield arr[index++]yield arr[index++]yield arr[index++]
}
  • 方式二:遍历数组,执行yield;
function* createIterator(arr) {for (const item of arr) {yield item}
}
  • 方式三:执行yield*,后面可以跟上一个可迭代对象,它会依次迭代其中每一个值;
function* createIterator(arr) {yield* arr
}

测试一下以上三种方法,执行结果都是一样的:

const names = ['curry', 'kobe', 'klay']
const iterator = createIterator(names)console.log(iterator.next()) // { value: 'curry', done: false }
console.log(iterator.next()) // { value: 'kobe', done: false }
console.log(iterator.next()) // { value: 'klay', done: false }
console.log(iterator.next()) // { value: undefined, done: true }

3.异步请求的处理方案

在进行异步请求时,如果出现了这样一个需求,下一次的请求需要拿到上一次请求的结果。如下是使用Promise封装的一个request方法。

function request(url) {return new Promise((resolve, reject) => {setTimeout(() => {resolve(url)}, 300)})
}

实现上面的需求可以怎么做呢?

3.1.方案一:使用Promise的then进行嵌套调用

  • 在then中拿到结果后,再去做下一次请求,以此类推;
  • 缺点:形成了回调地狱;
request('/aaa').then(res => {request(res + '/bbb').then(res => {request(res + '/ccc').then(res => {console.log(res) // /aaa/bbb/ccc})})
})

3.2.方案二:使用Promise的then的返回值

  • 虽然可以解决回调地狱问题,但是阅读性不佳;
request('/aaa').then(res => {return request(res + '/bbb')
}).then(res => {return request(res + '/ccc')
}).then(res => {console.log(res) // /aaa/bbb/ccc
})

3.3.方案三:使用Promise和Generator处理

function* getRequestData() {const res1 = yield request('/aaa')const res2 = yield request(res1 + '/bbb')const res3 = yield request(res2 + '/ccc')console.log(res3)
}
  • 手动执行生成器的next方法;
const generator = getRequestData()
generator.next().value.then(res => {generator.next(res).value.then(res => {generator.next(res).value.then(res => {generator.next(res) // /aaa/bbb/ccc})})
})

  • 自动执行生成器的next方法:如果手动执行嵌套层级过多的话是不方便的,那么可以借助递归的思想实现一个自动执行生成器的函数;
function autoGenerator(generatorFn) {const generator = generatorFn()function recursion(res) {const result = generator.next(res)// 如果done值为true,说明结束了if (result.done) return result.value// 没有结束,继续调用Promise的thenresult.value.then(res => {recursion(res)})}recursion()
}autoGenerator(getRequestData) // /aaa/bbb/ccc
  • 使用第三方库来执行生成器:像自动执行生成器函数,早就已经有第三方库帮助我们实现了,如co
const co = require('co')
co(getRequestData) // /aaa/bbb/ccc

3.4.方案四:使用async和await

async和await是我们解决异步回调的最终解决方案,它可以让我们异步的代码,看上去是同步执行的。

async function getRequestData() {const res1 = await request('/aaa')const res2 = await request(res1 + '/bbb')const res3 = await request(res2 + '/ccc')console.log(res3)
}getRequestData() // /aaa/bbb/ccc

4.async和await的原理

相信从上面讲述的四个异步请求的处理方案中,就可以看出来async、await和生成器的关系了。

  • 将生成器函数的*换成async
  • 将生成器函数中的yield换成await
  • 两种方案所体现出的效果和代码书写形式几乎差不多;

总结

  • async和await的原理其实就是Promise+生成器实现的;
  • 为什么async、await能够让异步代码看上去是同步执行的,其原因就在于生成器的next方法可以对函数内代码的执行进行控制,当上一次请求拿到结果后,再去执行下一次next;
  • 所以为什么说async和await只是Promise+生成器的语法糖,其原理就在这;

__EOF__

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m3NUTKRy-1649092542787)(https://blog.csdn.net/MomentYY/p/16100015.html)]MomentYY 本文链接:https://blog.csdn.net/MomentYY/p/16100015.html关于博主:评论和私信会在第一时间回复。或者直接私信我。版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!声援博主:如果您觉得文章对您有帮助,可以点击文章右下角**【[推荐](javascript:void(0)😉】**一下。您的鼓励是博主的最大动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401161.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Boot loader: Grub入门(转)

Boot Loader: Grub 在看完了前面的整个启动流程&#xff0c;以及核心模块的整理之后&#xff0c;你应该会发现到一件事情&#xff0c; 那就是『 boot loader 是加载核心的重要工具』啊&#xff01;没有 boot loader 的话&#xff0c;那么 kernel 根本就没有办法被系统加载的呢&…

网页设计趋势:模糊背景在网站中的经典应用案例

如今&#xff0c;网页设计领域出现了多种创新的设计方法&#xff0c;例如视差滚动&#xff0c;全屏背景&#xff0c;单页设计等等。今天这篇文章向大家介绍另一种新的网页设计趋势——模糊背景。这种类似于摄影拍摄的效果应用在网页中能够突出主题内容&#xff0c;同时若隐若现…

magento去除子分类的url地址中带有父分类的url key

2019独角兽企业重金招聘Python工程师标准>>> app/code/core/Mage/Catalog/Model/Url.php 找到如下代码 方法getCategoryRequestPath if (null $parentPath) {$parentPath $this->getResource()->getCategoryParentPath($category);}elseif ($parentPath /…

深度学习(三)之LSTM写诗

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 目录* 数据预处理 构建数据集模型结构生成诗 根据上文生成诗生成藏头诗 参考 根据前文生成诗&#xff1a; 机器学习业&…

C# 将PDF转为Excel

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 通常&#xff0c;PDF格式的文档能支持的编辑功能不如office文档多&#xff0c;针对PDF文档里面有表格数据的&#xff0c;如果…

SQL Server R2 地图报表制作(五)

SQL Server R2 地图报表制作&#xff08;五&#xff09; 2.3 向地图中添加线条层 在上述报表的基础上&#xff0c;我们接下来添加一个表示两个商店间路线的地图层&#xff0c;这里我们依旧使用SQL空间数据源&#xff0c;部分步骤与上一节类似&#xff0c;所不同的是相应的SQL语…

SQL Server 2005新特性之使用with关键字解决递归父子关系

1. 引言 现实项目中经常遇到需要处理递归父子关系的问题&#xff0c;如果把层次关系分开&#xff0c;放在多个表里通过主外键关系联接&#xff0c;最明显的问题就是扩展起来不方便&#xff0c;对于这种情况&#xff0c;一般我们会创建一个使用自连接的表来存放数据。例如存放会…

做订购系统必须要明白的几点

2019独角兽企业重金招聘Python工程师标准>>> 对价格的疑问&#xff1f; 1、用户容易把单位搞错&#xff0c;你标注的是单价&#xff0c;他理解的是整套的价格。譬如你标注的是一粒糖的价格&#xff0c;但卖的是一包糖10粒。 新增单&#xff1f;我拍了之后还想补充 2…

Oracle安装 - shmmax和shmall设置

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 一、概述 在Linux上安装oracle&#xff0c;需要对内核参数进行调整&#xff0c;其中有shmmax和shmall这两个参数&#xff…

彻底理解js中this的指向

首先必须要说的是&#xff0c;this的指向在函数定义的时候是确定不了的&#xff0c;只有函数执行的时候才能确定this到底指向谁&#xff0c;实际上this的最终指向的是那个调用它的对象&#xff08;这句话有些问题&#xff0c;后面会解释为什么会有问题&#xff0c;虽然网上大部…

带码农《手写Mybatis》进度3:实现映射器的注册和使用

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 作者&#xff1a;小傅哥 博客&#xff1a;https://bugstack.cn 沉淀、分享、成长&#xff0c;让自己和他人都能有所收获&…

01背包问题,动态规划求解

01背包问题&#xff1a; 1.递归思想 0- 1 背包问题如果采用递归算法来描述则非常清楚明白, 它的算法根本思想是假设用布尔函数knap( s, n) 表示n 件物品放入可容质量为s 的背包中是否有解( 当knap 函数的值为真时 说明问题有解,其值为假时无解) . 我们可以通过输入s 和n 的值, …

SonarQube代码质量管理平台安装与使用

SonarQube代码质量管理平台安装与使用 注原文地址&#xff1a;http://blog.csdn.net/hunterno4/article/details/11687269Sonar简介 Sonar是一个用于代码质量管理的开源平台&#xff0c;用于管理源代码的质量&#xff0c;可以从七个维度检测代码质量 通过插件形式&#xff0c;可…

docker容器编排原来这么丝滑~

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 前言&#xff1a; 请各大网友尊重本人原创知识分享&#xff0c;谨记本人博客&#xff1a;南国以南i 概念介绍&#xff1a…

互联网思维的“独孤九剑”

课前秀&#xff1a;三个段子 第一个段子&#xff1a;有一个毫无餐饮行业经验的人&#xff0c;他开了一家餐馆&#xff0c;菜品只有12道&#xff0c;在北京只有两家分店&#xff1b;仅两个月时间&#xff0c;就实现了所在商场餐厅坪效第一名&#xff1b;绿茶单位坪效大约是100元…

linux系统分析工具续-SystemTap和火焰图(Flame Graph)

本文为网上各位大神文章的综合简单实践篇&#xff0c;参考文章较多&#xff0c;有些总结性东西&#xff0c;自认暂无法详细写出&#xff0c;建议读文中列出的参考文档&#xff0c;相信会受益颇多。下面开始吧&#xff08;本文出自 “cclo的博客” 博客&#xff0c;请务必保留此…

MySQL8.0.x 版本安装步骤傻瓜式教程【官方版】

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 MySQL8.0.x 安装 一、下载 MySQL官网下载链接&#xff1a;https://downloads.mysql.com/archives/community/ 选择版本后…

不用电的计算机(二)

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 转载请注明出处&#xff1a;https://blog.csdn.net/morningli/p/16058594.html 上一篇讲到最早的计算机是什么样的&#xf…

CocoaPods did not set the base configuration of your project 问题解决方案

今天在使用pod install的时候&#xff0c;出现了 [!] CocoaPods did not set the base configuration of your project because your project already has a custom config set. In order for CocoaPods integration to work at all, please either set the base configuration…

在UnityUI中绘制线状统计图

Python微信订餐小程序课程视频 https://blog.csdn.net/m0_56069948/article/details/122285951 Python实战量化交易理财系统 https://blog.csdn.net/m0_56069948/article/details/122285941 先来个效果图 觉得不好看可以自己调整 1.绘制数据点 线状图一般由数据点和连线组…