前不久写了一篇webpack基本原理和AST用法的文章,本来想接着写webpack plugin的原理的,但是发现webpack plugin高度依赖tapable这个库,不清楚tapable而直接去看webpack plugin始终有点雾里看花的意思。所以就先去看了下tapable的文档和源码,发现这个库非常有意思,是增强版的发布订阅模式发布订阅模式在源码世界实在是太常见了,我们已经在多个库源码里面见过了:

  1. reduxsubscribedispatch
  2. Node.jsEventEmitter
  3. redux-sagatakeput

这些库基本都自己实现了自己的发布订阅模式,实现方式主要是用来满足自己的业务需求,而tapable并没有具体的业务逻辑,是一个专门用来实现事件订阅或者他自己称为hook(钩子)的工具库,其根本原理还是发布订阅模式,但是他实现了多种形式的发布订阅模式,还包含了多种形式的流程控制。

tapable暴露多个API,提供了多种流程控制方式,连使用都是比较复杂的,所以我想分两篇文章来写他的原理:

  1. 先看看用法,体验下他的多种流程控制方式
  2. 通过用法去看看源码是怎么实现的

本文就是讲用法的文章,知道了他的用法,大家以后如果有自己实现hook或者事件监听的需求,可以直接拿过来用,非常强大!

本文例子已经全部上传到GitHub,大家可以拿下来做个参考:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Engineering/tapable-usage

tapable是什么

tapablewebpack的核心模块,也是webpack团队维护的,是webpack plugin的基本实现方式。他的主要功能是为使用者提供强大的hook机制,webpack plugin就是基于hook的。

主要API

下面是官方文档中列出来的主要API,所有API的名字都是以Hook结尾的:

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

这些API的名字其实就解释了他的作用,注意这些关键字:Sync, Async, Bail, Waterfall, Loop, Parallel, Series。下面分别来解释下这些关键字:

Sync:这是一个同步的hook

Async:这是一个异步的hook

BailBail在英文中的意思是保险,保障的意思,实现的效果是,当一个hook注册了多个回调方法,任意一个回调方法返回了不为undefined的值,就不再执行后面的回调方法了,就起到了一个“保险丝”的作用。

WaterfallWaterfall在英语中是瀑布的意思,在编程世界中表示顺序执行各种任务,在这里实现的效果是,当一个hook注册了多个回调方法,前一个回调执行完了才会执行下一个回调,而前一个回调的执行结果会作为参数传给下一个回调函数。

LoopLoop就是循环的意思,实现的效果是,当一个hook注册了回调方法,如果这个回调方法返回了true就重复循环这个回调,只有当这个回调返回undefined才执行下一个回调。

ParallelParallel是并行的意思,有点类似于Promise.all,就是当一个hook注册了多个回调方法,这些回调同时开始并行执行。

SeriesSeries就是串行的意思,就是当一个hook注册了多个回调方法,前一个执行完了才会执行下一个。

ParallelSeries的概念只存在于异步的hook中,因为同步hook全部是串行的。

下面我们分别来介绍下每个API的用法和效果。

同步API

同步API就是这几个:

const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
} = require("tapable");

前面说了,同步API全部是串行的,所以这几个的区别就在流程控制上。

SyncHook

SyncHook是一个最基础的hook,其使用方法和效果接近我们经常使用的发布订阅模式,注意tapable导出的所有hook都是类,基本用法是这样的:

const hook = new SyncHook(["arg1", "arg2", "arg3"]);

因为SyncHook是一个类,所以使用new来生成一个实例,构造函数接收的参数是一个数组["arg1", "arg2", "arg3"],这个数组有三项,表示生成的这个实例注册回调的时候接收三个参数。实例hook主要有两个实例方法:

  1. tap:就是注册事件回调的方法。
  2. call:就是触发事件,执行回调的方法。

下面我们扩展下官方文档中小汽车加速的例子来说明下具体用法:

const { SyncHook } = require("tapable");

// 实例化一个加速的hook
const accelerate = new SyncHook(["newSpeed"]); // 注册第一个回调,加速时记录下当前速度
accelerate.tap("LoggerPlugin", (newSpeed) =>
console.log("LoggerPlugin", `加速到${newSpeed}`)
); // 再注册一个回调,用来检测是否超速
accelerate.tap("OverspeedPlugin", (newSpeed) => {
if (newSpeed > 120) {
console.log("OverspeedPlugin", "您已超速!!");
}
}); // 再注册一个回调,用来检测速度是否快到损坏车子了
accelerate.tap("DamagePlugin", (newSpeed) => {
if (newSpeed > 300) {
console.log("DamagePlugin", "速度实在太快,车子快散架了。。。");
}
}); // 触发一下加速事件,看看效果吧
accelerate.call(500);

然后运行下看看吧,当加速事件出现的时候,会依次执行这三个回调:

上面这个例子主要就是用了tapcall这两个实例方法,其中tap接收两个参数,第一个是个字符串,并没有实际用处,仅仅是一个注释的作用,第二个参数就是一个回调函数,用来执行事件触发时的具体逻辑。

accelerate.tap("LoggerPlugin", (newSpeed) =>
console.log("LoggerPlugin", `加速到${newSpeed}`)
);

上述这种写法其实与webpack官方文档中对于plugin的介绍非常像了,因为webpackplguin就是用tapable实现的,第一个参数一般就是plugin的名字:

call就是简单的触发这个事件,在webpackplguin中一般不需要开发者去触发事件,而是webpack自己在不同阶段会触发不同的事件,比如beforeRun, run等等,plguin开发者更多的会关注这些事件出现时应该进行什么操作,也就是在这些事件上注册自己的回调。

SyncBailHook

上面的SyncHook其实就是一个简单的发布订阅模式SyncBailHook就是在这个基础上加了一点流程控制,前面我们说过了,Bail就是个保险,实现的效果是,前面一个回调返回一个不为undefined的值,就中断这个流程。比如我们现在将前面这个例子的SyncHook换成SyncBailHook,然后在检测超速的这个插件里面加点逻辑,当它超速了就返回错误,后面的DamagePlugin就不会执行了:

const { SyncBailHook } = require("tapable");    // 使用的是SyncBailHook

// 实例化一个加速的hook
const accelerate = new SyncBailHook(["newSpeed"]); accelerate.tap("LoggerPlugin", (newSpeed) =>
console.log("LoggerPlugin", `加速到${newSpeed}`)
); // 再注册一个回调,用来检测是否超速
// 如果超速就返回一个错误
accelerate.tap("OverspeedPlugin", (newSpeed) => {
if (newSpeed > 120) {
console.log("OverspeedPlugin", "您已超速!!"); return new Error('您已超速!!');
}
}); accelerate.tap("DamagePlugin", (newSpeed) => {
if (newSpeed > 300) {
console.log("DamagePlugin", "速度实在太快,车子快散架了。。。");
}
}); accelerate.call(500);

然后再运行下看看:

可以看到由于OverspeedPlugin返回了一个不为undefined的值,DamagePlugin被阻断,没有运行了。

SyncWaterfallHook

SyncWaterfallHook也是在SyncHook的基础上加了点流程控制,前面说了,Waterfall实现的效果是将上一个回调的返回值作为参数传给下一个回调。所以通过call传入的参数只会传递给第一个回调函数,后面的回调接受都是上一个回调的返回值,最后一个回调的返回值会作为call的返回值返回给最外层:

const { SyncWaterfallHook } = require("tapable");

const accelerate = new SyncWaterfallHook(["newSpeed"]);

accelerate.tap("LoggerPlugin", (newSpeed) => {
console.log("LoggerPlugin", `加速到${newSpeed}`); return "LoggerPlugin";
}); accelerate.tap("Plugin2", (data) => {
console.log(`上一个插件是: ${data}`); return "Plugin2";
}); accelerate.tap("Plugin3", (data) => {
console.log(`上一个插件是: ${data}`); return "Plugin3";
}); const lastPlugin = accelerate.call(100); console.log(`最后一个插件是:${lastPlugin}`);

然后看下运行效果吧:

SyncLoopHook

SyncLoopHook是在SyncHook的基础上添加了循环的逻辑,也就是如果一个插件返回true就会一直执行这个插件,直到他返回undefined才会执行下一个插件:

const { SyncLoopHook } = require("tapable");

const accelerate = new SyncLoopHook(["newSpeed"]);

accelerate.tap("LoopPlugin", (newSpeed) => {
console.log("LoopPlugin", `循环加速到${newSpeed}`); return new Date().getTime() % 5 !== 0 ? true : undefined;
}); accelerate.tap("LastPlugin", (newSpeed) => {
console.log("循环加速总算结束了");
}); accelerate.call(100);

执行效果如下:

异步API

所谓异步API是相对前面的同步API来说的,前面的同步API的所有回调都是按照顺序同步执行的,每个回调内部也全部是同步代码。但是实际项目中,可能需要回调里面处理异步情况,也可能希望多个回调可以同时并行执行,也就是Parallel。这些需求就需要用到异步API了,主要的异步API就是这些:

const {
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");

既然涉及到了异步,那肯定还需要异步的处理方式,tapable支持回调函数和Promise两种异步的处理方式。所以这些异步API除了用前面的tap来注册回调外,还有两个注册回调的方法:tapAsynctapPromise,对应的触发事件的方法为callAsyncpromise。下面分别来看下每个API吧:

AsyncParallelHook

AsyncParallelHook从前面介绍的命名规则可以看出,他是一个异步并行执行的Hook,我们先用tapAsync的方式来看下怎么用吧。

tapAsync和callAsync

还是那个小汽车加速的例子,只不过这个小汽车加速没那么快了,需要一秒才能加速完成,然后我们在2秒的时候分别检测是否超速和是否损坏,为了看出并行的效果,我们记录下整个过程从开始到结束的时间:

const { AsyncParallelHook } = require("tapable");

const accelerate = new AsyncParallelHook(["newSpeed"]);

console.time("total time"); // 记录起始时间

// 注意注册异步事件需要使用tapAsync
// 接收的最后一个参数是done,调用他来表示当前任务执行完毕
accelerate.tapAsync("LoggerPlugin", (newSpeed, done) => {
// 1秒后加速才完成
setTimeout(() => {
console.log("LoggerPlugin", `加速到${newSpeed}`); done();
}, 1000);
}); accelerate.tapAsync("OverspeedPlugin", (newSpeed, done) => {
// 2秒后检测是否超速
setTimeout(() => {
if (newSpeed > 120) {
console.log("OverspeedPlugin", "您已超速!!");
}
done();
}, 2000);
}); accelerate.tapAsync("DamagePlugin", (newSpeed, done) => {
// 2秒后检测是否损坏
setTimeout(() => {
if (newSpeed > 300) {
console.log("DamagePlugin", "速度实在太快,车子快散架了。。。");
} done();
}, 2000);
}); accelerate.callAsync(500, () => {
console.log("任务全部完成");
console.timeEnd("total time"); // 记录总共耗时
});

上面代码需要注意的是,注册回调要使用tapAsync,而且回调函数里面最后一个参数会自动传入done,你可以调用他来通知tapable当前任务已经完成。触发任务需要使用callAsync,他最后也接收一个函数,可以用来处理所有任务都完成后需要执行的操作。所以上面的运行结果就是:

从这个结果可以看出,最终消耗的时间大概是2秒,也就是三个任务中最长的单个任务耗时,而不是三个任务耗时的总额,这就实现了Parallel并行的效果。

tapPromise和promise

现在都流行Promise,所以tapable也是支持的,执行效果是一样的,只是写法不一样而已。要用tapPromise,需要注册的回调返回一个promise,同时触发事件也需要用promise,任务运行完执行的处理可以直接使用then,所以上述代码改为:

const { AsyncParallelHook } = require("tapable");

const accelerate = new AsyncParallelHook(["newSpeed"]);

console.time("total time"); // 记录起始时间

// 注意注册异步事件需要使用tapPromise
// 回调函数要返回一个promise
accelerate.tapPromise("LoggerPlugin", (newSpeed) => {
return new Promise((resolve) => {
// 1秒后加速才完成
setTimeout(() => {
console.log("LoggerPlugin", `加速到${newSpeed}`); resolve();
}, 1000);
});
}); accelerate.tapPromise("OverspeedPlugin", (newSpeed) => {
return new Promise((resolve) => {
// 2秒后检测是否超速
setTimeout(() => {
if (newSpeed > 120) {
console.log("OverspeedPlugin", "您已超速!!");
}
resolve();
}, 2000);
});
}); accelerate.tapPromise("DamagePlugin", (newSpeed) => {
return new Promise((resolve) => {
// 2秒后检测是否损坏
setTimeout(() => {
if (newSpeed > 300) {
console.log("DamagePlugin", "速度实在太快,车子快散架了。。。");
} resolve();
}, 2000);
});
}); // 触发事件使用promise,直接用then处理最后的结果
accelerate.promise(500).then(() => {
console.log("任务全部完成");
console.timeEnd("total time"); // 记录总共耗时
});

这段代码的逻辑和运行结果和上面那个是一样的,只是写法不一样:

tapAsync和tapPromise混用

既然tapable支持这两种异步写法,那这两种写法可以混用吗?我们来试试吧:

const { AsyncParallelHook } = require("tapable");

const accelerate = new AsyncParallelHook(["newSpeed"]);

console.time("total time"); // 记录起始时间

// 来一个promise写法
accelerate.tapPromise("LoggerPlugin", (newSpeed) => {
return new Promise((resolve) => {
// 1秒后加速才完成
setTimeout(() => {
console.log("LoggerPlugin", `加速到${newSpeed}`); resolve();
}, 1000);
});
}); // 再来一个async写法
accelerate.tapAsync("OverspeedPlugin", (newSpeed, done) => {
// 2秒后检测是否超速
setTimeout(() => {
if (newSpeed > 120) {
console.log("OverspeedPlugin", "您已超速!!");
}
done();
}, 2000);
}); // 使用promise触发事件
// accelerate.promise(500).then(() => {
// console.log("任务全部完成");
// console.timeEnd("total time"); // 记录总共耗时
// }); // 使用callAsync触发事件
accelerate.callAsync(500, () => {
console.log("任务全部完成");
console.timeEnd("total time"); // 记录总共耗时
});

这段代码无论我是使用promise触发事件还是callAsync触发运行的结果都是一样的,所以tapable内部应该是做了兼容转换的,两种写法可以混用:

由于tapAsynctapPromise只是写法上的不一样,我后面的例子就全部用tapAsync了。

AsyncParallelBailHook

前面已经看了SyncBailHook,知道带Bail的功能就是当一个任务返回不为undefined的时候,阻断后面任务的执行。但是由于Parallel任务都是同时开始的,阻断是阻断不了了,实际效果是如果有一个任务返回了不为undefined的值,最终的回调会立即执行,并且获取Bail任务的返回值。我们将上面三个任务执行时间错开,分别为1秒,2秒,3秒,然后在2秒的任务触发Bail就能看到效果了:

const { AsyncParallelBailHook } = require("tapable");

const accelerate = new AsyncParallelBailHook(["newSpeed"]);

console.time("total time"); // 记录起始时间

accelerate.tapAsync("LoggerPlugin", (newSpeed, done) => {
// 1秒后加速才完成
setTimeout(() => {
console.log("LoggerPlugin", `加速到${newSpeed}`); done();
}, 1000);
}); accelerate.tapAsync("OverspeedPlugin", (newSpeed, done) => {
// 2秒后检测是否超速
setTimeout(() => {
if (newSpeed > 120) {
console.log("OverspeedPlugin", "您已超速!!");
} // 这个任务的done返回一个错误
// 注意第一个参数是node回调约定俗成的错误
// 第二个参数才是Bail的返回值
done(null, new Error("您已超速!!"));
}, 2000);
}); accelerate.tapAsync("DamagePlugin", (newSpeed, done) => {
// 3秒后检测是否损坏
setTimeout(() => {
if (newSpeed > 300) {
console.log("DamagePlugin", "速度实在太快,车子快散架了。。。");
} done();
}, 3000);
}); accelerate.callAsync(500, (error, data) => {
if (data) {
console.log("任务执行出错:", data);
} else {
console.log("任务全部完成");
}
console.timeEnd("total time"); // 记录总共耗时
});

可以看到执行到任务2时,由于他返回了一个错误,所以最终的回调会立即执行,但是由于任务3之前已经同步开始了,所以他自己仍然会运行完,只是已经不影响最终结果了:

AsyncSeriesHook

AsyncSeriesHook是异步串行hook,如果有多个任务,这多个任务之间是串行的,但是任务本身却可能是异步的,下一个任务必须等上一个任务done了才能开始:

const { AsyncSeriesHook } = require("tapable");

const accelerate = new AsyncSeriesHook(["newSpeed"]);

console.time("total time"); // 记录起始时间

accelerate.tapAsync("LoggerPlugin", (newSpeed, done) => {
// 1秒后加速才完成
setTimeout(() => {
console.log("LoggerPlugin", `加速到${newSpeed}`); done();
}, 1000);
}); accelerate.tapAsync("OverspeedPlugin", (newSpeed, done) => {
// 2秒后检测是否超速
setTimeout(() => {
if (newSpeed > 120) {
console.log("OverspeedPlugin", "您已超速!!");
}
done();
}, 2000);
}); accelerate.tapAsync("DamagePlugin", (newSpeed, done) => {
// 2秒后检测是否损坏
setTimeout(() => {
if (newSpeed > 300) {
console.log("DamagePlugin", "速度实在太快,车子快散架了。。。");
} done();
}, 2000);
}); accelerate.callAsync(500, () => {
console.log("任务全部完成");
console.timeEnd("total time"); // 记录总共耗时
});

每个任务代码跟AsyncParallelHook是一样的,只是使用的Hook不一样,而最终效果的区别是:AsyncParallelHook所有任务同时开始,所以最终总耗时就是耗时最长的那个任务的耗时;AsyncSeriesHook的任务串行执行,下一个任务要等上一个任务完成了才能开始,所以最终总耗时是所有任务耗时的总和,上面这个例子就是1 + 2 + 2,也就是5秒:

AsyncSeriesBailHook

AsyncSeriesBailHook就是在AsyncSeriesHook的基础上加上了Bail的逻辑,也就是中间任何一个任务返回不为undefined的值,终止执行,直接执行最后的回调,并且将这个返回值传给最终的回调:

const { AsyncSeriesBailHook } = require("tapable");

const accelerate = new AsyncSeriesBailHook(["newSpeed"]);

console.time("total time"); // 记录起始时间

accelerate.tapAsync("LoggerPlugin", (newSpeed, done) => {
// 1秒后加速才完成
setTimeout(() => {
console.log("LoggerPlugin", `加速到${newSpeed}`); done();
}, 1000);
}); accelerate.tapAsync("OverspeedPlugin", (newSpeed, done) => {
// 2秒后检测是否超速
setTimeout(() => {
if (newSpeed > 120) {
console.log("OverspeedPlugin", "您已超速!!");
} // 这个任务的done返回一个错误
// 注意第一个参数是node回调约定俗成的错误
// 第二个参数才是Bail的返回值
done(null, new Error("您已超速!!"));
}, 2000);
}); accelerate.tapAsync("DamagePlugin", (newSpeed, done) => {
// 2秒后检测是否损坏
setTimeout(() => {
if (newSpeed > 300) {
console.log("DamagePlugin", "速度实在太快,车子快散架了。。。");
} done();
}, 2000);
}); accelerate.callAsync(500, (error, data) => {
if (data) {
console.log("任务执行出错:", data);
} else {
console.log("任务全部完成");
}
console.timeEnd("total time"); // 记录总共耗时
});

这个执行结果跟AsyncParallelBailHook的区别就是AsyncSeriesBailHook被阻断后,后面的任务由于还没开始,所以可以被完全阻断,而AsyncParallelBailHook后面的任务由于已经开始了,所以还会继续执行,只是结果已经不关心了。

AsyncSeriesWaterfallHook

Waterfall的作用是将前一个任务的结果传给下一个任务,其他的跟AsyncSeriesHook一样的,直接来看代码吧:

const { AsyncSeriesWaterfallHook } = require("tapable");

const accelerate = new AsyncSeriesWaterfallHook(["newSpeed"]);

console.time("total time"); // 记录起始时间

accelerate.tapAsync("LoggerPlugin", (newSpeed, done) => {
// 1秒后加速才完成
setTimeout(() => {
console.log("LoggerPlugin", `加速到${newSpeed}`); // 注意done的第一个参数会被当做error
// 第二个参数才是传递给后面任务的参数
done(null, "LoggerPlugin");
}, 1000);
}); accelerate.tapAsync("Plugin2", (data, done) => {
setTimeout(() => {
console.log(`上一个插件是: ${data}`); done(null, "Plugin2");
}, 2000);
}); accelerate.tapAsync("Plugin3", (data, done) => {
setTimeout(() => {
console.log(`上一个插件是: ${data}`); done(null, "Plugin3");
}, 2000);
}); accelerate.callAsync(500, (error, data) => {
console.log("最后一个插件是:", data);
console.timeEnd("total time"); // 记录总共耗时
});

运行效果如下:

总结

本文例子已经全部上传到GitHub,大家可以拿下来做个参考:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Engineering/tapable-usage

  1. tapablewebpack实现plugin的核心库,他为webpack提供了多种事件处理和流程控制的Hook
  2. 这些Hook主要有同步(Sync)和异步(Async)两种,同时还提供了阻断(Bail),瀑布(Waterfall),循环(Loop)等流程控制,对于异步流程还提供了并行(Paralle)和串行(Series)两种控制方式。
  3. tapable其核心原理还是事件的发布订阅模式,他使用tap来注册事件,使用call来触发事件。
  4. 异步hook支持两种写法:回调和Promise,注册和触发事件分别使用tapAsync/callAsynctapPromise/promise
  5. 异步hook使用回调写法的时候要注意,回调函数的第一个参数默认是错误,第二个参数才是向外传递的数据,这也符合node回调的风格。

文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。

欢迎关注我的公众号进击的大前端第一时间获取高质量原创~

“前端进阶知识”系列文章:https://juejin.im/post/5e3ffc85518825494e2772fd

“前端进阶知识”系列文章源码GitHub地址: https://github.com/dennis-jiang/Front-End-Knowledges

webpack核心模块tapable用法解析的更多相关文章

  1. Webpack 核心模块 tapable 解析(转)

        原文出自:https://www.pandashen.com 前言 Webpack 是一个现代 JavaScript 应用程序的静态模块打包器,是对前端项目实现自动化和优化必不可少的工具,We ...

  2. webpack核心模块tapable源码解析

    上一篇文章我写了tapable的基本用法,我们知道他是一个增强版版的发布订阅模式,本文想来学习下他的源码.tapable的源码我读了一下,发现他的抽象程度比较高,直接扎进去反而会让人云里雾里的,所以本 ...

  3. [webpack]深入学习webpack核心模块tapable

    一.手动实现同步钩子函数 1.SyncHook class SyncHook { // 钩子是同步的 constructor(args){ this.tasks = []; } tap(name,ta ...

  4. webpack4核心模块tapable源码解析

    _ 阅读目录 一:理解Sync类型的钩子 1. SyncHook.js 2. SyncBailHook.js 3. SyncWaterfallHook.js 4. SyncLoopHook.js 二: ...

  5. 【饿了么】—— Vue2.0高仿饿了么核心模块&移动端Web App项目爬坑(三)

    前言:接着上一篇项目总结,这一篇是学习过程记录的最后一篇,这里会梳理:评论组件.商家组件.优化.打包.相关资料链接.项目github地址:https://github.com/66Web/ljq_el ...

  6. Nodejs之express第三方核心模块的中间件——body-parser

    Node中的核心模块分两类:一类是自带的核心模块,如http.tcp等,第二类是第三方核心模块,express就是与http对应的第三方核心模块,用于处理http请求.express在3.0版本中自带 ...

  7. Babel 配置用法解析

    Babel 配置用法解析 刚复工的时候我司业务太多了,我已不记得我们连续作战了多少天,最近算是有时间可以学习学习我的babel大宝贝了,上周末看了下babel的一些核心模块以及babel的一些配置,今 ...

  8. 手写webpack核心原理,再也不怕面试官问我webpack原理

    手写webpack核心原理 目录 手写webpack核心原理 一.核心打包原理 1.1 打包的主要流程如下 1.2 具体细节 二.基本准备工作 三.获取模块内容 四.分析模块 五.收集依赖 六.ES6 ...

  9. extern "c"用法解析

    转自: extern "c"用法解析 - 简书 引言 C++保留了一部分过程式语言的特点,因而它可以定义不属于任何类的全局变量和函数.但是,C++毕竟是一种面向对象的程序设计语言, ...

随机推荐

  1. vue & arrow function error

    vue & arrow function error <template> <div class="home"> <img alt=" ...

  2. Ajax & JSONP 原理

    Ajax & JSONP 原理 AJAX不是JavaScript的规范,它只是一个哥们"发明"的缩写:Asynchronous JavaScript and XML,意思就 ...

  3. Free Serverless

    Free Serverless BFF https://cloud.google.com/functions/ 微服务 Function as a Servcie,FaaS https://segme ...

  4. NGK官方又出助力市场计划方案 1万枚VAST任性送

    近期NGK官方的一系列动作,可以说是在向外界宣告:NGK2.0即将来袭,席卷加密数字货币市场.前一段时间,NGK官方宣布,NGK公链布局算力领域,打造NGK算力生态星空计划,并推出了SPC星空币.目前 ...

  5. 生态建设者为何青睐低风险、低成本的NGK算力?

    自从BGV推向市场以来,生态建设者的目光都聚集于BGV这个去中心化金融的新星,其实NGK的其他项目也都在稳健进行当中. NGK在未来将推出"算力市场奖励计划",NGK将会对算力市场 ...

  6. 人物传记JULLIAN MURPHY:投资哪家强,区块链必然>股票+房地产

    今年上半年在金融股市出现巨大波动的时候,星盟的项目审核经理JULLIAN MURPHY发现了一个有趣的现象:各种熔断和暴跌的背后,特斯拉的股票却从去年年末开始至今已经暴涨了12倍,即便中途有所回落,但 ...

  7. HTTPS原理解析

    HTTPS 一些概念 http 概述 HTTP是一个客户端(用户)和服务端(网站)之间请求和应答的标准,通常使用TCP协议.其本身位于TCP/IP协议族的应用层. 特点 - 客户端&服务器 - ...

  8. 谈谈 JS 垃圾回收机制

    谈谈 JS 垃圾回收机制 JS内存泄漏与垃圾回收机制 https://javascript.info/garbage-collection

  9. Python2和Python3编码的区别

    Python2 python2中有两种储存变量的形式,第一种:Unicode:第二种:按照coding头来的. 假设python2用utf8存储x='中文',当你print(x)的时候,终端接收gbk ...

  10. 【重磅】iNeuOS工业互联平台,系统集成业务模型和WEB组态视图建模集成3D模型

    目       录 1.      概述... 1 2.      平台演示... 2 3.      系统集成业务模型... 2 4.      WEB组态视图建模集成3D模型... 3 5.    ...