• Vue源码(下篇)


    上一篇是mount之前的添加一些方法,包括全局方法gloal-api,XXXMixin,initXXX,然后一切准备就绪,来到了mount阶段,这个阶段主要是

    • 解析template
    • 创建watcher并存入Dep
    • 更新数据时更新视图

    Vue源码里有两个mount

    • 第一个
    // src/platform/web/runtime/index.js
    
    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
      el = el && inBrowser ? query(el) : undefined
      // 核心方法,往下看
      return mountComponent(this, el, hydrating)
    }
    
    • 第二个
    // src/platforms/web/entry-runtime-with-compiler.js
    
    // 把上面的第一个取出来,在最后一行执行
    const mount = Vue.prototype.$mount
    // 把原本的替换掉,这就是第二个mount
    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
      el = el && query(el)
    
      const options = this.$options
      
      if (!options.render) {
        let template = getOuterHTML(el)
        if (template) {
          // compileToFunctions,来自 compiler/index.js
          const { render, staticRenderFns } = compileToFunctions(template, {
            shouldDecodeNewlines,
            shouldDecodeNewlinesForHref,
            delimiters: options.delimiters,
            comments: options.comments
          }, this)
          options.render = render
          options.staticRenderFns = staticRenderFns
      }
      // 把第一个mount执行
      return mount.call(this, el, hydrating)
    }
    
    // compiler/index.js
    
    function compileToFunctions (
        template: string,
        options?: CompilerOptions,
        vm?: Component
      ): CompiledFunctionResult {
        options = options || {}
    
      // 编译,核心内容
      const compiled = compile(template, options)
    }
    

    第二个mount才是在init里真正被执行的,也就是在第一个mount之前先被执行,叫做compiler阶段

    compiler阶段,整个阶段全程只执行一次,目的就是生成render表达式

    • parse,将templat转成AST模型树
    • optimize,标注静态节点,就是选出没有参数的固定内容的html标签
    • generate,生成render表达式
    <div id="el">Hello {{name}}</div>
    
    // compile方法就是把 html 转为 AST,效果如下
    {
        type: 1,
        div: 'div',
        attrsList: [{
            name: 'id',
            value: ''el
        }],
        attrs: [{
            name: 'id',
            value: ''el
        }],
        attrsMap: {
            id: 'el'
        },
        plain: false,
        static: false,
        staticRoot: false,
        children: [
            type: 2,
            expression: '"hello "+ _s(name)',
            text: 'Hello {{name}}',
            static: false
        ]
    }
    
    // 由上面的AST生成 render表达式,
    with (this) {
        return _c(
            "div",
            {
                attrs: {id: 'el'}
            },
            [
                _v("Hello "+_s(name))
            ]
        )
    }
    
    // render-helpers 下 index.js
    
    export function installRenderHelpers (target) {
      target._o = markOnce
      target._n = toNumber
      target._s = toString
      target._l = renderList
      target._t = renderSlot
      target._q = looseEqual
      target._i = looseIndexOf
      target._m = renderStatic
      target._f = resolveFilter
      target._k = checkKeyCodes
      target._b = bindObjectProps
      target._v = createTextVNode
      target._e = createEmptyVNode
      target._u = resolveScopedSlots
      target._g = bindObjectListeners
    }
    // 怎么没有_c,_c在上篇笔记的initRender方法里
    // _c对应元素节点、_v对应文本节点、_s对应动态文本
    

    到这里执行第一个mount,也就是mountComponent方法

    // instance/lifecycle.js
    
    export function mountComponent (
      vm: Component,
      el: ?Element,
      hydrating?: boolean
    ): Component {
      vm.$el = el
      // 挂载前,执行beforMount
      callHook(vm, 'beforeMount')
      let updateComponent
      // 定义updateComponent,vm._render将render表达式转化为vnode,vm._update将vnode渲染成实际的dom节点
      updateComponent = () => {
          // 核心内容,理解为 
          // var A = vm._render(),生成vDom
          // vm._update(A, hydrating),生成真实dom
          vm._update(vm._render(), hydrating)
      }
      // 首次渲染,并监听数据变化,并实现dom的更新
      new Watcher(vm, updateComponent, noop, {
        before () {
          if (vm._isMounted) {
            callHook(vm, 'beforeUpdate')
          }
        }
      }, true /* isRenderWatcher */)
      hydrating = false
      // 挂载完成,回调mount函数
      if (vm.$vnode == null) {
        vm._isMounted = true
        callHook(vm, 'mounted')
      }
      return vm
    }
    

    看看watch构造函数

    export default class Watcher {
      constructor (
        vm: Component,
        expOrFn: string | Function,
        cb: Function,
        options?: ?Object,
        isRenderWatcher?: boolean
      ) {
        this.vm = vm
        // 上面的函数传了true
        if (isRenderWatcher) {
          vm._watcher = this
        }
        // 当数据发生改变,_watchers会被循环更新,也就是视图更新
        vm._watchers.push(this)
        if (typeof expOrFn === 'function') {
          this.getter = expOrFn
        }
        if (this.computed) {
          this.value = undefined
          this.dep = new Dep()
        } else {
          // 把expOrFn执行了,启动了初次渲染
          this.value = this.get()
        }
      }
      get () {
        return this.getter.call(vm, vm)
      }
    }
    

    最值得研究的patch,这个函数特别的长,下面是超简略版

    // src/core/vdom/patch.js
    
    export function createPatchFunction (backend) {
      ...
      // Vue.prototype.__path__ = createPatchFunction()
      return function patch (oldVnode, vnode, hydrating, removeOnly) {
        // 对比
        console.log(oldVnode)
        console.log(vnode)
        // 最后返回的就是真实的可以用的dom
        return vnode.elm
      }
    }
    

    不管是初始化渲染还是数据更新,都是把整个页面的render表达式重新渲染生成全部的vdom,进行新旧的对比,这个方法里还有最牛逼的domDiff算法,这里就不研究了,百度很多大佬解析

    上面出现的几个重要的方法

    • _render函数主要执行compiler阶段,最后返回vDom
    • patch,在core/vdom/patch.js里,主要功能将对比新旧vDom转换为dom节点,最后返回的就是dom
    • _update主要是当数据改变时调用了patch函数

    vue的整个实现流程

    • 给Vue函数添加很多的方法【Global-api,XXXMixin】
    • 对参数进行解析和监听【initXXX】
    • 启动mount阶段
    • _render函数把模版解析成AST,再解析成vnode
    • 初次渲染,执行_update,实际执行的是patch方法,patch将vDom渲染成DOM,初次渲染完成
    • data属性变化,_render通过AST再次生成新的vDom,通过_update里的patch进行对比,渲染到html中

    image.png

    最好的调试方法是下载vue.js文件,不要压缩版的,不用脚手架,然后在js里打断点就行

  • 相关阅读:
    教研室课题卫星通信系统
    html5学习笔记03. Canvas简介,Canvas的使用方法
    ARCGIS RUNTIME FOR IOS总结(一)
    ARCGIS RUNTIME FOR IOS总结(三)
    html5学习笔记05.JavaScript 中的面向对象,继承和封装
    JAVA排序算法之 选择排序
    ARCGIS RUNTIME FOR IOS总结(六)
    ASP upload
    问题一百三十:字符矩阵排序
    美妙的微机原理2013/5/1
  • 原文地址:https://www.cnblogs.com/pengdt/p/12304007.html
Copyright © 2020-2023  润新知