在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
昨天有朋友问我vue在页面第一次加载时到底做了些什么,看来这个问题在很多朋友心中可能还比较模糊,今天我们一起来详细的看看vue的首次渲染过程 了解vue首次渲染全过程,我们应该从哪说起呢,很明显,是不是应该从入口文件说起啊,即main.js 1、vue初始化首先,我们看main.js中,第一个最关键的肯定是引入vue吧 import vue from 'vue' 其实,vue被打包后,dist文件夹中存在多个版本,分别是 那么,vue引入以后,是不是vue中的相关代码会被执行啊。那最新执行vue源码中的哪块代码呢(引入的vue就是vue源码中被打包后的vue),我们先得知道入口文件在哪 vue入口文件vue的入口文件主要在vue源码结构的src/platforms/web下
vue打包时,可以选择不同的vue入口文件来进行打包,不同的入口文件打包出来的vue版本不同。 完整版和运行时版本的区别完整版是运行时版本 + 编译器的组合 <body> <div id="app"> <p>我是index.html中的内容</p> </div> </body> new Vue({ template: '<div>我是template模板渲染出来的内容</div>' }).$mount('#app') 上面的情况, 如果是运行时版本,没有编译器,不会编译template中的内容,则页面只会存在原来的dom 下面我们来继续往下看
可以看出,入口文件先导入了vue,然后经过了一些处理,最终又导出了vue
这个文件也是一样,import了vue 经过了一些处理,然后又导出了vue,我们继续往上找,找core/index
这个文件也是一样,我们继续往上找,找./instance/index
在这里,我们找到了我们的vue构造函数的创建,是在源码的src/core/instance/index.js文件中。 那么,我们从刚刚上面的引用关系,就能发现,vue被我们引入到项目中后,首先会执行的文件的顺序是 src/core/instace/index.js ===> 1 src/core/index.js ===> 2 src/platforms/web/runtime/index.js ===> 3 src/platforms/web/entry-runtime-with-compiler.js 4 那么,我们再来看,每个文件都执行了些什么, 1.1、src/core/instace/index.js首先,此文件定义了vue构造函数,并初始化了一些vue的实例属性和实例方法,即,在vue.prototype原型下新增了各种方法和属性
下面,我们具体来看下,每一个方法具体初始化了vue的哪些实例属性或方法 1.1.1、initMixin(Vue) 1.1.2、stateMixin(Vue) 1.1.3、eventsMixin(Vue) 1.1.4、lifecycleMixin(Vue) 1.1.5、renderMixin(Vue) src/core/instace/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.' ) } } // 新增了一个config属性 Object.defineProperty(Vue, 'config', configDef) // 新增了一个静态成员 util Vue.util = { warn, extend, mergeOptions, defineReactive } // 新增了3个静态成员set delete nextTick Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 新增了一个静态成员 observable Vue.observable = <T>(obj: T): T => { observe(obj) return obj } // 初始化了options 此时options是空对象</T> Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) Vue.options._base = Vue // 注册了一个全局组件keep-alive builtInComponents内部就是keep-alive的组件导出 extend(Vue.options.components, builtInComponents) // 下面是分别初始化了Vue.use() Vue.mixin() Vue.extend() initUse(Vue) initMixin(Vue) initExtend(Vue) // 初始化Vue.directive(), Vue.component(), vue.filter() initAssetRegisters(Vue) } 1.2、src/core/index.js
可以看出,这个文件,主要是给vue新增了很多静态实例方法和属性,具体新增了哪些, 1.2.1 initGlobalAPI(Vue) 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.' ) } } // 新增了一个config属性 Object.defineProperty(Vue, 'config', configDef) // 新增了一个静态成员 util Vue.util = { warn, extend, mergeOptions, defineReactive } // 新增了3个静态成员set delete nextTick Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 新增了一个静态成员 observable Vue.observable = <T>(obj: T): T => { observe(obj) return obj } // 初始化了options 此时options是空对象</T> Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) Vue.options._base = Vue // 注册了一个全局组件keep-alive builtInComponents内部就是keep-alive的组件导出 extend(Vue.options.components, builtInComponents) // 下面是分别初始化了Vue.use() Vue.mixin() Vue.extend() initUse(Vue) initMixin(Vue) initExtend(Vue) // 初始化Vue.directive(), Vue.component(), vue.filter() initAssetRegisters(Vue) } 1.3、src/platforms/web/runtime/index.js1.4、src/platforms/web/entry-runtime-with-compiler.js
此文件,最主要的作用就重写了vue原型下的$mount方法。具体$mount方法中做了些什么,我们后面会讲 1.5、vue初始化总结上面写的整个过程,都是用户在使用vue时,引入vue文件后,立刻会执行的一些东西 这些执行完后,是不是会继续去执行我们项目中的main.js文件啊, new Vue({ router, store, render: h => h(App) }).$mount('#app') 这个时候,会开始调用,我们的vue构造函数 2、vue构造函数执行此时,会先执行vue构造函数,
可以看出,主要是执行了_init方法,从这里开始,vue的生命周期开始执行了
上面只是_init()方法中最主要的一部分代码,(代码太多,我就不全部截图了,你们自己到源码中看)。可以看出: 2.1、beforeCreate钩子在生命周期beforeCreate钩子之前,vue主要做的事情就是给vue原型新增各种属性和方法,给vue新增各种静态属性和方法,以及给vm实例新增各种属性和方法 2.2、created钩子上图可以看出,beforeCreate钩子执行结束后,主要执行了3个方法:initInjections, initState, initProvide // 把inject注入到vm实例 callHook(vm, 'beforeCreate') // 把inject注入到vm实例 initInjections(vm) // 初始化vm的$props,$methods,$data,computed,watch initState(vm) // 把provide注入vm实例 initProvide(vm) // 执行created生命周期 callHook(vm, 'created') 其实,重点是initState(vm)方法,该方法中,初始化了vm实例的$props, $data, $methods, computed, watch等。同时,在里面调用了一个initData()方法,该方法内会调用observer() 方法,将data中的数据都转化为响应式数据。即添加数据拦截器。 所以可以看出,在created生命周期之前,vm的$props, $data, $methods, computed, watch属性都会初始化完成, created生命周期走完以后,继续往下看
可以看出,这里判断了vm.$options.el是否存在,vm.$options.el是什么啊。 new Vue({ el: '#app' router, store, render: h => h(App) }) 故,vm.$options.el就是上面传入的el。 那大家可能会好奇了,如果不存在,那是不是就卡死了,后面都不会走了。是的,如果没有,就不会继续走了,要想代码继续往下走,必然要执行$mount方法。 new Vue({ router, store, render: h => h(App) }).$mount('#app') 这里没有传入el,所以源码中的
肯定是不会走的。但是,用户在new Vue的时候可以自己用new 出来的vue实例去调用$mount。这么一来,大家看我们官网的生命周期图,可能就更容易看懂了
好了,下面我们继续往下,下一步是执行 $mount,我们来看 $mount方法 2.3、$mount函数我们之前初始化的时候,重写过$mount还记得吗,所以,此时我们执行$mount时,执行的是重写后的mount
这里在重写前先存储了重写前的mount方法,然后在最后调用了重写前的mount方法。
这一步的主要作用就是判断是否有render函数, 如果没有,就会前判断是否存在template模板(options.template是否存在,options.template可能是id选择器,可能是dom),如果存在模板,就会获取到模板中的内容,并赋值给template,options.template不存在,那么会直接以el指定的dom为模板(即#app),获取到el下的dom,赋值给template template取到dom后,然后继续往下,将此template编译成render函数,并将编译出来的render函数挂载options.render属性下 然后会继续执行重写前的$mount,理解了这,我们就能理解生命周期图中的另一部分了 2.4、beforeMount下面,我们继续来看重写前的$mount函数的执行
可以看出\ $mount中主要是执行了函数mountComponent,我们继续看mountComponent函数 可以看出,此函数,主要做了以下4件事
第二:.vue文件编译成render
这种方式,就是自己传入了一个render函数,函数内用h函数前执行了App.vue文件。 第三、将template模板编译成render函数
行后的结果当做参数传给_update方法。_render方法我们前面说过,他内部渲染了render函数成为虚拟dom,故_render()的返回值是一个vnode。 我们先来看下_render()函数内部如何将render函数转化为虚拟dom的
然后我们再看_update函数内部做了啥
可以看出,_update函数中,执行了__patch__方法去对比两个新旧dom,从而找出差异,更新真实dom。如果是首次渲染,则直接将当前的vnode,生成真实的dom。 故得出结论,整个updateComponent方法的主要作用就是渲染render函数,更新dom 3、new 了一个watcher实例
可以看出,new一个watcher实例的同时,传入了updateComponent函数作为参数。
watcher分为3种,渲染watcher,$watch函数的watcher,computed的watcher。我们这里渲染页面的是渲染watcher 上面将我们传入的函数传给了getter
继续往下走,调用了get()
可以看出,get()中调用了我们传入的函数,而我们传入的函数就是渲染render函数,并触发虚拟dom更新真实dom,而返回的值,就是渲染后的真实dom,最后赋值给了this.value,而this.value最后会用于更新依赖者。而我们当前这个wather实例,是主vue实例的watcher,故可以理解为整个页面的watcher。当我们调用this.$fouceUpdate()时,就是调用这个实例的update方法,去更新整个页面。 此时,我们继续往下看
这内部,还做了个判断,如果vm._isMounted为true(即Mounted钩子已经执行过了),而vm._isDestroyed为fase时(即当前组件还未销毁)。此时,如果产生更新,则说明并非首次渲染,那么执行beforeUpdate钩子,后续肯定还会走updated。这里我们就不说updated的事了 new Watcher后,代码继续往下走
判断了当前vnode如果null,说明之前没有生成过虚拟dom,也就说明这次肯定是首次渲染,此时,vm._isMounted置为true。并执行mounted钩子函数,此时,首次渲染完成。 2.5、mounted可以看出,整个beforeMount 到 mounted过程中,主要做的工作就是 首次渲染整个过程就是这样。到此这篇关于浅谈vue首次渲染全过程的文章就介绍到这了,更多相关vue首次渲染内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论