DingMing

丁大铭的个人空间,用来分享一些前端小技巧,默默成长吧,哈哈

重新理解vue2

  |  
 阅读次数

背景

经过几年的前端开发,感觉自己牛逼了,而失去了钻研的动力。最近我面了别人几场,发现大家对vue都有自己的理解,回答都很不错。所以我准备再深入了解下vue。有一点自己的特色

重新项目

所有vue页面都离不开main.js, 去实例化vue的。这里它做了什么呢。

1
2
3
4
5
6
7
8
9
new Vue({
router,
render: h => h(App),
}).$mount('#app')

new Vue -> init -> 注入router ->
$mount挂载到具体的节点上 -> compile 模板语法解析 -> render ->
Vnode getter setter -> 收集依赖 通知 -> 监听器 -> patch -> 更新Dom

keep-alive

生命周期

  • 初次进入时:
    1.created > mounted > activated
    2.退出后触发 deactivated

  • 再次进入:
    1.只会触发 activated

  • 事件挂载的方法等,只执行一次的放在 mounted 中;组件每次进去执行的方法放在 activated 中

老方法 通过计算属性 监听 this.$route上的 meta 来进行 keep-alive的切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <keep-alive>
<router-view v-if="alive"></router-view>
</keep-alive>
<router-view v-if="!alive"></router-view>

computed: {
// 不需要keepAlive的组件请在router的meta信息中加入keepAlive属性,值为false
alive() {
const meta = (this.$route && this.$route.meta) || {};
// default set true, is alive
if (!meta.hasOwnProperty('keepAlive')) {
return true;
}
return !!meta.keepAlive;
},
},

新的使用

include定义缓存白名单,keep-alive会缓存命中的组件;exclude定义缓存黑名单,被命中的组件将不会被缓存;max定义缓存组件上限,超出上限使用LRU的策略置换缓存数据。

实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/core/components/keep-alive.js
export default {
name: 'keep-alive',
abstract: true, // 判断当前组件虚拟dom是否渲染成真是dom的关键

props: {
include: patternTypes, // 缓存白名单
exclude: patternTypes, // 缓存黑名单
max: [String, Number] // 缓存的组件实例数量上限
},

created () {
this.cache = Object.create(null) // 缓存虚拟dom
this.keys = [] // 缓存的虚拟dom的健集合
},

destroyed () {
for (const key in this.cache) { // 删除所有的缓存
pruneCacheEntry(this.cache, key, this.keys)
}
},

mounted () {
// 实时监听黑白名单的变动
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},

render () {
// 先省略...
}
}

可以看出,与我们定义组件的过程一样,先是设置组件名为keep-alive,其次定义了一个abstract属性,值为true。这个属性在vue的官方教程并未提及,却至关重要,后面的渲染过程会用到。props属性定义了keep-alive组件支持的全部参数。

  • 第一步:获取keep-alive包裹着的第一个子组件对象及其组件名;
  • 第二步:根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则执行第三步;
  • 第三步:根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在this.keys中的位置(更新key的位置是实现LRU置换策略的关键),否则执行第四步;
  • 第四步:在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max的设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key)。
  • 第五步:最后并且很重要,将该组件实例的keepAlive属性值设置为true。这个在@不可忽视:钩子函数 章节会再次出场。

$nextTick

在做项目的时候,我们经常会用到nextTick,简单的理解就是它就是一个setTimeout函数,将函数放到异步后去处理;将它替换成setTimeout好像也能跑起来,但它仅仅这么简单吗?那为什么我们不直接用setTimeout呢?让我们深入剖析一下。

 为了在数据更新操作之后操作DOM,我们可以在数据变化之后立即使用Vue.nextTick(callback);这样回调函数会在DOM更新完成后被调用,就可以拿到最新的DOM元素了。

  • 把回调函数放入callbacks等待执行
  • 将执行函数放到微任务或者宏任务中
  • 事件循环到了微任务或者宏任务,执行函数依次执行callbacks中的回调

再回到我们开头说的setTimeout,可以看出来nextTick是对setTimeout进行了多种兼容性的处理,宽泛的也可以理解为将回调函数放入setTimeout中执行;不过nextTick优先放入微任务执行,而setTimeout是宏任务,因此nextTick一般情况下总是先于setTimeout执行,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
我们都知道在生命周期mounted渲染的时候,
不能百分百保证所有的子组件都能够被渲染,
因此我们可以在mounted里面使用 this.$nextTick,
这样就能保证所有的子组件都能被渲染到。

mounted钩子在服务器端渲染期间不被调用。
mounted: function () {
this.$nextTick(function () {
//在数据发生变化,
//重新在视图上完成渲染后,在执行里面的方法
//这一句话等同与:
//将回调延迟到下次 DOM 更新循环之后执行
//等同于:在修改数据之后,然后等待 DOM 更新后在执行
})
}

谈谈Vue.use的原理

  • Vue的插件是一个对象, 就像Element.

  • 插件对象必须有install字段.

  • install字段是一个函数.

  • 初始化插件对象需要通过Vue.use().

    Vue.use()调用必须在new Vue之前.
    同一个插件多次使用Vue.use()也只会被运行一次.

vuex actions 和 mutations 的区别

Mutations

mutations 必须是同步函数,为什么?

1
2
3
4
5
6
7
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}

每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。
然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:
因为当 mutation 触发的时候,回调函数还没有被调用,
devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的
状态的改变都是不可追踪的。

Actions

Actions存在的意义是假设你在修改state的时候有异步操作,vuex作者不希望你将异步操作放在Mutations中,所以就给你设置了一个区域,让你放异步操作,这就是Actions

vuex为了解决mutations只有同步的问题,提出了actions(异步),专门用来解决mutations只有同步无异步的问题.

发布订阅js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

class Watcher{
constructor(fn){
this.fn = fn
}

update(){
this.fn()
}
}

class Dep{
constructor(){
this.list=[]
}

adds(sub){
this.list.push(sub)
}

notify(){
this.list.forEach(v=>v.update())
}
}

let watcher = new Watcher(function(){console.log(11)})

let dep = new Dep()

dep.adds(watcher)

dep.notify()

vue 中 computed 和 watch 的区别【描述】

  • computed 一般用于简化模板中变量的调用
  • watch 一般用于监听数据的变化,做一些逻辑处理或者异步处理,可以深度监听、立即执行
  • computed 和 watch 在源码里都是通过 Watcher 类创建出来的
  • 初始化时,先创建 computed 再创建 watch 。数据改变时,先执行 computed 再执行 watch