Vue进阶之Vue2源码解析

news2025/3/2 0:53:50

Vue2源码解析

  • 源码解析
    • 目录解析
    • package.json
    • 入口
      • 查找入口文件
      • 确定vue入口
      • this.\_init_ 方法
      • $mount 挂载方法
      • Vue.prototype._render
      • Vue.prototype._update
      • Vue.prototype._patch

vue2
vue3

源码解析

目录解析

vue2.6之后的版本都做的是兼容Vue3的内容,2.6版本前的内容才是纯vue2的内容,这里看的V2.6.14版本代码

这个是一个单仓库的内容,并不是多包,因此packages目录这里只是工具的集合,具体的代码在src目录下

  • flow 类似ts的类型声明,flow目录下的每一个文件,都可以理解为一个workspace
    • compiler.js 类似ts的结构
    • modules.js
      module中定义的变量,包含这些属性,和ts一模一样,可以说就是ts
      就比如:
declare module 'source-map' {
  declare class SourceMapGenerator {
    setSourceContent(filename: string, content: string): void;
    addMapping(mapping: Object): void;
    toString(): string;
  }
  declare class SourceMapConsumer {
    constructor (map: Object): void;
    originalPositionFor(position: { line: number; column: number; }): {
      source: ?string;
      line: ?number;
      column: ?number;
    };
  }
}

就能引用 SourceMapConsumer 的功能:
在这里插入图片描述

  • .flowconfig
    name_mapper — compiler映射文件 类似前面讲到的alias
  • .circleci
    • config.yml 流水线的配置,自动化流水线
      是国外CI品牌,后面会通过git action去做的,当进行指定动作的触发的时候,比如push的时候,push到master代码后,就能自动执行后续的内容,后面内容就是自动化流水线的内容
version: 2

defaults: &defaults
  working_directory: ~/project/vue
  docker:
    - image: vuejs/ci #流水线配置是国外CI品牌

jobs:
  install:
    <<: *defaults
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-vue-{{ .Branch }}-{{ checksum "yarn.lock" }}
            - v1-vue-{{ .Branch }}-
            - v1-vue-
      - run: npm install #npm安装依赖
      - save_cache: #缓存
          key: v1-vue-{{ .Branch }}-{{ checksum "yarn.lock" }}
          paths:
            - node_modules/
      - persist_to_workspace:
          root: ~/project
          paths:
            - vue

  lint-flow-types:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/project
      - run: npm run lint #eslint检查
      - run: npm run flow #flow检查
      - run: npm run test:types #检查类型

  test-cover:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/project
      - run: npm run test:cover
      - run:
         name: report coverage stats for non-PRs
         command: |
           if [[ -z $CI_PULL_REQUEST ]]; then
             ./node_modules/.bin/codecov
           fi

  test-e2e:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/project
      - run: npm run test:e2e -- --env phantomjs

  test-ssr-weex:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/project
      - run: npm run test:ssr #打包成ssr
      - run: npm run test:weex #打包成weex

  trigger-regression-test:
    <<: *defaults
    steps:
      - run:
          command: |
            curl --user ${CIRCLE_TOKEN}: \
            --data build_parameters[CIRCLE_JOB]=update \
            --data build_parameters[VUE_REVISION]=${CIRCLE_SHA1} \
            https://circleci.com/api/v1.1/project/github/vuejs/regression-testing/tree/master

workflows:
  version: 2
  install-and-parallel-test:
    jobs:
      - install
      - test-cover:
          requires:
            - install
      - lint-flow-types:
          requires:
            - install
      - test-e2e:
          requires:
            - install
      - test-ssr-weex:
          requires:
            - install
      - trigger-regression-test:
          filters:
            branches:
              only:
                - "2.6"
                - regression-test
          requires:
            - test-cover
            - lint-flow-types
            - test-e2e
            - test-ssr-weex
  weekly_regression_test:
    triggers:
      - schedule:
          # At 13:00 UTC (9:00 EDT) on every Monday
          cron: "0 13 * * 1"
          filters:
            branches:
              only:
                dev
    jobs:
      - install
      - test-cover:
          requires:
            - install
      - lint-flow-types:
          requires:
            - install
      - test-e2e:
          requires:
            - install
      - test-ssr-weex:
          requires:
            - install
      - trigger-regression-test:
          requires:
            - test-cover
            - lint-flow-types
            - test-e2e
            - test-ssr-weex

  • .github 提供的是谁提供的代码的贡献,要么是怎么提供一个commit代码的标准,社区贡献的一些资源
  • benchmarks:静态的站点/静态的说明
  • dist:打包时候的产物
  • examples:不同的模块的测试
  • flow:类型的处理
  • packages 和现在的monorepo还不一样,这个包可以理解为工具的库,
    • vue-template-compiler:vue模板的编译
  • scripts 一系列的node脚本
  • package.json:
    • scripts 这里的命令许多是针对scripts里的脚本去执行,因为vue2使用的是roll up,所以使用的是roll up去执行scripts里的各种各样的文件。
      在进行本地的开发,构建,部署的时候,我们的自定义脚本
  • src 重点
    • compiler 编译时,将vue templates最后转换为vue能识别的语言
    • core 核心代码,这里指的是runtime的核心代码,将模板编译之后的产物,在浏览器上运行。包含整体vue运行时的功能,也会包含vdom,diff
    • platforms 基于平台的,也就是host宿主,针对于浏览器web,weex端,兼容两端差异性
    • server 服务端,ssr去渲染的
    • sfc 单文件组件
      .vue文件的解析,找到对应的属性,转化成vue能够识别的语法
    • shared
      • constants 通用型的别名
      • utils 通用型的约束的定义
  • test 测试
  • types 类型 vue2使用的是flow,基本上包括了所有的类型,可能有人需要使用ts类型,vue2中使用的ts类型放在这里了
  • .babelrc babel工具
    代码编译的包,包含了vue怎么解析jsx的,怎么动态引用的
    babel/preset-env 能够在这里使用一些新的特性
  • .eslintrc 兼容的环境,ecma标准(es9),环境要支持到es6,全局中定义的环境变量
  • .flowconfig 类型的声明
  • .gitignore 忽略git提交的部分

package.json

  • config
    比较老一点版本的commit lint,进行一些commit的时候,就会执行cz-conventional-changelog,约束commit提交的标准,像feat,fix,docs等这些前缀
  • keywords:关键字
  • repository:代码仓库地址
  • lint-staged:
  • gitHooks:针对整个git生命周期做的
    • pre-commit:commit之前,校验代码规范,执行(lint-staged下的)eslint命令,并且git add
    • commit-msg:在git commit message的时候,node就会执行scripts/verify-commit-msg.js脚本,规范提交信息格式
      也可以用 git-cz 来做这个事情
  • files:代码提交之后,所有内容的产出
  • types:types类型的入口文件
  • unpkg:打包之后的文件,这里算是cdn的内容
  • jsdelivr:打包之后的内容
  • main:当前默认去引用的,umd的方式。
  • module:esm的方式
  • sideEffects:false,没有副作用
  • scripts:
    当前执行的时候,都是用的rollup的方式去做的
    rollup,一种打包工具
    • dev: rollup -w -c scripts/config.js --environment TARGET:web-full-dev
      读取的配置是:scripts/config.js,这里是打包的核心
      TARGET:能够读取到的环境变量
      –environment TARGET:注入的环境变量,就能通过process.env.TARGET找到这个环境变量,调用genConfig方法
      没有找到的话,就将getConfig这个方法透传出去

      if (process.env.TARGET) { //dev环境
        module.exports = genConfig(process.env.TARGET)
      } else { //build环境
        exports.getBuild = genConfig //将genConfig这个方法透传出去
        exports.getAllBuilds = () => Object.keys(builds).map(genConfig) //针对于所有打包的内容,都执行一遍genConfig方法,函数式编程的写法
      }
      

      genConfig:针对不同的版本,进行rollup的配置

      function genConfig(name) {
        //rollup的内容
        const opts = builds[name]
        const config = {
          input: opts.entry,
          external: opts.external,
          plugins: [
            flow(),//类型声明
            alias(Object.assign({}, aliases, opts.alias)) //别名的设置
          ].concat(opts.plugins || []), //加上参数传来的plugin
          output: {
            file: opts.dest,
            format: opts.format,
            banner: opts.banner,
            name: opts.moduleName || 'Vue'
          },
          onwarn: (msg, warn) => {
            if (!/Circular/.test(msg)) {
              warn(msg)
            }
          }
        }
      
        // built-in vars
        const vars = {
          __WEEX__: !!opts.weex,
          __WEEX_VERSION__: weexVersion,
          __VERSION__: version
        }
        // feature flags
        Object.keys(featureFlags).forEach(key => {
          vars[`process.env.${key}`] = featureFlags[key]
        })
        // build-specific env
        if (opts.env) {
          vars['process.env.NODE_ENV'] = JSON.stringify(opts.env)
        }
        config.plugins.push(replace(vars))
      
        if (opts.transpile !== false) {
          config.plugins.push(buble())
        }
      
        Object.defineProperty(config, '_name', {
          enumerable: false,
          value: name
        })
      
        return config
      }
      

      使用rollup可以进行一比一打包代码,使得打包体积变小

    • build:node scripts/build.js 执行的是build.js文件
      和刚说的区别在于,会调用bundle的generate,这样rollup会进行打包压缩,

      function buildEntry (config) {
        const output = config.output
        const { file, banner } = output
        const isProd = /(min|prod)\.js$/.test(file)
        return rollup.rollup(config)
          .then(bundle => bundle.generate(output))
          .then(({ output: [{ code }] }) => {
            if (isProd) {
              const minified = (banner ? banner + '\n' : '') + terser.minify(code, {
                toplevel: true,
                output: {
                  ascii_only: true
                },
                compress: {
                  pure_funcs: ['makeMap']
                }
              }).code
              return write(file, minified, true)
            } else {
              return write(file, code)
            }
          })
      }
      

      terser:js压缩工具

    • test:执行npm run lint,也就是执行 lint 里的命令

    • lint:执行eslint src scripts test,调用.eslintrc.js来匹配代码是否符合这里的规范

    • flow:类型检查

    • release:执行 scripts/release.sh脚本
      release.sh:

      #!/bin/bash
      set -e
      
      if [[ -z $1 ]]; then #找到这个版本
        echo "Enter new version: "
        read -r VERSION
      else
        VERSION=$1
      fi
      
      read -p "Releasing $VERSION - are you sure? (y/n) " -n 1 -r #是否要发布这个版本
      echo
      if [[ $REPLY =~ ^[Yy]$ ]]; then
        echo "Releasing $VERSION ..."
      
        if [[ -z $SKIP_TESTS ]]; then #执行指令
          npm run lint
          npm run flow
          npm run test:cover
          npm run test:e2e -- --env phantomjs
          npm run test:ssr
        fi
      
        # Sauce Labs tests has a decent chance of failing
        # so we usually manually run them before running the release script.
      
        # if [[ -z $SKIP_SAUCE ]]; then
        #   export SAUCE_BUILD_ID=$VERSION:`date +"%s"`
        #   npm run test:sauce
        # fi
      
        # build
        VERSION=$VERSION npm run build
      
        # update packages
        # using subshells to avoid having to cd back
        ( ( cd packages/vue-template-compiler
        npm version "$VERSION"
        if [[ -z $RELEASE_TAG ]]; then
          npm publish #执行发布
        else
          npm publish --tag "$RELEASE_TAG" #加上tag值
        fi
        )
      
        cd packages/vue-server-renderer
        npm version "$VERSION"
        if [[ -z $RELEASE_TAG ]]; then
          npm publish
        else
          npm publish --tag "$RELEASE_TAG"
        fi
        )
      
        # commit 执行git add,commit
        git add -A
        git add -f \
          dist/*.js \
          packages/vue-server-renderer/basic.js \
          packages/vue-server-renderer/build.dev.js \
          packages/vue-server-renderer/build.prod.js \
          packages/vue-server-renderer/server-plugin.js \
          packages/vue-server-renderer/client-plugin.js \
          packages/vue-template-compiler/build.js \
          packages/vue-template-compiler/browser.js
        git commit -m "build: build $VERSION"
        # generate release note
        npm run release:note
        # tag version
        npm version "$VERSION" --message "build: release $VERSION"
      
        # publish 发布
        git push origin refs/tags/v"$VERSION"
        git push
        if [[ -z $RELEASE_TAG ]]; then
          npm publish
        else
          npm publish --tag "$RELEASE_TAG"
        fi
      fi
      

入口

查找入口文件

  • package.json
    • main: “dist/vue.runtime.common.js”,
    • module: “dist/vue.runtime.esm.js”,
    • unpkg: “dist/vue.js”
    • scripts
      • build:“node scripts/build.js” 打包是从这里打包的,里面使用的是 config 里的builds,加上dev的TARGET:web-full-dev
  • scripts
    • config:
    'web-full-dev': {
        entry: resolve('web/entry-runtime-with-compiler.js'),
        dest: resolve('dist/vue.js'),
        format: 'umd', //打包成umd的格式
        env: 'development', //区分dev和prod
        alias: { he: './entity-decoder' },
        banner
      },
    
  • dist
    • vue.js 这个文件是 运行时和编译时在浏览器端进行的产物

关于config的builds配置项的解析:
通过resolve解析出是 src/platforms/web/entry-runtime-with-compiler.js,这也就是 vue2的入口文件
拓展:
web和weex区分:浏览器端和weex端
dev和prod区分:根据env来判断
moduleName:只有在service端需要这个部分,web端不需要
plugins:rollup引入的环境,支持到node端,commonJS端
banner:标题
runtime和compiler区分:runtime,vue本身真正能够运行的部分,compiler,将vue2的模板语言写法,转化成vue能够直接识别的语法

new Vue({
    data: xxx,
    template: '<div>{{name}}</div>'
})

通过 compiler 转换成 vue能识别的语法

// runtime,vue能够运行的:
new Vue({
    render(h) {
        return h('div', this.name)
    }
})

vue-cli 引用的项目中,在 webpack配置里,引入了 vue-loader,就会把vue模板给做这件事,vue本身是不会做这件事的

  • src
    • platforms
      • web
        • entry-runtime-with-compiler.js

runtime和compiler的入口,这里做的就是,将编译时候的方法compileToFunctions,和mount时候的方法在这里定义好了
这里做的是编译时的$mount
在这里插入图片描述

确定vue入口

vue的入口:

import Vue from 'vue'
  1. runtime下的Vue
  • src
    • platforms
      • web
        • runtime
          • index.js 上面 Vue引用的就是这个文件里的Vue

在这里插入图片描述
这里定义的$mount是非编译下的动作
在这里插入图片描述

  1. core下的Vue
    vue的核心代码:
  • src
    • core
      • index.js

在这里插入图片描述
initGlobalAPI:

  • src
    • core
      • global-api.js

在这里插入图片描述

  1. instance下的Vue —— Vue真正的入口
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

// Vue的入口就是一个关于Vue的构造函数
function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 核心:执行的这个方法
  this._init(options)
}

// 基于这个Vue所加的拓展
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

为什么Vue通过一个函数而不是通过一个class去做呢?
使用函数去做的话,就可以通过 Vue的原型Vue.prototype给他去注入,
但是通过这种方式注入的话,代码会太乱

this._init_ 方法

this._init函数定义的位置:

  • src
    • core
      • instance
        • init.js

在这里插入图片描述
这里定义的

initMixin:

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    // this指的是 new Vue的实例vm
    const vm: Component = this
    // a uid
    // 每初始化一次Vue,uid++,保证Vue节点进行区分
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    // 跳过不看
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    // 是我们当前实例的本身
    vm._isVue = true

    // merge options
    // 这里做的事是:初始化$options
    // options是 new Vue的时候传入的参数
    // _isComponent 传入的是Component的时候,这里已经是runtime的内容了
    // _isComponent:这个变量是在创建组件的时候,会注入这个变量
    if (options && options._isComponent) {
      // 是组件的情况
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      // 初始化组件内部的属性
      initInternalComponent(vm, options)
    } else {
      // 不是组件的情况,就是单一的节点了
      vm.$options = mergeOptions(
        // 当前自身相关的进行格式化
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    // 指向自身
    vm._self = vm
    // 初始化生命周期
    initLifecycle(vm)
    // 组件的监听
    initEvents(vm)
    // 就是$createElement,初始化render的方法
    initRender(vm)
    // 调用beforeCreate生命周期
    callHook(vm, 'beforeCreate')
    // 注入injected
    initInjections(vm) // resolve injections before data/props
    // 这里注入 data,methods,props
    initState(vm)
    // 注入provider
    initProvide(vm) // resolve provide after data/props
    // 调用created生命周期
    // beforeCreate和created之间的区别:1.created这里有provider, injected 2.data,props都有响应式了
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      // 有el元素,就通过$mount进行挂载-进行内容真实dom渲染 
      vm.$mount(vm.$options.el)
    }
  }
}

Vue初始化做的事:

  1. 合并了options配置
  2. 初始化生命周期
  3. 将events事件,render方法,provide,inject,data,props进行了响应式,并且调用了两个生命周期

$mount 挂载方法

  1. 编译时的mount
    src/platforms/web/entry-runtime-with-compiler.js
// 获取当前mount
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
    //找到模板
    let template = options.template
    if (template) {
      // 存在模板
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          // 进行模板的绘制
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      // 不存在模板则针对这个元素创建一个模板
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      // 通过compileToFunctions获取render的方法
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      // 原本的options是没有render的
      options.render = render
      // 这里是ssr的
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  // this:当前编译时所定义的mount
  // 这里的mount:runtime中导出来的mount
  return mount.call(this, el, hydrating) //这里可以理解为,将编译时的mount塞入到运行时的mount里面
  // 先定义的是运行时候的mount,然后拿着编译时候(将template转化为render时的方法,然后放到options里)塞到运行时的mount里面
}

编译原理动作:
(1)代码的parse,将模板转化为对象
(2)优化对象
(3)代码的生成
(4)最后返回纯js对象
将代码做了一下转换,转换成render的方法然后加进入

  1. web中的mount,公用的mount
    src/platforms/web/runtime/index.js
Vue.prototype.$mount = function (
  el?: any,
  hydrating?: boolean
): Component {
  //调用mountComponent方法
  return mountComponent(
    this,
    el && query(el, this.$document),
    hydrating
  )
}

mountComponent:
src/core/instance/lifecycle.js

export function mountComponent (
  vm: Component, //vue的实例
  el: ?Element, //要渲染的元素
  hydrating?: boolean //ssr:可以忽略
): Component {
  vm.$el = el //当前内容放到实例上
  // 这里按道理来讲是应该有render方法的
  if (!vm.$options.render) {
    // 没有render的话,这里就返回一个空的对象
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  // 这里执行beforeMount
  callHook(vm, 'beforeMount')

  // 组件不可能只渲染一次
  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    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)
    }
  } else {
    updateComponent = () => {
      // 调用实例中的_update方法,传入当前实例中_render方法
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  // 创建一个Watcher实例,传入vue实例,updateComponent方法(更新当前的组件)
  // 监听updateComponent的更新
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        // 初始化并且还没有被销毁
        callHook(vm, 'beforeUpdate') //调用beforeUpdate
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  // vm.$vnode为空,意味着没有渲染过
  if (vm.$vnode == null) {
    vm._isMounted = true // 设置当前实例为已挂载
    callHook(vm, 'mounted') //调用mounted,只能调用一次,后续要更新的话,在Watcher中调用更新的方法
  }
  return vm
}

首先,判断 render 有没有
存在,则进入 beforeMount 的阶段
然后,执行_update方法
定义 Watcher 监听的方法
最后再初始化的时候进入 mounted 阶段
=> beforeMount 和 mounted 之间,就定义了这样的一个Watcher,意味着监听的话,能够监听得到。在Watcher中,对元素进行了绘制的工作,对vm进行当前节点渲染

vm._update(vm._render(), hydrating) 这句话具体做了什么事情,则是接下来要掌握的内容。

Vue.prototype._render

src/core/instance/index.js 这个文件中
在这里插入图片描述
renderMixin(Vue)执行的方法,里面包含了 _render 方法

export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  installRenderHelpers(Vue.prototype)

  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }

  Vue.prototype._render = function (): VNode {
    // 获取当前实例
    const vm: Component = this
    // 调取render,_parentVnode(是组件的时候会有这个内容,没有这个的话就是根节点)
    const { render, _parentVnode } = vm.$options

    // 如果是组件的话,处理组件的内容
    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    // 定义了vnode
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // There's no need to maintain a stack because all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm
      // _renderProxy是渲染代理,然后获取$createElement渲染元素
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
        } catch (e) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } finally {
      currentRenderingInstance = null
    }
    // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }
}

render的事情,简单来说就是这句话:

// _renderProxy是渲染代理,然后获取$createElement渲染元素
vnode = render.call(vm._renderProxy, vm.$createElement)

这句话就会将元素渲染起来
这里的 $createElement是从createElement这里来的,最终从 vdom中的 createElement 得到
这个方法就是创建元素,因为当前是在浏览器端,这里能区分 weex 端还是 浏览器端,在这两端的环境下,创建element就会创建一个基础的元素节点(createEmptyVNode),这个基础的空节点既不是dom节点,也不是weex节点,它是一个对象节点
如果有子节点的话,会依次递归的调用子节点,最后生成一个完整的对象
=> 这里只需要知道,通过这个方法就能将当前的对象创建出来即可,得到的这个对象就能在浏览器端去渲染,在weex端去渲染
createElement
创建出来的就是这个东西:
在这里插入图片描述
_renderProxy是代理渲染:
src/core/instance/proxy.js

initProxy = function initProxy (vm) {
    if (hasProxy) {
      // determine which proxy handler to use
      const options = vm.$options
      // handlers对已有的render进行处理,getHandler是对render加了一些环境的判断
      const handlers = options.render && options.render._withStripped
        ? getHandler
        : hasHandler
      // 做了一个proxy,将vm创建过来,handlers就是render
      vm._renderProxy = new Proxy(vm, handlers)
    } else {
      vm._renderProxy = vm
    }
  }

可以将_renderProxy等同于调用了 vm 里面的 render

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

那么,这句话等同于

vnode = render($createElement)

这里createElement 就相当于是创建对象,就是虚拟dom的节点

src/core/vdom/create-element.js
在这里插入图片描述

<div>
   <p>
      <span></span>
   </p>
</div>

p和span对div来说都是children

将上面的代码转化为下面的对象:

const obj={
    name:"div",
    children:[
        {
            name:"p",
            children:[
                {
                    name:"span"
                }
            ]
        }
    ]
}

这样表达出来的好处是什么?
后续通过这个对象渲染成dom,weex都简单,这个是与平台无关的,因为vue消费的是这个对象。

Vue.prototype._update

src/core/instance/lifecycle.js

// vnode:render执行完创建出来的对象,
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    // vm._vnode:上一次的vnode节点,在_render中定义的
    const prevVnode = vm._vnode
    // 拿着上一次的_vnode和这次创建的vnode进行对比,更新
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      // 上一次的节点不存在的情况,这一次要进行初始化,patch就是比较的意思
      // $el元素:最后要渲染出来的元素
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      // 如果上一次节点存在的话,要拿上一次节点和这一次节点比较 —— diff的过程
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    // 加环境的判断
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    // 有没有当前的父节点,可跳过不看
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

update的动作——其实就是进行一次patch的动作

Vue.prototype._patch

src/platforms/web/runtime/index.js:

Vue.prototype.__patch__ = inBrowser ? patch : noop

src/platforms/web/runtime/patch.js:

export const patch: Function = createPatchFunction({ nodeOps, modules })

最终是通过createPatchFunction来比较的

src/core/vdom/patch.js: 这里是vue2的diff算法,双端比较

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2308145.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

unity lua属性绑定刷新

我们现在有一个 角色属性类叫heroModel,内容如下,当heroModel中的等级发生变化的时候&#xff0c;我们需要刷新界面显示等级信息&#xff0c;通常我们是在收到等级升级成功的协议的时候&#xff0c;发送一个事件&#xff0c;UI界面接受到这个事件的时候&#xff0c;刷新一下等级…

Ubuntu20.04安装Redis

目录 切换到root用户 使用 apt install redis 安装redis 修改配置文件 ​编辑 重新启动服务器 使用Redis客户端连接服务器 切换到root用户 如果没有切换到root用户的&#xff0c;切换到root用户。 使用 apt install redis 安装redis 遇到y/n直接y即可。 redis安装好之…

OSPF BIT 类型说明

注&#xff1a;本文为 “OSPF BIT 类型 | LSA 类型 ” 相关文章合辑。 机翻&#xff0c;未校。 15 OSPF BIT Types Explained 15 种 OSPF BIT 类型说明 Rashmi Bhardwaj Distribution of routing information within a single autonomous system in larger networks is per…

【深度学习】强化学习(RL)-A3C(Asynchronous Advantage Actor-Critic)

A3C&#xff08;Asynchronous Advantage Actor-Critic&#xff09;详解 A3C&#xff08;Asynchronous Advantage Actor-Critic&#xff09; 是 深度强化学习&#xff08;Deep Reinforcement Learning, DRL&#xff09; 领域的重要算法&#xff0c;由 DeepMind 在 2016 年提出。…

在 Mac mini M2 上本地部署 DeepSeek-R1:14B:使用 Ollama 和 Chatbox 的完整指南

随着人工智能技术的飞速发展&#xff0c;本地部署大型语言模型&#xff08;LLM&#xff09;已成为许多技术爱好者的热门选择。本地部署不仅能够保护隐私&#xff0c;还能提供更灵活的使用体验。本文将详细介绍如何在 Mac mini M2&#xff08;24GB 内存&#xff09;上部署 DeepS…

docker-compose部署onlyoffice8.3.0并支持ssl,且支持通过nginx代理,关闭JWT配置

编写docker-compose文件 mkdir -p /data/onlyoffice && echo "version: 3services:onlyoffice:container_name: OnlyOfficeimage: onlyoffice/documentserver:8.3.0restart: alwaysports:- 8088:80- 64431:443environment:TZ: Asia/ShanghaiJWT_ENABLED: falsevol…

【tplink】校园网接路由器如何单独登录自己的账号,wan-lan和lan-lan区别

老式路由器TPLINK&#xff0c;接入校园网后一人登录&#xff0c;所有人都能通过连接此路由器上网&#xff0c;无法解决遂上网搜索&#xff0c;无果&#xff0c;幸而偶然看到一个帖子说要把信号源网线接入路由器lan口&#xff0c;开启新世界。 一、wan-lan&#xff0c;lan-lan区…

DeepSeek开源周Day5压轴登场:3FS与Smallpond,能否终结AI数据瓶颈之争?

2025年2月28日&#xff0c;DeepSeek开源周迎来了第五天&#xff0c;也是本次活动的收官之日。自2月24日启动以来&#xff0c;DeepSeek团队以每天一个开源项目的节奏&#xff0c;陆续向全球开发者展示了他们在人工智能基础设施领域的最新成果。今天&#xff0c;他们发布了Fire-F…

[密码学实战]Java实现SM2数字信封(结合SM4对称加密)生成与解析

一、代码运行结果 二、什么是数字信封 2.1 基本概念 数字信封(Digital Envelope) 是一种结合对称加密与非对称加密的混合加密技术,通过以下步骤实现高效安全的数据传输: 对称加密:使用SM4算法加密原始数据,处理速度快,适合大数据量。非对称加密:使用SM2公钥加密SM4密…

Oracle 查询表空间使用情况及收缩数据文件

本文介绍Oracle收缩数据文件的相关操作&#xff0c;运维工作中有时会需要通过收缩数据文件来释放磁盘空间。 数据文件初始化方式&#xff1a; 1.我们创建表空间一般有两种方式初始化其数据文件&#xff0c;即指定初始大小为32G&#xff08;很大的值&#xff09;或指定初始大小为…

Grafana接入Zabbix数据源

1. 对接 Zabbix 1.1 安装 Zabbix 插件 在线安装&#xff1a; 1.2 配置 Zabbix 数据源 点击 Configuration > Data Sources > Add data source。选择 Zabbix&#xff0c;填写&#xff1a; URL&#xff1a;http://<zabbix-server>/api_jsonrpc.phpUsername&#x…

华为在不同发展时期的战略选择(节选)

华为在不同发展时期的战略选择&#xff08;节选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 来源&#xff1a;谢宁专著《华为战略管理法&#xff1a;DSTE实战体系》。本文有节选修改。 导言 从目前所取得的成就往回看&#xff0c;华为…

lua基础语法学习

lua基础语法学习 文章目录 lua基础语法学习1. 基础2. 输入输出3. 分支结构与循环结构4. 函数5. 元表与元方法6. 面向对象 1. 基础 注释 --单行注释--[[ 多行注释 --]]标识符 标识符以一个字母 A 到 Z 或 a 到 z 或下划线 _ 开头后加上 0 个或多个字母&#xff0c;下划线&…

【个人开发】deepspeed+Llama-factory 本地数据多卡Lora微调【完整教程】

文章目录 1.背景2.微调方式2.1 关键环境版本信息2.2 步骤2.2.1 下载llama-factory2.2.2 准备数据集2.2.3 微调模式2.2.3.1 zero-1微调2.2.3.2 zero-2微调2.2.3.3 zero-3微调2.2.3.4 单卡Lora微调 2.2.4 实验2.2.4.1 实验1&#xff1a;多GPU微调-zero12.2.4.2 实验2&#xff1a;…

【SpringBoot】数据访问技术spring Data、 JDBC、MyBatis、JSR-303校验

Spring Boot 数据访问技术及特性 目录标题 Spring Boot 数据访问技术及特性摘要1. 引言2. Spring Data架构与原理2.1 Spring Data概述2.2 Spring Data核心组件2.3 Spring Boot与Spring Data的集成机制 3. Spring Boot与JDBC的整合3.1 JDBC整合流程3.2 数据源自动配置3.3 JdbcTe…

手机放兜里,支付宝“碰一下”被盗刷?

大家好&#xff0c;我是小悟。 近期&#xff0c;网络上关于“支付宝‘碰一下’支付易被盗刷”的传言甚嚣尘上&#xff0c;不少用户对此心生疑虑。 首先&#xff0c;要明确一点&#xff1a;“碰一下”支付并不会像某些传言中所描述的那样容易被隔空盗刷。这一观点已经得到了支付…

vue框架后遗症∶被遗忘的dom操作

用多了vue、react等前端框架&#xff0c;不得不说用数据驱动视图来开发真的很香&#xff0c;但是也免不了会有不用这些框架的项目&#xff0c;dom操作还是很有必要的&#xff0c;一开始学习网页设计的时候就教过&#xff0c;后面一直开发项目基本上用框架。虽然有些想不起来了&…

基于深度学习+NLP豆瓣电影数据爬虫可视化推荐系统

博主介绍&#xff1a;资深开发工程师&#xff0c;从事互联网行业多年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的设计程序开发&#xff0c;开发过上千套设计程序&#xff0c;没有什么华丽的语言&#xff0c;只有…

快速列出MS Word中所有可用字体

Word中有很多字体&#xff0c;虽然在字体下拉列表中提供了字体的样例&#xff0c;但是并不全面&#xff0c;例如使用Batang字体的话&#xff0c;数字会显示成什么效果&#xff0c;就无法直观的看到。 打开Word应用程序&#xff0c;新建一个空白文档&#xff0c;按AltF11打开VBE…

【密码学实战】Java 实现 SM2 国密算法(签名带id、验签及 C1C3C2 加密解密)

前言 SM2是中国国家密码管理局发布的椭圆曲线公钥密码算法标准&#xff08;GB/T 32918&#xff09;&#xff0c;属于国密算法体系。与RSA和ECDSA相比&#xff0c;SM2在相同安全强度下密钥更短、计算效率更高。本文将介绍如何在Java中实现SM2的密钥生成、数字签名、验签、加密及…