vue相关原理

news/2024/5/2 4:17:42/文章来源:https://blog.csdn.net/qq_46262422/article/details/127500281

vue 原理

面试为什么要考察原理

  • 知其然知其所以然,各行各业通用的道理
  • 了解原理才能用的很好,专业性考察,技术的追求
  • 竞争激烈,则优录取
  • 大厂造轮子(业务定制:有些框架不能满足需求)

面试中如何考察,以何种方式考察

  • 考察重点,不考察细节。2/8原则
  • 和使用相关的原理

1. Vue响应式原理

  • vue响应式指的是:组件的data发生变化,立刻触发试图的更新

  • 原理:

    • Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty来劫持数据的setter,getter,在数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。
    • 通过原生js提供的监听数据的API,当数据发生变化的时候,在回调函数中修改dom
    • 核心API:Object.defineProperty
  • 简单API:Object.defineProperty的使用

    • 作用: 用来定义对象属性

    • 特点:

      • 默认情况下定义的数据的属性不能修改
      • 描述属性和存取属性不能同时使用,使用会报错
    • 响应式原理:

      • 获取属性值会触发getter方法
      • 设置属性值会触发setter方法
      • 在setter方法中调用修改dom的方法
  • 如何实现的监听数组

  • 嵌套对象,如何实现深度监听

  • Object.defineProperty的几个缺点

2. 虚拟dom和diff算法

2.1 虚拟dom和diff算法

  • 虚拟dom(Virtual dom)是实现vue和React的核心
  • diff算法是vdom中最核心最关键的部分

2.2 虚拟dom解决的问题

  • 真实的dom操作相当耗性能:操作一次dom触发一次渲染,渲染耗时
  • 以前面试题将常考使用jquery或者原生js操作dom时,如何做性能优化:将多条dom操作合并成一条
  • vue和react都是数据驱动视图,如何控制dom操作? 使用vdom

2.2 虚拟dom如何解决的问题

  • js计算要比dom渲染速度快
  • vdom使用js模拟dom结构,计算出最小的变更,操作dom

2.3 虚拟dom如何模拟dom结构 (手写虚拟dom树

 <div class='vdom' id='first'><p>内容</p><ul><li>1</li><li>2</li></ul></div>
{tag: 'div',data: {className: 'vdom',id: 'first'},children: [{tag: 'p',children: '内容'},{tag: 'ul',children: [{tag: 'li',children: '1'},{tag: 'li',children: '2'}]}]         }

2.4 从源码角度分析虚拟dom视图更新过程

  1. 调用init方法,返回一个patch函数,init方法的参数是一个数组,数组中是各种模块,根据传入的模块定制化patch函数
  2. 使用h函数返回生成vnode的方法
  3. 调用render生成真实的虚拟dom
  4. 调用 init 方法会返回一个 patch 函数,这个函数接受两个参数,第一个是旧的 vnode 节点或是 dom 节点,第二个参数是新的 vnode 节点,调用 patch 函数会对 dom 进行更新。

2.5 vue中虚拟dom比较流程(diff算法)

2.5.1 vue中虚拟dom比较流程

  1. 第一步:patch函数中对新老节点进行比较

    • 如果新节点不存在就销毁老节点
    • 如果老节点不存在,直接创建新的节点
    • 当两个节点是相同节点的时候,进入 patctVnode 的过程,比较两个节点的内部
    // 用于 比较 新老节点的不同,然后更新的 函数
    function patch (oldVnode, vnode, hydrating, removeOnly) {// 1. 当新节点不存在的时候,销毁旧节点if (isUndef(vnode)) {if (isDef(oldVnode)) invokeDestroyHook(oldVnode)return}let isInitialPatch = false// 用来存储 insert 钩子函数,在 插入节点之后调用const insertedVnodeQueue = []// 2. 如果旧节点 是未定义的,直接创建新节点if (isUndef(oldVnode)) {isInitialPatch = truecreateElm(vnode, insertedVnodeQueue)} else {const isRealElement = isDef(oldVnode.nodeType)// 当老节点不是真实的 dom 节点, 当两个节点是相同节点的时候,进入 patctVnode 的过程// 而 patchVnode 也是 传说中 diff updateChildren 的调用者if (!isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root nodepatchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)} else {// 当老节点是真实存在的 dom 节点的时候if (isRealElement) {// 当 老节点是 真实节点,而是在 ssr 环境的时候,修改 SSR_ATTR 属性if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {oldVnode.removeAttribute(SSR_ATTR)hydrating = true}....// 设置 oldVnode 为一个包含 oldVnode 的无属性节点oldVnode = emptyNodeAt(oldVnode)}// replacing existing elementconst oldElm = oldVnode.elm// 获取父亲节点,这样方便 删除或者增加节点const parentElm = nodeOps.parentNode(oldElm)// 在 dom 中插入新节点createElm(vnode,insertedVnodeQueue,oldElm._leaveCb ? null : parentElm,nodeOps.nextSibling(oldElm))// 递归 更新父占位符元素// 就是执行一遍 父节点的 destory 和 create 、insert 的 钩子函数// 类似于 style 组件,事件组件,这些 钩子函数if (isDef(vnode.parent)) {let ancestor = vnode.parentconst patchable = isPatchable(vnode)while (ancestor) {for (let i = 0; i < cbs.destroy.length; ++i) {cbs.destroy[i](ancestor)}ancestor.elm = vnode.elmif (patchable) {for (let i = 0; i < cbs.create.length; ++i) {cbs.create[i](emptyNode, ancestor)}const insert = ancestor.data.hook.insertif (insert.merged) {for (let i = 1; i < insert.fns.length; i++) {insert.fns[i]()}}} else {registerRef(ancestor)}ancestor = ancestor.parent}}// 销毁老节点if (isDef(parentElm)) {removeVnodes([oldVnode], 0, 0)} else if (isDef(oldVnode.tag)) {// 触发老节点 的 destory 钩子invokeDestroyHook(oldVnode)}}}// 执行 虚拟 dom 的 insert 钩子函数invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)// 返回最新 vnode 的 elm ,也就是真实的 dom节点return vnode.elm
    }
    

    如何判断两个节点是否相同?key、tagName、标签属性、input标签还要比较type类型

    function sameVnode (a, b) {return (a.key === b.key &&  // key值a.tag === b.tag &&  // 标签名a.isComment === b.isComment &&  // 是否为注释节点// 是否都定义了data,data包含一些具体信息,例如onclick , styleisDef(a.data) === isDef(b.data) &&  sameInputType(a, b) // 当标签是<input>的时候,type必须相同)
    }
    
    function sameInputType (a, b) {if (a.tag !== 'input') { return true }var i;var typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type;var typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type;return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
    }
    
  2. 第二步:patchVnode函数比较两个虚拟节点内部

    • 如果两个虚拟节点完全相同,返回
    • 当前vnode 的children 不是textNode,再分成三种情况
      • 有新children,没有旧children,创建新的
      • 没有新children,有旧children,删除旧的
      • 新children、旧children都有,执行updateChildren比较children的差异,这里就是diff算法的核心
    • 当前vnode 的children 是textNode,直接更新text
    function patchVnode (oldVnode,  // 旧节点vnode,     // 新节点insertedVnodeQueue,  // 插入节点的队列ownerArray,      // 节点 数组index,           // 当前 节点的removeOnly       // 只有在 patch 函数中被传入,当老节点不是真实的 dom 节点,当新老节点是相同节点的时候) {// 如果新节点和旧节点 相等(使用了 同一个地址,直接返回不进行修改)// 这里就是 当 props 没有改变的时候,子组件不会做渲染,而是直接复用if (oldVnode === vnode) {return}if (isDef(vnode.elm) && isDef(ownerArray)) {// clone reused vnodevnode = ownerArray[index] = cloneVNode(vnode)}const elm = vnode.elm = oldVnode.elm// 当 当前节点 是 注释节点(被 v-if )了,或者是一个 异步函数节点,那不执行if (isTrue(oldVnode.isAsyncPlaceholder)) {if (isDef(vnode.asyncFactory.resolved)) {hydrate(oldVnode.elm, vnode, insertedVnodeQueue)} else {vnode.isAsyncPlaceholder = true}return}// 当前节点 是一个静态节点的时候,或者 标记了 once 的时候,那不执行if (isTrue(vnode.isStatic) &&isTrue(oldVnode.isStatic) &&vnode.key === oldVnode.key &&(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {vnode.componentInstance = oldVnode.componentInstancereturn}let iconst data = vnode.data// 调用 prepatch 的钩子函数if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {i(oldVnode, vnode)}const oldCh = oldVnode.childrenconst ch = vnode.children// 调用 update 钩子函数if (isDef(data) && isPatchable(vnode)) {// 这里 的 update 钩子函数式 vnode 本身的钩子函数for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)// 这里的 update 钩子函数  是 用户传过来的 钩子函数if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)}// 新节点 没有 text 属性if (isUndef(vnode.text)) {// 如果都有子节点,对比更新子节点if (isDef(oldCh) && isDef(ch)) {if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)} else if (isDef(ch)) { // 新节点存在,但是老节点不存在// 如果老节点是  text, 清空if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')// 增加子节点addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)} else if (isDef(oldCh)) { // 老节点存在,但是新节点不存在,执行删除removeVnodes(oldCh, 0, oldCh.length - 1)} else if (isDef(oldVnode.text)) { // 如果老节点是  text, 清空nodeOps.setTextContent(elm, '')}// 新旧节点 text 属性不一样} else if (oldVnode.text !== vnode.text) {// 将 text 设置为 新节点的 textnodeOps.setTextContent(elm, vnode.text)}if (isDef(data)) {// 执行 postpatch 钩子函数if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)}
    }
    
  3. 第三步:updateChildren函数子节点进行比较diff算法

    • 第一步 头头比较。若相似,旧头新头指针后移(即 oldStartIdx++ && newStartIdx++),真实dom不变,进入下一次循环;不相似,进入第二步。
    • 第二步 尾尾比较。若相似,旧尾新尾指针前移(即 oldEndIdx-- && newEndIdx--),真实dom不变,进入下一次循环;不相似,进入第三步。
    • 第三步 头尾比较。若相似,旧头指针后移,新尾指针前移(即 oldStartIdx++ && newEndIdx--),未确认dom序列中的头移到尾,进入下一次循环;不相似,进入第四步。
    • 第四步 尾头比较。若相似,旧尾指针前移,新头指针后移(即 oldEndIdx-- && newStartIdx++),未确认dom序列中的尾移到头,进入下一次循环;不相似,进入第五步。
    • 第五步 若节点有key且在旧子节点数组中找到sameVnode(tag和key都一致),则将其dom移动到当前真实dom序列的头部,新头指针后移(即 newStartIdx++);否则,vnode对应的dom(vnode[newStartIdx].elm)插入当前真实dom序列的头部,新头指针后移(即 newStartIdx++)。
  • 但结束循环后,有两种情况需要考虑:

    • 新的字节点数组(newCh)被遍历完(newStartIdx > newEndIdx)。那就需要把多余的旧dom(oldStartIdx -> oldEndIdx)都删除,上述例子中就是c,d
    • 新的字节点数组(oldCh)被遍历完(oldStartIdx > oldEndIdx)。那就需要把多余的新dom(newStartIdx -> newEndIdx)都添加。
    function updateChildren (parentElm, oldCh, newCh) {let oldStartIdx = 0let newStartIdx = 0let oldEndIdx = oldCh.length - 1let oldStartVnode = oldCh[0]let oldEndVnode = oldCh[oldEndIdx]let newEndIdx = newCh.length - 1let newStartVnode = newCh[0]let newEndVnode = newCh[newEndIdx]let oldKeyToIdx, idxInOld, elmToMove, beforewhile (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {if (isUndef(oldStartVnode)) {oldStartVnode = oldCh[++oldStartIdx] // 未定义表示被移动过} else if (isUndef(oldEndVnode)) {oldEndVnode = oldCh[--oldEndIdx]} else if (sameVnode(oldStartVnode, newStartVnode)) { // 头头相似patchVnode(oldStartVnode, newStartVnode)oldStartVnode = oldCh[++oldStartIdx]newStartVnode = newCh[++newStartIdx]} else if (sameVnode(oldEndVnode, newEndVnode)) { // 尾尾相似patchVnode(oldEndVnode, newEndVnode)oldEndVnode = oldCh[--oldEndIdx]newEndVnode = newCh[--newEndIdx]} else if (sameVnode(oldStartVnode, newEndVnode)) { // 头尾相似patchVnode(oldStartVnode, newEndVnode)api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm))oldStartVnode = oldCh[++oldStartIdx]newEndVnode = newCh[--newEndIdx]} else if (sameVnode(oldEndVnode, newStartVnode)) { // 尾头相似patchVnode(oldEndVnode, newStartVnode)api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)oldEndVnode = oldCh[--oldEndIdx]newStartVnode = newCh[++newStartIdx]} else {// 根据旧子节点的key,生成map映射if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)// 在旧子节点数组中,找到和newStartVnode相似节点的下标idxInOld = oldKeyToIdx[newStartVnode.key]if (isUndef(idxInOld)) { // 没有key,创建并插入domapi.insertBefore(parentElm, createElm(newStartVnode), oldStartVnode.elm)newStartVnode = newCh[++newStartIdx]} else {// 有key,找到对应dom ,移动该dom并在oldCh中置为undefinedelmToMove = oldCh[idxInOld]patchVnode(elmToMove, newStartVnode)oldCh[idxInOld] = undefinedapi.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm)newStartVnode = newCh[++newStartIdx]}}}// 循环结束时,删除/添加多余domif (oldStartIdx > oldEndIdx) {before = isUndef(newCh[newEndIdx+1]) ? null : newCh[newEndIdx + 1].elmaddVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)} else if (newStartIdx > newEndIdx) {removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }}
    

2.5.2 树diff算法的时间复杂度是O(n^3)

  • 第一,遍历原来的树
  • 第二,遍历新生成的树
  • 第三,排序

所以时间复杂度就是O(n^3),复杂度太高,算法不推荐使用

2.5.3 vue优化时间复杂度为O(n)

  • 只比较同一层级

  • tag不同,直接删掉,不再继续深度比较

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bgrsgwvO-1666613503247)(img\image-20210517172113925.png)]

  • tag和key都相同不再深度比较,认为是相同节点

2.5.4 key可以优化v-for的性能,到底是怎么回事呢?

因为v-for大部分情况下生成的都是相同tag的标签,如果没有key标识,那么相当于每次头头比较都能成功。你想想如果你往v-for绑定的数组头部push数据,那么整个dom将全部刷新一遍(如果数组每项内容都不一样)

3. Vue 渲染过程

3.1 模板编译

  • 编译成render函数,render函数返回虚拟dom
  • 基于Vnode再执行patch和diff
  • 使用webpack vue-loader 会在开发环境下编译模板(生产环境中代码直接就是render函数),自己写的demo,通过script引入的vue.js会在浏览器执行的时候进行编译

3.2 一个组件渲染到页面,修改data触发视图更新(数据驱动视图)

  • 响应式:通过Object.defineProperty方法的setter和getter方法实现响应监听
  • 模板编译: 将模板编译成render函数,执行render函数生成虚拟dom
  • patch(diff算法在其中):通过patch方法比较虚拟dom,更新视图

3.2.1 初次渲染

  • 解析模板为render函数

  • 触发响应式,监听data的getter和setter,初次渲染并不会触发setter,但是如果模板中使用data中的数据

    就会触发getter

    <div id="app"><p>{{Value1}}</p>
    </div>
    <script>var app = new Vue({el: '#app',data: {Value1 : '你好' ,// 触发gettervalue2 : '哈哈哈' // 不触发getter }})
    </script>
    
  • 执行render函数,生成虚拟dom,patch(elem,vnode)

3.2.2 更新渲染

  • 修改data,触发setter
  • 执行render函数,生成newVnode
  • patch(vnode,newVnode)

3.2.3 异步渲染

  • 汇总dom修改,一次性更新视图
  • 减少dom操作次数,提高性能
  • 所以想要获取dom元素,需要在$nextTick中完成

4. v-model实现原理

4.1 v-model作用

<body><div id="app"><input type="text" v-model="inputValue"><p>{{inputValue}}</p></div><script>var app = new Vue({el: '#app',data: {inputValue : ''}})</script>
</body>

4.2 原理

  1. 在input中输入完内容的时候,调用了change事件,改变了inputValue的值:

    v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。

    v-model 在不同的 HTML 标签上使用会监控不同的属性和抛出不同的事件:

    • text 和 textarea 元素使用 value 属性和 input 事件;
    • checkbox 和 radio 使用 checked 属性和 change 事件;
    • select 字段将 value 作为 prop 并将 change 作为事件。
  2. 在通过响应式原理,通过监听inputValue的改变出发视图的变化

    <input v-model="val">
    <!-- 基本等价于,因为内部还有一些其他的处理 -->
    <input :value="val" @input="val = $event.target.value">
    

5. 路由原理

5.1 hash路由

5.1.1hash 路由的特点

  • hash变化触发网页的跳转,即浏览器的前进和后退

  • hash变化不会刷新页面,spa必须的特点

  • hash永远不会提交到server端

5.2 history路由

  • 用url规范的路由,但跳转时不刷新页面
  • history.pushState 使用它做页面跳转不会触发页面刷新
  • window.onpopstate 监听浏览器的前进和后退

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Spark NLP】第 19 章:生产化 NLP 应用程序

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

docker下快速部署openldap与PHPLdapAdmin

在一个组织中&#xff0c;为了简化各种内部系统的账号和密码的管理&#xff0c;往往就需要ldap来进行管理了。 对于ldap的实现方式也非常多&#xff0c;但在免费的开源系统中&#xff0c;openldap是ldap的首选系统。 同时&#xff0c;在这一切讲究快速的时代&#xff0c;采用d…

大数据ClickHouse进阶(二十二):ClickHouse优化

文章目录 ClickHouse优化 一、表优化 1、日期字段避免使用String存储 2、Nullable值处理 <

计算机毕业设计(附源码)python音蕾心动

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

云IDE的简单使用、体验与学习

云IDE的简单使用、体验与学习一、简单尝试二、官网展示的特点三、视频用例3.1、用Cloud IDE快速启动开源项目3.2、用Cloud IDE 在线提交PR云IDE产品介绍 云IDE使用教程 免费使用地址&#xff1a;点击【云IDE】&#xff0c;即可开始创建工作空间。 一、简单尝试 快速创建工作空…

学习用Python实现PPT的自动化

前言 在日常工作中&#xff0c;我们总是需要创建或修改PPT。但你也可以用Python来创建或修改PPT文件。本文将告诉你如何使用Python-pptx模块自动或用PPT模板生成ppt&#xff0c;以及如何通过实例修改现有的PPT。 &#xff08;文末送福利&#xff09; 1.Python模块python-ppt…

hbuilderx ios自定义基座真机测试

任务描述&#xff1a; 用uniapp框架写了一个app应用&#xff0c;需要在ios苹果手机上真机运行测试。 hbuilderx不再支持标准基座真机运行了&#xff0c;需要自定义基座运行 制定自定义基座需要准备的材料&#xff1a; ios的appid,profile文件&#xff0c;私钥证书&#xff0…

动视是否磨灭了暴雪的灵魂?

对于成千上万的人&#xff0c;也许是数百万人来说&#xff0c;暴雪是——或者曾经是——一家特殊的公司。 暴雪——游戏开发的典范 对于奇幻世界的关注&#xff0c;暴雪是无与伦比的。如果游戏没有准备好&#xff0c;它就不会发布。1998 年&#xff0c;尽管《魔兽争霸&#xf…

算法复杂度分析

复杂度分析 参考&#xff1a;《算法导论》、复杂度 - OI Wiki (oi-wiki.org)、一文弄懂算法的时间和空间复杂度分析 - 知乎 (zhihu.com)、算法讲解之复杂度分析 - 知乎 (zhihu.com)、算法的时间复杂度和空间复杂度-总结_zolalad的博客-CSDN博客_时间复杂度 算法复杂度分析的阶段…

梦开始的地方 —— C语言数据在内存中的存储(整形+浮点型)

文章目录整形在内存中的存储1. 数值类型的基本分类2. 整形在内存中的存储1. 原码、反码、补码2. 内存中为什么要存放补码&#xff1f;3. 大小端存储4. 无符号有符号数练习5. 有符号数无符号数小结浮点型在内存中的存储IEEE 754整形在内存中的存储 1. 数值类型的基本分类 整形…

AJAX基础+Axios快速入门+JSON使用+综合案例

目录1、 AJAX1.1 概述1.1.1 作用1.1.2 同步和异步1.2 快速入门1.2.1 服务端实现1.2.2 客户端实现1.3 案例1.3.1 需求1.3.2 分析1.3.2 后端实现1.3.3 前端实现2、 Axios异步框架2.1 基本使用2.2 快速入门2.2.1 后端实现2.2.2 前端实现2.3 请求方法别名3、 JSON3.1 概述3.2 JSON基…

GAS技能系统

HUT -》 在\Intermediate\Build\Win64\UE4Editor\Inc\的目录下 找到generated 头文件和cpp文件 里面有HUT根据UCLASS 和 Generate Body 生成的 定义 以及声明宏(UFUNCTION 里的CustomThunk元可以让用户自己手动添加宏定义和宏声明) 将wildcard改为通配符然后手动将自定义的…

Terraform 华为云实践 项目初始化

这个架构就是DNS加上负载均衡加ecs&#xff0c;最后vpc的架构。网络这块是DNS和VPC&#xff0c;对象存储是用来做terraform的后端来配置。 项目的初始化 Terraform Registry 华为云的terraform链接如上所示。 先将项目的目录结构建好&#xff0c;modules是我们的模块&#xf…

来一场关于元宇宙的灵魂辩论|BOOK DAO内容共建招募

「 备选问题 」1. 你认为元宇宙最重要的特点是什么&#xff1f;用一句话描述你理解的 “元宇宙”2. 元宇宙是游戏2.0吗&#xff1f;它与游戏有什么不同&#xff1f;3. 元宇宙是否需要区块链&#xff1f;是否需要NFT&#xff1f;各扮演什么角色&#xff1f;4. 元宇宙是否需要经济…

大数据项目之电商数仓、电商业务简介、电商业务流程、电商常识、业务数据介绍、电商业务表、后台管理系统

文章目录5. 电商业务简介5.1 电商业务流程5.2 电商常识5.2.1 SKU和SPU5.2.2 平台属性和销售属性5.2.2.1 平台属性5.2.2.2 销售属性6. 业务数据介绍6.2 电商业务表6.2.1 收藏商品6.2.2 加购物车6.2.3 领用优惠券6.2.4 下单6.2.5 支付6.2.6 退单6.2.7 退款6.2.8 评价6.3 后台管理…

部署简易POD image自己定义镜像

k8s部署pod apiversion: 版本 kind: 类型 metadata: 字面意识&#xff0c;元素信息&#xff0c;POD信息 name: POD名字 labels: 字母意识&#xff0c;标签 通过拓扑 label 进行副本调度 label的使用无非就是增删改查 还有个重要的标签namespace&#xff08;命名空间&…

针对垃圾渗滤液中膜产水脱氮工艺的设计,除氨氮树脂

垃圾渗滤液是指来源于垃圾填埋场中垃圾本身含有的水分、进入填埋场的雨雪水及其他水分&#xff0c;扣除垃圾、覆土层的饱和持水量&#xff0c;并经历垃圾层和覆土层而形成的一种高浓度的有机废水&#xff0c;有堆积的准备用于焚烧的垃圾渗漏出的水分。为什么要处理垃圾渗滤液&a…

黑马点评-达人探店

摘要&#xff1a;达人探店业务&#xff1a; 本质是发表blog和点赞等功能。利用Redis的Set实现点赞与取消点赞&#xff0c;然后利用SortedSet对点赞功能进行改进实现点赞排行的功能。 在学习的过程中&#xff0c;我们不应该急于写代码&#xff0c;首先分析业务逻辑&#xff0c;…

SpringBoot项目启动执行任务的几种方式

经过整理后得到以下几种常用方式&#xff0c;供大家参考。 1. 使用过滤器 init() &#xff1a;该方法在tomcat容器启动初始化过滤器时被调用&#xff0c;它在 Filter 的整个生命周期只会被调用一次。可以在这个方法中补充想要执行的内容。 Component public class MyFilter …

vs2017 外网远程调试

外网远程调试:由于外网的目标电脑IP无法直接访问&#xff0c;则需要第三方内网穿透工具辅助&#xff0c;本文使用NATAPP进行 注册一个账号&#xff1a;NATAPP -注册完成&#xff0c;登录后&#xff0c;在购买隧道中选择Free免费购买一个 购买成功后&#xff0c;在我的隧道中可…