zoukankan      html  css  js  c++  java
  • 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里打断点就行

  • 相关阅读:
    一个简单XQuery查询的例子
    《Microsoft Sql server 2008 Internals》读书笔记第七章Special Storage(1)
    《Microsoft Sql server 2008 Internals》读书笔记第八章The Query Optimizer(4)
    《Microsoft Sql server 2008 Internal》读书笔记第七章Special Storage(4)
    SQL Server中SMO备份数据库进度条不显示?
    《Microsoft Sql server 2008 Internal》读书笔记第七章Special Storage(5)
    《Microsoft Sql server 2008 Internal》读书笔记第七章Special Storage(3)
    《Microsoft Sql server 2008 Internal》读书笔记第八章The Query Optimizer(2)
    省市三级联动的DropDownList+Ajax的三种框架(aspnet/Jquery/ExtJs)示例
    FireFox意外崩溃时的手工恢复命令
  • 原文地址:https://www.cnblogs.com/pengdt/p/12304007.html
Copyright © 2011-2022 走看看