v-show和v-if
1.相同点:
v-show 和 v-if 都可以控制元素的显示与隐藏
2.不同点:
实现本质不同
v-show 的本质就是通过设置CSS 的display: none, v-if 是动态的向DOM树内添加或者删除DOM元素
3.编译的区别
v-show实际就是在控制CSS
v-if切换有一个局部编译\卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件
- 编译的条件
v-show都会编译,初始值为false,只是将display设为none,但它也编译了
v-if初始值为false,就不会编译了
- 性能比较
v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点
key的作用
概念:页面上的标签都对应具体的虚拟 dom 对象(虚拟 dom 就是 js 对象), 循环中 ,如果没有唯一 key , 页面上删除一条标签, 由于并不知道删除的是那一条! 所以要把全部虚拟 dom 重新渲染, 如果知道 key 为对应标签被删除掉, 只需要把渲染的 dom 为对应标签去掉即可!
实际工作场景:
当我们在使用v-for时,需要给单元加上key
<Comp :key="+new Date()" />
用+new Date()生成的时间戳作为key,手动强制触发重新渲染
<ul>
<li v-for="item in items" :key="item.id">...</li>
</ul>
那么这背后的逻辑是什么,key的作用又是什么?
作用:更准确、更快速、提高效率
背后的逻辑
当我们在使用v-for时,需要给单元加上key
- 如果不用 key,Vue 会采用就地复地原则:最小化 element 的移动,并且会尝试尽最大程度在同适当的地方对相同类型的 element,做 patch 或者 reuse。
- 如果使用了 key,Vue 会根据 keys 的顺序记录 element,曾经拥有了 key 的 element 如果不再出现的话,会被直接 remove 或者 destoryed
用+new Date()生成的时间戳作为key,手动强制触发重新渲染
- 当拥有新值的 rerender 作为 key 时,拥有了新 key 的 Comp 出现了,那么旧 key Comp 会被移除,新 key Comp 触发渲染
总结
key 是给每一个 vnode 的唯一 id,也是 diff 的一种优化策略,可以根据 key,更准确, 更快的找到对应的 vnode 节点
虚拟dom
Virtual DOM 其实就是一棵以 JavaScript 对象(VNode 节点)作为基础的树,用对象属性来描述节点,相当于在 js 和真实 dom 中间加来一个缓存,利用 dom diff 算法避免没有必要的 dom 操作,从而提高性能。
在 vue 中一般都是通过修改元素的 state,订阅者根据 state 的变化进行编译渲染,底层的实现可以简单理解为三个步骤:
1、用 JavaScript 对象结构表述 dom 树的结构,然后用这个树构建一个真正的 dom 树,插到浏览器的页面中。
2、当状态改变了,也就是我们的 state 做出修改,vue 便会重新构造一棵树的对象树,然后用这个新构建出来的树和旧树进行对比(只进行同层对比),记录两棵树之间的差异。
3、把记录的差异再重新应用到所构建的真正的 dom 树,视图就更新了。
它的表达方式就是把每一个标签都转为一个对象,这个对象可以有三个属性:tag、props、children
tag:必选。就是标签。也可以是组件,或者函数props:非必选。就是这个标签上的属性和方法children:非必选。就是这个标签的内容或者子节点,如果是文本节点就是字符串,如果有子节点就是数组。换句话说 如果判断 children 是字符串的话,就表示一定是文本节点,这个节点肯定没有子元素
diff算法
Diff 算法是一种对比算法。对比两者是 旧虚拟 DOM 和新虚拟 DOM,对比出是哪个 虚拟节点更改了,找出这个 虚拟节点并只更新这个虚拟节点所对应的 真实节点而不用更新其他数据没发生改变的节点,实现 精准地更新真实 DOM,进而 提高效率
他的对比过程如下:
在新老虚拟 DOM 对比时:
- 首先,对比节点本身,判断是否为同一节点,如果不为相同节点,则删除该节点重新创建节点进行替换
- 如果为相同节点,进行 patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的 children 没有子节点,将旧的子节点移除)
- 比较如果都有子节点,则进行
updateChildren,判断如何对这些新老节点的子节点进行操作(diff 核心)。 - 匹配时,找到相同的子节点,递归比较子节点
在 diff 中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从 O(n3)降低值 O(n),也就是说,只有当新旧 children 都为多个子节点时才需要用核心的 Diff 算法进行同层级比较。
虚拟 DOM 算法和真实 DOM 算法比较:
使用
虚拟 DOM算法的损耗计算 : 总损耗 = 虚拟 DOM 增删改 +(与 Diff 算法效率有关)真实 DOM 差异增删改+(较少的节点)排版与重绘
直接操作
真实 DOM的损耗计算: 总损耗 = 真实 DOM 完全增删改 +(可能较多的节点)排版与重绘
hash路由和history路由
- hash模式: 在浏览器中符号"
#",#以及#后面的字符称之为hash,用window.location.hash读取。特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。 - history模式 :
history采用HTML5的新特性: 且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更
computed和watche的区别
运行时机不同
computed是在 HTML,DOM 加载后马上执行的,如赋值;(属性将被混入到 Vue 实例) watch 它用于观察 Vue 实例上的数据变动,一般情况下是依赖项的值变化之后再执行,当然可以设置立刻执行
计算属性有缓存
代码内容不同
watcher可以写任意的逻辑代码;而计算属性必须是同步的计算,并返回
nextTick
页面的 DOM 还未渲染,这时候也没办法操作 DOM,如果想要操作 DOM,需要使用 nextTick 来解决这个问题
实现原理:nextTick 的核心是利用了如 Promise 、MutationObserver、setImmediate、setTimeout 的原生 JavaScript 方法来模拟对应的微/宏任务的实现,本质是为了利用 JavaScript 的这些异步回调任务队列来实现 Vue 框架中自己的异步回调队列。
使用场景:
1、在数据变化后执行的某个操作,而这个操作需要使用随数据变化而变化的 DOM 结构的时候,这个操作就需要方法在的回调函数中。 2、在 vue 生命周期中,如果在 created()钩子进行 DOM 操作,也一定要放在的回调函数中。
使用方法:
this.$nextTick(() => {
// 获取数据的操作...
})
mixin的优点和缺点
mixin,中文: 混入。 它提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。
举例:
组件 A 和组件 B 中的 methods 有相同的函数,如何做优化?
mixin 本质其实就是一个js对象,它可以包含我们组件中任意功能选项,如data、components、methods、created、computed等等
我们只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来
在Vue中我们可以局部混入跟全局混入
#局部混入
定义一个mixin对象,有组件options的data、methods属性
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
},
},
}
组件通过mixins属性调用mixin对象
Vue.component('componentA', {
mixins: [myMixin],
})
该组件在使用的时候,混合了mixin里面的方法,在自动执行create生命钩子,执行hello方法
#全局混入
通过Vue.mixin()进行全局的混入
Vue.mixin({
created: function () {
console.log('全局混入')
},
})
使用全局混入需要特别注意,因为它会影响到每一个组件实例(包括第三方组件)
PS:全局混入常用于插件的编写
当组件存在与mixin对象相同的选项的时候,进行递归合并的时候组件的选项会覆盖mixin的选项
但是如果相同选项为生命周期钩子的时候,会合并成一个数组,先执行mixin的钩子,再执行组件的钩子
Mixin 的缺陷
mixin 最大的问题是:来源不清!
解决方式:V3 的组合式 API
组件通讯方式?
prop 和 emit
父组件用自定义属性把数据传入子组件,子组件用prop接收, 子组件再用$emit调用父组件留在子组件上 的事件,父组件在事件中接收的参数就是回调函数的第一个参数
v-model 指令
本质上是 v-bind 和 v-on,,是一种语法糖,在一个组件上使用 v-model,默认会为组件绑定名为 value 的 prop 和名为 input 的事件。
.sync 修饰符
本质和 v-model 类似,也是一种语法糖,相比较之下,.sync 更加灵活,它可以给多个 prop 使用,而
v-model 在一个组件中只能有一个。
子组件调用父组件方法的名字前要加 update:
ref
ref 特性可以为子组件赋予一个 ID 引用,通过这个 ID 引用可以直接访问这个子组件的实例。当父组件中需要主动获取子组件中的数据或者方法时,可以使用 $ref 来获取。
使用 ref 时,有两点需要注意:
$refs 是作为渲染结果被创建的,所以在初始渲染的时候它还不存在,此时无法无法访问。
$refs 不是响应式的,只能拿到获取它的那一刻子组件实例的状态,所以要避免在模板和计算属性中使用它。
$parent 和 $children
$parent 属性可以用来从一个子组件访问父组件的实例,$children 属性 可以获取当前实例的直接子组件。
看起来使用 $parent 比使用prop传值更加简单灵活,可以随时获取父组件的数据或方法,又不像使用 prop 那样需要提前定义好。但使用 $parent 会导致父组件数据变更后,很难去定位这个变更是从哪里发起的,所以在绝大多数情况下,不推荐使用。
在有些场景下,两个组件之间可能是父子关系,也可能是更多层嵌套的祖孙关系,这时就可以使用 $parent。
$attrs 和 $listeners
当要传递的数据很多时,就需要在中间的每个组件都重复写很多遍,反过来从后代组件向祖先组件使用 events 传递也会有同样的问题。使用 $attrs 和 $listeners 就可以简化这样的写法。
$attrs 会包含父组件中没有被 prop 接收的所有属性(不包含class 和 style 属性),可以通过 v-bind="$attrs" 直接将这些属性传入内部组件。
$listeners 会包含所有父组件中的 v-on 事件监听器 (不包含 .native 修饰器的) ,可以通过 v-on="$listeners" 传入内部组件
provide 和 inject
provide 和 inject 需要在一起使用,它可以使一个祖先组件向其所有子孙后代注入一个依赖,可以指定想要提供给后代组件的数据/方法,不论组件层次有多深,都能够使用。
provide 和 inject 绑定不是响应的,它被设计是为组件库和高阶组件服务的,平常业务中的代码不建议使用。
dispatch 和 broadcast
vue 在2.0版本就已经移除了 $dispatch 和 $broadcast,因为这种基于组件树结构的事件流方式会在组件结构扩展的过程中会变得越来越难维护。但在某些不使用 vuex 的情况下,仍然有使用它们的场景。所以 element ui 和 iview 等开源组件库中对 broadcast 和 dispatch 方法进行了重写,并通过 mixin 的方式植入到每个组件中。
eventBus
对于比较小型的项目,没有必要引入 vuex 的情况下,可以使用 eventBus。相比我们上面说的所有通信方式,eventBus 可以实现任意两个组件间的通信。
它的实现思想也很好理解,在要相互通信的两个组件中,都引入同一个新的vue实例,然后在两个组件中通过分别调用这个实例的事件触发和监听来实现通信。
通过 $root 访问根实例
通过 $root,任何组件都可以获取当前组件树的根 Vue 实例,通过维护根实例上的 data,就可以实现组件间的数据共享。
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。对一个中大型单页应用来说是不二之选。
使用 Vuex 并不代表就要把所有的状态放入 Vuex 管理,这样做会让代码变的冗长,无法直观的看出要做什么。对于严格属于组件私有的状态还是应该在组件内部管理更好。 类似的还有 pinia 等
eventBus的原理?
Vue 的EventBus,是一种EventEmitter的实现,在Vue2.0中,使用新的构建方式(ES2015语法构建)实现的,类似于个单例的对象,用来传递组件间的消息。
EventBus 的原理很简单,就是一个Vue实例,他接受一个参数,也就是一个空对象,然后暴露出可以用来监听和发布事件的方法,比如on、emit、 once和off。 在组件之间通信时,可以使用EventBus 中的on方法来监听事件,并可以使用emit方法来发布事件,对于单次的事件绑定,可以使用once方法来实现,或者在不需要监听事件时可 以使用off方法来取消监听。
EventBus的优点是它可以在组件之间跨级通信,也可以在兄弟组件之间实现通信,这样就 可以很方便地实现复杂的组件之间的解耦。此外,EventBus 还有一个优势,就是代码修改和阅 读起来比较简洁直观。
vue生命周期
vue2 生命周期分为个四个阶段、八个钩子函数
- 初始化阶段:
beforeCreate与created - 挂载阶段:
beforeMount与mounted - 更新阶段:
beforeUpdate与updated - 销毁阶段:
beforeDestroy与destroyed
使用keep-alive组件时会有 activated 和 deactivated 两个钩子函数 ,分别在 keep-alive组件激活、停用时调用。该钩子在服务器端渲染时不被调用。
平时在开发中一般在 created 函数中发送 ajax 请求获取数据,在 mounted 中获取挂载完毕后的真实 DOM,destroy 中销毁定时器,延时器或绑定的一些事件
created和mounted的区别?
mounted和created都是vue对象生命周期的钩子 函数,执行时机不同。
-
created是在data配置项的数据挂载到vue对象本身后,会调用的钩子函数。此时,用this.的方式可以拿到data里的数据。但是数据还没有渲染到模板上。所以,访问dom时,内容还是原始的模板内容。
-
mounted是在组件的模板初次渲染完毕后会 调用的钩子函数。此时,data里的数据已经渲染到模 板上了。所以,访问dom时,已经和页面上看到的效 果一样了。
v-model和.sync
v-model本质是语法糖,当我们在input标签内写上v-model="test"后,它的效果和 :Value="test" @input="(res)=> test = res" 是一样的, .sync的使用: 在子组件上 :test.sync="data" ,就等于 :test="data" @update:test="(res) => data = res" 同时子组件可以用 $emit 调用名为 update:test的父组件方法,并传入参数
在 Vue2 中 v-model 只能在标签内使用一次, .sync可以多次使用,在 Vue3 中,v-model没有使用次数的限制, 但是没有 .sync
在Vue3中,自定义组件上使用v-model,相当于传递一个modelValue属性,同时触发update:modelValue事件
<ChildComponent v-model = "title">
相当于
<ChildComponent :modelValue = "title" @update:modelValue = "title = $event">
自定义指令
- 【作用是什么】:可以对普通 DOM 进行底层操作,例如聚焦输入框。
- 【怎么用】:可以通过 Vue.directive 全局注册一个指令,或者组件内部通过 directives 进行局部注册,它常用的钩子有 inserted,表示被绑定元素插入父节点时调用,还有 update,它表示指令所在组件的 VNode 更新时调用。
- 【场景】:我在实际项目中,用自定义指令处理过图片懒加载,原理就是当图片进入可视区的时候把图片的地址给图片的 src 属性就好啦。
- 【注意】:自定义指令相关的钩子在 Vue3 中发生了变化,主要体现在和组件的生命周期钩子保持了一致,更加容易记忆和理解了。
vuex的6个内容
- vuex 是为了解决整个网站状态数据共享问题的,虽然有父向子,子向父等数据传递,但在网站开发过程中一些无直接关联关系的组件也需要共享相同的数据时,就需要使用 vuex 了
vuex 中有六个主要的成员:
state是用来存储数据的mutations是用来修改 state 中的数据的方法actions是用来处理一些异步操作数据的行为方法getters有点类似计算属性,是对 state 中的数据做了一些处理modules是用来对复杂业务分模块的,每个模块也可以有 state,mutaions,actions,getterspluginsVue 3 的插件系统允许我们扩展 Vue 的功能和行为,并且可以在多个组件之间共享代码和逻辑。插件可以用于添加全局组件、指令、混入、过滤器等,并且可以在应用程序启动时自动安装。
git的常见操作
# 在当前目录新建一个Git代码库
$ git init
# 下载一个项目和它的整个代码历史
$ git clone [url]
添加当前目录的所有文件到暂存区
$ git add .
提交暂存区到仓库区
$ git commit -m [message]
# 列出所有本地分支
$ git branch
# 切换到指定分支,并更新工作区
$ git checkout [branch-name]
# 合并指定分支到当前分支
$ git merge [branch]
# 显示有变更的文件
$ git status
# 显示当前分支的版本历史
$ git log
# 显示所有远程仓库
$ git remote -v
# 取回远程仓库的变化,并与本地分支合并
$ git pull [remote] [branch]
# 上传本地指定分支到远程仓库
$ git push [remote] [branch]
# 重置暂存区与工作区,与上一次commit保持一致
$ git reset --hard
v3和v2的区别
- 性能更高了,主要得益于响应式的原理换成了
proxy,VNode diff的算法进行了优化。 - 体积更小了,删除了一些没必要或不常用到的 API,例如
filter、EventBus等;按需导入,能配合 Webpack 支持Tree Shaking。 - 对
TS支持更好啦,因为它本身源码就是用 TS 重写的。 Composition API(组合 API),相比较 Vue2 的options api,对于开发大型项目更利于代码的复用和维护。- 新特性,例如
Fragment、Teleport、Suspense等。
pinia和vuex的区别
vuex
- 有六个主要成员 分别是state、mutations、actions、getters、modules、plugins 而 pinia中只有state、getter、action
pinia
- 良好的Typescript支持,Vue3推荐使用TS来编写
- 无需再创建各个模块嵌套了,Vuex中如果数据过多,我们通常分模块来进行管理,稍显麻烦,而pinia中每个store都是独立的,互相不影响。
- 体积非常小,只有1KB左右。
- pinia支持插件来扩展自身功能。
- 支持服务端渲染。
ref和reative的区别
1. reactive
作用
接收对象类型数据的参数传入并返回一个响应式的对象 reactive必须要传入一个类型为对象的数据,不支持基本数据类型。
步骤
- 从vue包中导入reactive函数
- 在的初始值,使用变量接收返回值
2. ref
作用
接收简单类型或对象类型数据的参数传入并返回一个响应式的对象
- 从vue包中导入ref函数
- 在中执行ref函数并传入初始值,使用变量接收返回值
- 在模板中使用数据不需加
.value - 在代码中使用数据要加
.value
组合式api和选项式api
这里当然说的就是composition API,其两大显著的优化:
- 优化逻辑组织
- 优化逻辑复用
相同功能的代码编写在一块,而不像options API那样,各个功能的代码混成一块
逻辑复用
在vue2中,我们是通过mixin实现功能混合,如果多个mixin混合,会存在两个非常明显的问题:命名冲突和数据来源不清晰
而通过composition这种形式,可以将一些复用的代码抽离出来作为一个函数,只要的使用的地方直接进行调用即可