vue运行机制

vue运行机制

vue运行机制_第1张图片

vue核心的执行过程主要分为这几个阶段:

1)模板编译:生成可复用的render函数

2)响应式:通过Object.definedProperty监听到对象属性的get和set,实现双向绑定

3)初始渲染:执行render函数,访问data中的值,会被get监听,调用patch方法生成vdom

4)数据改变:数据发生改变会触发set,会进行更新re-render,通过patch方法对新旧vnode对比,更新视图

Vue 如何解析模板

  • 模板是什么

    • 本质:模板就是字符串

    • 与html格式很像,但是模板中是有逻辑的,可以嵌入JS变量,如v-if, v-for等

    • 视图最终还是需要由模板生成 html 来显示

    • 模板必须先要转换成JS代码

      • 有逻辑(v-if, v-for),必须用JS才能实现(图灵完备)

      • 转换为html渲染页面,必须用JS才能实现

      • 因此,模板要转换成render函数

  • render函数

    • render函数包含了模板中所有的信息,返回 vnode,解决了模板中的逻辑(v-if, v-for)问题

    • 如何找到最终生成的render函数
      找到vue源码,src/compiler/codegen/index.js,generate函数的render返回值

  • render函数与vdom

    • 模板生成 html:vm._c

    • vm._c 和 snabbdom 中的 h 函数的实现很像,都是传入标签,属性,子元素作为参数

    • Vue.js 的 vdom 实现借鉴了 snabbdom

    • updateComponent 中实现了 vdom 的 patch

    • 页面首次渲染执行 updateComponent

    • data 中每次修改属性,都会执行 updateComponent

Vue运行机制

第一步:解析模板成render函数:

  • 首先要知道render函数的生成是在打包的时候,为什么呢?webpack打包的时候我们使用到了vue-template-compiler这个loader,它的作用是将template编译成render函数,所以说编译是第一步;响应式监听是在代码执行的时候。

  • 编译过程compile分为parse,optimize,generate三部分

    • parse:将template解析成抽象语法树

    • optimize:每次重新渲染中,DOM中有一部分是不需要改变的,我们称之为static sub-trees,这一部分可以分离出来存储成常量,然后re-render的过程中也不再去渲染它,以及后边的patch过程也不再管它,比如我们常见的html的header等基本不会改变。

    • generate:generate过程会根据ast生成虚拟dom树,即vnode(在代码中是${code}),另外还会生成optimize中的静态树。

    function generate (
      ast: ASTElement | void,
      options: CompilerOptions
    ): CodegenResult {
      const state = new CodegenState(options)
      const code = ast ? genElement(ast, state) : '_c("div")'
      return {
        render: `with(this){return ${code}}`,
        staticRenderFns: state.staticRenderFns
      }
    }
    

vue demo

<div id="app">
  <p>普通属性:{{ message }}</p>
  <p>{{msg()}}</p>
  <p>{{ct}}</p>
  <input v-model="message">
  <div v-for="item in items">
      {{ item.text }}
    </div>
    <button v-on:click="bindClick">点我抓同伟</button>
</div>

// js
new Vue({
  el: '#app',
  data: {
    message: '以vue的名义',
    items: [{
        text: '达康书记'
    }, {
        text: '育良书记'
    }]
  },
  methods: {
    bindClick: function() {
        this.message = '这就抓同伟去';
    },
    msg: function() {
        return this.message + "这个方法每次都会执行";
    }
  },
  computed: {
    ct: function() {
        return this.message + "计算属性并不会每次都执行";
    }
  }
})

对应的render函数

with(this) {
    return _c('div', {
        attrs: {
            "id": "app"
        }
    },
    [_c('p', [_v("普通属性:" + _s(message))]), _v(" "), _c('p', [_v(_s(msg()))]), _v(" "), _c('p', [_v(_s(ct))]), _v(" "), _c('input', {
        directives: [{
            name: "model",
            rawName: "v-model",
            value: (message),
            expression: "message"
        }],
        domProps: {
            "value": (message)
        },
        on: {
            "input": function($event) {
                if ($event.target.composing) return;
                message = $event.target.value
            }
        }
    }), _v(" "), _l((items),
    function(item) {
        return _c('div', [_v("\n\t\t  " + _s(item.text) + "\n\t   ")])
    }), _v(" "), _c('button', {
        on: {
            "click": bindClick
        }
    },
    [_v("点我出奇迹抓同伟")])], 2)
}
  • 模板中的data信息都变成了js变量

  • 模板中的v-model,v-if,v-for变成了js中的逻辑

  • render函数返回虚拟树vnode,详情见 https://www.jianshu.com/p/fc0084c97e05

第二步:响应式开始监听

  • 通过Object.definedProperty监听到对象属性的get和set,该过程封装在Observer中

  • 将data的属性代理到vm上

第三步:首次渲染,显示页面,且绑定依赖

  • 初次渲染,执行updateComponent(负责patch),执行vm._render()

  • 执行render函数,会访问data中的数据,访问时会被响应式的get监听到,get中会收集依赖(watcher)添加到消息订阅器(Dep)中

  • 执行updateComponent,会走到dom的patch方法中

  • patch会将vdom渲染成真实dom,初次渲染完成

  • 疑问:为什么要监听get,而不是set?

    • 因为data中的很多属性,有些被用到,有些没被用到

    • 只有被用到的属性才会走get

    • 如果没走get,那么set的时候也不用关心

    • 这样可以避免不必要的渲染

第四步:data发生变化,会触发re-render

  • data发生改变,set监听到

  • set中执行updateComponent

  • updateComponent重新执行vm._render()

  • 新生成的vnode和之前的vnode对比,通过patch进行对比

  • 将差异渲染到html

参考:
https://www.cnblogs.com/dora-zc/p/11111813.html#vue-%E5%A6%82%E4%BD%95%E8%A7%A3%E6%9E%90%E6%A8%A1%E6%9D%BF

你可能感兴趣的:(vue)