Vue-源码知识-Vue2-总结

2021/10/23 Vue
// 数据驱动(渲染流程)
1. 为什么在 Vue2 中,可以通过 this.msg 方式获取 data 或者 props 上的值?
    1. 原因就是这里 proxy 函数通过代理了 vm 实例,当通过 this. 方式访问时
       实际访问的是 this._data.msg 或者 this._props.msg,具体实现就是遍历
        data 或 props,然后把每个 key 传入了 proxy 方法进行代理,实际上
        vm 上并没有这个属性,但是代理完就会指向 _data 或 _props 下的属性
       在新版的 Vue2 中,用户自定义的 data、props、methods 会暴露到 vm 实例上
    2. 示例: src/core/instance/state proxy()
        const sharedPropertyDefinition = {
            enumerable: true,
            configurable: true,
            get: noop,
            set: noop
        }
        export function proxy (target: Object, sourceKey: string, key: string) {
            sharedPropertyDefinition.get = function proxyGetter () {
                return this[sourceKey][key]
            }
            sharedPropertyDefinition.set = function proxySetter (val) {
                this[sourceKey][key] = val
            }
            Object.defineProperty(target, key, sharedPropertyDefinition)
        }
        proxy(vm, `_data`, key)
        proxy(vm, `_props`, key)
2. 如果在 Vue2 的实例中没有传入 el 属性,则不会进行 DOM 渲染,在新版的 Vue2 中,mount 支持传入挂载的 el 节点
    1. 示例: src/core/instance/init initMixin()
        if (vm.$options.el) {
            vm.$mount(vm.$options.el)
        }
3. $mount:
   1. 如果没有定义 render 方法,则会把 el 或者 template 字符串转换成 render 方法
   2. 如果有 render 函数会直接使用 render 函数进行 mount,如果没有会判断 template
      它可以是一个字符串也可以是一个 DOM 对象,Vue 会获取该对象的 innerHTML
      如果没有 template 会拿 el 的 outerHTML,也就是说会优先使用 render ——> template ——> el
   3. 第一次渲染时会把原本的 HTML 中 id = 'app'DOM 节点直接替换掉而不是插入
4. createElement:
    1. 把 children 转换成一维数组
    2. 判断是否是原生 HTML 标签或者是组件名称
    3. 生成 VNode
5. 渲染流程:new Vue ——> init ——> $mount ——> compile(如果是 Runtime-Only 则没有)——> render(createElement) ——> vnode ——> patch ——> DOM
----------------------------------------------------------------------------------------------
// 组件化
1. 组件 patch 流程:createComponent ——> 子组件初始化 ——> 子组件 render ——> 子组件 patch
2. activeInstance 为当前激活的 vm 示例,vm.$vnode 是组件的占位 vnode,vm._vnode 是组件的渲染 vnode
    1. vm.$vnode 占位 vnode 指的就是该组件的父 VNode,即 _parentVnode
    2. vm._vnode 渲染 vnode 指的就是该组件中的渲染 vnode
    3. vm._vnode.parent === vm.$vnode
3. 嵌套组件的插入顺序是先子后父
4. 配置合并(mixin 就是 mergeOptions)
    1. new Vue 根据对应的策略进行合并
    2. 组件配置会和大 Vue 的配置进行合并放在原型上
5. 生命周期(callHook),每个 Vue 实例的 $options 中的生命周期是个数组,会遍历执行
    1. beforeCreate 数据 data 等 watcher 执行前
    2. created 数据 data 等 watcher 执行后
    3. beforeMount 先父后子
    4. mounted 先子后父
    5. beforeUpdate watcher 执行完前执行
    6. updated watcher 执行完后执行
    7. beforeDestroy 先子后父
    8. destroyed 先子后父
6. 异步组件
    1. 异步组件实现的本质是 2 次渲染,先渲染成注释节点,当组件加载成功后,再通过 forceRender 重新渲染
    2. 异步组件 3 种实现方式中,高级异步组件的设计非常巧妙,它可以通过简单的配置实现了 lodaing、
        resolve、reject、timeout 4 种状态
    3. 示例
        1. 工厂函数
            Vue.component('HelloWorld', function(resolve, reject) {
                require(['./components/HelloWorld'], function(res) {
                    resolve(res)
                })
            })
        2. Promise
            Vue.component('HelloWorld', () => import('./components/HelloWorld.vue'))
        3. 高级组件
            Vue.component('HelloWorld', () => ({
                component: import('./components/HelloWorld.vue'),
                loading: {
                    template: '<div>loading</div>'
                },
                error: {
                    template: '<div>error</div>'
                },
                delay: 200,
                timeout: 1000
            }))
----------------------------------------------------------------------------------------------
// 响应式
1. nextTick
    1. nextTick 是要把执行的任务推入到一个队列中,在下一个 tick 同步执行
    2. 数据改变后触发渲染 watcher 的 update,但是 watchers 的 flush 是在 nextTick 后,
        所以重新渲染时异步的
2. Dep、Watcher、Observer(defineReactive)之间的关系
    1. Observer 主要作用就是遍历了 data,给 data 的每个 key 执行了 defineReactive
        defineReactive 主要作用就是使用 Object.defineProperty 给每个 key 加上了 getter、setter
        并且 new Dep(),getter 时会执行 dep.depend() 即收集当前 key 属性的 Watcher,
        在 setter 时会执行 dep.notify() 即会遍历 dep.subs 通知每个 Watcher 更新
    2. Watcher 主要作用是 update 方法用于更新操作,并且在 new Watcher 时会把 Dep.target = this
        即把 Dep.target 指向自己,最后释放 Dep.target = null,还有一个 addDep 方法用于调用
        Dep 中 addSub 方法,所以把 watcher push 进去的操作是在 Watcher 中执行的
        但是触发是在 Observer getter 时触发
    3. Dep 中有 subs 数组,由于存储 Watcher,有 addSub 用于 subs.push(watcher)
        notify 用于遍历 subs 通知所有 watcher for(let i = 0; i < subs.length; i++){ subs[i].update() }
    4. 总结
        1. Observer getter 触发依赖收集即 dep.depend(),depend 方法会判断如果有 Dep.target(指向 Watcher) 的话
            就会调用 Dep.target.addDep(Dep),实际就是 Watcher.addDep(Dep),而 addDep 就会调用 Dep.addSub(watcher)
            然后 addDep 会把 watcher push 到 subs 数组中
        2. Observer setter 触发派发更新即 dep.notify(),notify 方法会遍历 subs 中的每个 watcher,然后
            执行 watcher 中的 update(),update 方法就是更新操作
----------------------------------------------------------------------------------------------
// Event
1. event 在编译阶段生成相关的 data,对于 DOM 事件在 patch 过程中的创建阶段和更新阶段执行
    updateDOMListeners 生成 DOM 事件,对于自定义事件,会在组件初始化阶段通过 initEvents 创建
2. 原生 DOM 事件和自定义事件主要的区别在与添加和删除事件的方式不一样,并且自定义事件的派发
    是往当前实例上派发,但是可以利用在父组件环境定义回调函数来实现父子组件的通讯
----------------------------------------------------------------------------------------------
// keep-alive
1. 实际缓存的是 vnode,缓存数组会先删除最前面即最不常用的
2. 生命周期通过判断 vnode.data.keppAlive 变量决定调用特定的生命周期
3. 会调用 $forceUpdate 进行更新
4. 属于抽象组件,不会实际渲染,会渲染子节点
----------------------------------------------------------------------------------------------
// transition
1. 实际上源码只是管理了我们的 Css 类名,实际过渡效果还是通过 Css 实现的
2. transition-group 组件是为了做列表的过渡,它会渲染成真实的元素
3. 当我们去修改列表的数据的时候,如果是添加或删除数据,则会触发相应元素本身的过渡动画
    这点和 <transition> 组件实现效果一样,初次之外 <transition-group> 还实现了 move 的
    过渡效果,让我们的列表过渡动画更加丰富
----------------------------------------------------------------------------------------------
// Vue.use
1. 可以传一个函数或者对象,会默认执行对象中的 install 方法,如果没有 install 会判断是否传入的是函数
    是的话就执行,否则不执行
----------------------------------------------------------------------------------------------
// vue-router
1. Vue.use(vueRouter) 会执行
    1. 通过 Vue.mixin 混入了 beforeCreate 和 destroyed 生命周期的一些逻辑
    2. 全局注册了 <RouterView><RouterLink> 组件
    3.Vue.prototype 上定义了 $router 和 $route 属性
2. 初始化(beforeCreate 中 init)
    1. 对于 History 路由做降级操作,如果浏览器不支持,则自动换成 Hash 路由
    2. 非浏览器环境还有一个 abstract 路由
    3. 生成了路由映射表,包含有 pathList、nameMap、pathMap
3. 路由始终会维护当前的线路,路由切换的时候会把当前线路切换到目标线路,切换过程中会执行一系列
    的导航守卫钩子函数,会更新 URL,同样也会渲染对应的组件,切换完毕后会把目标线路更新替换
    当前线路,这样就会作为下一次的路径切换的依据
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
Last Updated: 2024/7/31 12:57:25
    飘向北方
    那吾克热-NW,尤长靖