当前位置: 首页 > Web前端 > vue.js

大前端进阶-理解vuejs源码1

时间:2023-03-31 18:25:00 vue.js

本文适合初次阅读vuejs源码准备阅读的童鞋。由于vuejs源码非常庞大,分布在各个文件夹中,如果想看懂源码,就需要了解清楚整个框架的脉络。本文从编译入口入手,在源码中寻找重点。准备打包源码浏览器调试比单纯阅读源码效率更高,那么vuejs如何添加sourceMap呢?把vue源码仓库fork到自己的github仓库,方便大家随意添加注释和修改。下载项目,打开package.json文件,在文件中找到:vuejsispackagedwithrollup,在dev命令最后加上--sourcemap。执行npmrundev打包生成带有sourcemap的vue文件。找到examples文件夹,随便找一个用例,把vue文件的引用地址改成新打包生成的文件。用浏览器打开html文件,打开控制台,就可以看到源码了。理解打包文件在命令行执行npmrunbuild,会打包所有版本的vue文件,打包结果如下:其中:common表示符合commonjs规范的文件。ems表示符合ESModule规范的文件。dev表示文件内容未压缩且可读。prod表示文件内容被压缩。runtime表示运行版本,不包含模板编译功能。也就是在声明组件的时候template模板不能编译,只能使用render函数。在vue-cli搭建的项目中,由于打包时template模板被编译成render函数,所以打包后引用的vue版本为runtime版本。在vue-cli创建的项目中执行vueinspect>out.js。可以将webpack的所有配置输出到out.js文件中,查看resolve配置可以看到打包后的vue版本:resolve:{alias:{vue$:'vue/dist/vue.runtime.esm.js'}}不是加上common,es表示是umd规范文件。该文件可以支持commonjs、ESModule、AMD,也可以通过window.Vue直接引用。不加rentime就是完整版本,包括runtime和compiler。整体代码比运行时版本多。入口文件vuejs项目的包入口文件可以看作是源码阅读的入口文件。打包时执行如下命令:rollup-w-cscripts/config.js--environmentTARGET:web-full-dev--sourcemap其中scripts/config.js为rollup配置文件路径,rollup配置文件需要export一个对象,其中input属性指定打包入口文件。scripts/config.js在文件末尾:.keys(builds).map(genConfig)}由于执行的打包命令中包含--environmentTARGET:web-full-dev,此时process.env.TARGET的值为web-full-dev,即配置通过genConfig和export获得。在getConfig方法中,通过constopts=builds[name]获取内置配置,其中name为web-full-dev。通过opts.entry指定输入属性值,其最终值为platforms/web/entry-runtime-with-compiler.js。platforms文件plus存放的是平台相关的代码,web文件夹是web相关的代码,weex文件夹是weex相关的代码。platforms/web/entry-runtime-with-compiler.js这个文件的功能并不复杂,只需要修改Vue原型上的$mount方法,增加一个静态方法compile:Vue.prototype.$mount=function(el?:string|Element,hydrating?:boolean):Component{//具体逻辑}//静态方法compileVue.compile=compileToFunctions在原有$mount方法的基础上增加了判断。当vue组件没有定义render时,判断是否传入了一个temple,如果传入了,就编译成render。具体逻辑可以简化为:constoptions=this.$options//ifnorenderif(!options.render){//获取模板lettemplate=options.template//....这里包含各种模板情况判断if(template){//编译模板渲染函数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//静态render函数,用于优化Dom渲染过程options.staticRenderFns=staticRenderFns}}//调用原来的mount方法returnmount.call(this,el,hydrating)其中compileToFunctions是模板编译的入口,等到后面的编译部分继续。platforms/web/runtime/index.jsentry-runtime-with-compiler.js文件中的Vue类是从platforms/web/runtime/index.js文件导入的。该文件为Vue类添加了特定于Web平台的功能,例如Dom操作。这个文件可以分为三个部分:Extendedconfig//添加一些web平台特有的辅助方法Vue.config.mustUseProp=mustUsePropVue.config.isReservedTag=isReservedTag//判断是否为保留标签,比如inputVue.config.isReservedAttr=isReservedAttr//它是保留属性吗?Vue.config.getTagNamespace=getTagNamespace//获取元素的命名空间Vue.config.isUnknownElement=isUnknownElement这里添加的方法大部分是Vue内部使用的,平时工作很少用到,所以不再详解。Addglobalbuilt-incomponentsanddirectives//添加web平台相关的全局内置组件和指令extend(Vue.options.directives,platformDirectives)extend(Vue.options.components,platformComponents)这里添加的组件有:transition和过渡组。这里添加的指令是:v-model和v-show。添加原型方法添加了两个关键方法:DomMount和DomUpdate。//添加虚拟Dom更新操作Vue.prototype.__patch__=inBrowser?patch:noop//添加挂载方法Vue.prototype.$mount=function(el?:string|Element,hydrating?:boolean):Component{el=el&&inBrowser?query(el):undefined//RendervirtualDomreturnmountComponent(this,el,hydrating)}mountComponent是Vnodes渲染的入口方法,后面会具体化为Vnodes渲染过程。core/index.jsplatforms/web/runtime/index.js中的Vue类是从core/index.js文件中导入的。core文件夹包含vue核心代码,与平台无关。这个文件只包含一个关键代码://给Vue类添加全局静态方法如Vue.extend、Vue.component等。initGlobalAPI(Vue)//...其余为ssr服务端渲染相关的全局属性,这里不再赘述。initGlobalAPI定义在core/global-api/index.js:exportfunctioninitGlobalAPI(Vue:GlobalAPI){//定义配置Object.defineProperty(Vue,'config',configDef)//辅助函数,不要直接使用,vuejs不保证正确执行Vue.util={warn,extend,mergeOptions,defineReactive}//设置全局set、delete和nextTickVue.set=setVue.delete=delVue.nextTick=nextTick//添加可观察方法Vue.observable=(obj:T):T=>{observe(obj)returnobj}//创建一个全局选项对象Vue.options=Object.create(null)//在选项中初始化组件、指令、过滤器属性。ASSET_TYPES.forEach(type=>{Vue.options[type+'s']=Object.create(null)})//Vue.useinitUse(Vue)//Vue.mixininitMixin(Vue)//Vue.extendinitExtend(Vue)//Vue.component,Vue.directive,Vue.filterinitAssetRegisters(Vue)}从core/instance/index.js文件导入core/instance/index.jscore/index.js中的Vue类,这个文件定义了Vue的构造函数和实例方法。定义构造函数Vue(options){//确保Vue不会作为函数被调用if(process.env.NODE_ENV!=='production'&&!(thisinstanceofVue)){warn('Vueisaconstructorandshouldbecalledwiththe`new`keyword')}//执行_init方法,它在initMixin中定义了this._init(options)}声明实例属性方法//添加实例方法属性initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)这里体现了vuejs对代码逻辑文件的划分,将不同的功能划分到不同的文件中。initMixin在core/instance/init.js文件中定义。exportfunctioninitMixin(Vue:Class){Vue.prototype._init=function(options?:Object){//...omitted}}这个文件主要是为Vue实例添加_init方法,在创建Vueinstance,此方法将立即被调用。此方法是Vue初始化过程下一部分的入口点。stateMixin在core/instance/state.js中定义:exportfunctionstateMixin(Vue:Class){//定义$data属性Object.defineProperty(Vue.prototype,'$data',dataDef)//定义$propsPropertyObject.defineProperty(Vue.prototype,'$props',propsDef)//定义get、set方法Vue.prototype.$set=setVue.prototype.$delete=del//定义watch方法Vue.prototype.$watch=function(expOrFn:string|Function,cb:any,options?:Object):Function{//...省略,响应式源码部分有详细解释}}eventsMixin在core/instance/events.js中定义:exportfunctioneventsMixin(Vue:Class){//定义$onVue.prototype.$on=function(event:string|Array,fn:Function):Component{//...省略}//define$onceVue.prototype.$once=function(event:string,fn:Function):Component{//...省略}//define$offVue.prototype.$off=function(event?:string|Array,fn?:函数):组件{//...省略}//定义$emitVue.prototype.$emit=function(event:string):Component{//...省略}}这里定义的事件注册方法逻辑很相似,和它将注册的方法存储在Vue实例的_events属性中。_events属性在_init方法执行期间被初始化。lifecycleMixin定义在core/instance/lifecycle.js中:exportfunctionlifecycleMixin(Vue:Class){//定义_update方法Vue.prototype._update=function(vnode:VNode,hydrating?:boolean){//调用__patch__执行更新渲染if(!prevVnode){//初始渲染vm.$el=vm.__patch__(vm.$el,vnode,hydrating,false/*removeOnly*/)}else{//更新vm.$el=vm.__patch__(prevVnode,vnode)}}//定义强制更新方法Vue.prototype.$forceUpdate=function(){}//定义销毁方法Vue.prototype.$destroy=function(){}}这个文件中定义的_update方法是响应过程的关键部分。作为观察者Watcher的回调函数,当vm的数据发生变化时会被调用。renderMixin在core/instance/render.js中定义:exportfunctionrenderMixin(Vue:Class){//定义nextTick方法Vue.prototype.$nextTick=function(fn:Function){//...省略}Vue.prototype._render=function():VNode{//内部调用options中的render方法生成虚拟Dom}}_render方法会配合_update,当_update更新时,比较虚拟Dom,_render方法用于生成虚拟Dom。综上所述,理顺了整个vuejs项目的web平台文件关系,如下:core/instance/index.js:声明Vue的构造函数和实例方法属性。core/index.js:为Vue添加静态方法。platforms/web/runtime/index.js:针对web平台,增加Dom渲染加载方法。platforms/web/entry-runtime-with-compiler.js:扩展Vue模板编译能力。如果是没有编译的runtime版本,则模板不需要处理。