vue框架
# Vue 的理解,优缺点
Vue 是一个构建数据驱动的渐进性框架,它的目标是通过 API 实现响应数据绑定和视图更新
优点:渐进式,组件化,轻量级,虚拟 dom,响应式,单页面路由,数据与视图分开
缺点:单页面不利于 seo,不支持 IE8 以下,首屏加载时间长
# MVVM
全称: Model-View-ViewModel
, Model
表示数据模型层。 view
表示视图层, ViewModel
是 View
和 Model 层的桥梁,数据绑定到 viewModel
层并自动渲染到页面中,视图变化通知 viewModel
层更新数据。
# 为什么说 Vue 是一个渐进式框架?
渐进式:通俗点讲就是,你想用啥你就用啥,咱也不强求你。你想用 component 就用,不用也行,你想用 vuex 就用,不用也可以
# vue 组件通讯方式
- props 和$emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的、
- $parent,$children 获取当前组件的父组件和当前组件的子组件
- $attrs 和$listeners A->B->C。解决深层组件间的通信, Vue 2.4 开始提供了$attrs 和$listeners 来解决这个问题
- 父组件中通过 provide 来提供变量,然后在子组件中通过 inject 来注入变量。
- $refs 获取组件实例
- envetBus 兄弟组件数据传递
- vuex 状态管理
# Vuex 的 Mutation 和 Action 的区别吗
Vuex 是一个专为 Vue 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。
Vuex 主要包括以下几个核心模块:
State
:定义了应用的状态数据Getter
:在 store 中定义“getter”(可以认为是 store 的计算属性),就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来, 且只有当它的依赖值发生了改变才会被重新计算Mutation
:是唯一更改 store 中状态的方法,且必须是同步函数Action
:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作 5. Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中
# v-if 和 v-for 优先级
1、v-for 优先于 v-if 被解析
2、如果同时出现,每次渲染都会先执行循环在判断条件,无论如何循环都不可避免,浪费了性能
3、要避免出现这种情况,在外层嵌套 template,在这一层进行 v-if 判断,然后在内部进行 v-for 循环
# vue 生命周期
Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载DOM-渲染、更新-渲染、卸载等一系列的过程,每个过程对应着生命周期钩子,就是说在达到某一阶段或条件时去触发的函数,目的就是为了完成一些动作或者事件
create阶段
:vue 实例被创建
beforeCreate: 创建前,此时 data 和 methods 中的数据都还没有初始化
created: 创建完毕,data 中有值,未挂载
-mount阶段
: vue 实例被挂载到真实 DOM 节点
beforeMount:可以发起服务端请求,去数据
mounted: 此时可以操作 DOM
update阶段
:当 vue 实例里面的 data 数据变化时,触发组件的重新渲染
beforeUpdate :更新前
updated:更新后
-destroy阶段
:vue 实例被销毁
beforeDestroy:实例被销毁前,此时可以手动销毁一些方法
destroyed:销毁后
# Vue 底层实现原理
vue 实例化的时候会创建一个 observe 实例,通过 Object.defineProperty 对 data 设置 get,set。 初始化编译的时候会触发 get 方法进行依赖收集,将观察者 watcher 对象添加到订阅者 dep 中。数据改变的时候会触发 set方法,通知 dep 中的 watcher 执行 update 方法, 将 watcher 添加到事件队列 queue 中,执行 nextTick(queue)。
Observer(数据监听器)
Observer 的核心是通过 Object.defineProprtty()来监听数据的变动,这个函数内部可以定义 setter 和 getter,每当数据发生变化,就会触发 setter。这时候 Observer 就要通知订阅者,订阅者就是 Watcher
Watcher(订阅者)
Watcher 订阅者作为 Observer 和 Compile 之间通信的桥梁,主要做的事情是:
- 在自身实例化时往属性订阅器(dep)里面添加自己
- 自身必须有一个 update()方法
- 待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调
Compile(指令解析器)
Compile 主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新试图
# computed 与 watch
计算属性本质上是 computed watcher,而侦听属性本质上是 user watcher。
就应用场景而言,计算属性适合用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来;而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑。
使用场景
computed:当一个属性受多个属性影响的时候使用,例:购物车商品结算功能。watch:当一条数据影响多条数据的时候使用,例:搜索数据
# key 的作用是什么?
- Key 的作用: 主要用来在虚拟 DOM 的 diff 算法中,在新旧节点的对比时辨别 vnode ,key可以精准的判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少了dom的操作量。常见的列子是结合 v-for 来进行列表渲染,或者用于强制替换元素/组件。
- 设置 Key 的好处:
- (1)数据更新时,可以尽可能的减少 DOM 操作;
- (2)列表渲染时,可以提高列表渲染的效率,提高页面的性能;
# v-model 中的实现原理
v-model 可以看成是 value+input 方法的语法糖(组件)。原生的 v-model ,会根据标签的不同生成不同的事件与属性。解析一个指令来。
# 为什么 Vue 采用异步渲染
如果采用同步更新的话,vue 观察到数据改变就进行一次计算、渲染,那么就会重复渲染。对属性进行多次操作的情况,我们并不关心中间的过程发生了什么,只需要知道最后的结果。
# nextTick 的实现原理是什么?
简单的概括,nextTick 只做了两件事情:
-将回调函数 cb 包装处理为箭头函数添加到事件队列中 -事件队列异步执行(执行的优先顺序为 promise.then => MutationObserver => setImmediate => setTimeout)
# 路由原理 history 和 hash 两种路由方式的特点
hash 模式
实现原理
:location.hash 的值实际就是 URL 中#后面的东西 它的特点在于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。可以为 hash 的改变添加监听事件
window.addEventListener("hashchange", funcRef, false);
特点
:每一次改变 hash(window.location.hash),都会在浏览器的访问历史中增加一个记录,实现前端路由“更新视图但不重新请求页面”的功能了
history 模式
实现原理
:利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。 这两个方法应用于浏览器的历史记录站,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。特点
:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。
# 路由懒加载的方式
Vue 异步组件技术
动态 import 语法
require.ensure()
// 异步组件
{
path: '/Demo',
name: 'Demo',
//打包后,每个组件单独生成一个chunk文件
component: reslove => require(['../views/Demo'], resolve)
}
//动态import 默认将每个组件,单独打包成一个js文件
const Demo = () => import('../views/Demo')
// require.ensure
const Demo = r => require.ensure([], () => r(require('../views/Demo')), 'Demo')
# keep-alive 的实现
作用:实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染
场景:tabs 标签页 后台导航,vue 性能优化
原理:Vue.js 内部将 DOM 节点抽象成了一个个的 VNode 节点,keep-alive 组件的缓存也是基于 VNode 节点的而不是直接存储 DOM 结构。它将满足条件(pruneCache 与 pruneCache)的组件在 cache 对象中缓存起来,在需要重新渲染的时候再将 vnode 节点从 cache 对象中取出并渲染。
# 异步组件
Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
优点
:节省打包出的结果,异步组件分开打包,采用 jsonp 的方式进行加载,有效解决文件过大的问题。
一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:
Vue.component("async-webpack-example", function(resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(["./my-async-component"], resolve);
});
使用局部注册的时候,可以直接提供一个返回 Promise 的函数:
new Vue({
// ...
components: {
"my-component": () => import("./my-async-component"),
},
});
# 什么是虚拟 DOM?
虚拟 DOM 是一个对象,一个用来表示真实 DOM 的对象
<ul id="list">
<li class="item">111</li>
<li class="item">222</li>
<li class="item">333</li>
</ul>;
let oldVDOM = {
// 旧虚拟DOM
tagName: "ul", // 标签名
props: {
// 标签属性
id: "list",
},
children: [
// 标签子节点
{
tagName: "li",
props: { class: "item" },
children: ["111"],
},
{
tagName: "li",
props: { class: "item" },
children: ["222"],
},
{
tagName: "li",
props: { class: "item" },
children: ["333"],
},
],
};
# diff 算法
Diff 算法是一种对比算法。对比两者是旧虚拟DOM和新虚拟DOM
,对比出是哪个虚拟节点
更改了,找出这个虚拟节点
,并只更新这个虚拟节点所对应的真实节点
,而不用更新其他数据没发生改变的节点,实现精准
地更新真实 DOM,进而提高效率
。
Diff 算法的原理: -
Diff同层对比
: 新旧虚拟 DOM 对比的时候,Diff 算法比较只会在同层级进行, 不会跨层级比较。 所以 Diff 算法是:深度优先算法。 时间复杂度:O(n) -Diff对比流程
: 当数据改变时,会触发 setter,并且通过 Dep.notify 去通知所有订阅者 Watcher,订阅者们就会调用 patch 方法,给真实 DOM 打补丁,更新相应的视图。patch方法
:方法作用就是,对比当前同层的虚拟节点是否为同一种类型的标签- 是:继续执行 patchVnode 方法进行深层比对
- 否:没必要比对了,直接整个节点替换成新虚拟节点
sameVnode方法
: patch 关键的一步就是 sameVnode 方法判断是否为同一类型节点, 怎么才算是同一类型节点?function sameVnode(oldVnode, newVnode) { return ( oldVnode.key === newVnode.key && // key值是否一样 oldVnode.tagName === newVnode.tagName && // 标签名是否一样 oldVnode.isComment === newVnode.isComment && // 是否都为注释节点 isDef(oldVnode.data) === isDef(newVnode.data) && // 是否都定义了data sameInputType(oldVnode, newVnode) // 当标签为input时,type必须是否相同 ); }
patchVnode方法
:- 找到对应的
真实DOM
,称为el
- 判断
newVnode
和oldVnode
是否指向同一个对象,如果是,那么直接return
- 如果他们都有文本节点并且不相等,那么将
el
的文本节点设置为newVnode
的文本节点。 - 如果
oldVnode
有子节点而newVnode
没有,则删除el
的子节点 - 如果
oldVnode
没有子节点而newVnode
有,则将newVnode
的子节点真实化之后添加到el - 如果两者都有子节点,则执行
updateChildren函数
比较子节点,这一步很重要
- 找到对应的
updateChildren方法
: 也是一个对比方法,也叫首尾指针法
,对比子节点是否一样patch(vnode, newVnode) ,数据改变二次渲染,对比新旧 VNode 是否相同节点然后更新 DOM
createElm(vnode, insertedVnodeQueue),先执行用户的 init 钩子函数,然后把 vnode 转换成真实 DOM(此时没有渲染到页面),最后返回新创建的 DOM
updateChildren(elm, oldCh, ch, insertedVnodeQueue), 如果 VNode 有子节点,并且与旧 VNode 子节点不相同则执行 updateChildren(),比较子节点的差异并更新到 DOM
# vue 性能优化
- 路由懒加载
- keep-alive缓存页面
- 使用v-show复用DOM
- v-for遍历避免同时使用v-if
- 事件销毁(定时器)
- 图片懒加载
- 第三方插件按需导入
- 无状态组件标记为函数式组件
- SSR
# 参考资料
https://vue-js.com/learn-vue/ (opens new window) Vue 源码系列-Vue 中文社区
https://ustbhuangyi.github.io/vue-analysis/ (opens new window) Vue.js 技术揭秘