vue源码解析-$mount

上一篇中,我们一起探讨了new Vue({...})背后发生了什么。那么当我们实例化vue之后,进行dom挂载又发生了什么呢?

image.png

细心的同学会发现:$mount方法在多个文件中被定义,如:

  • src/platform/web/entry-runtime-with-compiler.js
  • src/platform/web/runtime/index.js
  • src/platform/weex/runtime/index.js

之所以有多个地方,是因为$mount实现是和平台、构建方式都相关的
下面,我们选择compiler版本分析

一. $mount 主干代码如下:

Vue.prototype.$mount = function(el?: string | Element, hydrating?: boolean): Component {
  el = el && query(el)
  // query方法,实际上是对el参数做了一个转化,el可能是string 或者 element。如果是string,将返回document.querySelector(el) 
  // ...
  const options = this.$options

  if (!options.render) {
    // render函数不存在
    let template = options.template
    
    if (template) {
       // 如果存在template配置项:
       // 1. template可能是"#xx",那么根据id获取element内容
       // 2. 如果template存在nodeType,那么获取template.innerHTML 内容
    }else {
       // 如果template配置项不存在template,但是存在el:
      /*  
       * 例如: new Vue({ 
       *       el: "#app",
      *       ...
       *  })
       *
       */
      // 那么根据el获取对应的element内容
    }

    // 经过上面的处理,将获取的template做为参数调用compileToFunctions方法
    // compileToFunctions方法会返回render函数方法,render方法会保存到vm.$options下面
    const { render, staticRenderFns } = compileToFunctions(template, {...})
    options.render = render
  }
  return mount.call(this, el, hydrating)
}

从主干代码我们可以看出做了以下几件事

  • 由于el参数有两种类型,可能是string 或者 element,调用query方法,统一转化为Element类型
  • 如果没有手写render函数, 那么先获取template内容。再将template做为参数,调用compileToFunctions方法,返回render函数。
  • 最后调用mount.call,这个方法实际上会调用runtime/index.js的mount方法

注:

  1. vue compiler分别2个版本:一个是构建时版本,即我们使用vue-loader + webpack。另一个版本是:运行时版本,运行的时候,再去compiler解析。我们这里分析的是 运行时
  2. vue最终只认render函数,所以如果我们手动写render函数,那么就直接调用mount.call。反之,vue会将template做为参数,运行时调用compileToFunctions方法,转化为render函数,再去调用mount.call方法。
  3. 如果是构建时版本,vue-loader + webpack,会先将我们本地的代码转化成render函数,运行将直接调用mount.call。生产环境,我们推荐构建时的版本。个人学习推荐运行时版本。
  4. mount.call方法,实际上会调用runtime/index.js下面的$mount方法,而这个方法很简单,将会调用mountComponent方法。

二. mountComponent 主干代码如下:

export function mountComponent(vm: Component, el: ?Element, hydrating?: boolean): Component {
  // ...
  // 调用beforeMount生命周期函数
  callHook(vm, 'beforeMount')
  // ...
  // 定义updateComonent方法
  let updateComponent = () => {
      vm._update(vm._render(), hydrating)
  }
  
  // ...
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true)
  // ...
  // 调用生命周期函数mounted
  callHook(vm, 'mounted')
}

Watch类相关代码

Watch类有许多逻辑,这里我们只看和$mount相关的:

class Watcher {
    constructor(
        vm: Component,
        expOrFn: string | Function,
        cb: Function,
        options?: ?Object,
        isRenderWatcher?: boolean
    ){
        // ...
        if (typeof expOrFn === 'function') {
          this.getter = expOrFn
        }else {
          // ...
        }

        // ...
        this.value = this.lazy ? undefined : this.get()
    }

    get() {
      // ...
      value = this.getter.call(vm, vm)
      // ...
      // cleanupDeps方法后面我们会分析,这个在性能优化上比较重要
      return value;
    }
}

从上面代码,可以看出:

  • 先调用beforeMount钩子函数
  • 将updateComponent方法做为参数,实例化Watch。Watch在这个有2个作用:
    1、初始化的时候会执行回调函数
    2、当 vm 实例中的监测的数据发生变化的时候执行回调函数
    这里,我们先看第1个。 第2个将在数据变化监测章节分析
    执行回调后,我们看到vm._update(vm._render(), hydrating)方法,这个方法分2个步骤:
    (1) 执行render方法,返回最新的 VNode节点树
    (2) 调用update方法,实际上进行diff算法比较,完成一次渲染
  • 调用mounted钩子函数

三. 总结

  1. options上无render函数,对template, el做处理,获取template内容。
  2. 调用compileToFunctions方法,获取render函数,添加到options.render上
  3. 调用mount.call,实际上是调用mountComponent函数
  4. 调用beforeMount钩子
  5. 实例化渲染watcher,执行回调
  6. 根据render函数获取VNode节点树 (其实是一个js对象)
  7. 执行update方法,实际上是patch过程,vue会执行diff算法,完成一次渲染
  8. 调用mounted钩子

在下面的章节,我们将陆续分析: 响应式,compileToFunctions, 虚拟DOM,以及patch
码字不易,多多关注~😽

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容