数据绑定
1、实现原理
通过Object.defineProperty对data中的每项数据劫持getter和setter,在getter中收集依赖,在setter中触发更新。
如果是对象,会逐层进行以上处理。
如果是数组,会遍历进行以上处理,并另外修改了7个数组的原型方法,在里面增加了设置监听、触发更新的逻辑。
2、设计模式
观察者模式 / 发布订阅模式
3、缺点:
- 初始化时会对所有data进行绑定,不管有没有用到。而且对象和数组需要逐层遍历处理。
- 对象新增删除属性没法识别(所以vue提供了set方法)。数组索引和长度变更也没法识别
- 无法监听set、map、class这些类型的数据
4、vue3更新
换用proxy方式
- 直接代理对象而不是属性,无需逐层遍历;对象属性的新增和删除以及数组操作无需再额外处理。
- 拦截方式有很多种;
- 速度更快;
组件通信
1、父传子:props、$children、$ref
2、子传父:$emit+@事件名、$parent、slot
3、祖先-》子孙:provide + inject
4、跨层级:$attrs + $listeners
5、任意组件间:Vuex、bus(new Vue())
虚拟DOM
通过js对象来描述真实DOM,主要有节点名称、属性信息、孩子节点...
在浏览器端操作dom太耗性能,虚拟dom就是用来优化这个问题的。将dom更新放在js对象上去处理,完后再转成真实dom交给浏览器绘制。
diff
新旧vnode进行比较
- 不同节点类型。删除老节点,将newVNode转成真实dom渲染
- oldVNode有孩子而newVNode没有,删除老孩子
- oldVNode没孩子而newVNode有,新增新孩子
- 都有孩子,设置首尾游标,先老开始、老结束、新开始、新结束进行两两比较。然后首游标往后走,尾游标往前走,老的遍历完了新的还有,则新增;新的遍历完了老的还有,则删除。
computed和watch的区别
computed主要用于在data某数据项的基础上进行一个转换或简单处理,它有缓存,只有依赖的数据项发生变化时才重新渲染。
watch监听某个数据项,一般用于有大型操作或异步操作的时候。
优化
1、列表项设置唯一的key属性,以便高效的更新虚拟dom
1)比较新老vnode是不是同一个节点
2)在diff算法中,老开始、老结束、新开始、新结束两两比较没有一对相同的时候,将新开始的key在老孩子key列表中查找,如果存在,就将其对应的dom节点移动至开始位置,不存在则直接渲染新开始在开始位置。
2、设置缓存keep-alive
3、纯展示性的数据可以通过Object.freeze()冻结
4、v-for和v-if尽量不要一起使用(v-for优先级更高)
5、不频繁切换的优先选v-if而不是v-show,减少dom数
6、图片懒加载,可使用插件vue-lazyload
7、路由懒加载
const Foo = () => import('./Foo.vue')
8、第三方插件按需引入
9、无限列表参考vue-virtual-scroll-list 和 vue-virtual-scroller
10、SSR
生命周期
初始化事件、$parent、$root...
初始化data、props、computed、watch...
(有data)
将template或outerHtml转成render函数
(有$el)
渲染dom
data数据更新前
data数据更新后并渲染完
销毁前
销毁后(解绑、移除绑定事件、销毁子实例...)
还有、,组件被包裹在keep-alive里面时会被激活
还有,捕获来自子孙组件的错误
生命周期执行顺序:(P-父组件、C-子组件)
1、初始化:
P beforeCreate
P created
P beforeMount
C beforeCreate
C created
C beforeMount
C mounted
P mounted
2、父更新传给子组件的值:
P beforeUpdate
C beforeUpdate
C updated
P updated
3、子更新传给父组件的值:
P beforeUpdate
C beforeUpdate
C updated
P updated
4、子组件销毁:
C beforeDestroy
C destroyed
5、子组件销毁:
P beforeDestroy
C beforeDestroy
C destroyed
P destroyed
6、keep-alive
A列表页、B详情页(url上通过id来区分每个页面),A 设置了keep-alive
-》全局守卫beforeEach
-》A路由独享钩子beforeEnter
-》A组件路由钩子beforeRouteEnter
-》 全局守卫beforeResolve
-》全局守卫afterEach
-》 (A-beforeCreate,A-created,A-beforeMount,A-mounted,A-activated)
-》 A组件路由钩子beforeRouteEnter的next回调
-》A组件路由钩子beforeRouteLeave
-》全局守卫beforeEach
-》B路由独享钩子beforeEnter
-》B组件路由钩子beforeRouteEnter
-》全局守卫beforeResolve
-》全局守卫afterEach
-》(B-beforeCreate,B-created,B-beforeMount,B-mounted,A-deactivated)
B也设置keep-alive的差别:
(B-beforeCreate,B-created,B-beforeMount, A-deactivated, B-mounted, B-activated)
-》B组件路由钩子beforeRouteLeave
-》全局守卫beforeEach
-》A路由独享钩子beforeEnter
-》A组件路由钩子beforeRouteEnter
-》 全局守卫beforeResolve
-》全局守卫afterEach
-》(B-beforeDestroy,B-destroyed,A-activated)
B也设置keep-alive的差别:
(B-deactivated,B-beforeUpdate,A-activated, B-updated)
-》全局守卫beforeEach
-》B组件路由钩子beforeRouteUpdate
-》全局守卫beforeResolve
-》全局守卫afterEach
-》(B-beforeUpdate,B-updated)
keep-alive取消缓存
离开时销毁
deactivated() {this.$destroy();},
keep-alive实现原理
- 第一步:获取keep-alive包裹着的第一个子组件对象及其组件名;
- 第二步:根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则执行第三步;
- 第三步:根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在this.keys中的位置(更新key的位置是实现LRU置换策略的关键),否则执行第四步;
- 第四步:在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max的设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key)。
- 第五步:最后并且很重要,将该组件实例的keepAlive属性值设置为true。
vuex实现原理
vuex是专为vue应用程序实现状态管理的,采用集中式存储管理应用所有组件的状态。
vue插件都会通过install方法进行注册,调用的时候如下
Vue.use(Vuex)
use内部就调用了install方法。
install方法通过mixin的方式将vuex在beforeCreate阶段注入到vue根实例上,将store作为它的data,实现响应式,并能逐级传到子组件进行使用(this.$store)
vue用的虚拟dom,怎么做到事件绑定的?
- 原生:addEventListener
<div @click="fn()"></div>
- 组件:$on、$emit、$off
<my-component @click.native="fn" @click="fn1"></my- component>
vue组件的开发过程
数据驱动视图原理
每个组件实例都有相应的watcher实例 - 渲染组件的过程,会把属性记录为依赖 - 当我们操纵一个数据时,依赖项的setter会被调用,从而通知watcher重新计算,从而致使与之相关联的组件得以更新
data中有些数据没用到,那更新的时候vue是怎么做到不渲染的?
从以上“数据驱动视图原理”可知,没用到的数据不会存在watcher上,不存在的自然也不会去更新了
欢迎指正!后续会继续补充~