vue.$nextTick()
在下一次 dom 更新循环结束之后执行延迟回调,修改数据之后立即执行此方法,获取更新后的 dom
应用场景:
- 修改数据后立即更新 dom 结构,可以使用 Vue.$nextTick()
- 在 create 生命周期中需要操作 dom
vue 实例挂载过程中发生了什么?
挂载的过程实际上就是 app.mount() 这个过程主要做了两件事 初始化 and 建立更新机制
初始化:创建组件实例,初始化组件状态,创建各种响应式数据
建立更新机制:立即执行一次组件的更新函数,并首次执行一次组件的渲染函数,并执行 patch 将 VNode 转化为 dom,同时在执行渲染函数的同时会创建组件内部响应式数据与更新函数之间的依赖关系,数据发生改变立即触发组件的更新函数
Vue 的模板编译原理
vue 中有个独特的编译器模块,称为 compiler 它的主要作用是将用户编写的 template 编译为 js 中可执行的 render 函数
在 Vue 中,编译器先对 template 进行解析,这个过程称为 parse,生成一个 js 对象,这个对象称为 抽象语法树AST,然后对 AST 进行深加工,将 js 对象转化为可执行的 js 代码,也就是 render 函数,这个过程称为 transform
Vue 的响应式原理
- Vue2 中的数据响应式会根据数据类型做不同的处理,如果是对象,则通过 object.defineProperty() 拦截对象属性访问,当数据被访问或改变时,感知并作出反应;如果是数组,则采用覆盖数组原型的方法,扩展它的七个变更方法(push、pop、shift、unshift、splice、sort、reverse)
vue2 响应式有什么缺点
- 初始化的递归遍历会造成性能的损失
- 对于新增或者删除属性,无法进行拦截,需要依靠 Vue.set 和 delete 这样的 API 才能生效
- 通知和更新过程中需要维护大量的 dep 实例和 watcher 实例,额外占用内存较多
- ES6 的 map,set 数据结构不支持
- Vue3 利用 ES6 的 Proxy 机制代理需要响应化的数据
虚拟 dom
- 虚拟 dom 的好处
- 性能提升
直接操作真实的 dom 是有限制的,一个真实的元素有很多属性,如果直接对其进行操作,就会对许多额外的属性内容进行了操作,这是没有必要的。并且操作dom 是有代价的,频繁的操作 dom 容易引发页面的重绘和回流,如果通过抽象 VNode 进行中间处理,就可以有效的减少直接操作 dom 的次数,减少页面重绘或者回流的几率
- 方便跨平台实现
同一 VNode 节点可以渲染成不同平台的对应内容,比如:渲染在浏览器是DOM元素节点,渲染在Native(iOS、Android)变为对应的控件。Vue 3 中允许开发者基于VNode实现自定义渲染器(renderer),以便于针对不同平台进行渲染。
- 结构
没有统一的标准,一般包括tag
、props
、children
三项。
tag
:必选。就是标签,也可以是组件,或者函数。
props
:非必选。就是这个标签上的属性和方法。
children
:非必选。就是这个标签的内容或者子节点。如果是文本节点就是字符串;如果有子节点就是数组。换句话说,如果判断children
是字符串的话,就表示一定是文本节点,这个节点肯定没有子元素。
diff 算法
[^diff 算法]: 一种对比方法,通过对比新旧 VNode,得出哪个节点发生了改变,再去更新对应的节点,没有发生变化的节点不处理,提高了效率
策略:**深度优先,同层比较 **,比较只会在同层级进行, 不会跨层级比较;比较的过程中,循环从两边向中间收拢。
过程:首先判断 tag 是否相同,不同则删除该节点重新创建节点进行替换
tag 相同时,先替换属性,再比较子元素
-
新旧节点都有子元素时,采用双指针的方式进行对比,新旧头尾指针进行对比,循环向中间靠拢,根据情况调用 patchVNode 进行 patch 重复流程,调用
createElem
创建一个新节点,从哈希表寻找key
一致的VNode
节点再分情况操作。 -
新节点有子元素,旧节点没有子元素,则将子元素虚拟节点转化成真实节点插入即可。
-
新节点没有子元素,旧节点有子元素,则清空子元素,并设置为新节点的文本内容。
-
新旧节点都没有子元素时,即都为文本节点,则直接对比文本内容,不同则更新。
Vue 中 key 的作用
key
的作用主要是为了更加高效的更新虚拟 DOM
。
Vue 判断两个节点是否相同时,主要是判断两者的key
和元素类型tag
。因此,如果不设置key
,它的值就是 undefined,则可能永远认为这是两个相同的节点,只能去做更新操作,将造成大量的 DOM 更新操作。
为什么组件中的 data 是函数
在 new Vue() 中,data 可以是函数也可以是对象,不会造成数据污染
在组件中,data 必须为函数,目的是防止多个组件实例对象共用一个 data ,造成数据污染,而采用函数的形式,initData 时会将其作为工厂函数返回全新的data 对象
Vue 中组件的通信方式
- 父向子 props,子向父 $emit
- 兄弟组件:eventBus,Vuex
v-show 和 v-if
-
v-show 只是为元素添加了 css 属性,display:none,但元素仍存在,而
v-if
控制元素显示或隐藏是将元素整个添加或删除。 -
v-show
有更高的初始渲染消耗,v-if
有更高的切换消耗; -
频繁切换使用 v-show(手风琴菜单,tab 页签等),不频繁切换使用 v-if(用户登录之后,根据权限不同来显示不同的内容)
v-if 和 v-for 为什么不一起使用
-
在 vue2中,
v-for
的优先级比v-if
高,这意味着v-if
将分别重复运行于每一个v-for
循环中。如果要遍历的数组很大,而真正要展示的数据很少时,将造成很大的性能浪费。 -
Vue 3 中,则完全相反,
v-if
的优先级高于v-for
,所以v-if
执行时,它调用的变量还不存在,会导致异常。
为了过滤列表中的项目,比如:v-for = "user in users" v-if = "user.active"
。这种情况,可以定义一个计算属性,让其返回过滤后的列表即可。
<template>
<div>
<div v-for="(user,index) in activeUsers" :key="user.index" >
{{ user.name }}
</div>
</div>
</template>
<script>
export default {
name:'A',
data () {
return {
users: [{name: 'aaa',isShow: true},
{name: 'bbb',isShow: false}]
};
},
computed: {//通过计算属性过滤掉列表中不需要显示的项目
activeUsers: function () {
return this.users.filter(function (user) {
return user.isShow;//返回isShow=true的项,添加到activeUsers数组
})
}
}
};
</script>
为了避免渲染本该被隐藏的列表,比如v-for = "user in users" v-if = "showUsersFlag"
。这种情况,可以将v-if
移至容器元素上或在外面包一层template
即可。
<template v-for="(item, index) in list" :key="index">
<div v-if="item.isShow">{{item.title}}</div>
</template>
路由跳转的方式
- this.$router.push() 跳转到指定URL,向history栈添加一个新的记录,点击后退会返回至上一个页面
- this.$router.replace() 跳转到指定URL,替换history栈上一个记录,点击后退会返回至上上个页面
- this.$router.go(n) 类似window.history.go(n),向前或向后跳转n个页面,n可正(先后跳转)可负(向前跳转)
- router-link 利用路由实现跳转
vuex 的五个属性
state、mutation、actions、getters, module
state 存储状态
mutations 同步函数
actions 异步函数,并且调用的事mutations
getters 派生状态,类似与vue中的computed
module 组件,可以包含state,mutations,actions,getters,甚至module
Vue2 中的 set 方法
set 是 Vue2 中的一个全局 API,可手动添加响应式数据,解决数据变化视图未更新问题
当在项目中直接设置数组的某一项的值,或者直接设置对象的某个属性值,会发现页面并没有更新。这是因为Object.defineProperty()
的限制,监听不到数据变化,可通过this.$set(数组或对象,数组下标或对象的属性名,更新后的值)
解决。
mixin
使用场景:不同组件中经常会用到一些相同或相似的代码,这些代码功能相对独立,mixin提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能
Vue 中的修饰符
- 表单修饰符:
- lazy:其实就是当表单失去焦点后,才赋值
- number:自动将用户输入的值转化为数值类型,若无法被 parseFloat 解析,则返回原来的值
- trim:去除输入值首尾空格
- 事件修饰符:
- stop:阻止冒泡行为,当多标签重叠的时候,你点最上面的一层标签,会自动冒泡到他下面的所有标签上
- prevent:阻止默认事件,例如链接的跳转事件
- self:只当事件在该元素本身触发时触发回调,是除非你直接点击到我标签本身, 不接受其它标签上面的点击事件冒泡到我身上的事件
- once:事件只触发一次
router 和 route 的区别
- $router 是 VueRouter 的实例对象,是一个全局的路由对象,包含所有的路由的对象和属性
- $route 是当前激活的路由对象,可以从对象中获取一些数据,例如 name ,params,query 等
对 Vuex 的理解
概念:
Vuex 是 Vue 专用的状态管理库,它以全局方式集中管理应用的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
解决的问题:
Vuex 主要解决的问题是多组件之间状态共享。利用各种通信方式,虽然也能够实现状态共享,但是往往需要在多个组件之间保持状态的一致性,这种模式很容易出问题,也会使程序逻辑变得复杂。Vuex 通过把组件的共享状态抽取出来,以全局单例模式管理,这样任何组件都能用一致的方式获取和修改状态,响应式的数据也能够保证简洁的单向流动,使代码变得更具结构化且易于维护。
什么时候用:
Vuex 并非是必须的,它能够管理状态,但同时也带来更多的概念和框架。如果我们不打算开发大型单页应用或应用里没有大量全局的状态需要维护,完全没有使用Vuex的必要,一个简单的 store 模式就够了。反之,Vuex将是自然而然的选择。
用法:
Vuex 将全局状态放入state
对象中,它本身是一颗状态树,组件中使用store
实例的state
访问这些状态;然后用配套的mutation
方法修改这些状态,并且只能用mutation
修改状态,在组件中调用commit
方法提交mutation
;如果应用中有异步操作或复杂逻辑组合,需要编写action
,执行结束如果有状态修改仍需提交mutation
,组件中通过dispatch
派发action
。最后是模块化,通过modules
选项组织拆分出去的各个子模块,在访问状态(state)时需注意添加子模块的名称,如果子模块有设置namespace
,那么提交mutation
和派发action
时还需要额外的命名空间前缀。