最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue.js 源码 (1)——如何开始学习

    正文概述 掘金(Camol)   2021-06-03   391

    前言

    最近在看 vue.js 2.x 的源码,后面会陆续的写一些笔记和大家分享和学习。

    前置条件

    Flow

    Flow 是 facebook 出品的一个静态类型检查工具,它的语法和 Typescript 类似。vue 2.x 用了 Flow,所以没接触过的同学可能需要先了解一下。官网地址: flow.org/ 。

    Virtual Dom

    在学习 vue.js 之前,我们需要先了解一下 Virtual Dom —— 虚拟DOM。

    目前,社区里有两个 virtual dom 库,一个是 virtual-dom , 另一个是 snabbdom 。前者已经有5年没更新了,后者还在更新,特别指出,vue 2.x 引入的 virtual dom 和 diff 算法就是基于 snabbdom 改造的。所以,建议没了解过 snabbdom 可以看看相关的文章。

    • 关于Virtual DOM理解和Snabbdom源码浅析
    • snabbdom 源码阅读分析

    响应式(双向绑定)

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

    准备工作

    从github上获取源码

    项目地址:github.com/vuejs/vue 。fork 一份到自己仓库,克隆到本地,从 dev 创建一个 study 的学习分支,方便写注释。

    源码结构

    我们先看一下vue源码的目录结构,vue 源码包含以下几个目录:

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

    后续我们主要需要研究的是 前三个 目录里的代码,即编译器、核心代码和 web 平台相关的代码。

    如何调试

    vue 2.x 是使用 rollup 编译的,添加 --sourcemap 编译选项,可以生成 sourcemap

    {
        "scripts": {
            "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"
        }
    }
    

    我们可以新建一个示例,打开 chrome 开发者工具中的 source 标签,设置断点来调试。下图的 src 就是开启了sourcemap 后出现的

    Vue.js 源码 (1)——如何开始学习

    简单示例:

    <div id="app>{{count}}</div>
    <script scr="xxx/vue.js"></script> // 为了方便看源码,引用没有压缩混淆过的 vue
    <script>
        var vm = new Vue({
            data: {
                count: 1
            }
        })
        
       vm.$mount("#app")
    
    </script>
    

    入口文件

    我们直接从完整版的 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 函数来初始化。

    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.jsmountComponent

    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,也就是更新组件。

    总结

    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 源码 (1)——如何开始学习

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元