The first article leaves two questions :
1. How to trigger the update of the calculation property when the dependent property of the calculation property changes
2.watch
Option or $watch
What is the principle of the method
This article will analyze these two problems , In addition, take a brief look at how custom elements are rendered .
Compute properties
<p v-text="showMessage + ' I'm an unimportant string '"></p>
{
data: {
message: 'Hello Vue.js!'
},
computed: {
showMessage() {
return this.message.toUpperCase()
}
}
}
Take this simple example , First, the calculated properties will also be attached to vue
An attribute that becomes an instance on an instance :
for (var key in computed) {
var userDef = computed[key]
var def = {
enumerable: true,
configurable: true
}
if (typeof userDef === 'function') {
def.get = _.bind(userDef, this)
def.set = noop
} else {
def.get = userDef.get
? _.bind(userDef.get, this)
: noop
def.set = userDef.set
? _.bind(userDef.set, this)
: noop
}
Object.defineProperty(this, key, def)
}
adopt this.xxx
When accessing the calculated properties, we call the computed
The function in the options .
Secondly, there is no difference between calculating attributes and ordinary attributes in the stage of template compilation instruction parsing , This v-text
The instruction creates a Directive
example , This Directive
The instance will be initialized with showMessage + ' I'm an unimportant string '
Create a... For the unique flag Watcher
example ,v-text
The directive update
The method will be used by this Watcher
Collected by the instance , Added to its cbs
In the array ,Watcher
When instantiated, it assigns itself to Observer.target
, And then showMessage + ' I'm an unimportant string '
This expression evaluates , It will also call the function that calculates the attribute showMessage()
, After this function is called, it will reference all the properties it depends on , So this right here is message
, This triggers message
Of getter
, So this Watcher
The instance is added to message
Dependent collection objects dep
In the , Follow up when message
A change in the value of triggers its setter
It will traverse its dep
Collected in Watcher
example , Trigger Watcher
Of update
Method , Finally, it will traverse cbs
Of the instructions added to the update
Method , In this way, the instruction that depends on computing properties is updated .
It is worth noting that in this version , Calculated properties are not cached , Even if the dependent value does not change , Repeated reference to the value of the calculated attribute will also re execute the calculated attribute function we defined .
The listener
watch
The listener of the option declaration is also invoked at last $watch
Method , I already know in the first article $watch
The main method is to create a Watcher
example :
// exp Is the data we want to listen for , Such as :a、a.b
exports.$watch = function (exp, cb, deep, immediate) {
var vm = this
var key = deep ? exp + '**deep**' : exp
var watcher = vm._userWatchers[key]
var wrappedCb = function (val, oldVal) {
cb.call(vm, val, oldVal)
}
if (!watcher) {
watcher = vm._userWatchers[key] =
new Watcher(vm, exp, wrappedCb, {
deep: deep,
user: true
})
} else {
watcher.addCb(wrappedCb)
}
}
about Watcher
We are now familiar with , When instantiating, it will assign itself to Observer.target
, Then trigger the evaluation of the expression , That is, the attribute we want to listen on , Trigger it gettter
Then put the Watcher
Collect its dependent collection objects dep
in , As long as it's collected , This will be triggered after the subsequent attribute value changes Watcher
Update , It will also trigger the above callback .
Custom component rendering
<my-component></my-component>
new Vue({
el: '#app',
components: {
'my-component': {
template: '<div>{{msg}}</div>',
data() {
return {
msg: 'hello world!'
}
}
}
}
})
In the first article, we mentioned that each component option will be created as an inheritance vue
Constructor for :
Then, in the template compilation phase, traversing the custom element will add a v-component
attribute :
tag = el.tagName.toLowerCase()
component =
tag.indexOf('-') > 0 &&
options.components[tag]
if (component) {
el.setAttribute(config.prefix + 'component', tag)
}
Therefore, the user-defined component is also processed through instructions , Next, a link function will be generated ,component
Belong to terminal
A kind of instruction :
Then we return to the normal instruction compilation process ,_bindDir
The method will give v-component
Instruction to create a Directive
example , And then call component
The directive bind
Method :
{
bind: function () {
// el It's our custom element my-component
if (!this.el.__vue__) {
// Create a comment element to replace the custom element
this.ref = document.createComment('v-component')
_.replace(this.el, this.ref)
// Check for presence keep-alive Options
this.keepAlive = this._checkParam('keep-alive') != null
// Check for presence ref To reference the component
this.refID = _.attr(this.el, 'ref')
if (this.keepAlive) {
this.cache = {}
}
// Parse constructor , That is, return the constructor generated in the option merging stage during initialization ,expression Here is the command value my-component
this.resolveCtor(this.expression)
// Create a sub instance
var child = this.build()
// Insert the sub instance
child.$before(this.ref)
// Set up ref
this.setCurrent(child)
}
}
}
see build
Method :
{
build: function () {
// If there is a cache, return directly
if (this.keepAlive) {
var cached = this.cache[this.ctorId]
if (cached) {
return cached
}
}
var vm = this.vm
if (this.Ctor) {
var child = vm.$addChild({
el: this.el,
_asComponent: true,
_host: this._host
}, this.Ctor)// Ctor Is the constructor of the component
if (this.keepAlive) {
this.cache[this.ctorId] = child
}
return child
}
}
}
This method is used to create sub instances , Called $addChild
Method , Simplified as follows :
exports.$addChild = function (opts, BaseCtor) {
var parent = this
// The parent instance is the above we new Vue Example
opts._parent = parent
// The root component is the root component of the parent instance
opts._root = parent.$root
// Create an instance of the custom component
var child = new BaseCtor(opts)
return child
}
The above two methods mainly create an instance of the component constructor , Because the component constructor inherits vue
, So before new Vue
The initialization work will also be done again , What Observations 、 Traverse the custom component and all its child elements for template compilation, binding instructions, etc , Because we passed template
Options , So the way to pass through in the first article _compile
Calling in compile
This will be handled before the method :
// Here will be template The template string is converted to dom, The principle is simple , Create a document fragment , Create another div, Then set the template string to div Of innserHTML, Finally, put div All the elements in the document can be added to the document fragment
el = transclude(el, options)
// Compile and link the rest
compile(el, options)(this, el)
Finally, if there is keep-alive
Cache the instance , go back to bind
In the method child.$before(this.ref)
:
exports.$before = function (target, cb, withTransition) {
return insert(
this, target, cb, withTransition,
before, transition.before
)
}
function insert (vm, target, cb, withTransition, op1, op2) {
// Get target element , Here is the bind Method
target = query(target)
// The element is not currently in the document
var targetIsDetached = !_.inDoc(target)
// Determine whether to use transition mode to insert , If the element is not in the document, it is inserted with a transition
var op = withTransition === false || targetIsDetached
? op1
: op2
// If the target element has been inserted into the document and the component has not been mounted, you need to trigger attached Life cycle
var shouldCallHook =
!targetIsDetached &&
!vm._isAttached &&
!_.inDoc(vm.$el)
// Inserted into the document
op(vm.$el, target, vm, cb)
if (shouldCallHook) {
vm._callHook('attached')
}
return vm
}
op
Method will call transition.before
Method to insert an element into the document , For a detailed analysis of transition insertion, please refer to vue0.11 Version source code reading series 6 : Transition principle .
At this point, the component has been rendered ,bind
The last method was called setCurrent
:
{
setCurrent: function (child) {
this.childVM = child
var refID = child._refID || this.refID
if (refID) {
this.vm.$[refID] = child
}
}
}
If we set a reference, such as :<my-component v-ref="myComponent"></my-component>
, Then you can pass this.$.myComponent
Access to this sub component .
keep-alive
The working principle of is also very simple , Is to return the previous instance instead of creating a new instance , In this way, all States are still preserved .
summary
This series is basically over here , I'm sure not many people can see here , Because I wrote this series of source code reading for the first time , Overall, it's a little messy , In many places, the focus is not very prominent , The description may not be very detailed , It may not be very interesting to see , In addition, there will inevitably be mistakes , Welcome to point out .
Reading the source code is the only way that every developer can't go around , Whether it's to improve yourself or for an interview , After all, we should have a deeper understanding of what we are using all the time , This is also good for use , In addition, think and learn from others' excellent coding thinking , Can also make yourself better .
I have to say that reading the source code is very boring and boring , It's also difficult , It's easy to retreat , You can't understand many places if you don't know its function very well , Of course, we don't have to stick to these places , You don't have to read everything , A better way is to read with questions , For example, I want to understand the principle of a place , Then you can look at this part of the code , It's also interesting when you're immersed in it .
Don't talk much , White ~