脑壳疼的tapable

栏目: 编程工具 · 发布时间: 5年前

内容简介:好的,方法一共是上述这么多,第一眼看过去,懵逼树下你和我,所以我们还是一点点来,一个个的分析、学习和了解先来个使用的例子,例如前端开发者需要掌握哪些技能?ok,就是上面这两句,我们创建了个FontEnd前端开发
  1. tapable是个独立的库
  2. webpack中大量使用了这个库
  3. tapable主要是用来处理事件,解决的问题有点类似EventEmitter,不过功能更加强大

Q2:tapable方法有哪些?

const {
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    SyncLoopHook,
    AsyncParallelHook,
    AsyncParallelBailHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook 
 } = require("tapable");
复制代码

好的,方法一共是上述这么多,第一眼看过去,懵逼树下你和我,所以我们还是一点点来,一个个的分析、学习和了解

Q3:啥是SyncHook?

先来个使用的例子,例如前端开发者需要掌握哪些技能?

step1:首先我们要明确群体是前端开发

const {SyncHook}= require('tapable');
const FontEnd = new SyncHook();
复制代码

ok,就是上面这两句,我们创建了个FontEnd前端开发

step2:前端开发需要掌握哪些技能,例如webpack、react对吧

FontEnd.tap('webpack',()=>{
  console.log("get webpack")
});
FontEnd.tap('react',()=>{
  console.log("get react")
});
复制代码

ok,上面的tap就是用来绑定事件的,为前端开发添加了两个技能

step3:技能需要学习才能掌握,所以我们要有学习的动作

FontEnd.learn=()=>{
  FontEnd.call()
};
FontEnd.learn();
复制代码

step4:查看执行结果

get webpack
get react
复制代码

可以看到,通过上面的调用,我们的前端开发已经学会了react、webpack

step5:传参

前面知道FontEnd这个群体,需要学react、webpack,但落到个人角度,究竟哪一个开发者掌握这些技能了呢?

const {SyncHook}= require('tapable');
const FontEnd = new SyncHook();
FontEnd.tap('webpack',(name)=>{
  console.log(name+" get webpack")
});
FontEnd.tap('react',(name)=>{
  console.log(name+" get react")
});
FontEnd.start=(name)=>{
  FontEnd.call(name)
};
FontEnd.start('xiaoming');
复制代码

修改前面的代码,添加参数,预期是输出xxx get react

step6: 查看输出结果

undefined get webpack
undefined get react
复制代码

最终结果是undefined,也就是参数没传进去

step7:为SyncHook添加约定参数

这是因为 const FontEnd = new SyncHook(); 创建SyncHook的时候没有约定参数,只要为其添加参数即可,如下:

const {SyncHook}= require('tapable');
const FontEnd = new SyncHook(['name']);// 添加参数约定
FontEnd.tap('webpack',(name)=>{
  console.log(name+" get webpack")
});
FontEnd.tap('react',(name)=>{
  console.log(name+" get react")
});
FontEnd.start=(name)=>{
  FontEnd.call(name)
};
FontEnd.start('xiaoming');
复制代码

最终输出:

xiaoming get webpack
xiaoming get react
复制代码

SyncHook总结

  1. SyncHook目前来看比较像订阅发布
  2. 就像jquery中的add、fire方法,只不过这里是tap、call

Q4:SyncHook如何实现?

SyncHook实现比较简单,就是最简单的订阅发布

class SyncHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tap(name,task){
    this.tasks.push(task);
  }
  call(...args){
    const param =  args.slice(0,this.limit.length);
    this.tasks.forEach(item=>item(...param));
  }
}
复制代码
  1. limit是用来做参数校验的
  2. tasks用来收集订阅
  3. tap方法用来想tasks中添加方法
  4. call方法,先检验参数,然后再执行所有的已订阅方法

总结:原理比较简单,没有太多技术含量,主要就是一个同步的钩子函数

Q5:啥是SyncBailHook?

熔断机制,如果前一个事件 return true ,则不再执行下一个,还是前面的例子:

const {SyncBailHook} =require('tapable');
const FontEnd = new SyncBailHook(['name']);
FontEnd.tap('webpack',(name)=>{
  console.log(name+" get webpack ")
});
FontEnd.tap('react',(name)=>{
  console.log(name+" get react")
});
FontEnd.start=(...args)=>{
  FontEnd.call(...args)
};
FontEnd.start('xiaoming');
复制代码

此时,把函数从SyncHook换成SyncBailHook,执行的结果没有任何区别

but,思考一下,学习很容易会学不下去,所以修改一下我们的例子:

const {SyncBailHook} =require('tapable');
const FontEnd = new SyncBailHook(['name']);
FontEnd.tap('webpack',(name)=>{
  console.log(name+" get webpack ")
  return '学不动了啊!';
});
FontEnd.tap('react',(name)=>{
  console.log(name+" get react")
});
FontEnd.start=(...args)=>{
  FontEnd.call(...args)
};
FontEnd.start('xiaoming');
复制代码

此时仅输出:

xiaoming get webpack 
复制代码

后面的react没有执行

总结:

  1. SyncBailHook主要解决的问题是条件阻塞
  2. 当订阅事件符合某一判断时,不再执行下面的流程
  3. 应用场景,场景不断深入的场景,a、a+b、a+b+c、a+b+c+d这种场景

Q6:SyncBailHook如何实现?

SyncBailHook也十分简单,还是之前那个例子:

class SyncBailHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tap(name,task){
    this.tasks.push(task);
  }
  call(...args){
    const param =  args.slice(0,this.limit.length);
    this.tasks.some(item=>item(...param));// 只改了一行
  }
}
复制代码

可以看到,和上面SyncHook十分相似,无非就是把执行函数forEach,换成some,因为some是阻塞式执行,当返回true,则不会执行后面的内容

Q7:啥是SyncWaterfullHook?

还是先来个使用的例子,例如前端,技能都是一个个学的,要学完webpack再学react,例如:

const {SyncWaterfallHook} = require('tapable');
const FontEnd = new SyncWaterfallHook(['name']);
FontEnd.tap('webpack',(name)=>{
  console.log(name+" get webpack ")
  return '学完webpack了,该学react了';
});
FontEnd.tap('react',(name)=>{
  console.log(name+" get react")
});
FontEnd.start=(...args)=>{
  FontEnd.call(...args)
};
FontEnd.start('xiaoming');
复制代码

此时输出:

xiaoming get webpack 
学完webpack了,该学react了 get react
复制代码
  1. SyncWaterfallHook会将前一个任务的执行结果,传递给后一个
  2. 主要使用场景是处理逻辑之间相互依赖
  3. 实际效果和redux中的compose方法一毛一样

Q8:SyncWaterfullHook如何实现?

class SyncWaterfallHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tap(name,task){
    this.tasks.push(task);
  }
  call(...args){
    const param =  args.slice(0,this.limit.length);
    const [first,...others] = this.tasks;
    const ret = first(...param);
    others.reduce((pre,next)=>{
      return next(pre);
    },ret)
  }
}
复制代码

SyncWaterfallHook实现也比较简单

  1. 完全按照redux的compose来实现就行
  2. 第一步,取出第一个执行,并拿到结果ret
  3. 第二步,将结果ret,当作reduce的参数传递进去
  4. 第三步,遍历,不断把参数传给下一个函数

总结:SyncWaterfallHook主要还是用于函数之间对结果存在依赖的场景

Q9:啥是SyncLoopHook?

还是前面的例子,如果一次学不懂一门技术,那就要多学几遍,例如:

const FontEnd = new SyncLoopHook(['name']);
let num = 0;
FontEnd.tap('webpack',(name)=>{
  console.log(name+" get webpack ")
  return ++num === 3?undefined:'再学一次';
});
FontEnd.tap('react',(name)=>{
  console.log(name+" get react")
});
FontEnd.start=(...args)=>{
  FontEnd.call(...args)
};
FontEnd.start('xiaoming');
复制代码

上面执行的结果是:

xiaoming get webpack 
xiaoming get webpack 
xiaoming get webpack 
xiaoming get react
复制代码
  1. SyncLoopHook任务能够执行多次
  2. 返回undefined则停止执行,返回非undefined则继续执行当前任务

总结:主要场景是同一任务,需要执行多次

Q10:SyncLoopHook如何实现?

class SyncLoopHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tap(name,task){
    this.tasks.push(task);
  }
  call(...args){
    const param =  args.slice(0,this.limit.length);
    let index = 0;
    while(index<this.tasks.length){
      const result = this.tasks[index](...param);
      if(result === undefined){
        index++;
      }
    }
  }
}
复制代码
  1. 上面的实现是通过计数
  2. 如果结果不为undefined则下标index不移动
  3. 如果结果为undefined则下标index增加

也可以换doWhile来实现

class SyncLoopHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tap(name,task){
    this.tasks.push(task);
  }
  call(...args){
    const param =  args.slice(0,this.limit.length);
   this.tasks.forEach(task=>{
     let ret;
     do{
      ret = task(...param);
     }while(ret!=undefined)
   })
  }
}
复制代码
  1. 这种实现没有下标概念了
  2. 直接遍历tasks任务组,如果任务组中某一个任务执行的结果不是undefined则再次执行

总结:SyncLoopHook这个使用场景相对较少,不过了解一下也好

Q11:啥是AsyncParralleHook?

前面了解的都是同步hook,更关键的是异步hook

举个例子,同学小王说去学前端了,但你也不知道他什么时候学完,只有他学完告诉你,你才知道他学完了,例:

const {AsyncParallelHook} = require('tapable');
const FontEnd = new AsyncParallelHook(['name']);
FontEnd.tapAsync('webpack',(name,cb)=>{
  setTimeout(() => {
    console.log(name+" get webpack ")
    cb();
  }, 1000);
 
});
FontEnd.tapAsync('react',(name,cb)=>{
  setTimeout(() => {
    console.log(name+" get react")
    cb();
  }, 1000);
});
FontEnd.start=(...args)=>{
  FontEnd.callAsync(...args,()=>{
    console.log("end");
  })
};
FontEnd.start('小王');
复制代码

最终输出:

小王 get webpack 
小王 get react
end
复制代码
  1. AsyncParralleHook是异步并行钩子
  2. 使用场景,例如同时发起对两个接口的请求
  3. 注意:这次注册事件,不再是tap了,而是tapAsync
  4. 注意:这次的事件执行,不再是call了,而是callAsync
  5. 可以看出tapable中区分了同步、异步的订阅和发布
  6. 注意:想要让所有异步执行完成后,接收到通知,需要执行cb()

Q12:AsyncParralleHook如何实现?

class AsyncParallelHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapAsync(name,task){
    this.tasks.push(task);
  }
  callAsync(...args){
    const finalCallBack = args.pop();
    const param =  args.slice(0,this.limit.length);
    let index = 0;
    const done=()=>{
      index++;
      if(index === this.tasks.length){
        finalCallBack();
      }
    }
    this.tasks.forEach(item=>item(...param,done))
  }
}
复制代码
  1. AsyncParallelHook最简单就是通过计数
  2. 在实例上添加一个计数器
  3. 然后遍历tasks,当任务成功个数与任务总数相同时,执行finalCallBack

总结:AsyncParallelHook解决的问题和promise.all类似,都是用于解决异步并行的问题

Q13:AsyncParralleHook(2)如何使用promise?

前面虽然用:AsyncParralleHook能够解决异步,但并没有使用primise,也没有类promise的概念

const {AsyncParallelHook} = require('tapable');
const FontEnd = new AsyncParallelHook(['name']);
FontEnd.tapPromise('webpack',(name)=>{
  return new Promise((resolve)=>{
    setTimeout(() => {
      console.log(name+" get webpack ")
      resolve();
    }, 1000);
  })
});
FontEnd.tapPromise('react',(name,cb)=>{
  return new Promise((resolve)=>{
    setTimeout(() => {
      console.log(name+" get react ")
      resolve();
    }, 1000);
  })
});
FontEnd.start=(...args)=>{
  FontEnd.promise(...args).then(()=>{
    console.log("end");
  })
};
FontEnd.start('小王');
复制代码

调用上面的api后,输出:

小王 get webpack 
小王 get react 
end
复制代码
  1. 注意:此时绑定事件的方法叫做tapPromise
  2. 注意:此时执行事件的方法叫做promise

总结:

  1. tapable共有三种事件绑定方法:tap、tapAsync、tapPromise
  2. tapable共有三种事件执行方法:call、callAsync、promise

Q14:AsyncParralleHook(2)promise版如何实现?

class AsyncParallelHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapPromise(name,task){
    this.tasks.push(task);
  }
  promise(...args){
    const param =  args.slice(0,this.limit.length);
    const tasks = this.tasks.map(task=>task(...param));
    return Promise.all(tasks)
  }
}
复制代码
  1. 核心就是实现两个方法,tapPromise和promise
  2. tapPromise其实和之前的tap没有明显区别(简单实现的问题)
  3. promise的话,其实就是返回一个Promise.all

Q15:啥是AsyncParallelBailHook?

AsyncParallelBailHook这个钩子和前面的钩子不太一样 按前面的例子来讲:

  1. 同学小王说去学前端了,但你也不知道他什么时候学完,只有他学完告诉你,你才知道他学完了
  2. 小王学了webpack,学崩了,告诉了你
  3. 你听说小王学崩了,你就以为他学不下去了,你就对大家伙说,小王学崩了
  4. 但是小王同时也学了react却咬牙学完了
  5. 虽然学完了,但你已经对外宣布小王崩了,很打脸,所以就当不知道了

这就是AsyncParallelBailHook处理的事情

const {AsyncParallelBailHook} = require('tapable');
const FontEnd = new AsyncParallelBailHook(['name']);
FontEnd.tapPromise('webpack',(name)=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
      console.log(name+" get webpack ")
      reject('小王学崩了!');
    }, 1000);
  })
});
FontEnd.tapPromise('react',(name,cb)=>{
  return new Promise((resolve)=>{
    setTimeout(() => {
      console.log(name+" get react ")
      resolve();
    }, 2000);
  })
});
FontEnd.start=(...args)=>{
  FontEnd.promise(...args).then(()=>{
    console.log("end");
  },(err)=>{
    console.log("听说:",err)
  })
};
FontEnd.start('小王');
复制代码

上面代码执行结果是:

小王 get webpack 
听说: 小王学崩了!
小王 get react 
复制代码
  1. 上面例子,第一个并行任务返回了reject
  2. reject只要不是undefined,就会直接进入promise.all的catch
  3. 异步任务,react还是会执行,但成功后没有处理了

再看一个例子:

const {AsyncParallelBailHook} = require('tapable');
const FontEnd = new AsyncParallelBailHook(['name']);
FontEnd.tapPromise('webpack',(name)=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
      console.log(name+" get webpack ")
      reject();
    }, 1000);
  })
});
FontEnd.tapPromise('react',(name,cb)=>{
  return new Promise((resolve)=>{
    setTimeout(() => {
      console.log(name+" get react ")
      resolve();
    }, 2000);
  })
});
FontEnd.start=(...args)=>{
  FontEnd.promise(...args).then(()=>{
    console.log("end");
  },(err)=>{
    console.log("听说:",err)
  })
};
FontEnd.start('小王');
复制代码

和上面就改了1行,就是reject内容为空,此时输出:

小王 get webpack 
小王 get react 
end
复制代码
  1. 此时即便调用了reject也不会进入到catch
  2. reject返回空,后面的任务也会照常执行

总结:

  1. AsyncParallelBailHook,如果返回真值,则直接会走进catch
  2. 无论返回结果是什么,所有任务都会执行
  3. 主要场景是,并行请求3个接口,随便哪一个返回结果都行,只要返回了,就对返回进行处理(走catch)
  4. 如果用来处理同步,则和SyncBailHook效果一样
  5. 如果处理tapSync,则遇到return true最终的callback不会执行
  6. 如果处理promise,则遇到rejcet(true),则直接进入catch

Q16:AsyncParallelBailHook如何实现?

这个AsyncParallelBailHook真真烧脑了好一会

class AsyncParallelBailHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapPromise(name,task){
    this.tasks.push(task);
  }
  promise(...args){
    const param =  args.slice(0,this.limit.length);
    const tasks = this.tasks.map(task=>{
      return new Promise((resolve,reject)=>{
        task(...param).then((data)=>{
          resolve(data);
        },(err)=>{
          err? reject(err):resolve();
        });
      })
    });
    return Promise.all(tasks)
  }
}
复制代码
  1. 正常情况下,promise.all中任意一个任务reject,就会进入统一的catch
  2. 但我们需要的是根据reject的值来判断是否走如catch
  3. 所以我们在原有task外,再包一层promise
  4. 如果reject值为真,则执行reject
  5. 如果reject值为假,则执行resolve,就当什么也没发生

Q17:啥是AsyncSeriesHook?

前面讲的是异步并行,现在该说异步串行了,例如小王,学完webpack才去学的react,你也不知道他什么时候学完,但他学完一个就会告诉你一下,例:

const {AsyncSeriesHook} = require('tapable');
const FontEnd = new AsyncSeriesHook(['name']);
console.time('webpack');
console.time('react');
FontEnd.tapPromise('webpack',(name,cb)=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
      console.log(name+" get webpack ")
      console.timeEnd('webpack');
      resolve();
    }, 1000);
  })
});
FontEnd.tapPromise('react',(name,cb)=>{
  return new Promise((resolve)=>{
    setTimeout(() => {
      console.log(name+" get react ")
      console.timeEnd('react');
      resolve();
    }, 1000);
  })
});
FontEnd.start=(...args)=>{
  FontEnd.promise(...args).then(()=>{
    console.log("end");
  })
};
FontEnd.start('小王');
复制代码

上面代码执行结果:

小王 get webpack 
webpack: 1010.781ms
小王 get react 
react: 2016.598ms
end
复制代码
  1. 两个异步任务,变成了串行
  2. 从时间能够得出,两个1s的异步的任务,串行后总时间变成了2s

总结:AsyncSeriesHook解决的问题是异步串行,例如node的os.cpus()有限,可以把任务分批次执行,这样对性能有保障

Q18:AsyncSeriesHook如何实现?

class AsyncSeriesHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapPromise(name,task){
    this.tasks.push(task);
  }
  promise(...args){
    const param =  args.slice(0,this.limit.length);
    const [first,...others] = this.tasks;
    return others.reduce((pre,next)=>{
      return pre.then(()=>next(...param))
    },first(...param))
  }
}
复制代码
  1. 实现核心就是promise串行
  2. 取出第一个任务,执行拿到promise实例,然后通过reduce遍历

Q19:啥是AsyncSeriesBailHook?

还是前面的例子,如果小王学前端,学了webapck就彻底放弃了,那后面的react也就不用学了

const {AsyncSeriesBailHook} = require('tapable');
const FontEnd = new AsyncSeriesBailHook(['name']);
console.time('webpack');
console.time('react');
FontEnd.tapPromise('webpack',(name,cb)=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
      console.log(name+" get webpack ")
      console.timeEnd('webpack');
      reject('小王彻底放弃了');
    }, 1000);
  })
});
FontEnd.tapPromise('react',(name,cb)=>{
  return new Promise((resolve)=>{
    setTimeout(() => {
      console.log(name+" get react ")
      console.timeEnd('react');
      resolve();
    }, 1000);
  })
});
FontEnd.start=(...args)=>{
  FontEnd.promise(...args).then(()=>{
    console.log("end");
  }).catch((err)=>{
    console.log("err",err)
  })
};
FontEnd.start('小王');
复制代码

上面代码输出:

小王 get webpack 
webpack: 1010.518ms
err 小王彻底放弃了
复制代码
  1. 上面的代码只执行到webpack
  2. AsyncSeriesBailHook,任务如果return,或者reject,则阻塞了

场景:主要是异步串行,如果某一个任务执行的结果reject或者return,那么后面的都将不再执行

Q20:AsyncSeriesBailHook如何实现?

class AsyncSeriesBailHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapPromise(name,task){
    this.tasks.push(task);
  }
  promise(...args){
    const param =  args.slice(0,this.limit.length);
    const [first,...others] = this.tasks;
    return new Promise((resolve,reject)=>{
      others.reduce((pre,next,index,arr)=>{
        return pre
          .then(()=>next(...param))
          .catch((err=>{
            arr.splice(index,arr.length-index);
            reject(err);
          })).then(()=>{
            (index+1 === arr.length) && resolve();
          })
      },first(...param))
    })
  }
}
复制代码

AsyncSeriesBailHook实现难度要高很多

  1. 首先在reduce外再包一层promise
  2. 当遇到任何一个子任务进入catch的时候,则将reduce的第四个参数arr切割,使其无法再向下进行,也就是停止reduce的继续
  3. 同时所有promise后面再添加一个后置then,用来检测是否全部执行完成
  4. 为什么使用index+1,是因为后置then肯定是最后一个任务,但遍历index还处于上一个下标,所以只要加1就好

Q21:啥是AsyncSeriesWaterfallHook?

SyncWaterFallHook前面已经了解过了,就是前一个执行完的结果会传递给下一个执行函数,和AsyncSeriesWaterfallHook的区别就是,一个是同步一个是异步

具体来说,例如只有一本教材,小王学完,小张才能学

const FontEnd = new AsyncSeriesWaterfallHook(['name']);
FontEnd.tapAsync('webpack',(name,cb)=>{
    setTimeout(() => {
      console.log(name+" get webpack ")
      cb(null,'小李');
    }, 1000);
});
FontEnd.tapAsync('webpack',(name,cb)=>{
  setTimeout(() => {
    console.log(name+" get webpack ")
    cb(null,'小张');
  }, 1000);
});
FontEnd.tapAsync('webpack',(name,cb)=>{
  setTimeout(() => {
    console.log(name+" get webpack ")
    cb(null,'小红');
  }, 1000);
});
FontEnd.start=(...args)=>{
  FontEnd.callAsync(...args,(data)=>{
    console.log("全学完了",)
  })
};
FontEnd.start('小王');
复制代码

上面代码,最终输出:

小王 get webpack 
小李 get webpack 
小张 get webpack 
全学完了
复制代码

总结:这个的用法和SyncWaterFallHook的用法一致

Q22:AsyncSeriesWaterfallHook如何实现?

class AsyncSeriesWaterfallHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapAsync(name,task){
    this.tasks.push(task);
  }
  callAsync(...args){
    const param =  args.slice(0,this.limit.length);
    const finalCallBack = args.pop();
    let index = 0;
    const next = (err,data)=>{
      const task = this.tasks[index];
      if(!task)return finalCallBack();
      if(index === 0){
        task(...param,next)
      }else{
        task(data,next)
      }
      index++;
    }
    next();
  }
}
复制代码
  1. 主要是通过封装一个回调函数next
  2. 然后不断调用任务队列中的任务,调用的时候,再传递相同的回调函数进去

prmise版本的实现如下:

class AsyncSeriesWaterfallHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapPromise(name,task){
    this.tasks.push(task);
  }
  promise(...args){
    const param =  args.slice(0,this.limit.length);
    const [first,...others] = this.tasks;
    return others.reduce((pre,next)=>{
      return pre.then((data)=>{
        return data?next(data):next(...param);
      })
    },first(...param))
  }
}
复制代码
  1. promise的实现要相对简单一些
  2. 主要去看then方法中是否有内容,如果有的话,则传递个下一个函数,如果没有,则用初始参数

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Effective Modern C++

Effective Modern C++

Scott Meyers / O'Reilly Media / 2014-12 / USD 49.99

Learn how to program expertly with C++ with this practical book from Scott Meyers, one of the world's foremost authorities on this systems programming language. Scott Meyers takes some of the most dif......一起来看看 《Effective Modern C++》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换