前言

这篇文章我们一起来学习如何使用Promise,以及如何实现一个自己的Promise,讲解非常清楚,全程一步一步往后实现,附带详细注释与原理讲解。

如果你觉的这篇文章有帮助到你,️关注+点赞️鼓励一下作者,文章公众号首发,关注 前端南玖 第一时间获取最新的文章~

promise是什么?主要用来解决什么问题?

Promise是异步编程的一种解决方案,比传统解决方案--回调函数和事件--更合理更强大。

Promise特点:

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中),fulfilled(已成功)和reject(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其它操作都无法改变这个状态。这也是Promise(承诺)这个名字的由来。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected

promise主要用来解决:

  • 回调地狱
  • 并发请求
  • 异步方案优化(但它本身不是异步的,new Promise()后,它会立即执行)

promise基本用法

ES6规定,Promise对象是一个构造函数,用来生成Promise实例

下面代码创造了一个Promise实例

const promise = new Promise(function(resolve,reject){
//...
if(/*异步操作成功*/){
resolve(value)
}else{
//异步操作失败
reject(error)
}
})

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject.他们是两个函数,由JavaScript引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变成“成功”(即从pending变为resolve),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变成“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数,或用catch方法指定rejected状态的回调函数。

promise.then(res=>{
//success
},error=>{
//error
}).catch(err=>{})

then方法可以接受两个回调函数作为参数,第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接收Promise对象传出的值作为参数。

Ok,通过上面对promise基本用法的描述,我们大概知道了一个promise类里面都应该包含哪些内容了:

  • promise状态:pending,fulfilled,rejected

  • promise返回值

  • 执行器:promise执行入口(也就是你传入的那个函数)

  • resolve:改变promise状态为fulfilled

  • reject:改变promise状态为rejected

  • then:接收两个回调,onFulfilled, onRejected。分别在promise状态变为fulfiled或rejected后执行

  • catch:接受一个回调,在promise状态变为rejected后执行

简单实现一个promise

我们知道了一个promise内容至少包含以上那些内容,所以一个简单的promise内部至少是这样的

class myPromise {
static PENDING = 'pending'
static FULFILLEd = 'fulfilled'
static REJECTED = 'rejected'
constructor(init){
this.state = myPromise.PENDING // promise状态
this.promiseRes = null // promise返回值
const resolve = result=>{
//...
}
const reject = result=>{
//...
}
try{
init(resolve,reject) // init就是初始化执行器
}catch(err){
reject(err)
} }
then(onFulfilled,onRejected){
//...
}
catch(onRejected){
//...
}
}

OK,大概了解之后,我们再来一个一个的看里面每个部分的实现以及作用

Promise的执行器

它其实是我们在new Promise时传入的一个回调函数,这个函数本身是同步的,也就是说在new Promise时它就会执行,这也是我们操作promise的入口。

class myPromise{
//...
constructor(init){
try{
init(resolve,reject) // init就是初始化执行器
}catch(err){
reject(err) //这里主要是在init执行器函数出错时,用以让promise状态变为rejected
}
}
//...
}

该函数接受两个回调函数(resolve,reject)作为参数,用以改变Promise的状态

resolve与reject方法

这两个函数作为参数传到执行器函数中,用以后续改变Promise状态

class myPromise {
static PENDING = 'pending'
static FULFILLEd = 'fulfilled'
static REJECTED = 'rejected'
constructor(init){
this.state = myPromise.PENDING // promise状态
this.promiseRes = null // promise返回值
this.resolveCallback = [] //成功回调集合
this.rejectCallback = [] //失败回调集合
const resolve = result=>{
// 只有当状态为pending时才改变,保证状态一旦改变就不会再变
if(this.state === myPromise.PENDING){
this.state = myPromise.FULFILLEd //改变状态
this.promiseRes = result //返回值
//依次调用成功回调
this.resolveCallback.forEach(fn=>fn())
}
}
const reject = result=>{
// 只有当状态为pending时才改变,保证状态一旦改变就不会再变
if(this.state === myPromise.PENDING){
this.state = myPromise.REJECTED //改变状态
this.promiseRes = result //返回值
// 依次调用失败回调
this.rejectCallback.forEach(fn=>fn())
}
}
try{
init(resolve,reject) // 注意this指向
}catch(err){
reject(err)
} }
}

初步then方法

class myPromise {
static PENDING = 'pending'
static FULFILLEd = 'fulfilled'
static REJECTED = 'rejected'
constructor(init){
this.state = myPromise.PENDING // promise状态
this.promiseRes = null // promise返回值
this.resolveCallback = [] //成功回调集合
this.rejectCallback = [] //失败回调集合
const resolve = result=>{
// 只有当状态为pending时才改变,保证状态一旦改变就不会再变
if(this.state === myPromise.PENDING){
this.state = myPromise.FULFILLEd //改变状态
this.promiseRes = result //返回值
//依次调用成功回调
this.resolveCallback.forEach(fn=>fn())
}
}
const reject = result=>{
// 只有当状态为pending时才改变,保证状态一旦改变就不会再变
if(this.state === myPromise.PENDING){
this.state = myPromise.REJECTED //改变状态
this.promiseRes = result //返回值
// 依次调用失败回调
this.rejectCallback.forEach(fn=>fn())
}
}
try{
init(resolve,reject) // 注意this指向
}catch(err){
reject(err)
} }
then(onFulfilled,onRejected){
if(this.state === myPromise.FULFILLEd && typeof onFulfilled === 'function') {
onFulfilled(this.promiseRes)
}
if(this.state === myPromise.REJECTED && typeof onRejected === 'function') {
onRejected(this.promiseRes)
}
}
}

写到这里,我们的promise已经初步成型了,我们可以来测试一下:

const res1 = new myPromise((res,rej)=>{
res('成功啦~')
rej('失败啦~')
})
res1.then((res)=>{
console.log(res)
},err=>{
console.log(err)
})
// 按照预期,这里应该是只会打印出成功啦~



从上图看我们,是不是符合我们的预期,并且myPromise内部与原生的Promise也是非常相似的。你们是不是觉得这里已经没问题了,上面我们只是测了一下同步方法的执行,但别忘了,Promise主要是来解决异步问题的,我们再来试一下里面执行异步方法还符不符合我们的预期?

const res1 = new myPromise((res,rej)=>{
setTimeout(()=>res('成功啦~'),1000)
// rej('失败啦~')
})
res1.then((res)=>{
console.log(res)
})

这里我们预期本来是一秒之后打印成功啦,但它并没有如我们所愿,反而是什么也没打印出来,这是因为在setTimeout执行之前(pen ding)这个then方法已经执行过了,1s后状态变成fulfilled时,then也不会再执行了。

所以我们需要保证then方法的回调函数在promise状态变成fulfilledrejected时再执行,那么当promise状态为pending时我们先要把回调存在对应的队列中,等后续状态改变后再执行

较完整then方法

OK,这里我们修改一下我们的then方法,让其保证异步代码执行的正确性 (具体实现微任务我们可以用 mutationObserver,这里我们就用setTimeout来模拟一下)

class myPromise {
static PENDING = 'pending'
static FULFILLEd = 'fulfilled'
static REJECTED = 'rejected'
constructor(init){
this.state = myPromise.PENDING // promise状态
this.promiseRes = null // promise返回值
this.resolveCallback = [] //成功回调集合
this.rejectCallback = [] //失败回调集合
const resolve = result=>{
// 只有当状态为pending时才改变,保证状态一旦改变就不会再变
if(this.state === myPromise.PENDING){
this.state = myPromise.FULFILLEd //改变状态
this.promiseRes = result //返回值
//依次调用成功回调
this.resolveCallback.forEach(fn=>fn())
}
}
const reject = result=>{
// 只有当状态为pending时才改变,保证状态一旦改变就不会再变
if(this.state === myPromise.PENDING){
this.state = myPromise.REJECTED //改变状态
this.promiseRes = result //返回值
// 依次调用失败回调
this.rejectCallback.forEach(fn=>fn())
}
}
try{
init(resolve,reject) // 注意this指向
}catch(err){
reject(err)
} }
then(onFulfilled,onRejected){
if(this.state === myPromise.FULFILLEd && typeof onFulfilled === 'function') {
onFulfilled(this.promiseRes)
}
if(this.state === myPromise.REJECTED && typeof onRejected === 'function') {
onRejected(this.promiseRes)
}
if(this.state === myPromise.PENDING){
if(onFulfilled && typeof onFulfilled === 'function'){
this.resolveCallback.push(()=>
// 这里我们用setTimeout来模拟实现then的微任务
setTimeout(()=>{
onFulfilled(this.promiseRes)
},0)
)
}
if(onRejected && typeof onRejected === 'function'){
this.rejectCallback.push(()=>
// 这里我们用setTimeout来模拟实现then的微任务
setTimeout(()=>{
onRejected(this.promiseRes)
},0)
)
}
}
}
}

这里我们可以再测试一下上面那个异步函数的测试用例,发现它能够正确打印,OK,一个较完整的then方法就算实现了~

then的链式调用

then方法会返回一个新的Promise(️注意:不是原来的那个Promise)所以可以采用链式调用

采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

then(onFulfilled,onRejected){
const {promiseRes,state} = this
let promise = new myPromise((reso,reje)=>{
const resolveMyPromise = promiseRes => {
try{
if(typeof onFulfilled !== 'function'){
// 如果then的第一个回调不是一个函数,直接忽略,返回一个新的promise
reso(promiseRes)
}else{
// 获取第一个回调的执行结果
const res = onFulfilled(promiseRes)
// 看该执行结果是否是一个promise
if(res instanceof myPromise){
// 是一个promise,等它状态改变后再改变then返回的promise状态
res.then(reso,rej)
}else{
// 不是一个promise,将它作为新的promise的resolve
reso(res)
}
}
}catch(err){
//异常,直接将新的promise状态置为rejected
reje(err)
}
}
const rejectMyPromise = promiseRes => {
try{
if(typeof onRejected !== 'function'){
// 如果then的第二个回调不是一个函数,直接忽略,返回一个新的promise
reje(promiseRes)
}else{
// 获取第二个回调的执行结果
const res = onRejected(promiseRes)
// 看该执行结果是否是一个promise
if(res instanceof myPromise){
// 是一个promise,等它状态改变后再改变then返回的promise状态
res.then(reso,rej)
}else{
// 不是一个promise,将它作为新的promise的resolve
reje(res)
}
} }catch(err){
//异常,直接将新的promise状态置为rejected
reje(err)
}
}
if(state === myPromise.FULFILLEd) {
resolveMyPromise(promiseRes)
}
if(state === myPromise.REJECTED) {
rejectMyPromise(promiseRes)
}
if(state === myPromise.PENDING){
if(onFulfilled && typeof onFulfilled === 'function'){
this.resolveCallback.push(()=>
// 这里我们用setTimeout来模拟实现then的微任务
setTimeout(()=>{
resolveMyPromise(this.promiseRes)
},0)
)
}
if(onRejected && typeof onRejected === 'function'){
this.rejectCallback.push(()=>
// 这里我们用setTimeout来模拟实现then的微任务
setTimeout(()=>{
rejectMyPromise(this.promiseRes)
},0)
)
}
} })
return promise
}

catch方法

我们知道then的第二个回调其实与catch方法是一样的,所以catch方法我们可以这样实现

catch(onRejected) {
return this.then(undefined,onRejected)
}

Promise.resolve

将对象转为一个promise对象,根据参数不通可分为四种情况

  • 参数是一个Promise实例,直接返回该实例
  • 参数是一个thenable对象,将该对象转为Promise对象后,执行该对象的then方法
  • 没有参数,也是返回一个状态为resolved的新的Promise对象
  • 参数是一个一个原始值,返回一个新的Promise对象,状态为resolved

手动实现:

static resolve(v){
//1.参数是一个Promise实例,直接返回
if(v instanceof myPromise){
return v
}
//2.参数是一个thenable对象,转为Promise后执行该对象的then方法
if(typeof v === 'object' && typeof v.then === 'function'){
return new myPromise((res,rej)=>{
v.then(res,rej)
})
}
//3.没有参数,直接返回一个resolved状态的promise
if(!v){
return new myPromise(res=>{
res()
})
}
//4.参数是一个原始值,返回一个新的Promise,状态为resolved
return new myPromise(res=>{
res(v)
})
}

Promise.reject

返回一个新的Promise对象,状态为rejected

static reject(v){
return new myPromise((res,rej)=>{
rej(v)
})
}

Promise.all

该方法用于将多个Promise实例包装成一个新的Promise实例,如果有不是Promise的项,则让该项直接成功

用法:

const p = Promise.all([p1,p2,p3])

p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

Ok,了解完Promise.all我们动手来实现一遍

手动实现:

static all (promises){
return new myPromise((res,rej)=>{
let count = 0
const result = [];
function addFun(index,resf) {
result[index]=resf // 这里用索引别用push,保证返回的顺序
count++
if(count==promises.length) {
res(result)
}
}
[].forEach.call(promises,(promise,index)=>{
if(promise instanceof myPromise) {
promise.then(success=>{
// count ++
// result.push(success)
addFun(index,success)
},err=>{
rej(err)
})
}else{
addFun(index,promise)
}
})
})
}

Promise.race

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

用法:

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

手动实现:

static race(promises) {
return new myPromise((res,rej)=>{
[].forEach.call(promises,promise=>{
if(promise instanceof myPromise){
promise.then(success=>{
res(success)
},error=>{
rej(error)
})
}else{
res(promise)
}
})
})
}

完整代码

class myPromise {
static PENDING = 'pending'
static FULFILLEd = 'fulfilled'
static REJECTED = 'rejected'
constructor(init){
this.state = myPromise.PENDING // promise状态
this.promiseRes = null // promise返回值
this.resolveCallback = [] //成功回调集合
this.rejectCallback = [] //失败回调集合
const resolve = result=>{
// 只有当状态为pending时才改变,保证状态一旦改变就不会再变
if(this.state === myPromise.PENDING){
this.state = myPromise.FULFILLEd //改变状态
this.promiseRes = result //返回值
//依次调用成功回调
this.resolveCallback.forEach(fn=>fn())
}
}
const reject = result=>{
// 只有当状态为pending时才改变,保证状态一旦改变就不会再变
if(this.state === myPromise.PENDING){
this.state = myPromise.REJECTED //改变状态
this.promiseRes = result //返回值
// 依次调用失败回调
this.rejectCallback.forEach(fn=>fn())
}
}
try{
init(resolve,reject) // 注意this指向
}catch(err){
reject(err)
} }
then(onFulfilled,onRejected){
const {promiseRes,state} = this
let promise = new myPromise((reso,reje)=>{
const resolveMyPromise = promiseRes => {
try{
if(typeof onFulfilled !== 'function'){
// 如果then的第一个回调不是一个函数,直接忽略,返回一个新的promise
reso(promiseRes)
}else{
// 获取第一个回调的执行结果
const res = onFulfilled(promiseRes)
// 看该执行结果是否是一个promise
if(res instanceof myPromise){
// 是一个promise,等它状态改变后再改变then返回的promise状态
res.then(reso,rej)
}else{
// 不是一个promise,将它作为新的promise的resolve
reso(res)
}
}
}catch(err){
//异常,直接将新的promise状态置为rejected
reje(err)
}
}
const rejectMyPromise = promiseRes => {
try{
if(typeof onRejected !== 'function'){
// 如果then的第二个回调不是一个函数,直接忽略,返回一个新的promise
reje(promiseRes)
}else{
// 获取第二个回调的执行结果
const res = onRejected(promiseRes)
// 看该执行结果是否是一个promise
if(res instanceof myPromise){
// 是一个promise,等它状态改变后再改变then返回的promise状态
res.then(reso,rej)
}else{
// 不是一个promise,将它作为新的promise的resolve
reje(res)
}
} }catch(err){
//异常,直接将新的promise状态置为rejected
reje(err)
}
}
if(state === myPromise.FULFILLEd) {
resolveMyPromise(promiseRes)
}
if(state === myPromise.REJECTED) {
rejectMyPromise(promiseRes)
}
if(state === myPromise.PENDING){
if(onFulfilled && typeof onFulfilled === 'function'){
this.resolveCallback.push(()=>
// 这里我们用setTimeout来模拟实现then的微任务
setTimeout(()=>{
resolveMyPromise(this.promiseRes)
},0)
)
}
if(onRejected && typeof onRejected === 'function'){
this.rejectCallback.push(()=>
// 这里我们用setTimeout来模拟实现then的微任务
setTimeout(()=>{
rejectMyPromise(this.promiseRes)
},0)
)
}
} })
return promise
}
catch(onRejected) {
return this.then(undefined,onRejected)
}
static all (promises){
return new myPromise((res,rej)=>{
let count = 0
const result = [];
function addFun(index,resf) {
result[index]=resf // 这里用索引别用push,保证返回的顺序
count++
if(count==promises.length) {
res(result)
}
}
[].forEach.call(promises,(promise,index)=>{
if(promise instanceof myPromise) {
promise.then(success=>{
addFun(index,success)
},err=>{
rej(err)
})
}else{
addFun(index,promise)
}
})
})
}
static race(promises) {
return new myPromise((res,rej)=>{
[].forEach.call(promises,promise=>{
if(promise instanceof myPromise){
promise.then(success=>{
res(success)
},error=>{
rej(error)
})
}else{
res(promise)
}
})
})
}
static resolve(v){
//1.参数是一个Promise实例,直接返回
if(v instanceof myPromise){
return v
}
//2.参数是一个thenable对象,转为Promise后执行该对象的then方法
if(typeof v === 'object' && typeof v.then === 'function'){
return new myPromise((res,rej)=>{
v.then(res,rej)
})
}
//3.没有参数,直接返回一个resolved状态的promise
if(!v){
return new myPromise(res=>{
res()
})
}
//4.参数是一个原始值,返回一个新的Promise,状态为resolved
return new myPromise(res=>{
res(v)
})
}
static reject(v){
return new myPromise((res,rej)=>{
rej(v)
})
}
}

总结

OK,上面跟大家一起过了一遍Promise的用法以及自己动手实现了一遍Promise,想必看完这篇文章,大家对Promise会有一个更加清晰的认识。

我是南玖,感谢各位的:「点赞和关注」,我们下期见!

从如何使用到如何实现一个Promise的更多相关文章

  1. 教你一步一步实现一个Promise

    Promise我想现在大家都非常熟悉了,主要作用就是解决异步回调问题,这里简单介绍下. Promise规范是CommonJS规范之一,而Promise规范又分了好多种,比如 Promises/A.Pr ...

  2. 【原】手写一个promise

    上一篇文章中,我们介绍了Promise的基本使用,在这篇文章中,我们试着自己来写一个Promise,主要是学习Promise的内部机制,学习它的编程思想. !!!备注:本文写的不好,仅供自己学习之用, ...

  3. [翻译]简单的实现一个Promise

    英文原文为:https://www.promisejs.org/implementing/ 1. 状态机 因为 promise 对象是一个状态机,所以我们首先应该定义将要用到的状态. var PEND ...

  4. Promise原理—一步一步实现一个Promise

    promise特点 一个promise的当前状态只能是pending.fulfilled和rejected三种之一.状态改变只能是pending到fulfilled或者pending到rejected ...

  5. Promise原理讲解 && 实现一个Promise对象 (遵循Promise/A+规范)

    1.什么是Promise? Promise是JS异步编程中的重要概念,异步抽象处理对象,是目前比较流行Javascript异步编程解决方案之一 2.对于几种常见异步编程方案 回调函数 事件监听 发布/ ...

  6. 面试----你可以手写一个promise吗

    参考:https://www.jianshu.com/p/473cd754311f <!DOCTYPE html> <html> <head> <meta c ...

  7. promise对象的回调函数resolve的参数为另一个promise对象

    /*如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数. reject函数的参数通常是Error对象的实例,表示抛出的错误: resolve函数的参数除了正常的值 ...

  8. 自己实现一个Promise库

    源码地址 先看基本使用 const promise = new Promise((resolve, reject) => { resolve(value) // or reject(reason ...

  9. 实现一个Promise.all

    用js自己实现一个Promise.all let promiseAll = (promises) => { return new Promise((resolve, reject) => ...

  10. 掘金转载-手写一个Promise

    目录 一 什么是Promise ? 二 Promises/A+ 规范 2.1 术语 2.2 基本要求 2.2.1. Promise的状态 2.2.2. Then 方法 2.3 简易版实践 2.4 进一 ...

随机推荐

  1. Redis持久化 aof和rdb的原理配置

    目录 一.介绍 二.RDB持久化(全量写入) rdb原理 rdb模式 rdb触发情况 rdb优势和劣势 rdb文件配置 rdb命令配置 rdb数据恢复 三.AOF持久化(增量写入) aof原理 aof ...

  2. Mysql原有环境部署多个版本

    目录 一.环境准备 二.下载安装包 三.Mysql-5.7单独部署 四.启动Mysql-5.7 五.muliti使用 一.环境准备 原先已经有一个5.6版本的数据库在运行了,当前操作是完全不影响原数据 ...

  3. [源码解析] PyTorch 分布式(16) --- 使用异步执行实现批处理 RPC

    [源码解析] PyTorch 分布式(16) --- 使用异步执行实现批处理 RPC 目录 [源码解析] PyTorch 分布式(16) --- 使用异步执行实现批处理 RPC 0x00 摘要 0x0 ...

  4. Java synchonized 同步

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this ...

  5. 资源工作表中与资源有关的操作(Project)

    <Project2016 企业项目管理实践>张会斌 董方好 编著 这个内容,我需要专门写一篇吗? 不写吧,好像对不起我那股学习的劲:写吧,实在是--一句话就够了:所有与任务有关的新建.修改 ...

  6. openstack 一键部署云主机

    两边虚拟机时间不一样cinder会报错. 在computer节输入命令 ntpdate 192.168.10.100 systemctl restart openstack-cinder-volume ...

  7. JAVA实现QQ第三方登录

    首先在QQ互联: https://connect.qq.com/manage.html 申请账号,并且进行资料审核,同时创建应用(设置回调地址) 申请应用完后,会有app_ID.app_KEY等参数 ...

  8. JAVA判断IP是否是内网IP

    /** * 私有IP: * A类  10.0.0.0-10.255.255.255   * B类  172.16.0.0-172.31.255.255   * C类  192.168.0.0-192. ...

  9. UiPath RPA培训2021.4版本解读 (2021年5月)-RPA学习天地

    2021年5月26日Ui Path发布了新产品2021.4版本,我们来看看有什么新功能: 说明一下uipath的版本发布节奏: uipath的版本一般是每年发布2个版本,其中5月份发布的一般是FTS版 ...

  10. 【LeetCode】647. Palindromic Substrings 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:暴力循环 方法二:固定起点向后找 方法三:动 ...