学习VUE源码(从入口开始)

237 阅读2分钟

从入口开始

src/platfroms/web/entry-runtime-with-compiler.js

通过源码解决这道面试题

<!-- examples/02-debug/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Runtime+Compiler</title>
</head>
<body>
  <div id="app">
    Hello World
  </div>

  <script src="../../dist/vue.js"></script>
  <script>
    // 如果同时设置template和render此时会渲染什么?
    const vm = new Vue({
      el: '#app',
      template: '<h1>Hello Template</h1>',
      render(h) {
        return h('h1', 'Hello Render')
      }
    })
  </script>
</body>
</html>

观察以上代码,通过阅读源码,回答在页面上输出的结果

entry-runtime-with-compiler.js 都做了哪些事情

可以看出 这里重写了vue原型上的 $mount 方法

重写 $mount 方法

  • 🔲 获取 el 对象,判断是否是body 或者 html
//./util/index.js

/* @flow */

import { warn } from 'core/util/index'

export * from './attrs'
export * from './class'
export * from './element'

/**
 * Query an element selector if it's not an element already.
 */
export function query (el: string | Element): Element {
  if (typeof el === 'string') {
    const selected = document.querySelector(el)
    if (!selected) {
      process.env.NODE_ENV !== 'production' && warn(
        'Cannot find element: ' + el
      )
      return document.createElement('div')
    }
    return selected
  } else {
    return el
  }
}

找到el这个选择器对应的 DOM元素,如果没有找到,且当前处于开发环境,控制台打印警告,并且返回一个div元素。

拿到el 对应的 DOM元素之后判断是否是 body 和 html

// entry-runtime-with-compiler.js

// el 不能是 body 或者 html
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }
  • 🔲 判断 options 有无传入 render
  • 🔲 没有 则把template 转成 render函数
  • 🔲 有 则直接调用 mount 挂载 DOM
...
const options = this.$options
// resolve template/el and convert to render function
// 把 template/el 转换成 render 函数
if (!options.render) {
  ...
}
// 调用 mount 方法,渲染 DOM
return mount.call(this, el, hydrating)
...

调试代码

<!-- examples/02-debug/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Runtime+Compiler</title>
</head>
<body>
  <div id="app">
    Hello World
  </div>

  <script src="../../dist/vue.js"></script>
  <script>
    // 如果同时设置template和render此时会渲染什么?
    const vm = new Vue({
      el: '#app',
      template: '<h1>Hello Template</h1>',
      render(h) {
        return h('h1', 'Hello Render')
      }
    })
  </script>
</body>
</html>

运行到浏览器打开控制台,打开代码地图,找到src下platform/web 中的 entry-runtime-with-compiler.js 打断点

Call Stack 中可以看到,$mount 在vue的 _init 函数中被调用,_init 函数 在Vue 的构造函数中被调用, 构造函数在 new Vue实例的时候触发。

F10 进行下一步,这里el 对应的DOM 不是body也不是html,if 判断不会成立。

接下来判断 options中是否传入了render 函数, F10 继续

new Vue的时候有传入 render ,故而 if 判断不成立,直接执行mount函数挂载 DOM

按F10继续,mount函数的执行之后页面发生了视图的变化

总结

通过阅读源码以及调试代码的过程可以得到

  1. el 不能是html 和 body
  2. 如果没有render函数,将template转化为 render函数
  1. 如果有render函数, 直接调用mount挂载DOM
// 1. el 不能是 body 或者 html
if (el === document.body || el === document.documentElement) {
  process.env.NODE_ENV !== 'production' && warn(
  `Do not mount Vue to <html> or <body> - mount to normal elements
  instead.`
  )
  return this
}
const options = this.$options
if (!options.render) {
  // 2. 把 template/el 转换成 render 函数
  ……
}
// 3. 调用 mount 方法,挂载 DOM
return mount.call(this, el, hydrating)

下一章

通过调试入口文件 entry-runtime-with-compiler.js 已经找出Vue的构造函数的位置

// 构造函数的位置

src/core/instance/index.js

接下来继续阅读源码梳理Vue初始化的过程以及Vue实例的成员以及静态成员从哪里被定义的。