前端面试题之Promise问题

前言

在我们日常开发中会遇到很多异步的情况,比如涉及到 网络请求(ajax,axios等)定时器这些,对于这些异步操作我们如果需要拿到他们操作后的结果,就需要使用到回调函数。拿请求来说,如果我们需要拿到请求回来的数据我们就需要利用回调函数(见代码片段1),以下所有的请求都是使用jQuery的ajax模拟。

点击查看代码片段1
    // 代码片段1
$.ajax({
url: 'url',
type: 'post',
data: {
参数1: 值1,
参数2: 值2
},
success: function(res){
// success就是响应成功的回调函数,在此处可以获取到响应返回的内容
console.log(res)
}
})

在网络请求中会遇到请求之前的依赖,一个请求会依赖于另一个请求的时候,就会需要出现回调嵌套的问题(代码片段2)

点击查看代码片段2
    // 代码片段2
// 请求2的请求参数是请求1的响应结果
// 请求3的请求参数是请求2的响应结果
$.ajax({
url: '请求1的地址',
type: 'post',
data: {
参数1: 值1,
参数2: 值2
},
success: function(res){
// res是请求1的响应结果
console.log(res)
$.ajax({
url: '请求2的地址',
type: 'post',
data: {
// 参数1是请求1的响应结果
参数1: res,
参数2: 值2
},
success: function(res2){
// res2是请求2的响应结果
$.ajax({
url: '请求3的地址',
type: 'post',
data: {
// 参数1是请求2的响应结果
参数1: res2,
参数2: 值2
},
success: function(res3){
// res3是最终请求的结果
console.log(res3)
}
})
}
})
}
})

这样的代码就出现了js编程里面的著名回调地狱问题,为了解决这个问题我们需要利用es2015的promise和es2016的await来解决(代码片段3)

点击查看代码片段3
    // 代码片段3
// await不能写在同步的代码里面,会阻塞整个程序的进程,只能写在异步的代码里面
// async可以修饰一个函数让这个函数变成一个异步的函数
async function getRes(){
// $.ajax() 返回的是一个promise对象
// await可以等待一个promise状态结束,拿到响应的结果
const res1 = await $.ajax({
url: '请求1的地址',
type: 'post',
data: {
参数1: 值1,
参数2: 值2
}
})
const res2 = await $.ajax({
url: '请求2的地址',
type: 'post',
data: {
参数1: res,
参数2: 值2
}
})
const res3 = await $.ajax({
url: '请求3的地址',
type: 'post',
data: {
参数1: res2,
参数2: 值2
}
})
}
// 调用异步函数
getRes()

利用promise和await就可以将之前的回调嵌套地狱改成同步的代码方式,但是这里面大家要注意await的使用事项,await不允许出现在同步代码块里面,会阻塞同步代码执行,必须写在异步的代码块里面,加async让getRes函数成为一个异步的函数,这样getRes执行不会阻塞全局,getRes函数内部代码就是一个同步的方式执行,就解决了回调嵌套的问题。本来故事到这里就该说goodBye了,但是现在很多面试,尤其是一些大厂的面试要求我们自己实现promise和await,这章节先给大家实现promise。

Promise的封装

1. promise的介绍和使用

  • 状态(state)

    • pending

      • promise的初始状态,并且此状态可以转换成fulfilledrejected
    • fulfilled
      • promise的成功状态,不可以转换其他状态,并且必须有一个不可改变的最终值value
    • rejected
      • promise的失败状态,不可以转换其他状态,并且必须有一个不可改变的原因reason
  • 术语

    • 解决(fulfill)

      当调用resolve方法时promise的状态变成fulfill

    • 拒绝(reject)

      当调用reject方法时promise的状态变成reject

    • 终值(value)

      所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)。

    • 拒因(reason)

      也就是拒绝(失败)的原因,指在 promise 被拒绝时传递给拒绝回调的值。

  • 面向对象编程方式

    • Promise是面向对象编程方式,对应的构造函数是 Promise
    • 使用Promise的时候需要创建一个实例对象,使用实例对象的方法
    • Promise构造函数实例化的时候需要传递一个函数(handler),此函数里面可以接收两个参数,一个函数叫做resolve,一个函数叫做reject
    • 调用resolve时候promise实例的状态变为 fulfilled
    • 调用reject时候promise实例的状态变为 rejected
    • 此处要注意一个promise实例只有一个状态,也就是不能同时调用resolve和reject
点击查看代码
    // promise01就是实例化Promise构造函数得到的实例对象
const promise01 = new Promise((resolve,reject)=>{
// 此处调用resolve时promise状态会变成fulfilled
// 此处调用reject时promise状态会变成rejected
})
  • 实例对象的方法

    • then方法

      • 该方法有两个参数,可选,分别对应onFulfilled,onRejected
      • 当状态为fulfilled时,调用onFulfilled,得到一个终值 value
      • 当状态为rejected时,调用onRejected,得到一个拒因 reason
    • catch方法
      • 该方法有一个参数,可选,对应onRejected
      • 当状态为rejected时,调用onRejected,得到一个拒因
    • finally方法
      • 该方法有一个参数,可选
      • 该方法无论是promise状态成功还是失败都会调用
    • 注意:
      1. 实例方法可以实现链式调用,因为每一次方法调用完成之后都会返回一个promise实例
      2. then方法可以调用多次
点击查看代码
    const promise01 = new Promise((resolve,reject)=>{
})
// 使用实例对象方法 链式调用
promise01.then(
// onFulfilled
function(value){
// 当promise实例的状态为fulfilled调用
},
// onRejected
function(reason){
// 当promise实例的状态为rejected调用
}
).then(
// then方法可以调用多次
// 并且调用可以不传递参数
).catch(function(reason){
// 当promise实例的状态为rejected调用
}).finally(function(){
// 无论promise实例的状态为fulfilled或者rejected都会调用
})

2. 实现手写Promise

  1. 实现Promise的构造函数

    • 构造函数需要参数,参数必须是一个函数类型 这里面我们叫做handler,这儿会对handler类型进行验证,如果不是函数类型会抛出类型异常
    • handler函数需要两个形式参数 对应的是类自身的两个方法
      • resolve方法

        • 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
        • 调用方法promise的状态变为fulfilled
        • 调用该方法会得到一个终值value
        • 该方法可能异步调用
        • 该方法里面会用到实例的this,在调用的时候需要改变this的指向
      • reject方法
        • 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
        • 调用方法promise的状态变为rejected
        • 调用该方法会得到一个拒因reason
        • 该方法可能异步调用
        • 该方法里面会用到实例的this,在调用的时候需要改变this的指向
    • 实例里面属性
      • value 终值
      • reason 拒因
      • state 状态
        • 状态可以作为静态的属性,也可以作为实例的属性
        • 状态会多次用于判断,所以建议用常量保存
    • 实例里面的方法
      • then
      • catch
      • finally
    • 类自身的方法
      • resolve
      • reject
        // promise.js
    // 状态会多次用于判断,所以建议用常量保存
    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'
    class Promise{
    constructor(handler){
    // 构造函数需要参数,参数必须是一个函数类型 这里面我们叫做handler,这儿会对handler类型进行验证,如果不是函数类型会抛出类型异常
    if(typeof handler !== 'function'){
    throw new TypeError(`Promise构造函数的参数${handler}不是一个函数`)
    }
    this.state = PENDING // 用来存储promise的状态 初始化状态为pending
    this.reason = undefined // 拒因
    this.value = undefined // 终值
    // resolve,reject方法里面会用到实例的this,在调用的时候需要改变this的指向
    handler(this.resolve.bind(this), this.reject.bind(this))
    }
    resolve(value){
    // 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
    if(this.state !== PENDING) return
    // 调用方法promise的状态变为fulfilled
    this.state = FULFILLED
    // 调用该方法会得到一个终值value
    this.value = value
    }
    reject(reason){
    // 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
    if(this.state !== PENDING) return
    // 调用方法promise的状态变为rejected
    this.state = REJECTED
    // 调用该方法会得到一个拒因reason
    this.reason = reason
    }
    }
        // 试错
    const p = new Promise(1)
    console.log(p)
    // promise.js:9 Uncaught TypeError: Promise构造函数的参数1不是一个函数
    // 正确操作
    // 状态成功
    const p = new Promise((resolve, reject) => {
    resolve()
    })
    console.log(p)
    // Promise {state: 'fulfilled', reason: undefined, value: undefined}
    // 状态失败
    const p = new Promise((resolve, reject) => {
    reject()
    })
    console.log(p)
    // Promise {state: 'rejected', reason: undefined, value: undefined}
  2. 实现实例then方法

  • then方法接受两个参数,两个参数类型是函数

    • onFulfilled

      • 需要对onFulfilled类型进行验证,如果不是一个函数就设置为默认函数function(value){return value},这个函数是获取终值
      • 该函数在promise状态为fulfilled时候调用,传入终值
    • onRejected
      • 需要对onRejected类型进行验证,如果不是一个函数就设置为一个默认函数function(err){throw err},这个函数是获取拒因
      • 该函数在promise状态为rejected时候调用,传入拒因
        // promise.js
    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'
    class Promise{
    constructor(handler){...}
    resolve(value){...}
    reject(reason){...}
    then(onFulfilled, onRejected){
    // 参数可选要进行判断 如果传递不是一个函数 默认一个函数
    onFulfilled = typeof onFulfilled==='function' ? onFulfilled : value => value;
    onRejected = typeof onRejected==='function' ? onRejected : err => {throw err};
    if(this.state === FULFILLED){
    // 当promise状态为fulfilled时候调用onFulfilled
    onFulfilled(this.value)
    }
    if(this.state === REJECTED){
    // 当promise状态为rejected时候调用onRejected
    onRejected(this.reason)
    }
    }
    }
        // test.js
    // 成功状态
    const p = new Promise((resolve, reject) => {
    resolve('success')
    })
    p.then(
    res => {
    console.log('调用了onFullfilled')
    console.log(res)
    },
    err => {
    console.log('调用了onRejected')
    console.log(err)
    }
    )
    // 控制台输出 调用了onFullfilled sucess // 失败状态
    const p = new Promise((resolve, reject) => {
    reject('error')
    })
    p.then(
    res => {
    console.log(res)
    },
    err => {
    console.log(err)
    }
    )
    // 控制台输出 调用了onRejected error // handler函数异步调用resolve
    const p = new Promise((resolve, reject) => {
    setTimeout(()=>{
    resolve('success')
    },3000)
    })
    p.then(
    res => {
    console.log('调用了onFullfilled')
    console.log(res)
    },
    err => {
    console.log('调用了onRejected')
    console.log(err)
    }
    )
    // 控制台没有任何内容输出 onFulfilled函数并没有被调用
    • 此时经过测试,目前的promise可以正常的调用,此时针对于同步的调用没有问题,但是在handler里面 resolvereject 大部分情况下都是异步调用的,比如说延迟3s调用,此时then里面的onFulfilledonRejected是无法执行的,因为then函数在执行的时候promise的状态是pending,onFulfilledonRejected函数里面是做了判断,没有对pendng状态进行处理,针对于pending状态的需要处理
        // promise.js
    // ...
    class Promise {
    // 用来存储状态成功的回调函数
    // 用来存储状态失败的回调函数
    successCallBack = null
    errorCallBack = null
    constructor(handler) {...}
    resolve(value) {
    // ...
    // 异步调用的时候执行存储的onFulfilled函数,传入终值
    this.successCallBack(this.value) }
    reject(reason) {
    // ...
    // 异步调用的时候执行存储的onRejected函数,传入拒因
    this.errorCallBack(this.reason)
    }
    then(onFulfilled, onRejected) {
    // ...
    if (this.state === PENDING) {
    // 当resolve或者reject异步调用,then执行的时候promise状态等待
    // 将onFulfilled和onReject函数存储起来
    this.successCallBack = onFulfilled
    this.errorCallBack = onRejected
    }
    }
    }
        // test.js
    // 异步调用resolve
    const p = new Promise((resolve, reject) => {
    setTimeout(()=>{
    resolve('success')
    },3000)
    })
    p.then(
    res => {
    console.log('调用了onFullfilled')
    console.log(res)
    },
    err => {
    console.log('调用了onRejected')
    console.log(err)
    }
    )
    // 3s后控制台输出 调用了onFullfilled success
    • 此时promise的handler就可以异步调用 resolvereject,then里面可以获取到终值或据因
    • then函数支持多次调用,所以此时then的回调函数就不止一个,我们将之前的变量改成数组用来存储then的回调函数
        // promise.js
    // ...
    class Promise {
    // 用数组的方式存储失败和成功的回调函数
    successCallBack = []
    errorCallBack = []
    constructor(handler) {...}
    resolve(value) {
    // ...
    // 遍历数组将所有成功的函数执行
    this.successCallBack.forEach(fn=> fn())
    }
    reject(reason) {
    // ...
    // 遍历数组将所有失败的函数执行
    this.errorCallBack.forEach(fn=>fn())
    }
    then(onFulfilled, onRejected) {
    // ...
    if (this.state === PENDING) {
    // 当resolve或者reject异步调用,then执行的时候promise状态等待
    // 将onFulfilled和onReject函数存储起来
    this.successCallBack.push(()=>{
    onFulfilled(this.value)
    })
    this.errorCallBack.push(()=>{
    onRejected(this.reason)
    })
    }
    }
    }
    • 测试调用了两次then,状态成功之后两次then的成功回调都会被执行
        // test.js
    // 异步调用resolve
    const p = new Promise((resolve, reject) => {
    setTimeout(()=>{
    resolve('success')
    },3000)
    })
    p.then(
    res => {
    console.log('调用了onFullfilled')
    console.log(res)
    },
    err => {
    console.log('调用了onRejected')
    console.log(err)
    }
    )
    p.then(
    res => {
    console.log('调用了onFullfilled01')
    console.log(res)
    },
    err => {
    console.log('调用了onRejected01')
    console.log(err)
    }
    )
    // 3s后控制台输出 调用了onFullfilled success 调用了onFullfilled01 success
    • 但是目前then不支持链式调用,需要进行链式调用的支持
    • 支持链式调用的条件
      1. 只有then函数调用返回一个promise对象,一个新的promise对象newPromise
      2. then函数有一个返回值 这个返回值result就是新promise的onFulfill或onRejected的值
    • 接下来我们需要判断这个返回值result的类型
      1. 一个具体值
      2. 一个新的promise
    • 另外我们需要一个函数去处理这种链式调用的问题handlerChainPromise,这个函数接收参数如下
      1. result 老的promise的then返回值,作为新promise的onFulfill或onRejected
      2. resolve成功处理函数
      3. reject失败处理函数
    class Promise {
    then(onFulfilled, onRejected) {
    // 每一次调用返回一个promise
    return new Promise((resolve,reject) => {
    // 参数可选要进行判断 如果传递不是一个函数 默认一个函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    if (this.state === PENDING) {
    // 当resolve或者reject异步调用,then执行的时候promise状态等待
    // 将onFulfilled和onReject函数存储起来
    this.successCallBack.push(() => {
    let result = onFulfilled(this.value)
    this.handlerChainPromise(result, resolve, reject)
    })
    this.errorCallBack.push(() => {
    let result = onRejected(this.reason)
    this.handlerChainPromise(result, resolve, reject)
    })
    }
    if (this.state === FULFILLED) {
    // 当promise状态为fulfilled时候调用onFulfilled
    let result = onFulfilled(this.value)
    this.handlerChainPromise(result, resolve, reject)
    }
    if (this.state === REJECTED) {
    // 当promise状态为rejected时候调用onRejected
    let result = onRejected(this.reason)
    this.handlerChainPromise(result, resolve, reject)
    }
    })
    }
    handlerChainPromise(result,resolve,reject){
    // 如果返回的是一个promise就调用它的then方法
    // 如果返回的是一个具体值就直接返回值
    if(result instanceof Promise){
    result.then(resolve, reject)
    }else{
    resolve(result)
    }
    }
    }
    • 测试代码如下
        // 返回一个具体值(可以使任何类型)
    const p = new Promise((resolve, reject) => {
    setTimeout(()=>{
    resolve('success')
    },3000)
    })
    p.then(
    res => {
    return 11
    },
    err => {
    console.log(err)
    }
    ).then(
    res=>{
    console.log(res)
    }
    )
    // 返回一个promsie
    const p = new Promise((resolve, reject) => {
    setTimeout(()=>{
    resolve('success')
    },3000)
    })
    p.then(
    res => {
    return new Promise(resolve=>{
    setTimeout(()=>{
    resolve(22)
    }, 1000)
    })
    },
    err => {
    console.log(err)
    }
    ).then(
    res=>{
    console.log(res)
    }
    )
    • 以上一个完整的promise就封装成功了
    • 完整代码如下
    // 状态会多次用于判断,所以建议用常量保存
    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'
    class Promise {
    // 用来存储状态成功的回调函数
    // 用来存储状态失败的回调函数
    successCallBack = []
    errorCallBack = []
    constructor(handler) {
    // 构造函数需要参数,参数必须是一个函数类型 这里面我们叫做handler,这儿会对handler类型进行验证,如果不是函数类型会抛出类型异常
    if (typeof handler !== 'function') {
    throw new TypeError(`Promise构造函数的参数${handler}不是一个函数`)
    }
    this.state = PENDING // 用来存储promise的状态 初始化状态为pending
    this.reason = undefined // 拒因
    this.value = undefined // 终值
    // resolve,reject方法里面会用到实例的this,在调用的时候需要改变this的指向
    handler(this.resolve.bind(this), this.reject.bind(this))
    }
    resolve(value) {
    // 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
    if (this.state !== PENDING) return
    // 调用方法promise的状态变为fulfilled
    this.state = FULFILLED
    // 调用该方法会得到一个终值value
    this.value = value
    // 异步调用的时候执行存储的onFulfilled函数,传入终值
    this.successCallBack.forEach(fn => fn())
    }
    reject(reason) {
    // 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
    if (this.state !== PENDING) return
    // 调用方法promise的状态变为rejected
    this.state = REJECTED
    // 调用该方法会得到一个拒因reason
    this.reason = reason
    // 异步调用的时候执行存储的onRejected函数,传入拒因
    this.errorCallBack.forEach(fn => fn())
    }
    then(onFulfilled, onRejected) {
    // 每一次调用返回一个promise
    return new Promise((resolve,reject) => {
    // 参数可选要进行判断 如果传递不是一个函数 默认一个函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    if (this.state === PENDING) {
    // 当resolve或者reject异步调用,then执行的时候promise状态等待
    // 将onFulfilled和onReject函数存储起来
    this.successCallBack.push(() => {
    let result = onFulfilled(this.value)
    this.handlerChainPromise(result, resolve, reject)
    })
    this.errorCallBack.push(() => {
    let result = onRejected(this.reason)
    this.handlerChainPromise(result, resolve, reject)
    })
    }
    if (this.state === FULFILLED) {
    // 当promise状态为fulfilled时候调用onFulfilled
    let result = onFulfilled(this.value)
    this.handlerChainPromise(result, resolve, reject)
    }
    if (this.state === REJECTED) {
    // 当promise状态为rejected时候调用onRejected
    let result = onRejected(this.reason)
    this.handlerChainPromise(result, resolve, reject)
    }
    })
    }
    handlerChainPromise(result,resolve,reject){
    // 如果返回的是一个promise就调用它的then方法
    // 如果返回的是一个具体值就直接返回值
    if(result instanceof Promise){
    result.then(resolve, reject)
    }else{
    resolve(result)
    }
    }
    }

前端面试题之手写promise的更多相关文章

  1. 前端面试题整理——手写AJAX

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. 前端面试题整理——手写方法解析URL参数

    //拆分字符串形式 function queryToObj() { const res = {} const search = location.search.substr(1);//去掉前面的&qu ...

  3. 前端面试题整理——手写简易jquery

    class jQuery { constructor(selector) { const result = document.querySelectorAll(selector) console.lo ...

  4. 前端面试题整理——手写bind函数

    var arr = [1,2,3,4,5] console.log(arr.slice(1,4)) console.log(arr) Function.prototype.bind1 = functi ...

  5. 前端面试题整理——手写flatern摊平数组

    // flatern 是摊平数组 function flat(arr) { const isDeep = arr.some(item => item instanceof Array) if(! ...

  6. 字节跳动-前端面试题 Multi Promise Order

    字节跳动-前端面试题 Multi Promise Order Promise Order Async/Await async function async1 () { console.log('asy ...

  7. 前端面试题(JavaScript)

    (前端面试题大全,持续更新) 箭头函数特点?箭头函数和普通函数的区别 手写懒加载(考虑防抖和重复加载问题) 手写bind(为什么要加预参数,为什么要加new) apply, call, bind ne ...

  8. 金三银四,磨砺锋芒;剑指大厂,扬帆起航(2020年最全大厂WEB前端面试题精选)上

    金三银四,磨砺锋芒:剑指大厂,扬帆起航(2020年最全大厂WEB前端面试题精选)上 引言 元旦匆匆而过,2020年的春节又接踵而来,大家除了忙的提着裤子加班.年底冲冲冲外,还有着对于明年的迷茫和期待! ...

  9. 金三银四求职季,前端面试题小梳理(HTML、CSS、JS)

    好久没写学习记录,最近太多事,又到一年求职季,都说金三银四求职季,自己也做一下最近学习的一些前端面试题梳理,还是个小白,写的不对请指正,不胜感激. HTML篇 html语义化 用语义化的代码标签书写, ...

随机推荐

  1. 使用 & 进行高效率取余运算

    Java的HashMap源码中用到的(n-1)&hash这样的运算,这是一种高效的求余数的方法 结论:假设被除数是x,对于除数是2n的取余操作x%2n,都可以写成x&(2n-1),位运 ...

  2. Qt5获取系统文件图标,文件路径

    获取系统图标: QFileIconProvider icon_provider; QIcon icon = icon_provider.icon(QFileIconProvider::Folder); ...

  3. adb shell 查看当前与用户交互的 activity

    adb shell dumpsys activity activities | grep mActivityComponent

  4. noip模拟36

    \(\color{white}{\mathbb{荷花映日,莲叶遮天,名之以:残荷}}\) 今天再次翻车掉出前十 开题看错 \(t1\) 以为操作2的值固定发现是个简单题,然后 \(t2\) 开始大力 ...

  5. Python习题集(五)

    每天一习题,提升Python不是问题!!有更简洁的写法请评论告知我! https://www.cnblogs.com/poloyy/category/1676599.html 题目 打印99乘法表 解 ...

  6. 对easyui-validatebox的验证类型的扩展

    easyui为我们提供了validatebox类型的组件,使用它可以完成自动验证,十分方便.要注意的是,easyui中的各个组件都是有继承关系的.通过查看api,textbox继承validatebo ...

  7. Java字符串常量池及字符串判等解析

    一.理解"=="的含义 "=="常用于两个对象的判等操作,在Java中,"=="主要有以下两种用法: 1.基础数据类型:比较的是他们的值是否 ...

  8. DHCP的原理和配置

    前言 在大型企业网络中,会有大量的主机或设备需要获取IP地址等网络参数.如果采用手工配置,工作量大且不好管理,如果有用户擅自修改网络参数,还有可能会造成 IP地址冲突等问题.使用动态主机配置协议DHC ...

  9. Java基础系列(38)- 数组的使用

    数组的使用 For-Each循环 数组作方法入参 数组作返回值 For-Each循环 普通型 package array; import sun.security.util.Length; publi ...

  10. CF710F-String Set Queries【AC自动机,二进制分组】

    正题 题目链接:https://www.luogu.com.cn/problem/CF710F 题目大意 \(T\)次操作 往集合中加入一个字符串 往集合中删除一个字符串 给出一个模式串求出现的集合里 ...