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
importVuefrom'core/index'importconfigfrom'core/config'import{extend,noop}from'shared/util'import{mountComponent}from'core/instance/lifecycle'import{devtools,inBrowser,isChrome}from'core/util/index'import{query,mustUseProp,isReservedTag,isReservedAttr,getTagNamespace,isUnknownElement}from'web/util/index'import{patch}from'./patch'importplatformDirectivesfrom'./directives/index'importplatformComponentsfrom'./components/index'// install platform specific utilsVue.config.mustUseProp=mustUsePropVue.config.isReservedTag=isReservedTagVue.config.isReservedAttr=isReservedAttrVue.config.getTagNamespace=getTagNamespaceVue.config.isUnknownElement=isUnknownElement// install platform runtime directives & componentsextend(Vue.options.directives,platformDirectives)extend(Vue.options.components,platformComponents)// install platform patch functionVue.prototype.__patch__=inBrowser ? patch : noop// public mount methodVue.prototype.$mount=function(el?: string|Element,hydrating?: boolean): Component{
el =el&&inBrowser ? query(el) : undefinedreturnmountComponent(this,el,hydrating)}// ...exportdefaultVue
可以看出来第一句:import Vue from 'core/index' 引入了定义好的 Vue,后面基本上都是对 Vue 这个对象进行了一些扩展,所以可以分为两步看,先看初始化 Vue,再看扩展。
定义 Vue 的代码在 src/core/index.js 中:
importVuefrom'./instance/index'import{initGlobalAPI}from'./global-api/index'import{isServerRendering}from'core/util/env'import{FunctionalRenderContext}from'core/vdom/create-functional-component'initGlobalAPI(Vue)Object.defineProperty(Vue.prototype,'$isServer',{get: isServerRendering})Object.defineProperty(Vue.prototype,'$ssrContext',{get(){/* istanbul ignore next */returnthis.$vnode&&this.$vnode.ssrContext}})// expose FunctionalRenderContext for ssr runtime helper installationObject.defineProperty(Vue,'FunctionalRenderContext',{value: FunctionalRenderContext})Vue.version='__VERSION__'exportdefaultVue
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'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
exportfunctioninitMixin(Vue: Class<Component>){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 optionsif(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=vminitLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm,'beforeCreate')initInjections(vm)// resolve injections before data/propsinitState(vm)initProvide(vm)// resolve provide after data/propscallHook(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)}}}
挑一个说一下:里面的 mergeOptions 其实就是把传入的 options 最终合并到 vm 的 $options 上,所以就可以通过 $options.el 访问 new Vue({}) 代码里面的 el,通过 $options.data 访问 new Vue({}) 代码里面的 data,methods 和 mounted 等也是同样的道理。紧接着后面初始化了生命周期,事件,渲染,状态等等,最后有这样的代码:
if(vm.$options.el){vm.$mount(vm.$options.el)}
意思就是说我们代码里定义的的 el 会通过 $mount 挂载到 vm 上,也就是上面的 #app 了。一旦执行完 $mount 之后,dom上的双向绑定就会生效,定义的数据就会渲染到dom上。
exportfunctioninitGlobalAPI(Vue: GlobalAPI){// configconstconfigDef={}configDef.get=()=>configif(process.env.NODE_ENV!=='production'){configDef.set=()=>{warn('Do not replace the Vue.config object, set individual fields instead.')}}Object.defineProperty(Vue,'config',configDef)// exposed util methods.// NOTE: these are not considered part of the public API - avoid relying on// them unless you are aware of the risk.Vue.util={
warn,
extend,
mergeOptions,
defineReactive
}Vue.set=setVue.delete=delVue.nextTick=nextTickVue.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=Vueextend(Vue.options.components,builtInComponents)initUse(Vue)initMixin(Vue)initExtend(Vue)initAssetRegisters(Vue)}
可以看出来它在 Vue 上扩展了一些全局方法,扩展的方法都可以在官方文档的API中找到。
现在想一个问题:为什么 mounted 里面可以通过 this. 来获取到 data 里的 name 呢?
functioninitData(vm: Component){letdata=vm.$options.datadata=vm._data=typeofdata==='function'
? getData(data,vm)
: data||{}if(!isPlainObject(data)){data={}process.env.NODE_ENV!=='production'&&warn('data functions should return an object:\n'+'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm)}// proxy data on instanceconstkeys=Object.keys(data)constprops=vm.$options.propsconstmethods=vm.$options.methodsleti=keys.lengthwhile(i--){constkey=keys[i]if(process.env.NODE_ENV!=='production'){if(methods&&hasOwn(methods,key)){warn(`Method "${key}" has already been defined as a data property.`,vm)}}if(props&&hasOwn(props,key)){process.env.NODE_ENV!=='production'&&warn(`The data property "${key}" is already declared as a prop. `+`Use prop default value instead.`,vm)}elseif(!isReserved(key)){proxy(vm,`_data`,key)}}// observe dataobserve(data,true/* asRootData */)}exportfunctiongetData(data: Function,vm: Component): any{// #7573 disable dep collection when invoking data getterspushTarget()try{returndata.call(vm,vm)}catch(e){handleError(e,vm,`data()`)return{}}finally{popTarget()}}
它的逻辑大致是,判断 data 是不是一个 function,如果是就走下面的 getData,把 vm 指向 data,这样就可以使用 this.data 了,同时把这个方法赋值给了 vm._data,如果不是一个 function,就重新定义这个 data 为一个对象,紧接着拿到 props,keys 和 methods做一层遍历,如果在 data 中定义一个变量,就不能在 props 中定义这个变量了,methods 中也是如此,因为它们最终都会挂载到vm上,也就是new Vue 实例上,**这就是为什么我们定义同样的变量在 methods 或者 props 中就会报错的原因。**所以上面的例子中的 data 要返回一个对象:
我们一般在
main.js
中会这样写:接着在里面定义
data
,methods
,mounted
等,既然Vue
是可以 new 出来的,那 Vue 就应该是一个构造函数,在源码中,分为 定义Vue构造函数 和 扩展Vue构造函数,总体的代码定义在:src/platforms/web/runtime/index.js
中:可以看出来第一句:
import Vue from 'core/index'
引入了定义好的 Vue,后面基本上都是对 Vue 这个对象进行了一些扩展,所以可以分为两步看,先看初始化 Vue,再看扩展。定义 Vue 的代码在
src/core/index.js
中:这里除了使用
Object.defineProperty
去定义 Vue 的东西之外,比较关键的就是import Vue from './instance/index'
和initGlobalAPI(Vue)
了,我们一个一个来看,先看第一行的 Vue 的定义,进入src/core/instance/index.js
中,可以看到:逻辑特别清晰,一个名为 Vue 的构造函数,
this instanceof Vue
这行代码规定了 Vue 只能是一个构造函数,所以我们的代码中使用了new Vue({})
去实例化 Vue。后面的就是一堆 Mixin,比如 initMixin初始化混入,stateMixin状态混入,eventMixi事件混入,lifecycleMixin生命周期混入,renderMixin渲染混入。
来简单看一个 initMixin 方法,看它做了什么事情,代码在
src/core/instance/init.js
。挑一个说一下:里面的
mergeOptions
其实就是把传入的 options 最终合并到 vm 的$options
上,所以就可以通过$options.el
访问new Vue({})
代码里面的 el,通过$options.data
访问new Vue({})
代码里面的 data,methods 和 mounted 等也是同样的道理。紧接着后面初始化了生命周期,事件,渲染,状态等等,最后有这样的代码:意思就是说我们代码里定义的的
el
会通过$mount
挂载到 vm 上,也就是上面的#app
了。一旦执行完$mount
之后,dom上的双向绑定就会生效,定义的数据就会渲染到dom上。再来看上面提到的
initGlobalAPI(Vue)
,这个方法定义在:src/core/global-api/index.js
中:可以看出来它在 Vue 上扩展了一些全局方法,扩展的方法都可以在官方文档的API中找到。
现在想一个问题:为什么
mounted
里面可以通过this.
来获取到data
里的name
呢?还记得上面的初始化方法 initMixin 么,里面有一个
initState(vm)
,我们来看下它,在src/core/instance/state.js
中:可以看到它的逻辑,如果在
$options
定义了props
,就初始化 props,如果在$options
定义了methods
,就初始化 methods,如果在$options
定义了data
,就初始化 data,现在重点看下initData
,因为例子中是访问的 data 里的属性name。它的逻辑大致是,判断 data 是不是一个 function,如果是就走下面的
getData
,把 vm 指向 data,这样就可以使用this.data
了,同时把这个方法赋值给了vm._data
,如果不是一个 function,就重新定义这个 data 为一个对象,紧接着拿到 props,keys 和 methods做一层遍历,如果在 data 中定义一个变量,就不能在 props 中定义这个变量了,methods 中也是如此,因为它们最终都会挂载到vm上,也就是new Vue
实例上,**这就是为什么我们定义同样的变量在 methods 或者 props 中就会报错的原因。**所以上面的例子中的 data 要返回一个对象:那为什么可以通过 this.name 拿到 data 里的 name 呢?就是后面的
proxy
函数。proxy
定义了 get 和 set,然后通过Object.defineProperty
在 target(就是vm) 上定义了_data
属性,get 方法可以拿到vm[_data][key]
,这个 key 就是代码中传入的 name ,也就是说可以通过this._data.name
获取代码中的 data 里的 name,也就是说上面的console.log(this.name)
其实就是调用了console.log(this._data.name)
。这就是 proxy 的作用,把访问
this.name
中的 name,代理到了this._data.name
,前面的把vm._data
赋值给 data,代码中的proxy(vm, "_data", key)
就是把 data 赋值给了this._data
,所以在 methods 中,可以通过this.name
获取到 data 里面定义的 name。The text was updated successfully, but these errors were encountered: