new Vue 发生了什么?

前端精髓

共 3744字,需浏览 8分钟

 ·

2022-04-07 23:06


const app = new Vue({  template: '
hello world
'
,
el: '#app'})


Vue实际上是一个类

function Vue (options) {  if (!(this instanceof Vue)) {    // 必须通过new来调用    warn('Vue is a constructor and should be called with the `new` keyword')  }  // 注意options参数,是创建实例时候传入的对象  this._init(options)}


可以看到 Vue 只能通过 new 关键字初始化,然后会调用 this._init 方法。

Vue.prototype._init = function (options) {  const vm = this  vm._self = vm  // 这些代码之后再讲  initLifecycle(vm)  initEvents(vm)  initRender(vm)  callHook(vm, 'beforeCreate')  initInjections(vm) // resolve injections before data/props  initState(vm)  initProvide(vm) // resolve provide after data/props  callHook(vm, 'created')
// 先看这行代码 if (vm.$options.el) { vm.$mount(vm.$options.el) }}


先看$mount

// 先缓存之前的$mount方法const mount = Vue.prototype.$mount// 再重写$mount方法Vue.prototype.$mount = function (el) {  const options = this.$options  if (template) {    const { render, staticRenderFns } = compileToFunctions(template, {      shouldDecodeNewlines,      shouldDecodeNewlinesForHref,      delimiters: options.delimiters,      comments: options.comments    }, this)    options.render = render // render很重要,在后面会用到    options.staticRenderFns = staticRenderFns  }  return mount.call(this, el, hydrating) // 开始挂载}


注意compileToFunctions是把template转换成render函数,怎么转的看下下面

const compiler = require('vue-template-compiler')let str = `
999
`

console.log(compiler.compile(str))


发现包含ast和render这两个属性。注意这个render我们之后会用到

{  ast: {    type: 1,    tag: 'div',    attrsList: [],    attrsMap: { 'v-if': 'msg' },    rawAttrsMap: {},    parent: undefined,    children: [ [Object] ],    if: 'msg',    ifConditions: [ [Object] ],    plain: true,    static: false,    staticRoot: false,    ifProcessed: true  },  render: `with(this){return (msg)?_c('div',[_v("999")]):_e()}`,  staticRenderFns: [],  errors: [],  tips: []}


之前的$mount方法

Vue.prototype.$mount = function (el) {  el = el && query(el)  return mountComponent(this, el)}


终于来到了重点mountComponent方法

export function mountComponent () {  callHook(vm, 'beforeMount')  let updateComponent  updateComponent = () => {    const name = vm._name    const id = vm._uid    const startTag = `vue-perf-start:${id}`    const endTag = `vue-perf-end:${id}`
mark(startTag) const vnode = vm._render() // 重点 mark(endTag) measure(`vue ${name} render`, startTag, endTag)
mark(startTag) vm._update(vnode, hydrating) // 重点 mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */)}


mountComponent 核心就是先实例化一个渲染Watcher,在它的回调函数中会调用 updateComponent 方法,在此方法中调用 vm._render 方法先生成虚拟 Node,最终调用 vm._update 更新 DOM。


Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数,这块儿我们会在之后的章节中介绍。


Vue 的 _render 方法,它用来把实例渲染成一个虚拟 Node。

Vue.prototype._render = function () {  const vm = this  const { render, _parentVnode } = vm.$options  let vnode  try {    vnode = render.call(vm._renderProxy, vm.$createElement)  } catch (e) {}}


这段代码最关键的是 render 方法的调用,我们在平时的开发工作中手写 render 方法的场景比较少,而写的比较多的是 template 模板,在之前的 mounted 方法的实现中,会把 template 编译成 render 方法,但这个编译过程是非常复杂的,我们不打算在这里展开讲,之后会专门花一个章节来分析 Vue 的编译过程。


在 Vue 的官方文档中介绍了 render 函数的第一个参数是 createElement

render: function (createElement) {  return createElement('div', {     attrs: {        id: 'app'      },  }, this.message)}


再回到 _render 函数中的 render 方法的调用:

vnode = render.call(vm._renderProxy, vm.$createElement)


可以看到,render 函数中的 createElement 方法就是 vm.$createElement 方法。实际上,vm.$createElement 方法定义是在执行 initRender 方法的时候。


export function initRender (vm) {  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)}

结合我们之前的render


浏览 11
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报