1

学习总结:

1、 Vue 首次渲染的过程。
  • 在首次渲染之前,首先进行Vue初始化,初始化实例成员和静态成员
  • 当初始化结束之后,要调用Vue的构造函数new Vue(),在构造函数中调用了_init()方法,这个方法相当于我们整个Vue的入口
  • _init方法中,最终调用了$mount,一共有两个$mount,第一个定义在entry-runtime-with-compiler.js文件中,也就是我们的入口文件$mount,这个$mount()的核心作用是帮我们把模板编译成render函数,但它首先会判断一下当前是否传入了render选项,如果没有传入的话,它会去获取我们的template选项,如果template选项也没有的话,他会把el中的内容作为我们的模板,然后把模板编译成render函数,它是通过compileToFunctions()函数,帮我们把模板编译成render函数的,当把render函数编译好之后,它会把render函数存在我们的options.render中。
  • 接着会调用src/platforms/web/runtime/index.js文件中的$mount方法,在这个中首先会重新获取el,因为如果是运行时版本的话,是不会走entry-runtime-with-compiler.js这个入口中获取el,所以如果是运行时版本的话,我们会在runtime/index.js的$mount()中重新获取el。
  • 接下来调用mountComponent(),这个方法在src/core/instance/lifecycle.js中定义的,在mountComponent()中,首先会判断render选项,如果没有render选项,但是我们传入了模板,并且当前是开发环境的话会发送一个警告,目的是如果我们当前使用运行时版本的Vue,而且我们没有传入render,但是传入了模版,告诉我们运行时版本不支持编译器。接下来会触发beforeMount这个生命周期中的钩子函数,也就是开始挂载之前。
  • 然后定义了updateComponent(),在这个函数中,调用vm._rendervm._updatevm._render的作用是生成虚拟DOM,vm._update的作用是将虚拟DOM转换成真实DOM,并且挂载到页面上
  • 创建Watcher对象,在创建Watcher时,传递了updateComponent这个函数,这个函数最终是在Watcher内部调用的。在Watcher内部会用了get方法,当Watcher创建完成之后,会触发生命周期中的mounted钩子函数,在get方法中,会调用updateComponent()
  • 挂载结束,最终返回Vue实例。
2、Vue 响应式原理。

Vue响应式的重点就是数据劫持/发布订阅模式,其他更多介绍查看Vue的官网的 深入响应式原理一节,这里主要总结一下内部代码的实现。

  1. 先找找入口在哪里(src/core/instance/init.js)

    initState(vm) --> initData(vm) --> observe()

    //初始化Vue实例的状态,初始化了_props,methods,_data,computed,watch
    export function initState (vm: Component) {
      vm._watchers = []
      const opts = vm.$options
      if (opts.props) initProps(vm, opts.props)
      if (opts.methods) initMethods(vm, opts.methods)
      if (opts.data) {
        initData(vm) // 看这里
      } else {
        observe(vm._data = {}, true /* asRootData */)
      } 
      if (opts.computed) initComputed(vm, opts.computed)
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch)
      }
    }
    // 把 data 属性属性注入到Vue实例上
    function initData(vm) {
        let data = vm.$options.data
        // 初始化 _data, 组件中 data 是函数,调用函数返回结果,否则直接返回data
        data = vm._data = typeof data === 'function'
            ? getData(data, vm)
            : data || {}
            
        //...
          
        // 响应式处理
        observe(data, true /* asRootData */)
    }
  1. 现在终于到正题了 observe(value) 响应式的入口
    export function observe(alue: any, asRootData: ?boolean) {
        // 判断 value 是否是对象
         if (!isObject(value) || value instanceof VNode) {
            return
         }

         let ob: Observer | void
         // 如果 value 有 __ob__(observer对象) 属性 结束(说明已经做过响应化处理)
         if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
            ob = value.__ob__
         } else if (
            shouldObserve &&
            !isServerRendering() &&
            (Array.isArray(value) || isPlainObject(value)) &&
            Object.isExtensible(value) &&
            !value._isVue
         ) {
             // 创建一个 Observer 对象
             ob = new Observer(value)
         }
         if (asRootData && ob) {
            ob.vmCount++
         }
         return ob // 返回observer对象
    }
  1. 数据劫持
    export class Observer {
        value: any; // 观察对象
        dep: Dep; // 依赖对象
        vmCount: number; // 实例计数器
        
        constructor (value: any) {
            this.value = value
            this.dep = new Dep()
            this.vmCount = 0 // 初始化实例的 vmCount 为0
            def(value, '__ob__', this) // 将实例挂载到观察者对象的 __ob__ 属性
            // 数组的响应式处理
            if (Array.isArray(value)) {
                ...
                // 为数组中的每一个对象创建一个 observer 实例
                this.observeArray(value) 
            } else {
                // 遍历对象中的每一个属性,转换成 setter/getter
                this.walk(value)
            }
        }
        
        walk (obj: Object) {
            // 获取观察对象的每一属性
            const keys = Object.keys(obj)
            // 遍历每一个属性,设置为响应式数据
            for (let i = 0; i < keys.length; i++) {
                defineReactive(obj, keys[i])
            }
        }
        
        // Vue重写了数组 push,popt,splice等有副作用的方法,当这些方法被调用的时候,会发送通知;
        observeArray (items: Array<any>) {
            for (let i = 0, l = items.length; i < l; i++) {
                observe(items[i])
            }
        }
    }
    // 为对象定义一个响应式的属性
    exprot function defineReactive (obj, key, val, ...) {
        // 创建依赖对象实例
        const dep = new Dep()
        // 获取 obj 的属性描述符对象
        const property = Object.getOwnPropertyDescriptor(obj, key)
        if (property && property.configurable === false) {
            return
        }
        
        // 提供预定义的存取器函数
        const getter = property && property.get
        const setter = property && property.set
        if ((!getter || setter) && arguments.length === 2) {
            val = obj[key]
        }
        // 判断是否递归观察子对象,并将子对象属性都转成 getter/setter, 返回观察对象
        let childOb = !shallow && observe(val)
        
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: function reactiveGetter () {
                // 收集依赖,为每个属性收集依赖,如果这个属性的值是对象,也要为这个子对象收集依赖
                // 如果预定义的 getter 存在则 value 等于 getter 调用的返回值
                // 否则直接赋值属性值
                const value = getter ? getter.call(obj) : val
                // 如果存在当前依赖目标,即 watcher 对象,则建立依赖
                if (Dep.target) {
                    dep.depend()
                    // 如果子观察目标存在,建立子对象的依赖关系
                    if (childOb){
                        childOb.dep.depend()
                        // 如果属性是数组,则特殊处理收集数组对象依赖
                        if (Array.isArray(value)) {
                            dependArray(value)
                        }
                    }
                }
                return value // 返回属性值
            },
            set: function reactiveSetter (newVal) {
                const value = getter ? getter.call(obj) : val // 同上
                // 如果新值等于旧值或者新值旧值为NaN则不执行
                if (newVal === val || (newVal !== newVal && value !== value)) {
                    return       
                }
                if (getter && !setter) return // 如果没有 setter 直接返回
                // 如果预定义setter存在则调用,否则直接更新新值
                if (setter) {
                    setter.call(obj, newVal)
                } else {
                    val = newVal
                }
                // 如果新值是对象,观察子对象并返回 子的 observer 对象
                childOb = !shallow && observe(newVal)
                // 派发更新(发布更改通知)
                dep.notify()
            }
        
        })
    }

4.依赖收集

  • 首先调用Watcher对象的get方法,在get方法中调用pushTarget,把当前的watcher记录到Dep.target属性中
  • 访问data中成员的时候收集依赖,当访问这个属性值的时候会触发defineReactive的getter,在getter中收集依赖
  • 把属性对应的watcher对象添加到dep的subs数组中,也就是为属性收集依赖
  • 如果这个属性的值也是对象,要创建childOb,给子对象收集依赖,目的是子对象添加和删除成员时发送通知
// src/core/observer/dep.js
export default class Dep {
    static target: ?Watcher; // 静态属性,watcher 对象
    id: number; // dep 实例 Id
    subs: Array<Watcher>; // dep 实例对应的 watcher 对象/订阅者数组
    
    constructor () {
        this.id = uid++
        this.subs = []
    }
    
    // 添加新的订阅者 watcher 对象
    addSub (sub: Watcher) {
        this.subs.push(sub)
    }
    // 移除订阅者
    removeSub (sub: Watcher) {
        remove(this.subs, sub)
        // this.subs.splice(this.subs.indexOf(sub), 1)
    }
    // 将观察对象和 watcher 建立依赖
    depend () {
        if (Dep.target) {
            // 如果 target 存在,把 dep 对象添加到 watcher 的依赖中
            Dep.target.addDep(this)
        }
    }
    // 发送通知
    notify () {
        const subs = this.subs.slice()
        ...
        // 调用每个订阅者的update方法实现更新
        for (let i = 0, l = subs.length; i < l; i++) {
            subs[i].update()
        }
    }
}
  1. Watcher(观察者)
  • 当数据发生变化的时候,会调用dep.notify()发送通知,再调用wacher对象的update()方法
  • 在update()方法中会去调用queueWatcher()判断wacher是否被处理,如果没有的话添加到queue队列中,并调用flushSchedulerQueue()
3、虚拟 DOM 中 Key 的作用和好处。

key 的特殊属性主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

4、Vue 中模板编译的过程。
  1. 缓存公共的 mount 函数,并重写浏览器平台的 mount
  2. 判断是否传入了 render 函数,没有的话,是否传入了 template ,没有的话,则获取 el 节点的 outerHTML 作为 template
  3. 调用 baseCompile 函数
  4. 解析器(parse) 将模板字符串的模板编译转换成 AST 抽象语法树
  5. 优化器(optimize) - 对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化
  6. 通过 generate 将 AST 抽象语法树转换为 render 函数的 js 字符串
  7. 将 render 函数 通过 createFunction 函数 转换为 一个可以执行的函数
  8. 将 最后的 render 函数 挂载到 option 中
  9. 执行 公共的 mount 函数

我只是一穷小子
6 声望0 粉丝

« 上一篇
Part3·模块一