【Vue2源码学习分析】

news2024/10/6 4:06:35

# 文件结构

源码目录

# 调试环境搭建

  • 安装依赖: npm i
  • 安装rollup: npm i -g rollup
  • 修改dev脚本,添加sourcemap,package.json
 "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web- full-dev",
    
    
  • 运行开发命令: npm run dev
  • 引入前面创建的vue.js,samples/commits/index.html
<script src="../../dist/vue.js"></script>
    
    

术语解释:

  • runtime:仅包含运行时,不包含编译器
  • common:cjs规范,用于webpack1
  • esm:ES模块,用于webpack2+
  • umd: universal module definition,兼容cjs和amd,用于浏览器

# 入口

dev脚本中 -c scripts/config.js 指明配置文件所在

参数 TARGET:web-full-dev 指明输出文件配置项

// Runtime+compiler development build (Browser){ 'web-full-dev': { entry: resolve('web/entry-runtime-with-compiler.js'), // 入口 dest: resolve('dist/vue.js'),// 目标文件 format: 'umd', // 输出规范 env: 'development', alias: { he: './entity-decoder' }, banner, },}
    
    

# 初始化流程

  1. 入口 platforms/web/entry-runtime-with-compiler.js

扩展默认$mount方法:处理template或el选项

  1. platforms/web/runtime/index.js
  • 安装web平台特有指令和组件
  • 定义__patch__:补丁函数,执行patching算法进行更新
  • 定义$mount:挂载vue实例到指定宿主元素(获得dom并替换宿主元素)
  1. core/index.js

初始化全局api 具体如下:

 Vue.set = setVue.delete = delVue.nextTick = nextTickinitUse(Vue) // 实现Vue.use函数initMixin(Vue) // 实现Vue.mixin函数initExtend(Vue) // 实现Vue.extend函数initAssetRegisters(Vue) // 注册实现Vue.component/directive/filter
    
    
  1. core/instance/index.js

Vue构造函数定义

定义Vue实例API

 function Vue (options) { // 构造函数仅执行了_init this._init(options)}initMixin(Vue) // 实现init函数stateMixin(Vue) // 状态相关api $data,$props,$set,$delete,$watch eventsMixin(Vue)// 事件相关api $on,$once,$off,$emit lifecycleMixin(Vue) // 生命周期api _update,$forceUpdate,$destroy renderMixin(Vue)// 渲染api _render,$nextTick
    
    
  1. core/instance/init.js

创建组件实例,初始化其数据、属性、事件等

initLifecycle(vm) // $parent,$root,$children,$refs initEvents(vm) // 处理父组件传递的事件和回调initRender(vm) // $slots,$scopedSlots,_c,$createElement callHook(vm, 'beforeCreate') initInjections(vm) // 获取注入数据initState(vm) // 初始化props,methods,data,computed,watch initProvide(vm) // 提供数据注入callHook(vm, 'created')
    
    
  1. $mount

mountComponent

执行挂载,获取vdom并转换为dom

new Watcher()

创建组件渲染watcher

updateComponent()

执行初始化或更新

update()

初始化或更新,将传入vdom转换为dom,初始化时执行的是dom创建操作

render() src\core\instance\render.js

渲染组件,获取vdom

整体流程

new Vue() => _init() => $mount() => mountComponent() => updateComponent()/new Watcher() => render() => _update()

# 数据响应式

Vue一大特点是数据响应式,数据的变化会作用于UI而不用进行DOM操作。原理上来讲,是利用了JS语 言特性Object.defineProperty(),通过定义对象属性setter方法拦截对象属性变更,从而将数值的变化 转换为UI的变化

具体实现是在Vue初始化时,会调用initState,它会初始化data,props等,这里着重关注data初始 化,

整体流程

  1. initState (vm: Component) src\core\instance\state.js

初始化数据,包括propsmethodsdatacomputedwatch

  1. initData核心代码是将data数据响应化
function initData (vm: Component) { // 执行数据响应化 observe(data, true /* asRootData */)}
    
    
  1. core/observer/index.js

observe方法返回一个Observer实例

  1. core/observer/index.js
  • Observer对象根据数据类型执行对应的响应化操作
  • defineReactive定义对象属性的getter/setter,getter负责添加依赖,setter负责通知更新
  1. core/observer/dep.js

Dep负责管理一组Watcher,包括watcher实例的增删及通知更新

  1. Watcher
  • Watcher解析一个表达式并收集依赖,当数值变化时触发回调函数,常用于$watch API和指令中。
  • 每个组件也会有对应的Watcher,数值变化会触发其update函数导致重新渲染
 export default class Watcher { constructor () {} get () {} addDep (dep: Dep) {} update () {}}
    
    

相关API: $watcher

测试代码examples\test\02-1-reactive.html

# 数组响应化

数组数据变化的侦测跟对象不同,我们操作数组通常使用push、pop、splice等方法,此时没有办法得 知数据变化。所以vue中采取的策略是拦截这些方法并通知dep。

1. src\core\observer\array.js

为数组原型中的7个可以改变内容的方法定义拦截器

2. Observer中覆盖数组原型

 if (Array.isArray(value)) { // 替换数组原型 protoAugment(value, arrayMethods) // value.__proto__ = arrayMethods  this.observeArray(value)}
    
    

测试代码examples\test\02-2-reactive-arr.html

相关API: Vue.set()/delete()

 data: { arr: []}
    
    
 arr.length = 0 arr[index] = xxxVue.set() Vue.del()
    
    

# 异步更新队列

Vue高效的秘诀是一套批量、异步的更新策略

# 概念解释

  • 事件循环Event Loop:浏览器为了协调事件处理、脚本执行、网络请求和渲染等任务而制定的工 作机制。
  • 宏任务Task:代表一个个离散的、独立的工作单元。浏览器完成一个宏任务,在下一个宏任务执行 开始前,会对⻚面进行重新渲染。主要包括创建文档对象、解析HTML、执行主线JS代码以及各种 事件如⻚面加载、输入、网络事件和定时器等。
  • 微任务:微任务是更小的任务,是在当前宏任务执行结束后立即执行的任务。如果存在微任务,浏 览器会清空微任务之后再重新渲染。微任务的例子有 Promise 回调函数、DOM变化等。

# vue中的具体实现

  • 异步:只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变 更。

  • 批量:如果同一个 watcher 被多次触发,只会被推入到队列中一次。去重对于避免不必要的计算 和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列执行实际工作

  • 异步策略:Vue 在内部对异步队列尝试使用原生的 Promise.then 、 MutationObserver

    或 setImmediate ,如果执行环境都不支持,则会采用 setTimeout 代替。

1. update() core\observer\watcher.js

dep.notify()之后watcher执行更新,执行入队操作

2. queueWatcher(watcher) core\observer\scheduler.js

执行watcher入队操作

3. nextTick(flushSchedulerQueue) core\util\next-tick.js

nextTick按照特定异步策略执行队列操作

watcher中update执行三次,但run仅执行一次,且数值变化对dom的影响也不是立竿⻅影的。

请大家研究一下相关API: vm.$nextTick(cb)

# 虚拟DOM

# 概念

虚拟DOM(Virtual DOM)是对DOM的JS抽象表示,它们是JS对象,能够描述DOM结构和关系。应用 的各种状态变化会作用于虚拟DOM,最终映射到DOM上。

# 体验虚拟DOM

vue中虚拟dom基于snabbdom实现,安装snabbdom并体验

< !DOCTYPE html > <html lang = "en" > <head > </head><body><div id="app"></div > <!--安装并引入snabbdom--><script src = "../../node_modules/snabbdom/dist/snabbdom.js" > </script><script> / / 之前编写的响应式函数 function defineReactive(obj, key, val) { Object.defineProperty(obj, key, { get() { return val }, set(newVal) { val = newVal // 通知更新 update() } }) } // 导入patch的工厂init,h是产生vnode的工厂 const { init, h } = snabbdom // 获取patch函数 const patch = init([]) // 上次vnode,由patch()返回 let vnode; // 更新函数,将数据操作转换为dom操作,返回新vnode function update() { if (!vnode) { // 初始化,没有上次vnode,传入宿主元素和vnode vnode = patch(app, render()) } else { // 更新,传入新旧vnode对比并做更新 vnode = patch(vnode, render()) } } // 渲染函数,返回vnode描述dom结构 function render() { return h('div', obj.foo) } // 赋一个日期作为初始值 obj.foo = new Date().toLocaleTimeString() // 定时改变数据,更新函数会重新执行 setInterval(() => { obj.foo = new Date().toLocaleTimeString() }, 1000); < /script></body > </html>
    
    

# 优点

  • 虚拟DOM轻量、快速:当它们发生变化时通过新旧虚拟DOM比对可以得到最小DOM操作量,配 合异步更新策略减少刷新频率,从而提升性能
patch(vnode, h('div', obj.foo))
    
    
  • 跨平台:将虚拟dom更新转换为不同运行时特殊操作实现跨平台
 <script src="../../node_modules/snabbdom/dist/snabbdom-style.js"></script> <script>// 增加style模块const patch = init([snabbdom_style.default])function render() {// 添加节点样式描述return h('div', { style: { color: 'red' } }, obj.foo)}</script>
    
    
  • 兼容性:还可以加入兼容性代码增强操作的兼容性

# 必要性

vue 1.0中有细粒度的数据变化侦测,它是不需要虚拟DOM的,但是细粒度造成了大量开销,这对于大 型项目来说是不可接受的。因此,vue 2.0选择了中等粒度的解决方案,每一个组件一个watcher实例, 这样状态变化时只能通知到组件,再通过引入虚拟DOM去进行比对和渲染。

# 整体流程

1. mountComponent() core/instance/lifecycle.js

渲染、更新组件

 // 定义更新函数const updateComponent = () => {// 实际调用是在lifeCycleMixin中定义的_update和renderMixin中定义的_rendervm._update(vm._render(), hydrating) }
    
    

2. _render core/instance/render.js

生成虚拟dom

3. _update core\instance\lifecycle.js

update负责更新dom,转换vnode为dom

4. patch() platforms/web/runtime/index.js

__patch__是在平台特有代码中指定的

Vue.prototype.__patch__ = inBrowser ? patch : noop
    
    

测试代码,examples\test\04-vdom.html

# patch获取

patch是createPatchFunction的返回值,传递nodeOps和modules是web平台特别实现

 export const patch: Function = createPatchFunction({ nodeOps, modules })
    
    

1. platforms\web\runtime\node-ops.js

定义各种原生dom基础操作方法

2. platforms\web\runtime\modules\index.js

modules 定义了属性更新实现

watcher.run() => componentUpdate() => render() => update() => patch()
    
    

# patch实现

1. patch core\vdom\patch.js

首先进行树级别比较,可能有三种情况:增删改

  • new VNode不存在就删;
  • old VNode 不存在就增;
  • 都存在就执行diff执行更新

  1. patchVnode

比较两个VNode,包括三种类型操作:属性更新、文本更新、子节点更新

具体规则如下:

  • 新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren
  • 如果新节点有子节点而老节点没有子节点,先清空老节点的文本内容,然后为其新增子节点。
  • 当新节点没有子节点而老节点有子节点的时候,则移除该节点的所有子节点。
  • 当新老节点都无子节点的时候,只是文本的替换。

测试,04-vdom.html

 // patchVnode过程分解// 1.div#demo // 2.h1// 3.text// 4.p// 5.textupdateChildren updateChildren 文本相同跳过 updateChildren setTextContent
    
    

3. updateChildren

updateChildren主要作用是用一种较高效的方式比对新旧两个VNode的children得出最小操作补丁。执 行一个双循环是传统方式,vue中针对web场景特点做了特别的算法优化,我们看图说话:

在新老两组VNode节点的左右头尾两侧都有一个变量标记,在遍历过程中这几个变量都会向中间靠拢。 当oldStartIdx > oldEndIdx或者newStartIdx > newEndIdx时结束循环

下面是遍历规则:

  • 首先,oldStartVnode、oldEndVnode与newStartVnode、newEndVnode两两交叉比较,共有4种比较 方法。
  • 当 oldStartVnode和newStartVnode 或者 oldEndVnode和newEndVnode 满足sameVnode,直接将该 VNode节点进行patchVnode即可,不需再遍历就完成了一次循环。如下图

如果oldStartVnode与newEndVnode满足sameVnode。说明oldStartVnode已经跑到了oldEndVnode 后面去了,进行patchVnode的同时还需要将真实DOM节点移动到oldEndVnode的后面。

如果oldEndVnode与newStartVnode满足sameVnode,说明oldEndVnode跑到了oldStartVnode的前 面,进行patchVnode的同时要将oldEndVnode对应DOM移动到oldStartVnode对应DOM的前面

如果以上情况均不符合,则在old VNode中找与newStartVnode相同的节点,若存在执行 patchVnode,同时将elmToMove移动到oldStartIdx对应的DOM的前面。

当然也有可能newStartVnode在old VNode节点中找不到一致的sameVnode,这个时候会调用 createElm创建一个新的DOM节点。

至此循环结束,但是我们还需要处理剩下的节点。

当结束时oldStartIdx > oldEndIdx,这个时候旧的VNode节点已经遍历完了,但是新的节点还没有。说 明了新的VNode节点实际上比老的VNode节点多,需要将剩下的VNode对应的DOM插入到真实DOM 中,此时调用addVnodes(批量调用createElm接口)

但是,当结束时newStartIdx > newEndIdx时,说明新的VNode节点已经遍历完了,但是老的节点还有 剩余,需要从文档中删 的节点删除。

# 模板编译

模板编译的主要目标是将模板**(template)转换为渲染函数(render)**

template => render()
    
    

# 模板编译必要性

Vue 2.0需要用到VNode描述视图以及各种交互,手写显然不切实际,因此用户只需编写类似HTML代码 的Vue模板,通过编译器将模板转换为可返回VNode的render函数。

# 体验模板编译

带编译器的版本中,可以使用template或el的方式声明模板,06-1-compiler.html

 (function anonymous(){with(this){return _c('div',{attrs:{"id":"demo"}},[_c('h1',[_v("Vue模板编 译")]),_v(" "),_c('p',[_v(_s(foo))]),_v(" "),_c('comp')],1)}})
    
    

输出结果大致如下:

 (function anonymous() {with(this){return _c('div',{attrs:{"id":"demo"}},[_c('h1',[_v("Vue模板编译")]), _v(" "),_c('p',[_v(_s(foo))]), _v(" "),_c('comp')],1)}})
    
    
  • 元素节点使用createElement创建,别名_c _
  • _本文节点使用createTextVNode创建,别名_v
  • 表达式先使用toString格式化,别名_s
  • 其他渲染helpers:src\core\instance\render-helpers\index.js

# 整体流程

1. compileToFunctions

若指定template或el选项,则会执行编译,platforms\web\entry-runtime-with-compiler.js

2. 编译过程

编译分为三步:解析、优化和生成,src\compiler\index.js

测试代码06-1-compiler.html

# 模板编译过程

实现模板编译共有三个阶段:解析、优化和生成

  1. 解析 - parse

解析器将模板解析为抽象语法树,基于AST可以做优化或者代码生成工作。 调试查看得到的AST,**/src/compiler/parser/index.js**,结构如下:

解析器内部分了HTML解析器、文本解析器和过滤器解析器,最主要是HTML解析器

  1. 优化 - optimize

优化器的作用是在AST中找出静态子树并打上标记。静态子树是在AST中永远不变的节点,如纯文本节 点。

标记静态子树的好处:

  • 每次重新渲染,不需要为静态子树创建新节点
  • 虚拟DOM中patch时,可以跳过静态子树

测试代码,06-2-compiler-optimize.html 代码实现,src/compiler/optimizer.js - optimize

  1. 代码生成 - generate
  • 将AST转换成渲染函数中的内容,即代码字符串
  • generate方法生成渲染函数代码,src/compiler/codegen/index.js

生成的code⻓这样

 `_c('div',{attrs:{"id":"demo"}},[ _c('h1',[_v("Vue.js测试")]), _c('p',[_v(_s(foo))])])`
    
    

# 典型指令的实现:v-if、v-for

  1. 解析v-if:parser/index.js

processIf用于处理v-if解析

  1. 代码生成,codegen/index.js

genIfConditions等用于生成条件语句相关代码

生成结果:

 "with(this){return _c('div',{attrs:{"id":"demo"}},[(foo) ? _c('h1',[_v(_s(foo))]) : _c('h1',[_v("no title")]),_v(" "),_c('abc')],1)}"
    
    
  1. 解析v-for:parser/index.js

processFor用于处理v-for指令

解析结果:v-for="item in items" for:'items' alias:'item'

  1. 代码生成,src\compiler\codegen\index.js

genFor用于生成相应代码

生成结果

 "with(this){return _c('div',{attrs:{"id":"demo"}},[_m(0),_v(" "),(foo)?_c('p', [_v(_s(foo))]):_e(),_v(" "),_l((arr),function(s){return _c('b',{key:s},[_v(_s(s))])}),_v(" "),_c('comp')],2)}"
    
    

v-if,v-for这些指令只能在编译器阶段处理,如果我们要在render函数处理条件或循环只能使用if 和for

 Vue.component('comp', {props: ['foo'],render(h) { // 渲染内容跟foo的值挂钩,只能用if语句if (this.foo=='foo') { return h('div', 'foo')}return h('div', 'bar') }})
    
    
 (function anonymous(){with(this){return _c('div',{attrs:{"id":"demo"}},[_m(0),_v(" "),(foo)?_c('p', [_v(_s(foo))]):_e(),_v(" "),_c('comp')],1)}})
    
    

# 组件化机制

# 组件声明:Vue.component()

initAssetRegisters(Vue) src/core/global-api/assets.js

组件注册使用extend方法将配置转换为构造函数并添加到components选项

# 组件实例创建及挂载

观察生成的渲染函数

 "with(this){return _c('div',{attrs:{"id":"demo"}},[ _c('h1',[_v("虚拟DOM")]),_v(" "), _c('p',[_v(_s(foo))]),_v(" "),_c('comp') // 对于组件的处理并无特殊之处],1)}"
    
    

# 整体流程

首先创建的是根实例,首次_render()时,会得到整棵树的VNode结构,其中自定义组件相关的主要有: createComponent() - src/core/vdom/create-component.js

1. 组件vnode创建

createComponent() - src/core/vdom/patch.js

创建组件实例并挂载,vnode转换为dom

2. 整体流程:

new Vue() => $mount() => vm._render() => createElement() => createComponent() => vm._update() => patch() => createElm => createComponent()

# 创建组件VNode

1. _createElement - src\core\vdom\create-element.js

_createElement实际执行VNode创建的函数,由于传入tag是非保留标签,因此判定为自定义组件通过 createComponent去创建

2. createComponent - src/core/vdom/create-component.js

创建组件VNode,保存了上一步处理得到的组件构造函数,props,事件等

# 创建组件实例

根组件执行更新函数时,会递归创建子元素和子组件,入口createElm

1. createEle() core/vdom/patch.js line751

首次执行_update()时,patch()会通过createEle()创建根元素,子元素创建研究从这里开始

2. createComponent core/vdom/patch.js line144

自定义组件创建

// 组件实例创建、挂载if (isDef(i = i.hook) && isDef(i = i.init)) { i(vnode, false /* hydrating */ )}if (isDef(vnode.componentInstance)) { // 元素引用指定vnode.elm,元素属性创建等 initComponent(vnode, insertedVnodeQueue) // 插入到父元素 insert(parentElm, vnode.elm, refElm) if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true}
    
    

# 总结

Vue源码学习使我们能够深入理解原理,解答很多开发中的疑惑,规避很多潜在的错误,写出更好的代 码。学习大神的代码,能够学习编程思想,设计模式,训练基本功,提升内力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1798576.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SkyWalking之P0业务场景输出调用链路应用

延伸扩展&#xff1a;XX业务场景 路由标签打标、传播、检索 链路标签染色与传播 SW: SkyWalking的简写 用户请求携带HTTP头信息X-sw8-correlation “X-sw8-correlation: key1value1,key2value2,key3value3” 网关侧读取解析HTTP头信息X-sw8-correlation&#xff0c;然后通过SW…

Linux网络编程:网络层协议|IP

目录 前言&#xff1a; 1.IP协议 1.1.IP协议格式 1.2.网段划分 1.2.1.知识引入 1.2.2.IP地址划分和子网掩码 1.3.IP地址分类 1.3.1.特殊IP地址 ​编辑 1.3.2.私有IP和公网IP 1.3.3.浅谈NAT技术 1.4.路由 1.4.1.什么是路由 1.4.2.路由表 1.5.网络层数据切片和组装…

Allegro导入DXF文件

阿里狗导入DXF文件 点击File–>Import–>DXF&#xff0c;注意DXF file那边不能使用中文路径和文件名以及非法字符&#xff0c;DXF units一般为mm&#xff0c;结构那边一般都用mm制作图&#xff0c;右边三个选项只需要勾选中间那个&#xff0c;意思是以增加的形式导入&am…

jail管理器CBSD实践@FreeBSD

CBSD介绍 CBSD是为FreeBSD jail子系统、bhyve、QEMU/NVMM和Xen编写的管理层。该项目定位为一个综合解决方案的单一集成工具&#xff0c;用于使用预定义的软件集以最少的配置快速构建和部署计算机虚拟环境。 虽然CBSD没有提供额外的操作系统级功能&#xff0c;但它极大地简化了…

Codeforces Round 951 (Div. 2) F. Kostyanych‘s Theorem(思维题 交互好题)

题目 交互题&#xff0c;n&#xff08;n<1e5&#xff09;个点的完全图&#xff0c;无向的&#xff0c;初始恰好删了n-2条边 每次询问可以输入一个d&#xff1a;? d 交互器会输出一个当前度>d的点v&#xff0c; 如果有多个这样的点&#xff0c;输出度最小的&#xff…

搭建自己的DNS服务器

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

STM32-呼吸灯仿真

目录 前言: 一.呼吸灯 二.跑马灯 三. 总结 前言: 本篇的主要内容是关于STM32-呼吸灯的仿真,包括呼吸灯,跑马灯的实现与完整代码,欢迎大家的点赞,评论和关注. 接上http://t.csdnimg.cn/mvWR4 既然已经点亮了一盏灯,接下来就可以做更多实验了, 一.呼吸灯 在上一个的基础上…

Informer

I n f o r m e r Informer Informer 摘要&#xff1a; 长序列时间序列的预测 i n f o r m e r informer informer优点&#xff1a; P r o b s p a r e Probspare Probspare自关注机制&#xff0c;在时间复杂度和内存使用方面达到 O ( N l o g N ) O(NlogN) O(NlogN),在序列依…

plsql 实现自动补全语句

1.打开plsql程序所在文件目录 2.在此目录下新建文件auto_complete.txt &#xff08;文件名随便起&#xff09; 3.在auto_complete.txt 中写入关系对应&#xff1a; 4.在配置-首选项-用户界面-编辑器 中引入auto_complete.txt 即可 5.开一个sql窗口&#xff0c;输入sf 后敲个…

hadoop部署hive

1.安装mysql数据库 这里采用docker部署mysql,如果没有安装docker #安装yum工具 yum install -y yum-utils device-mapper-persistent-data lvm2 --skip-broken #设置docker镜像源 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/…

golang的函数为什么能有多个返回值?

在golang1.17之前&#xff0c;函数的参数和返回值都是放在函数栈里面的&#xff0c;比如函数A调用函数B&#xff0c;那么B的实参和返回值都是存放在函数A的栈里面&#xff0c;所以可以轻松的返回多个值。 其他的编程语言大都使用某个寄存器来存储函数的返回值。 但是从golang…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于两阶段鲁棒的多综合能源微网-共享储能电站协同优化运行策略》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

Android 安装调试 TelephonyProvider不生效

直接安装TelephonyProvider的时候&#xff0c;&#xff08;没有重启&#xff09;发现数据库没有生效。 猜测应该是原本的数据库没有删除后重建更新。 解决方法&#xff1a;杀掉phone进程 adb shell am force-stop com.android.phone 查看device进程 adb shell ps | grep <…

实验二、网络属性设置《计算机网络》

精神状态 be like&#xff1a;边写边崩溃&#xff0c;越写越得劲儿。 目录 一、实验目的&#xff1a; 二、实验内容 三、实验步骤&#xff1a; 四、实验小结 一、实验目的&#xff1a; 掌握 IP 地址、子网掩码等网络属性的设置。 二、实验内容 预备知识&#xff1a; 1、…

MySQL有哪些锁?

文章目录 前言一、全局锁1.全局锁是怎么用的&#xff1f;2.全局锁应用场景是什么&#xff1f;3.加全局锁又会带来什么缺点呢&#xff1f;4.既然备份数据库数据的时候&#xff0c;使用全局锁会影响业务&#xff0c;那有什么其他方式可以避免&#xff1f; 二、表级表1.MySQL 表级…

vue2组件封装实战系列之space组件

组件之 GfSpace 多个同类型的内容比如 div/span/button/li 等&#xff0c;实现水平、垂直方向的均匀布局的功能 效果预览 属性 参数类型说明可选值默认值flexBoolean是否 flex 布局true/falsefalsegapNumber内容间距-10directionString内容排列方向row/columnrow 代码实现 这…

使用Colaboratory免费GPU资源微调Llama3-8b

Llama3微调过程 准备工作 Google Colaboratory Google Colaboratory&#xff0c;也称为 Colab&#xff0c;是一个基于云的平台&#xff0c;允许用户编写和执行 Python 代码。 它为机器学习和数据分析任务提供了便利的环境&#xff0c;并内置了对 TensorFlow 等流行库的支持。…

【uniapp】带圆角渐变边框实现

1. 效果图 2. 代码实现 <image class"item-left-img" :src"url" mode"aspectFill" />.item-left-img {width: 240rpx;height: 320rpx;border: 6rpx solid transparent;background-clip: padding-box, border-box;background-origin: padd…

企业建站响应式网站建设平台版源码系统 海量模版可选择 带完整的安装代码以及搭建教程

系统概述 企业建站响应式网站建设平台版源码系统是一款集创新性、实用性和便捷性于一体的建站解决方案。它旨在为用户提供一站式的网站建设服务&#xff0c;无论你是新手还是经验丰富的开发者&#xff0c;都能通过该系统轻松实现网站的构建与部署。 该系统采用先进的技术架构…

Oracle和mysql中插入时间字段

例如有id 和 times两个字段 Oracle insert into xxx values|(1,sysdate) mysql insert into xxx values(1,now()) 在 MySQL 中&#xff0c;SYSDATE() 函数也是可用的&#xff0c;它与 NOW() 类似&#xff0c;但略有不同&#xff1a; NOW…