Vue2 基础面试题

v-show 和 v-if 区别

  • v-show 通过 CSS display 控制显示和隐藏
  • v-if 通过判断组件真实渲染和销毁,而不是显示和隐藏
  • 频繁切换显示状态用 v-show,否则用 v-if

v-if

  • v-ifv-for 一起使用时,v-for 具有比 v-if 更高的优先级,意味着:v-if 将分别重复运行于每个 v-for 循环中,会造成性能问题。所以,不推荐 v-ifv-for 同时使用
const compiler = require('vue-template-compiler')const res = compiler.compile(`<div v-if="true" v-for="i in 3">{{message}}</div>`)with (this) {return renderList(3, function(i) {return true ? createElement('div', [createTextVNode(toString(message))]) : createEmptyVNode()})
}


v-show

const compiler = require('vue-template-compiler')const res = compiler.compile(`<p v-show="flag === 'a'">A</p>`)with (this) {return createElement('p',{directives: [{ name: 'show', rawName: 'v-show', value: flag === 'a', expression: "flag === 'a'" },],},[createTextVNode('A')])
}// v-show 操作的是样式 https://github.com/vuejs/vue/blob/dev/src/platforms/web/runtime/directives/show.js
bind (el: any, { value }: VNodeDirective, vnode: VNodeWithData) {vnode = locateNode(vnode)const transition = vnode.data && vnode.data.transitionconst originalDisplay = el.__vOriginalDisplay =el.style.display === 'none' ? '' : el.style.displayif (value && transition) {vnode.data.show = trueenter(vnode, () => {el.style.display = originalDisplay})} else {el.style.display = value ? originalDisplay : 'none'}
}


为何在 v-for 中用 key

  • 必须用 key,且不能是 index 和 random
  • diff 算法中通过 tag 和 key 来判断,是否是 sameNode
  • 减少渲染次数,提升渲染性能

描述 Vue 组件生命周期(父子组件)

  • beforeCreate 在初始化事件生命周期之后,数据被观测(observer)之前调用
  • created 实例已经创建完成之后被调用
  • 可以进行一些数据、资源请求。在这个阶段无法与 DOM 进行交互,如果非想要,可以通过 $nextTick 访问
  • beforeMount 在 DOM 挂载之前被调用,相关的 render 函数首次被调用(如果有 template 会转换成 render 函数)
  • 在此时也可以对数据进行更改,不会触发 updated
  • mounted 创建 vm.$el 并替换 el,并在挂载之后调用该钩子
  • 可以访问到 DOM 节点,使用 $refs 属性对 DOM 进行操作,也可以像后台发送请求,拿到返回数据
  • beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和和打补丁之前
  • 可以在这个钩子中进一步地更改状态,这不会触发附加的重新渲染
  • updated 由于数据更改导致虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子
  • 避免在此期间更改状态,可能会导致更新无限循环
  • beforeDestroy 实例销毁之前调用
  • destroyed Vue 实例销毁后调用,调用后,Vue 实例所有东西都会解绑定,所有事件监听会被移除,所有子实例也会被销毁可以执行一些优化操作,清除定时器,解除绑定事件

注意:除了 beforeCreate 和 created 钩子之外,其他钩子均在服务器端渲染期间不调用

Vue.prototype._init = function (options?: Object) {// ...initLifecycle(vm)initEvents(vm) // 初始化事件相关的属性initRender(vm) // vm 添加了一些虚拟 dom、slot 等相关的属性和方法callHook(vm, 'beforeCreate')initInjections(vm)initState(vm) // props、methods、data、watch、computed等数据初始化initProvide(vm)callHook(vm, 'created')
}


mounted (渲染完成)执行顺序是先子后父

function patch(oldVnode, vnode, hydrating, removeOnly) {// 定义收集所有组件的insert hook方法的数组const insertedVnodeQueue = []if (isUndef(oldVnode)) {createElm(vnode, insertedVnodeQueue)}
}
function createElm(vnode, insertedVnodeQueue, ...) {// createChildren会递归创建子组件(递归createElm)createChildren(vnode, children, insertedVnodeQueue)if (isDef(data)) {// 执行所有的create钩子invokeCreateHooks(vnode, insertedVnodeQueue)}
}
function invokeCreateHooks (vnode, insertedVnodeQueue) {// 把vnode push到insertedVnodeQueueif (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}// 调用insert方法把DOM插入到父节点,因为是递归调用,子元素会优先调用insert
insert(parentElm, vnode.elm, refElm)
function invokeInsertHook(vnode, queue, initial) {// 依次调用insert方法queue[i].data.hook.insert(queue[i])
}
const componentVNodeHooks = {// 依次执行 mounted 方法insert(vnode: MountedComponentVNode) {callHook(componentInstance, 'mounted')},
}


$destroy (销毁完成)执行顺序是先子后父

Vue.prototype.$destroy = function() {callHook(vm, 'beforeDestroy')// 递归触发子组件销毁钩子函数vm.__patch__(vm._vnode, null)callHook(vm, 'destroyed')
}


Vue 组件如何通讯

  • 父 -> 子通过 props,子 -> 父通过 $on $emit
  • 在父组件中提供数据子组件进行消费 provide、inject
  • ref 获取实例的方式调用组件的属性或方法
  • 自定义事件 event.$on、event.$off、event.$emit
  • vuex 状态管理实现通信

描述组件渲染和更新过程

  • 生成 render 函数,其生成一个 vnode,它会 touch 触发 getter 进行收集依赖
  • 在模板中哪个被引用了就会将其用 Watcher 观察起来,发生了 setter 也会将其 Watcher 起来
  • 如果之前已经被 Watcher 观察起来,发生更新进行重新渲染

双向数据绑定 v-model 的实现原理

v-model本质上是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件

  • text 和 textarea 元素使用 value 属性和 input 事件
  • checkbox 和 radio 使用 checked 属性和 change 事件
  • select 字段将 value 作为 prop 并将 change 作为事件
const compiler = require('vue-template-compiler')const res = compiler.compile(`<input v-model="name" type="text" />`)with (this) {return createElement('input', {directives: [{ name: 'model', rawName: 'v-model', value: name, expression: 'name' }],attrs: { type: 'text' },domProps: { value: name },on: {input: function($event) {if ($event.target.composing) returnname = $event.target.value},},})
}


对 MVVM 的理解

  • Model:代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑。我们可以把 Model 称为数据层,因为它仅仅关注数据本身,不关心任何行为
  • View:用户操作界面。当 ViewModel Model 进行更新的时候,会通过数据绑定更新到 View
  • ViewModel:业务逻辑层,View 需要什么数据,ViewModel 要提供这个数据;View 有某些操作,ViewModel 就要响应这些操作

总结: MVVM模式简化了界面与业务的依赖,解决了数据频繁更新。MVVM 在使用当中,利用双向绑定技术,使得 Model 变化时,ViewModel 会自动更新,而 ViewModel 变化时,View 也会自动变化

computed 和 watch 的区别

computed

  • computed 具有缓存性,computed 的值在 getter 执行后是会缓存的,只有它依赖的属性值改变之后,下一次获取 computed 的值时才会重新调用对应的 getter 来计算
  • computed 适用于比较消耗性能的计算场景,可以提高性能

watch

  • 更多的是观察作用,类似于数据监听的回调函数,用于观察 props$emit 或本组件的值,当数据变化时来执行回调进行后续操作
  • 无缓存性,页面重新渲染时值不变化也会执行

computed watch 都支持对象的写法

vm.$watch('obj', {deep: true, // 深度遍历immediate: true, // 立即触发handler: function(val, oldVal) {}, // 执行的函数
})
var vm = new Vue({data: { a: 1 },computed: {aPlus: {// this.aPlus 时触发get: function() {return this.a + 1},// this.aPlus = 1 时触发set: function(v) {this.a = v - 1},},},
})

为何组件 data 必须是一个函数

  • 一个组件被复用多次的话,也就是创建多个实例。本质上,这些实例用的都是同一个构造函数,如果 data 是对象的话(引用数据类型),会影响到所有实例
  • 为了组件不同实例 data 不冲突,data 必须是一个函数

自定义 v-model

v-model 可以看成是 value + input 方法的语法糖

  • 自定义:自己写 model 属性,里面放上 prop event
<template><input type="text" :value="text" @input="$emit('change', $event.target.value)" />
</template><script>
export default {model: {prop: 'text',event: 'change',},props: {text: String,},
}
</script>


相同逻辑如何抽离

  • Vue.mixin ,给组件每个生命周期、函数都混入一些公共逻辑
  • mixin 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项时也会有选择性进行合并

何时使用异步组件

核心就是把组件变成一个函数,依赖 import() 语法,可以实现文件的分割加载

  • 加载大组件
  • 路由异步加载

何时使用 keep-alive

常用的两个属性:includeexclude,允许组件有条件的进行缓存

两个生命周期:activateddeactivated,用来得知当前组件是否处于活跃状态

  • 缓存组件实例,用于保留组件状态或避免重复渲染
  • 多个静态 Tab 页的切换时,来优化性能

何时需要使用 beforeDestory

  • 解绑自定义事件 event.$off
  • 清除定时器
  • 解绑自定义的 DOM 事件,如:windowscroll

action 和 mutation 有何区别

  • action 中可以处理异步,mutation 中不可以
  • mutation 做的是原子操作,action 可以整合多个 mutation

vue-router 常用的路由模式

hash 路由

  • hash 变化会触发网页跳转,即浏览器的前进、后退
  • hash 变化不会刷新页面,SPA 必需的特点
  • hash 永远不会提交到 server 端(前端自生自灭)

history 路由

  • url 规范的路由,但跳转时不刷新页面
  • 需要 server 端配合,可参考:后端配置例子
  • pushState 不会触发 hashchange 事件,popstate 事件只会在浏览器某些行为下触发,比如点击后退、前进按钮

vnode 描述一个 DOM 结构

  • Vue 中的真实 DOM
<div id="div1" class="container"><p>vdom</p><ul style="font-size: 20px;"><li>a</li></ul>
</div>

  • Vue 中的虚拟 DOM
{tag: 'div',props: {className: 'container',id: 'div1',},children: [{tag: 'p',children: 'dom',},{tag: 'ul',props: { style: 'font-size: 20px' },children: [{ tag: 'li', children: 'a' }],},],
}


数据响应式原理

核心 API:Object.defineProperty

  • 存在一些问题,Vue 3.0 启动 Proxy
  • Proxy 可以原生支持监听数组变化
  • 但是 Proxy 兼容性不好,且无法 polyfill

问题

  • 深度监听,需要递归到底,一次性计算量大
  • 无法监听新增属性/删除属性(Vue.setVue.delete
  • 不能监听数组变化(重新定义原型,重写 pushpop 等方法)

简单实现 Vue 中的 defineReactive

// 触发更新视图
function updateView() {console.log('视图更新')
}// 重新定义数组原型
const arrayProto = Array.prototype
// 创建新对象,原型指向 arrayProto ,再扩展新的方法不会影响原型
const arrayMethods = Object.create(arrayProto)
;['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {arrayMethods[method] = function() {arrayProto[method].call(this, ...arguments) // 原始操作updateView() // 触发视图更新}
})// 重新定义属性,监听起来
function defineReactive(target, key, value) {observer(value) // 进行监听// 不可枚举的不用监听const property = Object.getOwnPropertyDescriptor(target, key)if (property && property.configurable === false) returnObject.defineProperty(target, key, {get() {return value},set(newValue) {if (newValue !== value) {observer(newValue) // 值修改后进行监听// value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值value = newValueupdateView() // 触发更新视图}},})
}// 监测数据的变化
function observer(target) {// 不是对象或数组(vue 是判断是否是数组、纯粹对象、可扩展对象)if (typeof target !== 'object' || target === null) {return target}// 不要这样写,污染全局的 Array 原型/* Array.prototype.push = function () {updateView()} */if (Array.isArray(target)) {target.__proto__ = arrayMethods}// 重新定义各个属性(for in 也可以遍历数组)for (let key in target) {if (!Object.hasOwnProperty.call(target, key)) returndefineReactive(target, key, target[key])}
}const data = {name: 'zhangsan',age: 20,info: {address: '北京', // 需要深度监听},nums: [10, 20, 30],
}observer(data)data.name = 'lisi'
data.age = 21
data.info.address = '上海' // 深度监听
// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
// delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
data.nums.push(4) // 监听数组


diff 算法

diff 算法过程:

  • 同级元素进行比较,再比较子节点
  • 先判断一方有子节点,一方没有子节点情况(如果新的 children 没有子节点,将旧的子节点移除)
  • 之后比较都有子节点的情况(核心 diff),递归比较子节点

正常 diff 两个树的时间复杂度是 O(n^3),但实际情况我们很少会跨级移动 DOM。所以,只有当新旧 children 都为多个子节点时才需要核心的 diff 算法进行同层级比较

  • Vue2 核心 diff 算法采用了双端比较的算法,同时从新旧 children 的两端开始比较,借助 key 值找到可复用的节点,再进行相关操作。相比 React 的 diff 算法,同样情况可以减少移动节点次数,减少不必要的性能损耗
  • Vue3 核心 diff 算法采用了最长递增子序列


双端比较算法

  • 使用 旧列表 的头一个节点 oldStartNode 与 新列表 的头一个节点 newStartNode 对比
  • 使用 旧列表 的最后一个节点 oldEndNode 与 新列表 的最后一个节点 newEndNode 对比
  • 使用 旧列表 的头一个节点 oldStartNode 与 新列表 的最后一个节点 newEndNode 对比
  • 使用 旧列表 的最后一个节点 oldEndNode 与 新列表 的头一个节点 newStartNode 对比

树 diff 的时间复杂度 O(n^3)

  • 对于旧树上的点 E 来说,它要和新树上的所有点比较,复杂度为 O(n)
  • 点 E 在新树上没有找到,点 E 会被删除,然后遍历新树上的所有点找到对应点(X)去填空,复杂度增加到 O(n^2)
  • 这样的操作会在旧树的每个点进行,最终复杂度为 O(n^3),1000 个节点,要计算 1 亿次,算法不可用

优化时间复杂度到 O(n)

  • 只比较同一层级,不跨级比较
  • tag 不相同,则直接删掉重建,不再深度比较
  • tag key,两者都相同,则认为是相同节点,不再深度比较

简述 diff 算法过程

在 Vue 中,主要是 patch()patchVnode() updateChildren 这三个方法来实现 Diff 的

  • 当 Vue 中的响应式数据发生变化时,就会触发 updateCompoent()
  • updateComponent() 会调用 patch() 方法,在该方法中进行比较,调用 sameVnode 判断是否为相同节点(判断 key、tag 等静态属性),如果是相同节点的话执行 patchVnode 方法,开始比较节点差异,如果不是相同节点的话,则进行替换操作                                            patch() 接收新旧虚拟 DOM,即 oldVnodevnode
  1. 首先判断 vnode 是否存在,如果不存在,删除旧节点
  2. 如果 vnode 存在,再判断 oldVnode,如果不存在,只需新增整个 vnode 即可
  3. 如果 vnode oldVnode 都存在,判断两者是不是相同节点,如果是,调用 patchVnode() ,对两个节点进行详细比较
  4. 如果两者不是相同节点,只需将 vnode 转换为真实 DOM 替换 oldVnode
  • patchVnode 同样接收新旧虚拟 DOM,即 oldVnodevnode
  1. 首先判断两个虚拟 DOM 是不是全等,即没有任何变动,是的话直接结束函数,否者继续执行
  2. 其次更新节点的属性,接着判断 vnode.text 是否存在,存在的话只需更新节点文本即可,否则继续执行
  3. 判断 vnode oldVnode 是否有孩子节点
    1. 如果两者都有孩子节点,执行 updateChildren() ,进行比较更新
    2. 如果 vnode 有孩子,oldVnode 没有,则直接删除所有孩子节点,并将该文本属性设为空
    3. 如果 oldVnode 有孩子,vnode 没有,则直接删除所有孩子节点
    4. 如果两者都没有孩子节点,就判断 oldVnode.text 是否有内容,有的话情况内容即可
  • updateChildren 接收三个参数:parentElm 父级真实节点、oldCh 为 oldVnode 的孩子节点、newCh 为 Vnode 的孩子节点oldCh 和 newCh 都是一个数组。正常我们想到的方法就是对这两个数组一一比较,时间复杂度为 O(NM)。Vue 中是通过四个指针实现的
  1. 首先是 oldStartVnode newStartVnode 进行比较(两头比较),如果比较相同的话,就可以执行 patchVnode
  2. -如果 oldStartVnode newStartVnode 匹配不上的话,接下来就是 oldEndVnode newEndVnode 做比较了(两尾比较)
  3. 如果两头和两尾比较都不是相同节点的话,就开始交叉比较,首先是 oldStartVnode newEndVnode 做比较(头尾比较)
  4. 如果 oldStartVnode 和 newEndVnode 匹配不上的话,就 oldEndVnode newStartVnode 进行比较(尾头比较)如果这四种比较方法都匹配不到相同节点,才是用暴力解法,针对 newStartVnode 去遍历 oldCh 中剩余的节点,一一匹配

图片来源:图文并茂地来详细讲讲Vue Diff算法

 

Vue 为何是异步渲染,$nextTick 何用

  • 因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染。异步渲染(合并 data 修改),再更新视图,可以提高渲染性能
class Watcher {update() {if (this.computed) {// ...} else if (this.sync) {// ...} else {// 当数据发生变化时会将watcher放到一个队列中批量更新queueWatcher(this)}}
}const queue: Array<Watcher> = []
let has: { [key: number]: ?true } = {}
let waiting = false
let flushing = false
// 在派发更新时并不会每次修改都触发watcher的回调
export function queueWatcher(watcher: Watcher) {const id = watcher.id// has对象保证同一个watcher只添加一次if (has[id] == null) {has[id] = trueif (!flushing) {queue.push(watcher)} else {let i = queue.length - 1while (i > index && queue[i].id > watcher.id) {i--}queue.splice(i + 1, 0, watcher)}// 通过waiting保证nextTick的调用逻辑只有一次if (!waiting) {waiting = true// 调用nextTick方法 批量的进行更新nextTick(flushSchedulerQueue)}}
}function flushSchedulerQueue () {// 1.因为父组件的创建过程是先于子的,所以watcher的创建也是先父后子,执行顺序也是先父后子// 2.用户自定义watcher要优先于渲染watcher执行// 3.如果一个组件在父组件watcher执行期间被销毁,那么它对应的watcher执行都可以被跳过queue.sort((a, b) => a.id - b.id) // 由小到大
}

  • $nextTick 在 DOM 更新完之后,触发回调,用于获得更新后的 DOM
  • $nextTick 方法主要是使用了 宏任务 和 微任务,定义一个异步方法,多次调用 nextTick 会将方法存入队列中,通过这个异步方法清空当前队列

Vue 2.4 之前都是使用微任务,但是微任务的优先级过高,有些情况下可能会出现比事件冒泡更快的情况,但如果都是用宏任务,有可能会出现渲染的性能问题

新版,默认使用微任务,但在特殊情况下会使用宏任务,比如:v-on

  • 对于实现宏任务,会先判断是否能用 setImmediate,不能的话降级为 MessageChannel ,以上都不行的话就是用 setTimeout
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {macroTimerFunc = () => {setImmediate(flushCallbacks)}
} else if (typeof MessageChannel !== 'undefined' &&(isNative(MessageChannel) ||// PhantomJSMessageChannel.toString() === '[object MessageChannelConstructor]')
) {const channel = new MessageChannel()const port = channel.port2channel.port1.onmessage = flushCallbacksmacroTimerFunc = () => {port.postMessage(1)}
} else {macroTimerFunc = () => {setTimeout(flushCallbacks, 0)}
}


Vue 常见性能优化方式

  • 合理使用 v-showv-if
  • 合理使用 computed
  • v-for 时加 key(Vue 会进行复用),以及避免和 v-if 同时使用
  • 自定义事件、DOM 事件及时销毁
  • 合理使用异步组件、路由懒加载
  • 合理使用 keep-alive(SPA 页面)
  • data 层级不要太深,不要讲所有数据都放在 data 中(会增加 getter setter,收集对应 watcher
  • 使用 vue-loader 在开发环境做模板编译(预编译)
  • webpack 层面的优化
  • 前端通用的性能优化,如图片懒加载、防抖、节流
  • 使用 SSR

释义参数 vue-template-compiler

render 中的参数释义

_c = createElement
function installRenderHelpers(target) {target._o = markOncetarget._n = toNumbertarget._s = toStringtarget._l = renderListtarget._t = renderSlottarget._q = looseEqualtarget._i = looseIndexOftarget._m = renderStatictarget._f = resolveFiltertarget._k = checkKeyCodestarget._b = bindObjectPropstarget._v = createTextVNodetarget._e = createEmptyVNodetarget._u = resolveScopedSlotstarget._g = bindObjectListenerstarget._d = bindDynamicKeystarget._p = prependModifier
}

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

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

相关文章

PolarDN MISC做题笔记

cat flag 使用01打开flag.png,发现图片尾部有padding的数据。D0 CF 11 E0 A1 B1 1A E1为office2007以前版本的文件头。将其另存为flag.doc,打开发现提示需要密码。&#xff08;可以注意到&#xff1a;D0CF11E0非常类似DOCFILE&#xff09; 使用john的office2john.py 提取hash …

【操作系统】处理机调度算法

实验3 处理机管理 一、实验目的 在多道程序或多任务系统中&#xff0c;系统中同时处于就绪态的进程有若干个&#xff0c;即能运行的进程数远远大于处理机个数。为了使系统中的各个进程能有条不紊的运行&#xff0c;必须按照某种调度策略&#xff0c;选择一个进程占用处理机。…

SpringBoot指定外部环境配置

nohup java -Xms256m -Xmx512m -Dfile.encodingUTF-8 -jar /usr/local/xxxx.jar --spring.profiles.activeprod > system.log 2>&1 & --spring.profiles.activeprod修改的是多环境配置中内部application.properties里的spring.profiles.active值 -Dspring.config…

消息队列MQ 保证消息不丢失(消息可靠性)

文章目录 概述RabbitMQ 怎么避免消息丢失&#xff08;可靠传输&#xff09;RocketMQ 怎么确保消息不丢失Kafka 怎么保证消息不丢失activeMQ 怎么避免消息丢失MQ 宕机了消息是否会丢失线上服务宕机时&#xff0c;如何保证数据100%不丢失吗&#xff1f;消息队列消息持久化 概述 …

思伟老友记 | 携手并进17年 中泰公司的稳步发展和企业传承

17年携手并进 合作共赢 2023年是中泰&#xff08;福建&#xff09;混凝土发展有限公司携手思伟软件的第17年。在这关键的17年间&#xff0c;我们共同经历了一个行业的兴盛发展&#xff0c;也相互见证了彼此的荣耀成长。中泰从泉州惠安洛阳江边一个简单的搅拌站&#xff0c;到如…

h-table(表格列表组件的全封装)

文章目录 概要h-table的封装过程查询组件封装 h-highForm结果页右侧工具栏封装RightToolbar结果页列表组件h-table结果页vue页面使用js文件有需要的请私信博主&#xff0c;还请麻烦给个关注&#xff0c;博主不定期更新组件封装&#xff0c;或许能够有所帮助&#xff01;&#x…

如何做代币分析:以 SOL 币为例

作者&#xff1a;lesleyfootprint.network 编译&#xff1a;cicifootprint.network 数据源&#xff1a;Solana Token Dashboard &#xff08;仅包括以太坊数据&#xff09; 在加密货币和数字资产领域&#xff0c;代币分析起着至关重要的作用。代币分析指的是深入研究与代币…

springmvc基于springboot 的音乐播放系统 _7sdu8

这就意味着音乐播放系统的设计可以比其他系统更为出色的能力&#xff0c;可以更高效的完成最新的ymj排行榜、ymj音乐资讯等功能。 此系统设计主要采用的是JAVA语言来进行开发&#xff0c;JSP技术、采用SSM框架技术&#xff0c;框架分为三层&#xff0c;分别是控制层Controller&…

vue3 vuex

目录 Vuex 是什么 什么是“状态管理模式”&#xff1f; 什么情况下我应该使用 Vuex&#xff1f; 使用方法&#xff1a; 提交载荷&#xff08;Payload&#xff09; 对象风格的提交方式 使用常量替代 Mutation 事件类型 Mutation 必须是同步函数 在组件中提交 Mutation …

redis GEO 类型原理及命令详解

目录 前言 一、GeoHash 的编码方法 二、Redis 操作GEO类型 前言 我们有一个需求是用户搜索附近的店铺&#xff0c;就是所谓的位置信息服务&#xff08;Location-Based Service&#xff0c;LBS&#xff09;的应用。这样的相关服务我们每天都在接触&#xff0c;用滴滴打车&am…

使用ENV工具编译RT-Thread【详细过程讲解:从下载到编译、设置】

感兴趣的宝子&#xff0c;可以点个赞收藏&#xff0c;便于后期有需要的时候能快速找到~~ ENV编译编译RT-Thread工程的详细过程讲解 ENV简介ENV的下载设置ENV使用ENV编译RT-Thread工程◆ 打开ENV◆ 输入打包命令◆ 查看并打开工程文件◆ 使用menuconfig 对生成项目的RT-Thread配…

【Git企业实战开发】Git常用开发流操作总结

【Git企业实战开发】Git常用开发流操作总结 大家好 我是寸铁&#x1f44a; 总结了一篇Git常用开发流操作总结的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 现在刚做项目的伙伴&#xff0c;可能你之前学过git&#xff0c;但是一实战发现不熟悉 没关系&#xff0c;看寸铁这篇…

【Linux】普通用户sudo失败怎么办

普通用户&#xff0c;sudo失败报错怎么办 问题分析如何解决成功 问题分析 新建的普通用户sudo失败 sudo提权&#xff0c;是以root的身份执行命令。 当我们用sudo提升权限的时候&#xff0c;这里有个问题&#xff0c;Linux会提示我们输入当前普通用户的密码——这就有点不好。…

【Linux取经路】基础I/O之重定向的实现原理

文章目录 一、再来理解重定向1.1 输出重定向效果演示1.2 重定向的原理1.3 dup21.4 输入重定向效果演示1.5 输入重定向代码实现 二、再来理解标准输出和标准错误2.1 同时对标准输出和标准错误进行重定向2.2 将标准输出和标准错误重定向到同一个文件 三、再看一切皆文件四、结语 …

Elasticsearch从入门到精通-01认识Elasticsearch

Elasticsearch从入门到精通-01认识Elasticsearch &#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是程序员行走的鱼 &#x1f342;博主从本篇正式开始ES学习&#xff0c;希望小伙伴可以一起探讨 &#x1f4d6; 本篇主要介绍和大家一块简单认识下ES并了解ES中的主要角色…

SpringBoot集成Mqtt发送消息

1. MQTT简介 MQTT是一种物联网消息协议&#xff0c;为Message Queuing Telemetry Transport的缩写&#xff0c;即消息队列传输探测&#xff0c;协议基于发布订阅模式进行通信&#xff0c;有开销低、带宽小、轻量的特点&#xff0c;通常应用在物联网数据采集、移动应用、智能硬…

H5获取手机相机或相册图片两种方式-Android通过webview传递多张照片给H5

需求目的&#xff1a; 手机机通过webView展示H5网页&#xff0c;在特殊场景下&#xff0c;需要使用相机拍照或者从相册获取照片&#xff0c;上传后台。 完整流程效果&#xff1a; 如下图 一、H5界面样例代码 使用html文件格式&#xff0c;文件直接打开就可以展示布局&#…

【每日一题】2583. 二叉树中的第 K 大层和-2024.2.23

题目: 2583. 二叉树中的第 K 大层和 给你一棵二叉树的根节点 root 和一个正整数 k 。 树中的 层和 是指 同一层 上节点值的总和。 返回树中第 k 大的层和(不一定不同)。如果树少于 k 层,则返回 -1 。 注意,如果两个节点与根节点的距离相同,则认为它们在同一层。 示…

canvas水波纹效果,jquery鼠标水波纹插件

canvas水波纹效果&#xff0c;jquery鼠标水波纹插件 效果展示 jQuery水波纹效果&#xff0c;canvas水波纹插件 HTML代码片段 <div class"scroll04wrap"><h3>发展历程</h3><div class"scroll04"><p>不要回头&#xff0c;一…

前端工程Bem架构及其封装

文章目录 简介语法在vue3项目中引用sass创建bem.scss文件修改vite.config.tsvue文件中使用结果 这是我学习记录的笔记&#xff0c;如有不正&#xff0c;欢迎补充 简介 首先认识一下什么是bem架构&#xff1f;BEM的意思就是块&#xff08;block&#xff09;、元素&#xff08;e…