Vue.js源码分析——页面首次渲染

最近在看Vue的响应式原理,记录下自己的总结,方便后续查看。
如果这篇文章能给你一些思考,不要吝啬的帮我点上👍 哦
要是写的有啥错误 ❌ ,也拜托大佬们不吝赐教

Vue初始化

下面是vue的初始化模板 , 这段代码很简单,最后会在页面输出 hello world,下面我们从new Vue 开始来分析下他是如何实现的

<template>
  <div>
    {{ message }}
  </div>
</template>
<script>
new Vue({
  data() {
    return {
      message: "hello world",
    };
  },
});
</script>

执行流程

new Vue 之前,Vue初始化实例成员和静态成员的地方我们就不过多赘述,直接从new Vue()开始`

Created with Raphaël 2.2.0 new Vue() Vue.prototype._init(option) vm.$mount() mountComponen(this, el, hydrating) new Wather() 结束

调试

new Vue的时候打了个断点,之后进入函数,去执行_init()方法

Vue.prototype._init

这个函数在/core/instance/init.js下面定义,里面初始化了一些vue的实例方法,其中关键的代码块为

 if (vm.$options.el) {
   vm.$mount(vm.$options.el)
 }

vm.$mount

这个函数在/platforms/web/entry-runtime-with-compiler.js里面定义,这个函数的核心作用是把模板编译成render函数 ,大致分为三个部分

  1. 首先判断如果el是body或者html标签,在开发环境会报警告。
  2. 之后去判断用户传递参数options.render函数,如果不传,解析模板
  3. return mount.call(this, el, hydrating)

下面重点看一下对于第二点的代码块,下面是对源码的简要摘录,主要是去理解首次渲染的流程

  if (!options.render) {
    let template = options.template
    // 如果模板存在
    if (template) {
      if (typeof template === 'string') {
      	// 如果模板是id选择器,例如我们传递的‘#app’
        if (template.charAt(0) === '#') {
          // 获取对应DOM对象的innerHMTL
          template = idToTemplate(template)
          /* 在生产环境对于空模板告警 */ 
        }
      } else if (template.nodeType) {
      	// 如果模板是dom元素,返回innerHTML
        template = template.innerHTML
      } else {
        /* 两种情况都不是,发出警告当前模板不合法,并且返回当前实例 */
        return this
      }
    } else if (el) {
      // 如果没有template,获取el的outerHTML作为模板
      template = getOuterHTML(el)
    }
	/**----------上面的一大段代码,其实就是根据不同的情况解析模板,给template赋值,最后template的值 都是对应dom的innnerHTM---------------L */
    if (template) {
      // 将模板转换成render函数
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      // 将render函数存储到options选项中去
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }

继续往下走会执行mount这个方法

// 调用mount方法渲染dom
return mount.call(this, el, hydrating)

mount

这个函数在/platforms/web/runtime/index.js里面定义,是对Vue.prototype.$mount的改写,这个函数的核心作用是渲染dom

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

mountComponent

这个函数在core/instance/lifecycle.js下面定义,主要分为几个步骤

  1. 首先判断当前选项是不是存在render函数,如果没有在生产的时候会发出警告⚠️( 这个判断的目的是如果我们当前是 运行时 环境,并且我们通过选项传入了模板,此时如果是开发环境会发出警告,提示当前使用的是运行时版本,编译器是无效的,应该传入render函数或者使用带有编译器的版本)
  2. 触发beforeMount生命周期函数
  3. updateComponent函数赋值
  4. 创建Watcher对象的时候传递了updateComponent
  5. 最后触发了mounted生命周期函数

这个函数的核心代码块是3,4步骤,下面我们根据源码分析下他具体做了什么

let updateComponent
// 如果是开发环境并启动了性能监测
if(process.env.NODE_ENV !== 'production' && config.performance && mark){
	...
}else{
  updateComponent = () => {
   // _update函数的作用是把虚拟dom转成真实dom,更新到界面上。
   // 注意此时是赋值并没有执行呢
    vm._update(vm._render(), hydrating)
  }
}
// 创建Watcher对象的时候传递了updateComponent,所以updateComponent的执行是在Watcher中调用的
new Watcher(vm, updateComponent, noop, {
  before () {
    if (vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'beforeUpdate')
    }
  }
}, true /* isRenderWatcher */)

接下来我们就看updateComponentnew Watcher里面什么时候调用的

new Watcher

这个函数在/core/observer/watcher.js中定义,这里提一下,我们在vue中的Watcher有三种,一种是渲染watcher,也就是我们当前创建的watcher。第二种是计算watcher。第三种监听器的watcher。剩下的两种watcher在以后的文章中进行分析

  1. this.getter赋值
    这个函数在开始创建了很多属性,然后执行一个判断,判断第二个参数expOrFn是否为函数, 首次渲染的时候,我们传递的是updateComponent,所以是expOrFn是函数,我们直接把这个函数赋值给getter
  if (typeof expOrFn === 'function') {
   this.getter = expOrFn
  } else {
    ...
  }
  1. this.value赋值
    之后给this.value赋值,当前是渲染watcher, this.lazy = false,也就是不延迟执行,所以就是把get()方法赋值给了this.value。下面看get()做了什么
   this.value = this.lazy
      ? undefined
      : this.get()
  1. 定义get()方法
 get () {
   // 把当前的watcher对象存入到栈中
   pushTarget(this)
   let value
   const vm = this.vm
   try {
     // 这个地方调用了updateComponent,并改变函数内部指向为vue实例
     // 也就是说,这个函数执行完,我们界面的数据就会完成更新
     value = this.getter.call(vm, vm)
   } catch (e) {
     ...
   } finally {
   		...  
   }
   return value
 }

总结

  1. Vue初始化,实例成员,静态成员
  2. new Vue()
  3. this._init()
  4. vm.$mount
    • 这是/platforms/web/entry-runtime-with-compiler.js的$mount, 这个函数的核心作用是帮我们把模板编译成render函数
    • 如果没有传递render,把模板编译成render函数
    • compileToFunction()生成render()渲染函数
    • options.render = render
  5. vm.$mount
    • 这个是/platforms/web/runtime/ind ex.js中的$mount
    • 调用mountComponent()
  6. mountComponent(this,el)
    • /core/instance/lifecycle.js中定义
    • 判断是否有render选项, 如果没有但是传入了模板,并且当前是开发环境会发出警告
    • 触发beforeMount
    • 定义 updateComponent
      • vm._update(vm.render(), ... )
      • vm.render()渲染,渲染虚拟DOM
      • vm._update()更新,将虚拟DOM转换成真实DOM
    • 创建Watcher实例
      • 传入了updateComponent方法
      • 调用get()方法
    • 触发mounted
    • return vm
  7. watcher.get()
    • 创建完watcher会调用一次get
    • 调用updateComponent()
    • 调用 vm.render()创建VNode
    • 调用 vm._update(vnode, ...)
  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值