参考:
Vue技术解密
面试官:Vue实例挂载的过程
vue中Runtime-Compiler和Runtime-only的区别
对不同构建版本的解释
如有谬误,欢送指出~
更多学习笔记请戳:https://github.com/6fa/WebKno...
本文目录:
1.源码外围目录
2.源码构建
3.Runtime-Only 和 Runtime-Compiler
4.入口(runtime+compiler模式)
1.源码外围目录
Vue2源码可查看官网github:https://github.com/vuejs/vue
源码次要放在src目录:
src├── compiler # 编译相干 ├── core # 外围代码 ├── platforms # 不同平台的反对├── server # 服务端渲染├── sfc # .vue 文件解析├── shared # 共享工具代码compiler
- 寄存编译相干代码,包含
- 将模板解析为AST树(形象语法树)、AST树优化、代码生成
core
- 外围代码,包含
- 内置组件、全局 API 封装、工具函数
- Vue实例化、响应式、虚构DOM
platforms
- vue.js的入口
- 2 个目录代表 2 个次要入口,别离打包成运行在 web 上和 weex 上的 Vue.js
server
- 服务端渲染相干
sfc
- 把.vue文件解析成一个js对象
shared
- 一些工具,会被浏览器端Vue.js和服务端Vue.js共享
2.源码构建
在源码的package.json文件中能够看到,构建代码的入口是scripts/build.js:
{ //... "scripts": { //... "build": "node scripts/build.js", "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer", "build:weex": "npm run build -- weex", //... }}scripts/build.js的重点代码:
- 先从配置文件(config)读取配置,再通过命令行参数对构建配置(builds)做过滤,以构建出不同用处的Vue.js
- build()外面通过rollup(打包工具)来构建
//scripts/build.jslet builds = require('./config').getAllBuilds()// filter builds via command line arg// 通过比对命令行参数对builds过滤if (process.argv[2]) { const filters = process.argv[2].split(',') builds = builds.filter(b => { return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1) })} else { // filter out weex builds by default // 默认状况下过滤掉weex构建 builds = builds.filter(b => { return b.output.file.indexOf('weex') === -1 })}build(builds)再来看一下config配置文件:
// script/config.js//配置是遵循 Rollup 的构建规定的const builds = { // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify 'web-runtime-cjs': { entry: resolve('web/entry-runtime.js'), //构建入口 dest: resolve('dist/vue.runtime.common.js'), //构建后的文件地位 format: 'cjs', //构建格局,cjs示意遵循CommonJS,es则ES Module,umd则UMD标准 banner }, // Runtime+compiler CommonJS build (CommonJS) 'web-full-cjs': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.common.js'), format: 'cjs', alias: { he: './entity-decoder' }, banner }, //...}3.Runtime-Only 和 Runtime-Compiler
编译器(compiler):
用来将模板字符串编译成为 JavaScript 渲染函数的代码
运行时(runtime):
用来创立 Vue 实例、渲染并解决虚构 DOM 等的代码。基本上就是除去编译器的其它所有
runtime + compiler(完整版):
如果写了template,那么须要在运行时将template编译成render函数(生成虚构DOM)。因为在 Vue2中,最终渲染都是通过 render 函数,如果写 template 属性,则须要编译成 render 函数,则须要compiler。
// 须要编译器new Vue({ template: '<div>{{ hi }}</div>'})// 不须要编译器new Vue({ render (h) { return h('div', this.hi) }})runtime only(只有运行时):
须要借助webpack的vue-loader将.vue文件编译成js文件:vue文件外部的模板会在构建时预编译成 JavaScript。(此时main.js里的new Vue选项仍然不能够写template属性,只有.vue文件里能够写)
因为不须要编译器compiler了,所以体积缩小了约30%,应该尽可能应用runtime only版本。
4.入口(runtime+compiler模式)
runtime+compiler模式入口文件地位:src/platforms/web/entry-runtime-with-compiler.js
代码的重点是:
- 引入了Vue(vue的入口为./runtime/idex)
- 在Vue的原型上定义了$mount办法
- 导出Vue
import config from 'core/config'import { warn, cached } from 'core/util/index'import { mark, measure } from 'core/util/perf'import Vue from './runtime/index'import { query } from './util/index'import { compileToFunctions } from './compiler/index'import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'//...//定义$mount办法const mount = Vue.prototype.$mountVue.prototype.$mount = function (){......}//...Vue.compile = compileToFunctionsexport default Vue4.1 Vue入口
下面引入Vue的地位为:src/platforms/web/runtime/index.js
代码重点:
- 从core/index 引入Vue
- 对Vue对象进行一些拓展,比方定义了Vue原型上的根底$mount办法
从 core/index.js 又能够看到
- 从core/instance/index 导入了Vue对象,因而真正的Vue实例定义在core/instance/index
- 初始化全局API : initGlobalAPI(Vue)
// src/platforms/web/runtime/index.jsimport Vue from 'core/index'import config from 'core/config'import { extend, noop } from 'shared/util'import { mountComponent } from 'core/instance/lifecycle'import { devtools, inBrowser } from 'core/util/index'import { query, //......} from 'web/util/index'//......// install platform specific utilsVue.config.mustUseProp = mustUseProp//......// public mount methodVue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating)}//......export default Vue// core/indeximport Vue from './instance/index'import { initGlobalAPI } from './global-api/index'import { isServerRendering } from 'core/util/env'import { FunctionalRenderContext } from 'core/vdom/create-functional-component'initGlobalAPI(Vue)//......export default Vue4.1.1 Vue的定义
地位core/instance/index,重点:
- 定义了Vue构造函数(为什么不必class,是因为前面将Vue当参数传递,次要是给vue的原型拓展办法,这些拓展办法扩散在各个模块,如果是class则难以实现)
- xxMixin都是在Vue的prototype上拓展一些办法,比方_init办法就是在initMixin里增加的
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'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)}initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)export default Vue4.1.2 initGlobalAPI
下面的是给Vue.prototype拓展办法,则initGlobalAPI则是给Vue自身增加静态方法。
地位:src/core/global-api/index.js
//......export function initGlobalAPI (Vue: GlobalAPI) { // config const configDef = {} configDef.get = () => config if (process.env.NODE_ENV !== 'production') { configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } } Object.defineProperty(Vue, 'config', configDef) // Vue.util 裸露的办法最好不要依赖,因为它可能常常会发生变化,是不稳固的 Vue.util = { warn, extend, mergeOptions, defineReactive } Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 2.6 explicit observable API Vue.observable = <T>(obj: T): T => { observe(obj) return obj } Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // this is used to identify the "base" constructor to extend all plain-object // components with in Weex's multi-instance scenarios. Vue.options._base = Vue extend(Vue.options.components, builtInComponents) initUse(Vue) initMixin(Vue) initExtend(Vue) initAssetRegisters(Vue)}4.2 挂载
$mount办法重点:
- 如果定义了template或el,则将template或el的innerHTML 转换为 render函数
- 转换是通过 compileToFunctions 实现的
- 最初调用Vue原型上的共享$mount办法挂载(这么做是为了复用,runtime模式间接用,省却了下面转换成render函数的步骤。地位在src/platform/web/runtime/index.js)
- $mount办法理论会去调用mountComponent办法(定义在src/core/instance/lifecycle.js)
const mount = Vue.prototype.$mountVue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component { el = el && query(el) //... const options = this.$options // 将template 或者 el的outerHTML 转换为render函数 if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') { //template为字符串 if (template.charAt(0) === '#') { template = idToTemplate(template) //idToTemplate是通过id来获得元素的innerHTML //... } } else if (template.nodeType) { //template为节点 template = template.innerHTML } else { //有效template if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { //如果不存在template但有el template = getOuterHTML(el) } if (template) { //... // compileToFunctions 将template转为render函数 const { render, staticRenderFns } = compileToFunctions(template, { //...传递的选项 }, this) options.render = render options.staticRenderFns = staticRenderFns //... } } return mount.call(this, el, hydrating)}// src/platform/web/runtime/index.js// 可复用的共享$mountVue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating)}4.3 总结
runtime+compiler模式入口(src/platforms/web/entry-runtime-with-compiler.js)
从runtime/index引入vue(src/platforms/web/runtime/index.js)
从core/index 引入Vue
从core/instance/index 导入了Vue对象,因而真正的Vue实例定义在core/instance/index
- 定义了Vue构造函数
- 拓展Vue的prototype上一些办法
- 初始化全局API : initGlobalAPI(Vue)
- 对Vue对象进行一些拓展,比方定义了Vue prototype上的根底$mount办法
在vue原型上定义$mount(留神和共享的$mount办法不同)
- 将template转换为render函数,转换是通过 compileToFunctions 实现的
- 调用根底$mount办法(src/platform/web/runtime/index.js)
- 根底$mount办法理论会调用mountComponent办法(src/core/instance/lifecycle.js)