Series of articles:
- Vue source code interpretation (Design)
- Vue source code interpretation (Rollup)
- Vue source code interpretation (entry to constructor overall flow)
- Vue source code interpretation (Responsive principle introduction and Prop)
The methods to deal with
After analyzing the logic related to props, we will examine the logic related to methods, which is much simpler than props.
export function initState (vm: Component) {
// omit the code
const opts = vm.$options
if (opts.methods) initMethods(vm, opts.methods)
}
Copy the code
In the initState() method, initMethods() is called and passed in the current instance VM and the written methods. Next, look at the concrete implementation of the initMethods method:
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 $.`
)
}
}
vm[key] = typeofmethods[key] ! = ='function' ? noop : bind(methods[key], vm)
}
}
Copy the code
As you can see from the above code, the most important piece of code in the initMethods() method implementation is:
/ / empty function
function noop () {}
vm[key] = typeofmethods[key] ! = ='function' ? noop : bind(methods[key], vm)
Copy the code
It first determines whether the methods defined are of type function, if not, it assigns an empty noop function, and if so, it binds the method to the current vm instance. The purpose of this is to point this in methods to the current instance, so that instance-related properties or methods such as props, data, and computed can be easily accessed in methods in the form of this. XXX.
In a development environment, it also makes the following judgments:
- Must be
function
Type.
// Throw an error: Method sayHello has type null in the Component definition.
// Did you reference the function correctly?
export default {
methods: {
sayHello: null}}Copy the code
- Naming cannot be combined with
props
Conflict.
// Throw error: Method name has already been defined as a prop.
export default {
props: ['name']
methods: {
name () {
console.log('name')}}}Copy the code
- Names cannot conflict with existing instance methods.
Method $set conflicts with an existing Vue instance Method.
// Avoid defining component methods that start with _ or $.
export default {
methods: {
$set () {
console.log('$set')}}}Copy the code
After analyzing the above initMethods process, the following flow chart can be obtained:
The data processing
Data processing in Vue is a little different between the root instance and the child component. Next, we will focus on data processing in the child component.
export function initState (vm: Component) {
const opts = vm.$options
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)}}Copy the code
In the above code, opts.data is first judged, if true it is a child component (the default is used if the child component does not display defined data), otherwise it is the root instance. There is no need to perform initData for the root instance, just observe vm._data.
Next, a detailed analysis of the initData process, it is defined in SRC/core/instance/state. A method of js file:
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === '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 instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
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)) { proxy(vm,`_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)}Copy the code
Although the initData() method is a bit long, a closer look reveals that it does four things: type judgment, naming conflict judgment, proxy proxy, and observe(data).
Then, the above pieces are explained in detail:
- Type determination value: For subcomponents, since components can be reused multiple times, the function must return an object through the factory function pattern so that the type reference problem can be avoided when components are reused multiple times.
// Child Component
// Throw error: data functions should return an object
export default {
data: {
msg: 'Hello, Data'}}Copy the code
In the case where data is a function, the getData method is called to evaluate, and the getData method is defined as follows:
export function getData (data: Function, vm: Component) :any {
pushTarget()
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, `data()`)
return{}}finally {
popTarget()
}
}
Copy the code
Code analysis: pushTarget is related to reactive dependency collection and will be explained in more detail later. The value of getData is wrapped in a try/catch, returned by a call to data.call(vm, VM), and handled with handleError if the function call fails.
- Naming conflict judgment: due to the
props
andmethods
Has a higher priority, thereforedata
Attribute names cannot be combinedprops
,methods
In naming conflicts, as bothprops
,methods
ordata
It’s all reflected in the example. Another naming conflict is that you cannot start with$
or_
Because it is easy to expose methods, attributes, or methods private to the instance$
Initial method/attribute conflict.
// 1. Name conflict with methods
Method name has already been defined as a data property.
export default {
data () {
return {
name: 'data name'}},methods: {
name () {
console.log('methods name')}}}// 2. Name conflict with props
// The data property name is already declared as a prop.
// Use prop default value instead.
export default {
props: ['name'],
data () {
return {
name: 'data name'}}}// 3. Cannot start with $or _
export default {
data () {
return {
$data: '$data'
_isVue: true}}}Copy the code
- The proxy agent: It has been introduced before
proxy
The role of agency, we talked aboutproxy
The agent_props
For example, here is the proxy_data
With the agent_props
It’s the same thing.
export default {
data () {
return {
msg: 'Hello, Msg'}}}/ / agent before
console.log(this._data.msg)
proxy(vm, '_data'.'msg')
/ / agent
console.log(this.msg)
Copy the code
- observe(data):
observe
The function is to recursively respond to all attributes (including nested attributes) of the passed valuedefineReactive
, which will be covered in more detail in a later chapterobserve
The realization principle of theinitData
As long as you knowobserve(data)
thedata
Make all properties of the object returned by the function reactive.
After analyzing the implementation of initData, you can get the overall flow chart of initData.