参考:Vue技术解密面试官:Vue实例挂载的过程Vue中Runtime-Compiler和Runtime-only的区别不同构建版本的解释如有错误请指出out~更多学习注意事项请戳:https://github.com/6fa/WebKno...本文内容:1.源码核心目录2.源码构建3.Runtime-Only和Runtime-Compiler4.入门(运行时+编译模式)1.源码核心目录Vue2源码可以在官方github上查看:https://github.com/vuejs/vue源码主要放在src目录下:src├──compiler#编译相关├──core#核心代码├──platforms#不同平台支持├──server#服务端渲染├──sfc#.vue文件解析├──shared#共享工具代码compiler存放编译相关代码,包括将模板解析成AST树(抽象语法树)、AST树优化、code生成核心核心代码,包括内置组件、全局API封装、工具函数Vue实例化、响应式、虚拟DOMplatformsvue.js两个目录代表两个主要入口,分别打包成网页运行的Vue和weex。jsserver服务端渲染相关sfc将.vue文件解析成js对象共享一些工具,会被浏览器端Vue.js和服务端Vue.js共享2.源码构建可见源代码的package.json文件,构建代码的入口点是scripts/build.js:{//..."scripts":{//..."build":"nodescripts/build.js","build:ssr":"npmrunbuild--web-runtime-cjs,web-server-renderer","build:weex":"npmrunbuild--weex",//...}}scripts/build.js关键代码:从配置文件开始(config)读取配置,然后通过命令行参数过滤构建配置(builds)通过rollup(打包工具)构建不同用途的Vue.jsbuild()//scripts/build.jsletbuilds=require('./config').getAllBuilds()//通过命令行过滤构建arg//通过比较命令行参数过滤构建if(process.argv[2]){constfilters=process.argv[2].split(',')builds=builds.filter(b=>{returnfilters.some(f=>b.output.file.indexOf(f)>-1||b._name.indexOf(f)>-1)})}else{//默认过滤掉weex构建//默认过滤掉weex构建builds=builds.filter(b=>{returnb.output.file.indexOf('weex')===-1})}build(builds)看一下config配置文件://script/config.js//配置是constbuildsthatfollowRollup'sbuildrules={//Runtimeonly(CommonJS).由捆绑器使用,例如Webpack&Browserify'web-runtime-cjs':{entry:resolve('web/entry-runtime.js'),//构建入口dest:resolve('dist/vue.runtime.common.js'),//构建后文件所在位置format:'cjs',//buildformat,cjs表示遵循CommonJS,es表示ESModule,umd表示UMD规范banner},//Runtime+compilerCommonJSbuild(CommonJS)'web-full-cjs':{entry:resolve('web/entry-runtime-with-compiler.js'),dest:resolve('dist/vue.common.js'),格式:'cjs',alias:{he:'./entity-decoder'},banner},//...}3.Runtime-OnlyandRuntime-Compiler编译器(compiler):用于将模板字符串编译成JavaScript渲染函数的代码运行时(运行时):用于创建Vue实例、渲染和处理虚拟DOM等的代码基本上是除编译器之外的所有其他内容。runtime+compiler(fullversion):如果你写一个模板,你需要添加模板被编译成一个渲染函数(生成一个虚拟DOM)。因为在Vue2中,最终的渲染是通过render函数。如果写模板属性,需要编译成render函数,需要编译器。//需要编译器newVue({template:'
{{hi}}
'})//不需要编译器newVue({render(h){returnh('div',this.hi)}})runtimeonly(仅在运行时):需要使用webpack的vue-loader将.vue文件编译成js文件:vue文件里面的模板在构建的时候会预编译成JavaScript。(此时main.js中新的vue选项还是不能写template属性,只能写.vue文件)由于不需要编译器,体积减少了30%左右,runtimeonly版本应尽可能多地使用。4.入口(runtime+compilermode)Runtime+compilermode入口文件位置:src/platforms/web/entry-runtime-with-compiler.js代码的重点是:vue的引入(vue的入口是./runtime/idex)定义了Vue原型上的$mount方法,用于从'core/config'导出Vueimport配置import{warn,cached}from'core/util/index'import{mark,measure}from'core/util/perf'importVuefrom'./runtime/index'import{query}from'./util/index'import{compileToFunctions}from'./compiler/index'import{shouldDecodeNewlines,shouldDecodeNewlinesForHref}from'./util/compat'//...//定义$mount方法constmount=Vue.prototype.$mountVue.prototype.$mount=function(){...}//...Vue.compile=compileToFunctionsexportdefaultVue4.1以上Vue入口引入Vue的位置是:src/platforms/web/runtime/index.js代码重点:从core/index导入Vue扩展Vue对象,比如在Vueprototypefromcore上定义基本的$mount方法/索引.js还可以看到Vue对象是从core/instance/index导入的,所以真正的Vue实例是在core/instance/index定义的,用于初始化全局API:initGlobalAPI(Vue)//src/platforms/web/runtime/index.jsimportVuefrom'core/index'importconfigfrom'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'//...//安装特定于平台的utilsVue.config.mustUseProp=mustUseProp//...//publicmountmethodVue.prototype.$mount=function(el?:string|Element,hydrating?:布尔值):组件{el=el&&inBrowser?query(el):undefinedreturnmountComponent(this,el,hydrating)}//......exportdefaultVue//core/indeximportVuefrom'./instance/index'import{initGlobalAPI}from'./global-api/index'import{isServerRendering}from'core/util/env'import{FunctionalRenderContext}from'core/vdom/create-functional-component'initGlobalAPI(Vue)//...exportdefaultVue4.1.1定义位置Vue的是core/instance/index,关键点:定义了Vue的构造函数(为什么不用class,因为Vue后面会作为参数传递),主要是扩展Vue的原型,这些扩展方法分散在各个模块中,如果是类的话,很难实现)xxMixin是在Vue的原型上扩展了一些方法,比如在initMixin中增加了_init方法从'./init'导入{initMixin}从'./state'导入{stateMixin}从'./render'导入{eventsMixin}从'./events'导入{lifecycleMixin}从'./lifecycle'import{warn}from'../util/index'functionVue(options){if(process.env.NODE_ENV!=='production'&&!(thisinstanceofVue)){warn('Vue是一个构造函数,应该使用`new`关键字')}this._init(options)}initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)exportdefaultVue4.1.2上面的initGlobalAPI适用于Vue。原型扩展方法,initGlobalAPI是给Vue本身添加一个静态方法位置:src/core/global-api/index.js//......exportfunctioninitGlobalAPI(Vue:GlobalAPI){//configconstconfigDef={}configDef.get=()=>configif(process.env.NODE_ENV!=='production'){configDef.set=()=>{warn('不要替换Vue.config对象,而是设置单独的字段。')}}Object.defineProperty(Vue,'config',configDef)//Vue.util暴露的方法最好不要依赖,因为它可能经常会发生变化,是不稳定的Vue.util={warn,extend,mergeOptions,defineReactive}Vue.set=setVue.delete=delVue.nextTick=nextTick//2.6显式可观察APIVue.observable=
(obj:T):T=>{observe(obj)returnobj}Vue.options=Object.create(null)ASSET_TYPES.forEach(type=>{Vue.options[type+'s']=Object.create(null)})//这用于标识“基础”构造函数以扩展Weex的多组件中的所有普通对象//实例场景。Vue.options._base=Vue扩展(Vue.options.components,builtInComponents)initUse(Vue)initMixin(Vue)initExtend(Vue)initAssetRegisters(Vue)}4.2mount$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)constmount=Vue.prototype.$mountVue.prototype.$mount=function(el?:string|Element,hydrating?:boolean):Component{el=el&&query(el)//...constoptions=this.$options//将模板或el的outerHTML转换为arenderfunctionif(!options.render){lettemplate=options.templateif(template){if(typeoftemplate==='string'){//模板是一个字符串if(template.charAt(0)==='#'){template=idToTemplate(template)//idToTemplate是通过id获取元素的innerHTML//...}}elseif(template.nodeType){//template是一个节点template=template.innerHTML}else{//模板无效if(process.env.NODE_ENV!=='production'){warn('无效模板选项:'+template,this)}returnthis}}elseif(el){//如果有没有模板但是有eltemplate=getOuterHTML(el)}if(template){//...//compileToFunctions将模板转换为渲染函数const{render,staticRenderFns}=compileToFunctions(template,{//...passedoptions},this)options.render=renderoptions.staticRenderFns=staticRenderFns//...}}returnmount.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):undefinedreturnmountComponent(this,el,hydrating)}4.3总结Runtime+compilermodeentry(src/platforms/web/entry-runtime-with-compiler.js)importVuefromruntime/index(src/platforms/web/runtime/index.js)ImportVuefromcore/indexImportVuefromcore/instance/index导入了Vue对象,所以真正的Vue实例定义定义了core/instance/index中的Vue构造函数,扩展了Vue的原型。一些方法初始化了全局API:initGlobalAPI(Vue)扩展了Vue对象,比如在Vue原型上定义了基本的$mount方法,在Vue原型上定义了$mount(注意区别于共享的$mount方法)和模板转换为renderfunction,转换是通过compileToFunctions实现的调用基本的$mount方法(src/platform/web/runtime/index.js)基本的$mount方法实际上会调用mountComponent方法(src/core/instance/lifecycle.js)