Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
webVueBlog opened this issue Feb 22, 2023 · 0 comments
Open

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

webVueBlog opened this issue Feb 22, 2023 · 0 comments

Comments

@webVueBlog
Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant