1. The entrance
Since my native debugging code is built with NPM run dev, I can find the actual running commands in package.json
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev"
Copy the code
From our initial analysis, we can find the compile entry in scripts/config.js via TARGET:web-full-dev
// Runtime+compiler development build (Browser)
'web-full-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.js'),
format: 'umd'.env: 'development'.alias: { he: './entity-decoder' },
banner
}
Copy the code
This leads us to the web/ entry-Runtime-with-Compiler.js file, which we find in the last line
export default Vue
Copy the code
The Vue is our final output constructor in the Vue. Where does it come from
entry-runtime-with-compiler.js -> web/runtime/index -> core/index -> core/instance/index
I finally found you, but I didn’t give up
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'
function Vue (options) {
if(process.env.NODE_ENV ! = ='production' &&
!(this instanceof Vue)
) {
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)
export default Vue
Copy the code
This time we can see Vue for what it really is. It’s a constructor, but it’s a bit simple. Just one line of code to initialize this._init(options), and we don’t even see where the _init method is defined.
The advantage of this is that the constructor logic is split into different parts, separated according to logic decoupling. Different Mixin functions provide different function methods for Vue, where the _init method is defined in initMixin.
2. Initialize the function
So let’s look at what the _init function does, it’s a lot of code, so I’m just going to comment on it, but we’re just going to focus on the main flow.
let uid = 0
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options? :Object) {
const vm: Component = this
// a uid
vm._uid = uid++
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// Component instance skipped temporarily
initInternalComponent(vm, options)
} else {
/ / resolveConstructorOptions method inheritance from constructor of the superclass
Options {components: {}, directives: {}, filters: {}}
Options => Vue. Options is defined as initGlobalAPI(Vue) in core/index.js.
// initGlobalAPI see 2.1
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if(process.env.NODE_ENV ! = ='production') {
// proxy The proxy VM is used to prompt errors and specify key names if the key is not defined on the instance but
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
_isDestroyed vm._inactive VM.$parent vm.$children
/ / see 2.2
initLifecycle(vm)
Register events defined in the component such as
/ / see 2.3
initEvents(vm)
// Initialize the rendering function
initRender(vm)
// Call the component's beforeCreate hook
callHook(vm, 'beforeCreate')
// Inject will be skipped temporarily
initInjections(vm) // resolve injections before data/props
// See 2.4 for initialization data
initState(vm)
// provide skip temporarily
initProvide(vm) // resolve provide after data/props
The difference between beforeCreate and Created is the middle data initialization functions, especially initSate
callHook(vm, 'created')
// Mount the node
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
Copy the code
Now that we’ve learned about the only calling method in the constructor, the this._init method, we can basically see the logical flow of the instantiation. Because the methods called in the function are highly encapsulated, there is a lot of logic in the code, but a lot of code. In fact, we will look at some of the methods in the above process to further clarify the initialization logic.
2.1 initGlobalAPI
We mentioned vm.contructor.options in the initialization function earlier, but we didn’t see where to define it. In fact, when we looked for the definition of the constructor Vue earlier, Js -> web/runtime/index -> core/index -> core/instance/index. They’re doing a bunch of things in their own scope, and there’s a line of code in core/ Index
initGlobalAPI(Vue)
Copy the code
Let’s look at the logic of initGlobalAPI in core/global-api/index.js
export function initGlobalAPI (Vue: GlobalAPI) {
// config
const configDef = {}
configDef.get = () = > config
Object.defineProperty(Vue, 'config', configDef)
// Static utility functions that define Vue but are not part of the open API we try not to use
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
// Set delete nextTick is initialized here
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T= > {
observe(obj)
return obj
}
// Our main character Options is introduced
Vue.options = Object.create(null)
// ASSET_TYPES are defined in SHARE /constants ASSET_TYPES = [' Component ', 'directive', 'filter']
ASSET_TYPES.forEach(type= > {
Vue.options[type + 's'] = Object.create(null)})//
Vue.options._base = Vue
// KeepAlive logic is skipped first
extend(Vue.options.components, builtInComponents)
// Some common method definitions implement the use mixin extend method in functions through decoupling
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
Vue.directive Vue.component vue. filter is defined by iterating through ASSET_TYPES
initAssetRegisters(Vue)
}
Copy the code
2.2 initLifecycle
How exactly is the lifecycle initialized at initLifecycle
export function initLifecycle (vm: Component) {
const options = vm.$options
// Look up for the real parent node
// locate first non-abstract parent
let parent = options.parent
if(parent && ! options.abstract) {while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
$parent $root $children $refs, etc
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
// The lifecycle defines the associated attribute initialization
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
Copy the code
In fact, you can see that the initLifecycle function is a little simpler than we thought, it just defines the attributes associated with the instance and parent node and the lifecycle description attributes. This is understandable because the lifecycle hook functions are defined in the component and are triggered during initialization and rendering updates, so in theory hook calls should be created during the actual rendering process as well, like the callHook(VM, ‘created’) called in the _init function. Modify lifecycle properties and call functions in created hook calls.
2.3 initEvents
The initEvents function itself is a simple process for recording updateComponentListeners for functions defined on the component
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
Copy the code
2.4 initState
Finally, we will look at initState, which is an important function, to see what the framework does between beforeCreate and created
export function initState (vm: Component) {
vm._watchers = []
// This code is really short and a few short lines of code initialize props Methods data computed
const opts = vm.$options
/ / see against 2.4.1
if (opts.props) initProps(vm, opts.props)
/ / see 2.4.2
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
/ / see 2.4.3
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)}if (opts.computed) initComputed(vm, opts.computed)
if(opts.watch && opts.watch ! == nativeWatch) { initWatch(vm, opts.watch) } }Copy the code
Against 2.4.1 initProps
How is initProps initialized
function initProps (vm: Component, propsOptions: Object) {
// propsOptions defines the Props as {name: String}
// propsData is the data received by the component in the form {name: 'Joke'}
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = vm.$options._propKeys = []
constisRoot = ! vm.$parent// root instance props should be converted
if(! isRoot) { toggleObserving(false)}// Walk through the prop definition
for (const key in propsOptions) {
keys.push(key)
// validateProp is used to get the prop value (propsData data or default value)
const value = validateProp(key, propsOptions, propsData, vm)
if(process.env.NODE_ENV ! = ='production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
// We can see our error indicating that the old friend does not allow the parent component to pass the prop value
defineReactive(props, key, value, () = > {
if(! isRoot && ! isUpdatingChildComponent) { warn(`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
} else {
// Add responsiveness for props, where toggleObserving(false) is used for this function
// When value is an object, responses are not added further recursively
defineReactive(props, key, value)
}
// the vm proxy _props is the same as our props
if(! (keyin vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)}Copy the code
2.4.2 initMethods
InitMethods is much simpler
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if(process.env.NODE_ENV ! = ='production') {
if (typeofmethods[key] ! = ='function') {
warn(
`Method "${key}" has type "The ${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly? `,
vm
)
}
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`)}}// All the above are checksum errors in the development environment
The only actual logic in the formal environment is to copy all methods under methods to VM and specify this as VM
vm[key] = typeofmethods[key] ! = ='function' ? noop : bind(methods[key], vm)
}
}
Copy the code
2.4.3 initData
Let’s move on to the logic of initData
function initData (vm: Component) {
let data = vm.$options.data
// If it is a function, point this to the VM and execute the function
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// Returns an object verification prompt
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 instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
// Check whether the methods and props keys are identical
const key = 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
)
} else if(! isReserved(key)) {// VM proxy _data
proxy(vm, `_data`, key)
}
}
// Reactive data will be analyzed separately in the following article
// observe data
observe(data, true /* asRootData */)}Copy the code
2.4.4 initComputed and initWatch
We’ll get to watch later
3. The conclusion
Previously, we briefly analyzed the flow of Vue function instantiation.
-
Find the entry through entry-runtime-with-Compiler. js, and trace back to the Vue function definition
-
_init function flow
Initialize life cycle data -> Initialize component events -> Initialize render -> call beforeCreate -> Initialize data -> Call Created -> Mount node
-
Vue static method initializes initGlobalAPI logic
-
Instantiate the process-related function initLifecycle initEvents initState logic
-
InitState Specifies the initialization logic of props Methods data
There are some processes that have not been analyzed, which I will continue in a later article
-
InitRender initializes the render
-
InitState initializes computed Watch in data
-
Observe (data) data monitoring in initData
Last but not least, the code of the post is more. The analysis of the wrong place hope to help correct, there is not clear place can also be put forward, we communicate ~