ChickenDreamFactory / fe-chicken

✨✨✨ 集锦 前端JavaScript 手写题,编程题,Not just for interviews

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

94.vm.$mount(el)做了什么

webVueBlog opened this issue · comments

vm.$mount(el) 做了什么?

首先 el 必填。

如果 vm 提供了 render 函数,优先使用。

如果没有 render,再看是否提供了 template, 如果 template 是个选择器符,则获取对应 dom 的 innerHTML.

如果 template 不是选择器符,则把它编译成 render 函数。

如果 render 和 template 都没有,把 el 选择器对应的 dom 的 outerHTML 作为模版编译成 render 函数。

最终,运行 render 函数生成 vnode,交给 vm._update 执行 patch(vnode 的 diff 运算,并挂载到真实 Dom 上)

从Vue.js 2.0开始,它引入了虚拟DOM,将粒度调整为中等粒度,即一个状态所绑定的依赖不再是具体的DOM节点,而是一个组件。这样状态变化后,会通知到组件,组件内部再使用虚拟DOM进行比对。这可以大大降低依赖数量,从而降低依赖追踪所消耗的内存。—— 《深入浅出Vue.js》

mount 挂载

src/platform/web/runtime/index.js 文件中,看不带编译器的 $mount 是如何运行的。

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

获取需要挂载的 dom节点,然后调用 mountComponent 方法。我们继续转到src/core/instance/lifecycle.js 看 mountComponent。

function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el // 需要挂载的 dom 元素赋值给 $el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
  }
  callHook(vm, 'beforeMount') // 触发 beforeMount 钩子函数

  let updateComponent
  updateComponent = () => {
      // _update 中会调用 patch, _render 会生成 vdom 树
      vm._update(vm._render(), hydrating)
    }
  // updateComponent 会在 Watcher 实例化过程中被调用
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

如果没有提供 el 和 render 函数,则创建一个空的 vnode, 并触发 beforeMount 钩子。

updateComponent 函数用来比对 virtual dom,并更新dom节点。

接下来新建一个渲染 watcher,每个组件对应一个渲染 watcher,一个渲染 watcher 对应多个 dep。watcher 在实例化的过程中,会执行 updateComponent 函数,每个状态的 dep 都会把 watcher 添加到订阅列表中。当用户修改 vm.$data 上的属性时,对应的 dep 会调用 notify 方法,通知订阅的 watchers 依次 update,也就是更新组件。

入口文件

直接从完整版的 vue 的入口文件开始看,即 src/platform/web/entry-runtime-with-compiler.js。从命名上可以看出来,这是带有模版编译器的运行时版本。

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el) // document.querySelector 获取 dom 元素

  const options = this.$options
  // 如果没有 render 函数,则使用 template
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
            // 如果 template 是选择器符号,则获取对应 dom 的 innerHTML。
            // <script type="text/x-template>...</script>">
          template = idToTemplate(template)
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        return this
      }
    } else if (el) {
      // 如果没有 template,则把 el 的 outerHTML 作为 template
      template = getOuterHTML(el)
    }
    if (template) {
      // 将 template 转换成渲染函数
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  // 调用原先定义的 mount 方法挂载
  return mount.call(this, el, hydrating)
}

带有编译器的版本,扩展了 mount 方法。如果用户没有定义 render 函数,会尝试寻找 template 模版,如果 template 是选择器描述符,则获取内部的 html ,最终都是编译成 render 函数来初始化。

源码结构

src
├── compiler // 编译器,将 template 编译成 render 函数
├── core // Vue构造函数,一些静态方法,vdom和响应式原理
├── platforms // web/weex
├── server // 服务端渲染
├── sfc // *.vue 单文件组件的编译方法
└── shared // 公用的工具函数

响应式(双向绑定)

关于 vue 的响应式原理,我们在看到相关代码时,再具体分析。我们现在只需要知道它是通过Object.defineProperty() 定义存取器 getter 和 setter 来实现的。

Flow

Flow 是 facebook 出品的一个静态类型检查工具,它的语法和 Typescript 类似。vue 2.x 用了 Flow