当前位置: 首页 > news >正文

(源码篇02)webpack5中的事件调度系统和NormalModuleFactary核心逻辑

1. 书接上回,从 this.factorizeQueue.add(options, callback); 开始

不是很清楚上下文的兄弟,可以去看下我之前写的 (源码篇01)浅析webpack5中Compiler中重要的hook调用过程

此文比较干,各位读者开始阅读前,请准备好 瓜子,可乐,矿泉水,好好享受阅读源码的乐趣。

1.1 通过 factorizeQueue 了解 AsyncQueue

那么 这个 this.factorizeQueue 指的是什么呢?谁来创建的呢?首先此处的this 指的是 Compilation 的 实例对象,那就去 Compilation 查找,全局搜索一下 Compilation.js 文件,找到了,代码如下(中文注释是我加的):

/** @type {AsyncQueue<Module, Module, Module>} 进程依赖项队列 */this.processDependenciesQueue = new AsyncQueue({name: "processDependencies",parallelism: options.parallelism || 100,processor: this._processModuleDependencies.bind(this)});/** @type {AsyncQueue<Module, string, Module>} 添加模块队列 */this.addModuleQueue = new AsyncQueue({name: "addModule",parent: this.processDependenciesQueue,getKey: module => module.identifier(),processor: this._addModule.bind(this)});/** @type {AsyncQueue<FactorizeModuleOptions, string, Module | ModuleFactoryResult>} 分解队列 */this.factorizeQueue = new AsyncQueue({name: "factorize",parent: this.addModuleQueue,processor: this._factorizeModule.bind(this)});/** @type {AsyncQueue<Module, Module, Module>} 构建队列 */this.buildQueue = new AsyncQueue({name: "build",parent: this.factorizeQueue,processor: this._buildModule.bind(this)});/** @type {AsyncQueue<Module, Module, Module>} 重新构建的队列 */this.rebuildQueue = new AsyncQueue({name: "rebuild",parallelism: options.parallelism || 100,processor: this._rebuildModule.bind(this)});

你会发现好家伙,这不仅仅只有一个队列呀,这里是有一堆的队列。但是都是 AsyncQueue 创建的不同名称的实例。
那根据 this.factorizeQueue.add(options, callback); 我们下一步就是去看 AsyncQueue 中的 add 方法了。

1.2 深入 AsyncQueue 中的 add 方法

在深入了解add 之前,我们先要了解 它的 构造函数都需要哪些参数,以便我们更好的理解其内部的工作原理。

constructor({ name, parallelism, parent, processor, getKey }) {this._name = name;this._parallelism = parallelism || 1;this._processor = processor;this._getKey =getKey || /** @type {(T) => K} */ (item => /** @type {any} */ (item));/** @type {Map<K, AsyncQueueEntry<T, K, R>>} */this._entries = new Map();/** @type {ArrayQueue<AsyncQueueEntry<T, K, R>>} */this._queued = new ArrayQueue();/** @type {AsyncQueue<any, any, any>[]} */this._children = undefined;this._activeTasks = 0;this._willEnsureProcessing = false;this._needProcessing = false;this._stopped = false;this._root = parent ? parent._root : this;if (parent) {if (this._root._children === undefined) {this._root._children = [this];} else {this._root._children.push(this);}}this.hooks = {/** @type {AsyncSeriesHook<[T]>} */beforeAdd: new AsyncSeriesHook(["item"]),/** @type {SyncHook<[T]>} */added: new SyncHook(["item"]),/** @type {AsyncSeriesHook<[T]>} */beforeStart: new AsyncSeriesHook(["item"]),/** @type {SyncHook<[T]>} */started: new SyncHook(["item"]),/** @type {SyncHook<[T, Error, R]>} */result: new SyncHook(["item", "error", "result"])};this._ensureProcessing = this._ensureProcessing.bind(this);}

入参如下:name, parallelism, parent, processor, getKey。name 字段是绑在实例身上的 _name,_parallelism 先忽略,parent 属性是比较重要的, 有 parent属性的情况下,实例的 this._root 指向的是 父级的 parent._root,否则指向的就是实例本身。并且会把自身丢进 父级的 this._root._children 里。

(另外在此构造函数里的 hooks就是给当前实例绑定一些执行周期的钩子,比较容易扩展,都是老 webpack玩家了,这里就不过多的介绍了。这里要注意 新建实例的 _willEnsureProcessing,_needProcessing_stopped 默认值都是 false 状态。)

分析一下 1.1 中的代码:

this.addModuleQueue = new AsyncQueue({name: "addModule",parent: this.processDependenciesQueue,getKey: module => module.identifier(),processor: this._addModule.bind(this)
});
this.factorizeQueue = new AsyncQueue({name: "factorize",parent: this.addModuleQueue,processor: this._factorizeModule.bind(this)
});
this.buildQueue = new AsyncQueue({name: "build",parent: this.factorizeQueue,processor: this._buildModule.bind(this)
});
  1. this.addModuleQueue 中的 _root 指向的是 this.processDependenciesQueue._root,也就是 processDependenciesQueue自身,
  2. this.factorizeQueue 中的 _root 指向的是 this.addModuleQueue._root,而 this.addModuleQueue._root 指向的是 processDependenciesQueue
  3. this.buildQueue 中的 _root 指向的是 this.factorizeQueue._root,而 this.factorizeQueue._root 指向的是 processDependenciesQueue

这意味着上面创建的队列中的 父子关系是如下图的:

知道了父子关系以后,就开始看 add 函数具体执行了什么操作,直接断点到这里,看下入参都是什么?(记不着的兄弟去看我前一篇文章。)

进入 add 函数内部。

大致的逻辑就是 判断一下 this._stopped 当前的实例队列是不是暂停的状态,是暂停的状态就 直接 callback 抛出错误。
那核心的步骤就在 this.hooks.beforeAdd.callAsync这个hook了,老规矩,debug 这个 hook 看一下有哪些 监听者。

发现这个hook 没有一个监听者,直接执行了本身的callback 函数,继续 debug 下去。进入hook的内部,

部分没有走到的逻辑 直接跳过,根据 item 和 callback 实例化AsyncQueueEntry 对象,并赋值给 newEntry变量。 稍微看下 AsyncQueueEntry 类。


const QUEUED_STATE = 0;
const PROCESSING_STATE = 1;
const DONE_STATE = 2;class AsyncQueueEntry {/*** @param {T} item the item* @param {Callback<R>} callback the callback*/constructor(item, callback) {this.item = item;/** @type {typeof QUEUED_STATE | typeof PROCESSING_STATE | typeof DONE_STATE} */this.state = QUEUED_STATE;this.callback = callback;/** @type {Callback<R>[] | undefined} */this.callbacks = undefined;this.result = undefined;/** @type {WebpackError | undefined} */this.error = undefined;}
}

可以看到对于 AsyncQueueEntry 的this.state字段有 3 个状态可选(看 jsDoc 部分,也就是 this.state 的上一行),初始创建的 实例是 QUEUED_STATE 状态,也就是排队状态。

大致知道了 newEntry 变量承载了什么东西,继续debug下一步。

没有进入 if 语句的 成功分支,走了 else 部分(也就是红色圈中部分)。此处的代码量比较少,但是比较核心。
首先看 148 行 this._queued.enqueue(newEntry); 就是把当前的 newEntry 实例 丢入到 自身的 队列里。
接下来 149 行,找到该实例的父级 也就是 _nameprocessDependencies 的实例。在 150 行 将其父级的 _needProcessing 变更为 true, 因为默认创建的实例的 _willEnsureProcessing 的状态为 false,所以此处会走进 151 行的判断逻辑,去执行 152-154 行的代码,将父级(processDependencies)_ensureProcessing 丢进定时器里(注意是父级的,异步函数会在同步函数执行完毕以后调用,另外异步函数也是有优先级之分的哈,不一定是最先丢进去的,最先执行)。
继续debug, 触发 156 的 hook 钩子。

这个钩子真可怜,没有人注册监听事件。(就不放s图了,上次放的s图导致违规了)。

至此同步的函数执行完毕了,别忘了之前还向定时器里丢了一个异步的 父级(processDependencies)_ensureProcessing 方法,下一步直接断点蹲在 _ensureProcessing处。

1.3 浅浅的总结一下~

  1. webpack 中的 Compilation 实例,创建了 4 个有父子关系的异步队列
  2. 最先开始 工作的是 factorizeQueue 队列,执行了 factorizeQueue 队列的 add 方法。
  3. 在 add 方法中,将 factorizeQueue队列的父级(processDependencies)队列的_ensureProcessing 方法放入了定时器中。
  4. 至此,同步函数执行完毕,开始执行 父级(processDependencies)_ensureProcessing 的方法。

2. 异步队列要开始了

2.1 执行 processDependencies 队列的 _ensureProcessing 方法

直接断点到 _ensureProcessing 方法,验证一下猜想。

相关代码贴出来

	_ensureProcessing() {console.log("queue _ensureProcessing");// 100 的并发量while (this._activeTasks < this._parallelism) {const entry = this._queued.dequeue();if (entry === undefined) break;this._activeTasks++;entry.state = PROCESSING_STATE;console.log(`异步队列名称:${this._name},开始工作了:${entry.item}`);this._startProcessing(entry);}this._willEnsureProcessing = false;if (this._queued.length > 0) return;if (this._children !== undefined) {// this._children 是之前创建的 addModule factorize 和  build的 AsyncQueue// lib/Compilation.js 的 944 行for (const child of this._children) {while (this._activeTasks < this._parallelism) {const entry = child._queued.dequeue();if (entry === undefined) break;this._activeTasks++;entry.state = PROCESSING_STATE;child._startProcessing(entry);}if (child._queued.length > 0) return;}}if (!this._willEnsureProcessing) this._needProcessing = false;}

开始分析流程,this 指向的是 processDependencies 的实例队列,而在上一步是给它的子队列factorizeQueue里放入了东西newEntry

所以会直接从 273 行,break 出来,开始执行 289行,将自己的 _willEnsureProcessing 状态改为了 false。

通过之前提到的父子关系(老父亲带着3个儿子),我们继续看 281 行的代码。很显然是可以走进这个条件判断的,继续debug。

for (const child of this._children) {while (this._activeTasks < this._parallelism) {const entry = child._queued.dequeue();if (entry === undefined) break;this._activeTasks++;entry.state = PROCESSING_STATE;child._startProcessing(entry);}if (child._queued.length > 0) return;}

这块代码的主要含义还是,遍历子队列,并取出相关的任务 赋值给 entry变量,如果entry 不存在,说明子队列为空,直接 break,开始遍历下一个子队列。如果子队列有数据,对父级当前进行的任务数量加一(this._activeTasks++;),然后把相关任务的状态改为 PROCESSING_STATE,也就是任务进行中,调用子队列的 _startProcessing 把任务传入。

毫无疑问的是,我们的 factorizeQueue 子队列中存在任务newEntry,继续debug 验证猜想。

下一步应该是进入factorize 队列的_startProcessing 方法了(注意队列已经切换了)。继续 debug,

注意看,此时的 this 指向 是 factorize 队列。那就继续调试 _startProcessing 方法。

调试 this.hooks.beforeStart hook,结果如下:

还是未绑定任何的插件,继续执行下去。

没有出现 err,直接忽略,开始看 316 行,此处的执行的 this._processor,让我们回顾一下 1.2章节 中的关于 constructor 的部分,核心代码如下图:

这意味着 我们要去看 实例化 factorize 时候传入的 processor 方法是什么?然后去执行它。
回顾 1.2章节 的代码

processor 指的是 compilation 中的 _factorizeModule 方法

断点到 _factorizeModule 上,你会发现其主要执行的是 factory.create 方法,而此时的 factoryNormalModuleFactary 的实例,这里本质上也就是去 执行 NormalModuleFactary 类的 create 方法。

2.2 执行 NormalModuleFactary 类的 create 方法

断点进入该 create 函数,核心代码逻辑如下:

742755 行,很明显的都可以看出仅仅是变量的赋值和变量的初始化,而所有的变量都是存在了resolveData 对象里。走一下断点,看一下里面都是什么数据。

此处没啥难点,继续向下走,要进入beforeResolve的hook里了,直接断点进入,


发现此hook也没有被任何插件所监听,那就直接进入此hook的callback里。


err 和 result 变量都是undefined,毫无疑问的又进入到了 801 行的 factorize 的hook了,那就继续断(慢慢的就会明白 webpack5 插件强大的背后,是难读的设计和调试)。开始调试 NormalModuleFactaryfactorize 的 hook。

2.3 调试 NormalModuleFactaryfactorize 的 hook

老规矩,首先查看监听此 hook 的插件有哪些,见下图

终于有插件进行监听了(注意此处NormalModuleFactory插件的stage属性),说明这个 hook 还是蛮重要的。继续断点,进入ExternalModuleFactoryPlugin的内部。

发现此插件的逻辑分为两部分,第一部队是去一步一步解构赋值传入的 resolveData 的数据。(不明白此处作者为啥不使用 es6 的对象解构语法去处理。。。。),然后生命两个函数,最后调用handleExternals 函数,传入 ExternalModuleFactoryPlugin插件 实例的 externals 属性,注意看此处的 externals 属性是 /^(\/\/|https?:\/\/|std:)/ (这个正则表达式用于匹配字符串的开头是否以”//“或”http://“或”https://“或”std:“)。

ExternalModuleFactoryPlugin 是 Webpack 的内置插件之一,它的作用是帮助 Webpack 处理外部的模块引入。在使用 Webpack 打包时,如果一个模块需要从外部引入,可以通过设置 externals 属性实现。ExternalModuleFactoryPlugin 插件会通过检查这些外部模块的配置,生成对应的 ExternalModule 对象,在 Webpack 运行过程中当这些模块被请求时,ExternalModuleFactoryPlugin 会负责处理这个请求。

进入handleExternals 函数里,结果如下图:


由于我们的本次的依赖是本地依赖,所以正则匹配失败了,那么结果就是要走到 callback 里了。转了一圈啥也没有做,callback 为空,那就继续断点,向下执行,进入 NormalModuleFactory 插件里。

2.4 进入 NormalModuleFactory 插件执行factorize 的 hook 【核心逻辑】

断点进入:


进入以后你会发现,这个hook 又触发了 resolve 的hook,这不就是套娃吗?然后继续断点看监听了resolve的hook上都有哪些插件?


你会发现监听此hook的还是这哥们自身,这不是耍我们呢吗?????
实则不然,他完全可以写的很优雅,但是为了能尽可能的把hook给暴露出去,增强webpack的定制化能力,不得不这么做。

如果是你,易读和强大的能力你会选择哪个呢?

继续断点下去。


你会发现此hook的逻辑竟然高达 400 行左右的代码,一起去断点看看做了什么吧,我会把某些当前无用的方法过滤掉。

const {contextInfo,context, // 上下文:/Users/fujunkui/Desktop/github-project/webpack/examples/01-basedependencies,dependencyType,request, // 路径是:/Users/fujunkui/Desktop/github-project/webpack/examples/01-base/src/index.jsassertions,resolveOptions,fileDependencies,missingDependencies,contextDependencies,
} = data
const contextScheme = getScheme(context) // undefined
let scheme = getScheme(request) // undefined
if (!scheme) {const requestWithoutMatchResource = requestscheme = getScheme(requestWithoutMatchResource)if (!scheme && !contextScheme) {const firstChar = requestWithoutMatchResource.charCodeAt(0)const secondChar = requestWithoutMatchResource.charCodeAt(1)console.log('dependencies start with')noPreAutoLoaders = firstChar === 45 && secondChar === 33 // startsWith "-!"noAutoLoaders = noPreAutoLoaders || firstChar === 33 // startsWith "!"noPrePostAutoLoaders = firstChar === 33 && secondChar === 33 // startsWith "!!";const rawElements = requestWithoutMatchResource.slice(noPreAutoLoaders || noPrePostAutoLoaders? 2: noAutoLoaders? 1: 0,).split(/!+/)unresolvedResource = rawElements.pop()elements = rawElements.map((el) => {const { path, query } = cachedParseResourceWithoutFragment(el)return {loader: path,options: query ? query.slice(1) : undefined,}})scheme = getScheme(unresolvedResource)}const continueCallback = () => {// ....}this.resolveRequestArray(contextInfo,contextScheme ? this.context : context,elements,loaderResolver,resolveContext,(err, result) => {if (err)return continueCallback(err)loaders = resultcontinueCallback()},)if (scheme) {// ...条件进不去}else if (contextScheme) {// ...条件进不去}else { defaultResolve(context) }
}

部分核心代码的运行时的参数截图如下:

此处主要是判断 此文件的request路径是否以某些行内 loader 的方式进行开头的。 详情见官网

resolveRequestArray是必须要执行的,此处附上相关的运行结果

最后走进了 defaultResolve(context) 里。进入 此函数看执行过程。

最后进入resolveResource函数里,断点进入。

调用resolver.resolve的方法,获取到 unresolvedResource 的真实路径。

关于此处,如果你不了解 resolver.resolve的方法的原理,可以去看我前面的文章,【webpack核心库】耗时7个小时,用近50张图来学习enhance-resolve中的数据流动和插件调度机制,可以搜索一下上面的标题,也是干货满满。

继续断点,走到callback 接受 resolve 处理以后的结果如下图。

进入callback 里。

发现最后走到了 continueCallback 函数里。

关于此函数,内容有些多,此处就不做进行解析了。直接断点回到刚刚的 this.hooks.resolve.callAsync的部分。在其 callback 内部打上断点。


这样就可以跳过continueCallback的部分,

2.5 进入 resolve hook 的内部

继续断点

因为 err 和 result都为空,最后走到了afterResolve的hook里。
大家都应该会调试了,直接说结果了,没有插件去监听afterResolve的hook,所以直接进其内部了。

进入此hook内部以后,又是一堆的错误判断,最后走进了 createModule 的 hook。

2.6 进入 createModule 的 hook 【将路径等信息变为Module】

此hook还是无插件监听,直接上图看其callback做了啥:

此处对整体的webpack来说是比较核心的,将路径代表的文件给变成了模块化的Module对象。(读者可以好好理解其背后的含义)

至于Module 对象 不是本章的重点,这里只需要明白 路径代表的文件变为了 Module对象 即可。
然后继续向下执行,调用 module的hook。直接看注册此hook的SideEffectsFlagPlugin插件。

Webpack 5 中的 SideEffectsFlagPlugin 主要用于优化项目的构建速度和代码体积。它能够在打包时根据配置文件中的 “sideEffects” 属性,自动确定哪些模块是有副作用的(即会对全局状态产生影响),哪些模块是纯粹的(即不会对全局状态产生影响)。当一个模块被判断为无副作用时,Webpack 5 会将其标记为 “side-effect-free”,这样就可以进行一系列优化操作,如 tree shaking、scope hoisting 等,以减小最终打包出来的代码体积和提高加载速度。因此,通过合理配置 “sideEffects” 属性,并使用 SideEffectsFlagPlugin 插件,可以帮助开发者更好地优化自己的 Webpack 5 项目。

此插件里注册了两个监听函数。其执行结果如下:

执行完毕,将根据路径创建的 module 实例通过callback传递出去。继续单步断点,看此callback 会走进哪个hook里。

最后走进了 factorize 的 hook,

至此,实现了一个 文件变成了一个 Module的过程。继续向下执行callback,看下一步是做什么?

走到了 factory.create 的 callback 函数了,也就是 _factorizeModule方法。还记得这个方法是哪个队列的 processor方法吗? 贴上代码:

this.factorizeQueue = new AsyncQueue({name: "factorize",parent: this.addModuleQueue,processor: this._factorizeModule.bind(this)
});

让我们继续执行下去,


执行到 callback 里的 callback,继续进入此 callback(链路太长了,绕来绕去的)

进入到 AsyncQueue.js文件。


再调用 _handleResult 去处理_processor后的结果。

2.7 进入 _handleResult 方法

上图:

这个方法比较简单,直接调用了 result 的 hook,此hook也没有插件进行监听,直接执行自身的callback 函数,进入 此 hook的内部。

红色圈中部分,将队列子项AsyncQueueEntry的状态改为完成的状态。然后找到父级,将父级用于计数当前激活任务的值减一。

// 如果父级用有需要进行的任务并且父级需要去执行。就把相关任务添加进异步里。
if (root._willEnsureProcessing === false && root._needProcessing) {root._willEnsureProcessing = true;setImmediate(root._ensureProcessing);}

当前父级不需要执行什么任务,此处跳过。继续向下执行。

  if (inHandleResult++ > 3) {process.nextTick(() => {callback(error, result);if (callbacks !== undefined) {for (const callback of callbacks) {callback(error, result);}}});} else {callback(error, result);if (callbacks !== undefined) {for (const callback of callbacks) {callback(error, result);}}}

此时的 inHandleResult 判断没有走进if分支,走到else分支。这里你会发现不管走进那个条件里,核心代码都是一样的,唯一的区别在于是否包裹在process.nextTick里。

继续调试下去,执行callback(error, result);,此处的 callback是从 AsyncQueueEntry身上取到的。回想一下这里的 callback的来源是哪里?

AsyncQueue 调用add时候传入的 callback 函数。

回想一下调用这个方法的位置:

兜兜转转的,又回到了 Compilation.js 这个文件。继续调试,大概率是要进入 addModule这个方法的。

	addModule(module, callback) {this.addModuleQueue.add(module, callback);}

看到这个代码,感觉是不是有些相似,又回到了本文(梦)开始的地方。

关于addModuleQueue.add后,接下来的路,各位读者,还需要我再出一篇文章去解析吗?有需要的读者,可以在评论区评论告诉我哈。毕竟每个 AsyncQueue 里的具体执行内容不一样。

相关文章:

(源码篇02)webpack5中的事件调度系统和NormalModuleFactary核心逻辑

1. 书接上回&#xff0c;从 this.factorizeQueue.add(options, callback); 开始 不是很清楚上下文的兄弟&#xff0c;可以去看下我之前写的 &#xff08;源码篇01&#xff09;浅析webpack5中Compiler中重要的hook调用过程。 此文比较干&#xff0c;各位读者开始阅读前&#xf…...

Vue2.x源码:new Vue()做了啥?

vue源码版本vue2.5.2 new Vue()做了啥? new Vue()会执行_init方法&#xff0c;而_init方法在initMixin函数中定义。 src/core/instance/index.js文件中定义了Vue function Vue (options) {this._init(options) }initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycl…...

WinForm | C# 弹出简易的消息提示框 (仿Android Toast消息提示)

ApeForms Toast消息提示 文章目录ApeForms Toast消息提示前言方法原型及参数释义消息驻留延时消息弹出模式队列模式抢占模式复用模式UI库安装与使用获取示例源码前言 在使用手机的时候经常会见到屏幕的中下方会弹出消息提示框&#xff0c;它就是Toast&#xff0c;以下是百度百…...

1、DRF实战总结:DRF特点、序列化与RESTful API规范

Django这种基于MVC开发模式的传统框架&#xff0c;非常适合开发基于PC的传统网站&#xff0c;因为它同时包括了后端的开发(逻辑层、数据库层) 和前端的开发(如模板语言、样式)。现代网络应用Web APP或大型网站一般是一个后台&#xff0c;然后对应各种客户端(iOS, android, 浏览…...

SIP协议及其简单介绍

SIP协议及其简单介绍概述流程SIP流程两台设备建立会话原理使用场景概述 SIP&#xff08;Session Initiation Protocol&#xff0c;会话初始化协议&#xff09;是一个应用层协议&#xff0c;用于在互联网上创建、修改和终止多媒体会话。SIP是一个客户端/服务器协议&#xff0c;…...

安全防御第四天:防病毒网关

一、恶意软件1.按照传播方式分类&#xff08;1&#xff09;病毒病毒是一种基于硬件和操作系统的程序&#xff0c;具有感染和破坏能力&#xff0c;这与病毒程序的结构有关。病毒攻击的宿主程序是病毒的栖身地&#xff0c;它是病毒传播的目的地&#xff0c;又是下一次感染的出发点…...

Postman接口与压力测试实例

Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。它提供功能强大的 Web API & HTTP 请求调试。 1、环境变量和全局变量设置 环境变量可以使用在以下地方&#xff1a; URLURL paramsHeader valuesform-data/url-encoded valuesRaw body contentHelper fi…...

TCP/IP socket

## TCP Socket 收发缓冲区: 每个socket在linux内核中都有一个发送缓冲区和一个接收缓冲区。 只要对端将数据发送过来&#xff0c;linux内核TCP/IP协议栈就会负责将数据缓存到socket对应的接收缓冲区中&#xff0c;无论是否调用recv。 recv()所做的工作&#xff0c;只是把内核缓…...

“工作三年,跳槽要求涨薪50%”,合理吗?

如果问在TI行业涨工资最快的方式是什么&#xff1f;回答最多的一定是&#xff1a;跳槽&#xff01;前段时间&#xff0c;知乎上这样一条帖子引发了不少IT圈子的朋友的讨论 &#xff0c;有网友提问 “程序员跳槽要求涨薪50%过分吗&#xff1f;”截图来源于知乎&#xff0c;如侵删…...

Vue学习计划九:了解Vue动画效果以及过渡动画和动态组件的使用方法

Vue.js 是一个流行的 JavaScript 框架&#xff0c;它提供了很多工具和功能&#xff0c;可以帮助开发人员创建动态、交互式的 Web 应用程序。其中之一就是动画效果&#xff0c;Vue.js 提供了一系列的 API 和指令&#xff0c;使得添加动画效果变得非常容易。 在 Vue.js 中&#…...

【Linux】进程理解与学习Ⅲ-环境变量

环境&#xff1a;centos7.6&#xff0c;腾讯云服务器Linux文章都放在了专栏&#xff1a;【Linux】欢迎支持订阅&#x1f339;相关文章推荐&#xff1a;【Linux】冯.诺依曼体系结构与操作系统【Linux】进程理解与学习Ⅰ-进程概念浅谈Linux下的shell--BASH【Linux】进程理解与学习…...

【三】一起算法---栈:STL stack、手写栈、单调栈

纸上得来终觉浅&#xff0c;绝知此事要躬行。大家好&#xff01;我是霜淮子&#xff0c;欢迎订阅我的专栏《算法系列》。 学习经典算法和经典代码&#xff0c;建立算法思维&#xff1b;大量编码让代码成为我们大脑的一部分。 ⭐️已更系列 1、基础数据结构 1.1、链表➡传送门 1…...

电路设计的一些概念

锁存器的产生 论述1 (转)时序电路&#xff0c;生成触发器&#xff0c;触发器是有使能端的&#xff0c;使能端无效时数据不变&#xff0c;这是触发器的特性。 组合逻辑&#xff0c;由于数据要保持不变&#xff0c;只能通过锁存器来保存。 第一个代码&#xff0c;由于是时序逻…...

【Linux】Linux下权限的理解

前言&#xff1a;在之前我们已经对基本的指令进行了深入的学习&#xff0c;接下来我将带领大家学习的是关于权限的相关问题。在之前&#xff0c;我们一直是使用的【root】用户&#xff0c;即为“超级用户”&#xff0c;通过对权限的学习之后&#xff0c;我们就会慢慢的切换到普…...

Prometheus监控实战系列十七:探针监控

目前对于应用程序的监控主要有两种方式&#xff0c;一种被称为白盒监控&#xff0c;它通过获取目标的内部信息指标&#xff0c;来监控目标的状态情况&#xff0c;我们前面介绍的主机监控、容器监控都属于此类监控。另一种则是“黑盒监控”&#xff0c;它指在程序外部通过探针的…...

题目:JPA的懒加载失效是什么情况?

题目&#xff1a;JPA的懒加载失效是什么情况&#xff1f;Q1&#xff1a;什么是JPA的懒加载&#xff1f;Q2&#xff1a;JPA的懒加载会在什么情况下失效&#xff1f;Q3&#xff1a;如何避免JPA的懒加载失效&#xff1f;前言&#xff1a;在使用JPA进行数据库操作时&#xff0c;懒加…...

十六、消息推送

一、什么是消息推送&#xff1f; 消息推送通常是指网站的运营工作等人员&#xff0c;通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。 消息推送一般又分为 Web 端消息推送和移动端消息推送。 消息推送无非是推&#xff08;push&#xff09;和拉&#xff08;p…...

PMP项目管理-【第一章】引论

项目知识体系&#xff1a; 项目管理知识体系&#xff1a; 1.1 项目特性 独特性&#xff1a;独特性会带来不确定性(风险) 临时性&#xff1a;1> 任何项目都有起始终止时间 2> 项目具备临时性&#xff0c;项目成果可能是永久的 1.2 项目驱动变革 从商业角度来看&#xff0c…...

前端布局小案例,分享3个漂亮的卡片组件

当今互联网发展迅猛&#xff0c;各种应用、网站和软件层出不穷&#xff0c;其中前端技术的发展更是让人瞩目。随着用户对于界面设计的要求越来越高&#xff0c;漂亮的卡片组件在各类网页设计中变得越来越流行。本文将分享三个精美的卡片组件&#xff0c;帮助您在前端开发中轻松…...

博客重载记录

博客重载记录流控算法实现open系统调用流程二分查找前言&#xff1a; 有时候看了一些比较好的文章&#xff0c;过几天就忘了&#xff0c;想想不如自己实现一遍博客代码或按博客结构自己写一遍&#xff0c;加深印象&#xff0c;但把别人的内容改个名字变成自己的博客&#xff0c…...

open-cv绘制简单形状line() circle() rectangle() polylines() putText() cvtColor()

OpenCV彩色图像中一个像素是按照“B-G-R”模式组织的。 绘图函数的一些公众参数&#xff1a; img &#xff1a;图像对象 color&#xff1a; 颜色&#xff0c;如果彩色用一个三元组表示&#xff0c;三元组的元素按照B-G-R组织&#xff0c;三元组(0,255,0)中B为0&#xff0c;G为2…...

基于 PyTorch + LSTM 进行时间序列预测(附完整源码)

时间序列数据&#xff0c;顾名思义是一种随时间变化的数据类型。 例如&#xff0c;24小时内的温度、一个月内各种产品的价格、某家公司一年内的股票价格等。深度学习模型如长短期记忆网络&#xff08;LSTM&#xff09;能够捕捉时间序列数据中的模式&#xff0c;因此可以用于预…...

GEE页面介绍

目录一、背景二、用户界面三、数据类型&#xff1a;栅格1、请求图像集合2、学习查看栅格元数据3、矢量实例一&#xff1a;四、数据集五、数据属性1、空间分辨率2、时间分辨率六可视化多个波段1、真彩色(TCI)2彩色红外&#xff08;CI&#xff09;3、伪色 1 和 2 (FC1/FC2)七、可…...

python自动发送邮件,qq邮箱、网易邮箱自动发送和回复

在python中&#xff0c;我们可以用程序来实现向别人的邮箱自动发送一封邮件&#xff0c;甚至可以定时&#xff0c;如每天8点钟准时给某人发送一封邮件。今天&#xff0c;我们就来学习一下&#xff0c;如何向qq邮箱&#xff0c;网易邮箱等发送邮件。 一、获取邮箱的SMTP授权码。…...

hastcat

hashcat 下载地址: https://hashcat.net/hashcat/ 案例 Usage: hashcat [options]... hash|hashfile|hccapxfile [dictionary|mask|directory]...https://xz.aliyun.com/t/4008破解linux shadow /etc/shadow中密码格式: $id$salt$encrypted如:$1$2eWq10AC$NaQqalCk3 1表…...

242. 一个简单的整数问题

Powered by:NEFU AB-IN Link 文章目录242. 一个简单的整数问题题意思路代码242. 一个简单的整数问题 题意 给定长度为 N的数列 A&#xff0c;然后输入 M行操作指令。 第一类指令形如 C l r d&#xff0c;表示把数列中第 l∼r个数都加 d 第二类指令形如 Q x&#xff0c;表示询问…...

docker安装Redis高可用(一主二从三哨兵)

本次教程使用docker swarm安装 准备三台机器 hostIP用途node1192.168.31.130redis-master01&#xff0c;redis哨兵节点01node2192.168.31.131redis-slave01, redis哨兵节点02node3192.168.31.132redis-slave02 redis哨兵节点02 注意事项&#xff1a; 1&#xff1a;需要保证三…...

安全防御之入侵检测篇

目录 1.什么是IDS&#xff1f; 2.IDS和防火墙有什么不同&#xff1f;3.IDS的工作原理&#xff1f; 4.IDS的主要检测方法有哪些&#xff1f;请详细说明 5.IDS的部署方式有哪些&#xff1f; 6.IDS的签名是什么意思&#xff1f;签名过滤器有什么用&#xff1f;例外签名的配置作…...

学习系统编程No.10【文件描述符】

引言&#xff1a; 北京时间&#xff1a;2023/3/25&#xff0c;昨天摆烂一天&#xff0c;今天再次坐牢7小时&#xff0c;难受尽在不言中&#xff0c;并且对于笔试题&#xff0c;还是非常的困难&#xff0c;可能是我做题不够多&#xff0c;也可能是没有好好的总结之前做过的一些…...

网络基础认识

目录 一、计算机网络背景 1.1 网络发展 1.2 "协议"由来 二、网络协议初识 2.1 协议分层 2.2 OSI七层模型 2.3 TCP/IP五层模型 三、网络协议栈 四、数据包封装与分用 五、网络传输基本流程 5.1 同局域网的两台主机通信 5.2 跨网络的两台主机通信 六、网络…...

企业网站组网方案/成都网站seo

and then interview#$%^&* again转载于:https://www.cnblogs.com/rexhost/archive/2005/07/08/188977.html...

西宁市建设网站多少钱/网站seo属于什么专业

asp.net1.1的情况 一个页面上有一个服务器控件的Button&#xff0c;为什么在Page_Load里加上了 Button1.Attributes["onclick"]"javascript:return window.confirm(请确定输入无误&#xff1f;);"; 却弹不出确认对话框。 因为是在msn上交流的&#xff0…...

徐州网站制作建设/客源软件哪个最好

Android Studio 关联 Android 源码比较方便&#xff0c;一般下载后可自动关联&#xff0c;但是 Android Studio 默认使用的 JDK 是内嵌的&#xff0c;是不带源码的。所以在查看 JDK 源码时&#xff0c;看到的是反编译 class 而成的代码&#xff0c;没有注释。那么应该如何关联 …...

医程通 网站做的太/网站及推广

洛谷 P2505 旅行 题解 洛谷 P2502 解题思路 将速度从小到大排 枚举断点 连接两个点 假如起点可到达终点 更新答案 输出记得化简 代码 #include<algorithm> #include<iostream> #include<cstdio> using namespace std; int xx,yy,ta,wo,n,m,s,t; double …...

一个空间放几个网站/网络推广客服好做吗

set hive.cli.print.headertrue 1.设置前 2.设置后 3.进阶设置&#xff08;点击下方链接查看&#xff09; Hive sql查询结果显示表头&#xff08;header&#xff09;如何配置&#xff1a;只显示列名&#xff0c;不显示表名...

建设银行官网网站首页/搜易网托管模式的特点

梅学堂语数英精品学习资源免费领一二年级的小朋友&#xff0c;大部分处于形象思维快速发展&#xff0c;逻辑思维发展相对迟缓的状态。总的来说&#xff0c;孩子们的思维水平是在进步的&#xff0c;但是相对于解题来说他们依然会有所欠缺。例如&#xff0c;对初中生高中生来说&a…...