You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 构造器 * @param {*} options */functionVue(options){if(process.env.NODE_ENV!=='production'&&!(thisinstanceofVue)){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)exportdefaultVue
这个文件的主要作用是定义了一个 Vue 构造器,里面通过执行 _init 函数进行初始化,这也说明了 Vue 必须通过 new 关键字来初始化。Vue 构造器接收的 options 就是在 new Vue 时传入的配置项。
// 挂载到 Vue 原型上Vue.prototype._init=function(options?: Object){constvm: Component=this// a uidvm._uid=uid++letstartTag,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 observedvm._isVue=true// merge options// 合并配置项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 selfvm._self=vm// 初始化生命周期initLifecycle(vm)// 初始化事件initEvents(vm)// 初始化渲染initRender(vm)// beforeCreate 生命周期钩子函数阶段callHook(vm,'beforeCreate')// 初始化 injectionsinitInjections(vm)// resolve injections before data/props// 初始化 props,methods,data 等initState(vm)// 初始化 provideinitProvide(vm)// resolve provide after data/props// created 生命周期钩子函数阶段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){vm.$mount(vm.$options.el)}}}
// 合并配置项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)}
exportfunctionmergeOptions(parent: Object,child: Object,vm?: Component): Object{if(process.env.NODE_ENV!=='production'){checkComponents(child)}if(typeofchild==='function'){
child =child.options}// 规范化 propsnormalizeProps(child,vm)// 规范化 injectnormalizeInject(child,vm)// 规范化 directivesnormalizeDirectives(child)// Apply extends and mixins on the child options,// but only if it is a raw options object that isn't// the result of another mergeOptions call.// Only merged options has the _base property.if(!child._base){if(child.extends){parent=mergeOptions(parent,child.extends,vm)}if(child.mixins){for(leti=0,l=child.mixins.length;i<l;i++){parent=mergeOptions(parent,child.mixins[i],vm)}}}// 合并配置项代码constoptions={}letkeyfor(keyinparent){mergeField(key)}for(keyinchild){if(!hasOwn(parent,key)){mergeField(key)}}functionmergeField(key){conststrat=strats[key]||defaultStratoptions[key]=strat(parent[key],child[key],vm,key)}returnoptions}
functionnormalizeProps(options: Object,vm: ?Component){constprops=options.propsif(!props)returnconstres={}leti,val,name// props 是数组或对象的情况if(Array.isArray(props)){i=props.lengthwhile(i--){val=props[i]if(typeofval==='string'){name=camelize(val)res[name]={type: null}}elseif(process.env.NODE_ENV!=='production'){warn('props must be strings when using array syntax.')}}}elseif(isPlainObject(props)){for(constkeyinprops){val=props[key]name=camelize(key)res[name]=isPlainObject(val)
? val
: {type: val}}}elseif(process.env.NODE_ENV!=='production'){warn(`Invalid value for option "props": expected an Array or an Object, `+`but got ${toRawType(props)}.`,vm)}options.props=res}
constmount=Vue.prototype.$mountVue.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.`)returnthis}constoptions=this.$options// resolve template/el and convert to render functionif(!options.render){lettemplate=options.templateif(template){if(typeoftemplate==='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)}}}elseif(template.nodeType){template=template.innerHTML}else{if(process.env.NODE_ENV!=='production'){warn('invalid template option:'+template,this)}returnthis}}elseif(el){template=getOuterHTML(el)}if(template){/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&config.performance&&mark){mark('compile')}const{render,staticRenderFns}=compileToFunctions(template,{outputSourceRange: process.env.NODE_ENV!=='production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments},this)options.render=renderoptions.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')}}}returnmount.call(this,el,hydrating)}
Vue源码系列--初始化
最近在看 Vue 的源码架构,打算在公司组织 Vue 源码的分享会,所以准备做一系列关于 Vue 源码的技术输出。
目录结构
先来大致看下 vue 目录结构,这里只列出 src 目录下的文件结构。
Vue 的入口文件
分析 Vue 源码首先要找到 Vue 的入口文件,从入口开始,一步步深入了解实现原理。在跟目录下的 package.json 文件中找到构建配置。
可以看到,针对不同模块的构建输出设置了不同的构建脚本,这里只看 dev 脚本的,也就是运行
npm run dev
。运行脚本的时候,会找到 scripts/config.js 执行,然后在 config.js 中找到 web-full-dev 的配置。可以看到,构建时的入口文件是 web/entry-runtime-with-compiler.js 。这里的 web 其实是配置的相对路径的别名,相关的配置都写在 scripts/alias.js 中。
顺着路口文件一路找下去,可以看到 Vue 被定义在 src/core/instance/index.js 中。
这个文件的主要作用是定义了一个 Vue 构造器,里面通过执行 _init 函数进行初始化,这也说明了 Vue 必须通过 new 关键字来初始化。Vue 构造器接收的 options 就是在 new Vue 时传入的配置项。
new Vue 的过程
通过内部定义的 Vue 构造器可以看到,new Vue 时,内部会通过调用 _init 函数进行初始化。_init 函数是挂载在 Vue 原型上的方法,代码定义在 initMixin 中,也就是 src/core/instance/init.js。
_init 函数的作用主要是合并初始化配置项,初始化事件监听,初始化渲染等操作。可以看到的是,initState 函数也就是初始化配置项是在 beforeCreate 钩子函数阶段后,created 钩子函数阶段前完成的,所以在 beforeCreate 钩子函数阶段是不能访问和操作数据的。
合并配置选项
这段代码就是用来合并配置选项的。这里只考虑根实例的初始化,组件实例的初始化暂不考虑,也就是 new Vue 初始化。所以代码会进入到 else 代码块,执行 mergeOptions 函数进行合并。mergeOptions 函数接受 3 个参数,第一个参数执行一个函数并得到返回值,其值就相当于是 Vue.options。第二个参数就是传入的配置项,第三个参数是 Vue 实例。Vue.options 定义在 src/core/global-api/index.js 中。
首先给 Vue.options 赋值了一个空对象,然后通过遍历 ASSET_TYPES 数组给 Vue.options 添加属性。ASSET_TYPES 定义在 src/shared/constants.js 中。
然后再执行拷贝函数将 KeepAlive 组件赋值给 components 属性值。最终 Vue.options 的值如下:
到此就明白了 mergeOptions 函数的参数类型了。接下来看下 mergeOptions 函数是怎么合并配置选项的。
mergeOptions 函数首先会对一些配置项进行规范化,比如 props,inject 依赖注入,directives 自定义指令。拿props 为例,这个属性是用来父组件向子组件传参用的,其值的写法有很多种。
对于第 1 种和第 2 种写法,在 Vue 内部会统一规范化成第 3 种形式的写法。规范化函数如下:
对选项的值规范化后,就开始进入到选项合并阶段了,首先会遍历 Vue 内部提供的默认配置项并自行 mergeField 函数,再接着遍历传入的配置项执行 mergeField 函数。mergeField 函数时关键,函数内部会针对不同的配置项进行相应的合并处理。
strats 是定义的一个空对象,Vue 内部会将配置项合并函数作为属性挂载到这个对象上,包括 data 选项合并函数,生命周期选项合并函数,watch,props,methods 等选项合并函数。Vue 的配置项合并规则在官方文档中解释得很清楚了,合并规则就是:
methods
、components
和directives
,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。整个 options.js 的作用都是用来合并配置项的。
Vue 的挂载
合并配置项完成后,接着就会进行一些初始化。
需要注意的是,对于 props,methods,data 等配置项的初始化是发生在 beforeCreate 钩子函数之后, created 钩子函数之前的,所以,在 beforeCreate 钩子函数阶段是不能访问好操作数据的,必须是至少在 created 钩子函数阶段才能访问和操作。
完成了上面的初始化之后,就会进入到挂载阶段。
$mount 函数定义在 src\platforms\web\entry-runtime-with-compiler.js。
整个函数的作用都是在把模板编译成 render 函数,再把 VDOM 转换成真实的 DOM 元素放入到 document 文档中。这里有一个需要知道的点就是,如果配置项中没有定义 render 配置项,就会选取 template 配置项作为模板编译成 render 函数,如果 template 配置项也不存在,那就直接选取 外部 HTML 作为模板进行编译。优先级是 ender 函数选项-template选项-外部HTML。最后会通过调用定义在 src\core\instance\lifecycle.js 中的 mountComponent 函数转换成真实 DOM。
在 mountComponent 函数内部,会进入 beforeMount 钩子函数阶段和 mounted 钩子函数阶段,需要注意的是,vue 的挂载完成是在 beforeMount 钩子函数之后和 mounted 钩子函数之前发生的,所以 vm.$el 至少是在 mounted 钩子函数阶段才能访问到。
Vue 生命周期
总结
new Vue的时候,内部会调用 _init 函数进行初始化,主要是初始化事件监听,初始化渲染,初始化配置项,实例挂载等操作。初始化的时候,会执行 beforeCreate, created, beforeMount, mounted 生命周期钩子函数。
The text was updated successfully, but these errors were encountered: