Vue source code analysis [3] – VUE response type
The following code and analysis process need to be viewed in conjunction with vue.js source code, by comparing the break points one by one.
Template code
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src=". /.. /.. /oldVue.js"></script>
</head>
<body>
<div id="app">
<h2>Start saving</h2>
<div>Monthly deposit :¥{{money}}</div>
<div>Storage :{{num}} months</div>
<div>¥{{total}}</div>
<button @click="getMoreMoney">{{arryList[0].name}} Save more</button>
<msg-tip :msginfo='msgText' :totalnum='total'></msg-tip>
</div>
<script>
debugger;
// Define a new component
var a = {
props: ['msginfo'.'totalnum'].data: function () {
return {
count: 0}},template: {{totalNum}}
}
var app = new Vue({
el: '#app'.components: { msgTip: a},
beforeCreate(){},created(){},beforeMount(){},mounted: () = >{},beforeUpdate(){},updated(){},beforeDestroy(){},destroyed(){},data: function () {
return {
money: 100.num: 12.arryList: [{name:'subtrees'}].msgText: "Excellent Naco:"}},computed: {
total() {
return this.money * this.num; }},watch: {// It was not written at the beginning, but added in the chapter of Watch
money: {handler(newVal, oldVal){
this.msgText = newVal+this.msgText
},
deep:true.immediate:true}},methods: {
getMoreMoney() {
this.money = this.money * 2
this.arryList.unshift({name: 'trees'}}}})</script>
</body>
</html>
Copy the code
1. Introduction
The structure of this paper is based on points, lines and planes.
- The point is the function
- The line is the execution flow of the function
- Surface is a detailed interpretation of the source code
It is not recommended to read the source code directly, many functions are very long, and the link is very long, in the absence of a general understanding of the function, probability, you will find after reading the source code, WC I just read the source code? But I don’t remember what they did. Therefore, first look at the role, then look at the process, and then expand to see the source code.
Concept 2.
2-1. What is the Vue response
When the data changes, the page is re-rendered, which is called Vue responsiveness
2-2. What do we need to do to complete this process
- Detect changes in data
- Collect what data the view depends on
- When data changes, automatically “notify” the part of the view that needs to be updated and update it
They correspond to professional idioms:
- Data hijacking/data proxy
- Depend on the collection
- Publish and subscribe model
3-3. Translation translation
Ma Zi Chang: The data has changed, it will be re-rendered, you translate for the translator
We need to know which data has changed (data hijacking), we need to know which data has changed, we need to know which data is being used by the view (dependent on collection), and finally, we need to tell the page that the data you depend on has changed, so update it now (publish subscribe mode).
4. 4
To make it easier for you to read, introduce some basic concepts.
- Watcher: An observer
- We can think of Watcher as an intermediary, notifying it when data changes, and then notifying it elsewhere.
- Dep: Subscriber
- Collecting dependencies requires finding a place to store dependencies for dependencies, so we created a Dep that collects dependencies, removes dependencies, sends messages to dependencies, and so on. Dep is used for dependency collection and dispatch update operations that decouple attributes, “to be more specific” : its main purpose is to store Watcher observer objects.
3. Overall process
Vue instantiates an object as follows:
- After a new instance is created, Vue calls compile to convert el to VNode.
- Call initState to create hooks for props, data, and observers for its object members (adding getters and setters).
- Mount, set up a Watcher directly corresponding to Render at mount time, and compile the template to generate render function, execute vm._update to update the DOM.
- Whenever data changes, the corresponding Watcher is notified to perform a callback to update the view.
- When a value is assigned to an attribute of the object, the set method fires.
- The set call triggers notify() of the Dep to notify the corresponding Watcher of the change.
- Watcher calls the update method.
In the process:
- An Observer is used to add Dep dependencies to data.
- Dep is data. Each object, including its children, has one of these objects. When the bound data changes, Watcher is notified through dep.notify().
- Compile is an HTML instruction parser that scans and parses the instructions of each element node, replaces the data according to the instruction template, and binds the corresponding update function.
- Watcher is a bridge between the Observer and Compile. When Compile parses the directive, it creates a Watcher and binds the update method to the Dep object.
data:{
Dep:{
watch1,
watch2,
}
}
Copy the code
3. initState
3-1. Basic information
The state new Vue is initialized, and function initMixin is executed to initialize each state.
Function:
Initialization state
: Initializes them separatelyProps, Methods, Data, computed, and Watch
It is also a data responsive entry pointpriority
:Props, Methods, data, computed
Attributes in objects must be unique, and their priorities are the same as those in the list order. Keys in computed cannot be the same as keys in props and data, and methods have no impact
Source:
function initState(vm) {
vm._watchers = []; // Initialize the observer queue
var opts = vm.$options; // Initialize parameters
// Check if there is an props attribute, and if there is an observer, add the props attribute
$options = mergeOptions() vm.$options = mergeOptions()
// It is mounted here
if (opts.props) {
// Initialize the props to check whether the props data format is canonical. If so, add it to the observer queue
initProps(vm, opts.props);
}
if (opts.methods) {
Methods bubble events into the outermost layer of vm[key] virtual DOM
initMethods(vm, opts.methods);
}
if (opts.data) {
// Initialize data to get options.data and add them to listener
initData(vm);
} else {
// If value has an __ob__ instantiation dep object, add an __ob__ attribute to value.
// Adding vm._data to the Observer returns the object instantiated by the new Observer
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { // Calculate attributes
// Initialize the calculated property and determine if the property's key is in data, add the calculated property's key to the listener
initComputed(vm, opts.computed);
}
The watch / / options
if(opts.watch && opts.watch ! == nativeWatch) {// Initialize WatchinitWatch(vm, opts.watch); }}Copy the code
3-2. initProps
Function:
Initialize data Gets data for options.props, sets a response for each property of the props object, and proxies it to the VM instance
Source:
if (opts.props) {
// Initialize the props to check whether the props data format is canonical. If so, add it to the observer queue
initProps(vm, opts.props);
}
// Initialize the props to check whether the props data format is canonical. If so, add it to the observer queue
function initProps(
vm,
propsOptions // Props example: {msginfo: {type: null}, totalNum: {type: null}}
// This format is processed by the normalizeProps function
) {
debugger
{msginfo: "totalnum: 1200 ",totalnum: 1200}
var propsData = vm.$options.propsData || {};
var props = vm._props = {}; / / mount _props
var keys = vm.$options._propKeys = []; / / mount _propKeys
varisRoot = ! vm.$parent;// If the $parent attribute exists, this node is not the root node
// Set not to listen to observers
if(! isRoot) { toggleObserving(false); // shouldObserve = false;
}
// Pass the props key through the function
var loop = function (key) {
debugger. };// Loop to check whether props are qualified data and add observers
for (var key in propsOptions) loop(key);
toggleObserving(true);
}
Copy the code
Loop:
Obtain the default value of props[key], and verify prop
var loop = function (key) {
debugger
keys.push(key);
// Get the default value of props[key]
var value = validateProp(
key, // Key of the props object. Example: "totalnum"
propsOptions, // Example: {msginfo: {type: null}, totalNum: {type: null}}
propsData, {msginfo: "totalnum: 1200 ",totalnum: 1200}
vm
);
/* Istanbul ignore else */
{
// The hump aBc becomes a-bc
// Match uppercase letters and replace both sides with - before converting to lowercase
var hyphenatedKey = hyphenate(key);
// Check whether the attribute is reserved.
//var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
// Output a warning
warn(
("\" " + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
vm
);
}
Subscribers are notified of new value changes by defineProperty's set method
defineReactive(props, key, value, function () {
if(vm.$parent && ! 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 ); }}); }// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if(! (keyin vm)) { // If the VM doesn't have props, add it to the VM so that this.[propsKey] gets the value
proxy(vm, "_props", key); }};Copy the code
3-2-1. validateProp
Execution process:
- Check whether prop type is a Boolean value
- Define a value as the value of the key in the props object, perform a series of operations on it, and finally return it
- If type is a Boolean
- Set value to false if key is not propsData’s own property and default is not defined
- Set value to true if value is null, or value is the same as key, and type is not a string, or if the Boolean index is less than a string, it matches a Boolean type first
- If value is not defined, try to get the default value and add observations to value
- Check whether prop is qualified
- Returns the final value
Source:
function validateProp(
key, // Key of the props object. Example: "totalnum"
propOptions, // Example: {msginfo: {type: null}, totalNum: {type: null}}
propsData, {msginfo: "totalnum: 1200 ",totalnum: 1200}
vm
) { / / vm this attribute
var prop = propOptions[key]; // Get props attributes of a component definition. Example: {type: null}
varabsent = ! hasOwn(propsData, key);// Whether key is not its own attribute
var value = propsData[key]; // Get a value example: "Excellent Negu:"
/ * * * prop. Type: * * getTypeIndex = null * * getTypeIndex = null * * getTypeIndex = null * * getTypeIndex = null * * getTypeIndex = null * * getTypeIndex * Check whether the type in props meets the desired type, if so, return 0 or subscript, otherwise -1 */
var booleanIndex = getTypeIndex(Boolean, prop.type);
if (booleanIndex > -1) { // If the value is Boolean
// Set value to false if key is not propsData instantiated, or if default is not defined
if(absent && ! hasOwn(prop,'default')) {
value = false;
} else if (
value === ' ' // If value is null
|| value === hyphenate(key) // Or key out - when the form is the same as value
) {
// Check whether prop.type is a string
var stringIndex = getTypeIndex(String, prop.type);
// If it is not a string,
// If the Boolean index is less than the string, the Boolean type is matched first.
// Force the value to true
if (
stringIndex < 0 ||
booleanIndex < stringIndex) {
value = true; }}}debugger
// If value is not defined
if (value === undefined) {
// Try to get the default value
value = getPropDefaultValue(vm, prop, key);
var prevShouldObserve = shouldObserve;
toggleObserving(true);
observe(value);
toggleObserving(prevShouldObserve);
}
{
// Check whether prop is qualified
assertProp(
prop, // The type value of the attribute
key, // Key in the props property
value, // View property value
vm, // VueComponent component constructor
absent //false
);
}
return value
}
Copy the code
3-2-1-1. getPropDefaultValue
Function:
Return the default value of the prop property.
Execution process:
- If there is no default on prop, return undefined
- If prop’s default is a function and type is not a function declaration, defalut is returned, otherwise defalut is returned
Associated documents:
If (props) [] or {} is the default value, an error will be reported. If (props) [] or {} is the default value, an error will be reported.
SelectLogisticsNos: {type: Array, default: false // Props: {visible: {type: Boolean, default: false}, selectLogisticsNos: {type: Array, default: () = > [] / / / / Object/Array must use the plant function default: [] / / complains}}Copy the code
So why do it? Because when our objects operate, they need to point their this to the VM instance.
Source:
function getPropDefaultValue(vm, prop, key) {
// Check whether default in prop is instantiated by prop
if(! hasOwn(prop,'default')) {
return undefined
}
var def = prop.default;
/** * If the default value of the property key is invalid, the factory function must be used for properties of type Object/Array. Example: {visible: {type: Boolean, default: SelectLogisticsNos: {type: Array, default: () => [] //Object/Array must use factory functions},} */
if ("development"! = ='production' && isObject(def)) {
warn(
'Invalid default value for prop "' + key + '" : +
'Props with type Object/Array must use a factory function ' +
'to return the default value.',
vm
);
}
// The original PROP value was also not defined in the previous render,
// Return the previous default value to avoid unnecessary monitoring triggers
if (vm && vm.$options.propsData &&
vm.$options.propsData[key] === undefined&& vm._props[key] ! = =undefined
) {
return vm._props[key]
}
// prop default is a function, and type is not a function, then defalut is executed, otherwise defalut is returned
return typeof def === 'function'&& getType(prop.type) ! = ='Function'
? def.call(vm)
: def
}
Copy the code
3-3. initMethods
Function:
Apply a series of constraints to functions, fix methods objects, and mount individual functions directly to vUE
Execution process:
- Iterate over the methods object
- Constraints on functions
- Raises a warning if the event corresponding to the key of the function object does not exist
- If a key is defined in a property, the same key cannot be defined in methods
- Check if a string is specified
$
Or a letter beginning with _. Events cannot begin with$
or_
Initial letter
- Mount the event directly to the VUE, giving an empty function if the function is empty, and returning a function function if it exists
Associated documents:
When we use events in vue, we use this.fn() directly, i.e. Vue.fn(). So why does it work that way?
It is because the initMethods last step mounts each function directly to the VM.
Source:
if (opts.methods) {
Methods bubble events into the outermost layer of vm[key] virtual DOM
initMethods(vm, opts.methods);
}
function initMethods(
vm,
methods ƒ getMoreMoney()}
) {
debugger
var props = vm.$options.props;
// Loop methods event objects
for (var key in methods) {
{
// If the event corresponding to the key does not exist, a warning is issued
if (methods[key] == null) {
warn(
"Method \"" + key + "\" has an undefined value in the component definition. " +
"Did you reference the function correctly?",
vm
);
}
// If a key is defined in an attribute, the same key cannot be defined in methods
if (props && hasOwn(props, key)) {
warn(
("Method \"" + key + "\" has already been defined as a prop."),
vm
);
}
// isReserved Checks whether a string starts with a letter $or _
// Events cannot start with a letter $or _, because methods starting with $and _ are generally built-in methods that repeat
if ((key in vm) && isReserved(key)) {
warn(
"Method \"" + key + "\" conflicts with an existing Vue instance method. " +
"Avoid defining component methods that start with _ or $."); }}// Put the event in the outermost object, give an empty function if the function is empty, and return the function if it has a function
vm[key] = methods[key] == null? noop : bind(methods[key], vm); }}Copy the code
3-4. initData
InitState (VM) : Vue
$attrs: (...) $listeners: [] $createElement: ƒ (a, B, c, D) $options: {components: {... }, the methods: {... }, el: "# app... $parent: undefined $refs: {} $root: Vue {_uid: 0, _isVue: true, $options: {... }, _renderProxy: Proxy, _self: Vue,... $scopedSlots: {} $slots: {} $vNode: undefined _c: ƒ (a, b, c, d) arguments: (...) caller: (...) Prototype: {constructor: ƒ} __proto__: ƒ () [[FunctionLocation]]: oldvue.js :6695 [[FunctionLocation]]: Scopes: Scopes[3] _directInactive: false _events: {} _hasHookEvent: false _inactive: null _isBeingDestroyed: False _isVue: false _isVue: true _isVue: false _isVue: true _renderProxy: Proxy {_uid: 0, _isVue: true, $options: {... }, _renderProxy: Proxy, _self: Vue,... } _self: Vue {_uid: 0, _isVue: true, $options: {... }, _renderProxy: Proxy, _self: Vue,... } _staticTrees: null _uid: 0 _vnode: null _watcher: null $data: (...) $isServer: (...) $props: (...) $ssrContext: (...) ƒ reactiveGetter() set $notices: ƒ reactiveSetter (newVal) __proto__ : ObjectCopy the code
As you can see, the VM is already mounted with the common properties and functions.
Function:
Initialize data to obtain options.data, evaluate data, methods, and props, delegate each value of data to vm, and add them to listener.
Execution process:
- Reset data, if data is a function, take the return of data, not its own value, and mount the _data attribute (object) of data to Vue.
- After the reset, data is not an object, reset data to {} with a warning
- Traverse the key in the data object, warning if the key is the same as the property name in methods or props
- If the key does not start with $or _, proxy properties on the data object to the VM instance
- Set up a responsivity for data on a data object
Associated documents:
When we use the data attribute in vue, we directly use this. XXX, i.e. Vue.xxx. So why does it work that way?
Proxy (VM, “_data”, key); Mount each function directly to the VM.
Source:
if (opts.data) {
// Initialize data to get options.data and add them to listener
initData(vm);
} else {
// If value has an __ob__ instantiation dep object, add an __ob__ attribute to value.
// Adding vm._data to the Observer returns the object instantiated by the new Observer
observe(vm._data = {}, true /* asRootData */);
}
function initData(vm) {
var data = vm.$options.data;
// If data is a function, take the return of data, not its own value, and mount _data(object) with the value of data to Vue.
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
{money: 100, num: 12}
if(! isPlainObject(data)) {// Reset data to {} if it is not an object and issue a warning log
data = {};
"development"! = ='production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
);
}
// example: ["money", "num", "arryList", "msgText"]
var keys = Object.keys(data);
var props = vm.$options.props; // Get the props attribute
ƒ getMoreMoney()}
var methods = vm.$options.methods; // Get the event
var i = keys.length; // Get the length of the data key
while (i--) { / / circulation data
var key = keys[i];
{
if (methods && hasOwn(methods, key)) { // A warning is issued if the key in the data is the same as the key defined in the event
warn(
("Method \"" + key + "\" has already been defined as a data property."), vm ); }}if (props && hasOwn(props, key)) { // Raises a warning if the key in the data is the same as the key defined in the props property
"development"! = ='production' && warn(
"The data property \"" + key + "\" is already declared as a prop. " +
"Use prop default value instead.",
vm
);
} else if(! isReserved(key)) {// If the key does not start with $or _
proxy(vm, "_data", key); // Set the listener to target, which is the key on each sourceKey on the Vue instance}}// Set the response for the data on the data object
observe(data, true);
}
Copy the code
3-4-1. proxy
Function:
Set up the proxy, proxy properties on data and props directly to Vue, and set the GET and set methods
Source:
proxy(vm, "_props", key);
proxy(vm, "_data", key);
function proxy(
target, / / the Vue instance
sourceKey, // Listen on key on Vue. Example: sourceKey = '_data'
key / / example: msgText
) {
debugger
// Mount the get and set methods respectively
sharedPropertyDefinition.get = function proxyGetter() { // Set the get function
return this[sourceKey][key] // sourceKey = "_data", key = "num"
};
sharedPropertyDefinition.set = function proxySetter(val) {// Set the set function
this[sourceKey][key] = val;
};
// Set the listener to target, which is the key on each sourceKey on the Vue instance
/** * target = * {$attrs: (...) , $children: [], $createElement method: ƒ (a, b, c, d), $listeners: (...). . ƒ, msgText: ƒ getter (), get msgText: ƒ proxyGetter()
Object.defineProperty(target, key, sharedPropertyDefinition);
}
var sharedPropertyDefinition = { // Share attribute definitions
enumerable: true.configurable: true.get: noop,
set: noop
};
Copy the code
After executing, mount money, num, and arryList from data directly to Vue instance.
3-5 observe
Function initData(vm) Observe (data, true) in the last step;
Function:
Creates an observer instance for an object, returning the existing observer instance if the object has already been observed, or creating a new observer instance otherwise
Execution process:
- If data is not an object or an instantiated VNode, do nothing (non-objects and VNode instances do not respond) and exit the function
- The first time it came in, there was nothing in value
__ob__
Of, if exist__ob__
Property, which indicates that observations have been made and returns directly__ob__
Properties,ob = value.__ob__
- If not, no.
__ob__
If value is an object and an array, create an observer instance,ob = new Observer(value);
- If OB exists, the counter on OB is incremented
- Finally return to OB
Source:
observe(data, true);
function observe(
value, // The data object is passed in for the first time.
// 例:{ arryList: [{…}],money: 100,msgText: "优秀的乃古:"num: 12 }
asRootData //asRootData = true
) {
debugger
// Data is not an object or instantiated VNode
if(! isObject(value) || valueinstanceof VNode) {
return
}
var ob;
// The first time it comes in, there is no __ob__ in value
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve && / / shouldObserve is true! isServerRendering() &&// Not in the server node environment
(Array.isArray(value) || isPlainObject(value)) && // Is an array or an object
/** * Object. IsExtensible is used to determine whether objects can be extensible
Object.isExtensible(value) && ! value._isVue/ / _isVue is false
) {
// instantiate the dep object to add an __ob__ attribute to value
ob = new Observer(value);
}
// If the value is RootData, that is, the value passed to data when we create a Vue instance, only RootData will count every time it observes Vue.
// vmCount is used to count the number of times the Vue instance is used. For example, if we have a component logo, we need to display the logo in the header and tail of the page.
// if this component is used, then vmCount is the number and the value is 2
if (asRootData && ob) { // the root node data and ob exist
ob.vmCount++; // Count the number of VMS
}
// Instantiate the dep object to add an __ob__ attribute to value
// example: {dep: dep {id: 7, subs: Array(0)}, value: {__ob__: Observer}, vmCount: 0}
return ob
}
Copy the code
3-6 Observer
Function:
Observer constructor, for Observe.
The observer class, attached to each object being observed, adds an __ob__ attribute to value, and the attributes of the object are converted to getters/setters, collecting dependencies and notifying updates
Execution process:
- Mount the current Observer to data’s __ob__
- If value is an array, traverse each entry in the array and observe
- If it is an object, iterate over each property and convert it to a getter/setter
Source:
ob = new Observer(value);
var Observer = function Observer(value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
// Set the listener value to be an object
debugger
// Add the __ob__ attribute to value, which is the Observer object, value.__ob__ = this;
Each object in Vue.$data has an __ob__ attribute, including the Vue.$data object itself
def(value, '__ob__'.this);
debugger
/** * value is array * hasProto = '__proto__' in {} * used to determine whether an object has a __proto__ attribute, __proto__ is not a standard property, so some browsers do not support it, such as IE6-10, Opera10.1, That's because we'll override the array's default seven prototype methods via a __proto__ prototype chain that manipulates data to implement array-responsive */
if (Array.isArray(value)) { // Check whether it is an array
var augment = hasProto //__proto__ exists in all advanced browsers
? protoAugment
: copyAugment;
augment(value, arrayMethods, arrayKeys);
this.observeArray(value); // Iterate over value, execute observe(items); Eventually, the walk method is executed
} else {
// Set responsiveness for each property of the object, including nested objects
this.walk(value); }};// When the current browser supports' __proto__ ', the array API is mounted on the value prototype
function protoAugment(
target, // 就是上面的value(当为数组格式时)
src, // Array API collection
keys // The set of API keys will not be used here
) {
target.__proto__ = src;
}
// If the current browser does not support '__proto__', set the proxy to value and mount the array method
function copyAugment(
target, // 就是上面的value(当为数组格式时)
src, // Array API collection
keys // Set of API keys
) {
for (var i = 0, l = keys.length; i < l; i++) {
varkey = keys[i]; def(target, key, src[key]); }}Copy the code
3-6-1 def
Function:
Define attributes with defineProperty, and override the key on the passed object as the passed function
Source:
/** Define attributes with defineProperty. The first argument is the object, the second is the key, the third is the function, and the fourth is whether it can be enumerated. * /
function def(
obj, Obj = Array {}, Array prototype object
key, // key = "push
val, ƒ mutator()
enumerable // enumerable = undefined
) {
ƒ mutator(); // Obj
/ / such as: obj = {push: ƒ mutator (), __proto__ : Array (0)}
Object.defineProperty(obj, key, {
value: val, / / value
enumerable:!!!!! enumerable,// Defines whether the attributes of an object can be used in a for... Are enumerated in the in loop and object.keys ().
writable: true.// You can rewrite value
configurable: true // The 6464x feature specifies whether the attributes of the object can be deleted without any additional writable information
// Whether other features can be modified.
});
}
Copy the code
The def function has another function:
The def function is useful in several places. We know that Vue can respond to an array because we overwrote the array method, which looks like this:
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
// Overwrite the array methods
var methodsToPatch = [
'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
];
/** * if the array interceptor method is used to update data, it will be called if the data is updated with 'push','pop','shift','unshift','splice','sort','reverse' methods
methodsToPatch.forEach(function (
method // The method name of the array
) {
// Get the various native methods of the array
var original = arrayProto[method];
debugger
def(arrayMethods, method, function mutator() {
var args = [], len = arguments.length;
while (len--) args[len] = arguments[len];
var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) {
// Observe the array data
ob.observeArray(inserted);
}
// Update notifications
ob.dep.notify();
return result
});
});
DefineProperty defines attributes for the passed function with defineProperty. The first argument is object, the second is key, the third is vue, and the fourth is whether enumeration is possible. * /
function def(
obj, Obj = Array {}, Array prototype object
key, // key = "push
val, ƒ mutator()
enumerable // enumerable = undefined
) {
debugger
ƒ mutator(); // Obj
/ / such as: obj = {push: ƒ mutator (), __proto__ : Array (0)}
Object.defineProperty(obj, key, {
value: val, / / value
enumerable:!!!!! enumerable,// Defines whether the attributes of an object can be used in a for... Are enumerated in the in loop and object.keys ().
writable: true.// You can rewrite value
configurable: true // The 6464x feature specifies whether the attributes of the object can be deleted, and whether any features other than writable can be modified.
});
}
Copy the code
3-6-2 walk
Function:
Iterate over each property and convert them to getters/setters.
Source:
Observer.prototype.walk = function walk(obj) {
debugger
var keys = Object.keys(obj); Keys = ["money", "num", "arryList"]
for (var i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]); // Pass each key to the object}};Copy the code
Let’s take a look at the defineReactive function, which is very long and will be explained in Section 4.
DefineReactive does:
- Define a reactive property on object
- Change the property key in obj to a getter/setter reactive property
- Dependencies are collected in getters and fired in setters.
The 3-6-3 summary
Observe this function detects object changes by passing in an obj (the object whose change needs to be traced) and passing through defineReactive for each attribute. Add set and GET methods to each attribute. It’s worth noting that Observe makes recursive calls.
Observe The ob returned after the operation is complete:
{
dep: {id: 2.subs: Array(0)},
value: {
"money": 100."num": 12."arryList": [{"name": "Subtree"}},vmCount: 1.__proto__: Object
}
Copy the code
Return to initData function observe(data, true)
At this point, the data format is already in the following format:
{
arryList: [{ name: "Subtree"}].money: 100.num: 12.__ob__: Observer {value: {... },dep: Dep, vmCount: 1}, get arryList: ƒ reactiveGetter(), set arryList: ƒ reactiveSetter(newVal), get money: ƒ reactiveSetter(newVal), get num: ƒ reactiveGetter(), set num: ƒ reactiveSetter(newVal),__proto__: Object
}
Copy the code
Each property already has the corresponding GET and set methods mounted.
3-7. initComputed
Function:
Execution process:
- Pass in a collection of computed properties
- Create a new listener, Watchers, and mount it to _computedWatchers of the VM.
- Define getters, taking itself if the evaluated property value is a function, and its GET property otherwise
- If getter is empty Warning: Compute property missing getter
- If not Node SSR render, add observer to Watchers calculated attribute
- Determine the attribute key for computed
- If not in the VM, define the computed properties and add the computed properties’ keys and computed functions directly to the VM (so we can use this.propsxx directly).
- If in the VM, and in $data or props, caution: the computed properties are already defined in data or prop and cannot be repeated
Source:
if (opts.computed) {
initComputed(vm, opts.computed);
}
function initComputed(
vm,
computed // Example: {total: ƒ total()}
) {
debugger
// Create a new listener empty object and mount it to _computedWatchers of the VM
var watchers = vm._computedWatchers = Object.create(null);
// The server is rendered to determine whether it is a Node server environment
var isSSR = isServerRendering();
for (var key in computed) {
var userDef = computed[key]; // get the calculation function
// Define getters. If the evaluated property value is a function, take itself, otherwise take its GET property
var getter = typeof userDef === 'function' ? userDef : userDef.get;
// If the getter is empty, the computed property is missing the getter
if ("development"! = ='production' && getter == null) {
warn(
("Getter is missing for computed property \"" + key + "\"."),
vm
);
}
// If it is not Node SSR render, add observer to Watchers calculated attribute
// This step shows that computed is also a watcher
if(! isSSR) { watchers[key] =new Watcher(
vm, //vm vode
getter || noop, //noop is empty
noop, // The callback function
computedWatcherOptions // The argument lazy = true
);
/** * watchers, example: * {total:{vm: Vue, deep: false, user: false, lazy: true, sync: false,... }} * /
}
if(! (keyin vm)) {
// If computed attribute key is not in the VM,
// Define computed attributes and add computed attributes keys and computed functions to the VM
defineComputed(vm, key, userDef);
} else {
// Warning: calculated properties are already defined in data or prop and cannot be repeated
if (key in vm.$data) {
warn(("The computed property \"" + key + "\" is already defined in data."), vm);
} else if (vm.$options.props && key in vm.$options.props) {
warn(("The computed property \"" + key + "\" is already defined as a prop."), vm); }}}}Copy the code
3-7-1. defineComputed
Function:
Defines a calculated property and adds the property’s data to an object listener
Source:
function initComputed(vm, computed) {
for (var key in computed) {
var userDef = computed[key]; // get the calculation function
if(! (keyin vm)) {
// If computed attribute key is not in the VM,
// Define computed attributes and add computed attributes keys and computed functions to the VMdefineComputed(vm, key, userDef); }}}function defineComputed(
target, / / target
key, //key
userDef / / value
) {
varshouldCache = ! isServerRendering();// If not node server is browser
if (typeof userDef === 'function') { // Attribute value if it is a function
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key) // If not node server is the browser to create calculated properties to get values to collect DEP dependencies
: userDef; // The node server calls this function directly
sharedPropertyDefinition.set = noop; // Assign an empty function
} else {
sharedPropertyDefinition.get = userDef.get ?// If userdef.get exists(shouldCache && userDef.cache ! = =false ? / / cache
createComputedGetter(key) : // Create calculated properties to get values to collect DEP dependencies
userDef.get
) :
noop; // Give an empty function if userdef. get does not exist
sharedPropertyDefinition.set = userDef.set // If userdef.set exists
? userDef.set
: noop;
}
if ("development"! = ='production' &&
sharedPropertyDefinition.set === noop) { // Warning if the value is equal to an empty function
sharedPropertyDefinition.set = function () {
warn(
("Computed property \"" + key + "\" was assigned to but it has no setter."),
this
);
};
}
// Add object listener
Object.defineProperty(target, key, sharedPropertyDefinition);
}
var sharedPropertyDefinition = { // Share attribute definitions
enumerable: true.configurable: true.get: noop,
set: noop
};
Copy the code
3-7-2. createComputedGetter
Function:
Create calculated properties to get values to collect DEP dependencies
Source:
function createComputedGetter(key) {
// This method is called when the user values
return function computedGetter() {
// The object instantiated by Watcher
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
// When dirty is true, it will be evaluated, where dirty is used as a cache
//this.value gets the value this.getter
watcher.evaluate();
}
if (Dep.target) {
// Add Watcher to watcher.newdeps.push (dep); A DEP object
// loop deps collect newDeps dep Collect dependencies again when newDeps data is empty
watcher.depend();
}
/ / the return value
return watcher.value
}
}
}
Watcher.prototype.evaluate = function evaluate() {
this.value = this.get(); / / get the value
this.dirty = false; // Slacker flag indicates that the value has been fetched once
};
Copy the code
3-8. initWatch
Function:
Iterate over watch, if the property value is an array, iterate over the property value (watch configuration item) to create watch
Source:
if(opts.watch && opts.watch ! == nativeWatch) {// Initialize Watch
initWatch(vm, opts.watch);
}
// Initialize the Watch listener
function initWatch(
vm,
watch // example: {money: {deep: true, immediate: true, handler: ƒ}}
) {
debugger
// Loop the watch object
for (var key in watch) {
var handler = watch[key]; // Get a single Watch configuration item
// If it is an array handler
if (Array.isArray(handler)) {
// loop array creation listener
for (var i = 0; i < handler.length; i++) {
createWatcher(
vm, // VM is a vUE object
key, //key
handler[i]// function or object); }}else {
// loop array creation listener
createWatcher(
vm, // VM is a vUE object
key, / / example: "money"
handler // Example: {deep: true, immediate: true, handler: ƒ}); }}}Copy the code
3-8-1. createWatcher
Function:
Iterate over watch, if the property value is an array, iterate over the property value (watch configuration item) to create watch
Execution process:
- Handler if the watch configuration item is an object (in common format), get the execution function of the watch configuration item handler.
- If the configuration item handler of Watch is a string, obtain the corresponding execution function directly from the VM.
- $watch is used to execute the watch function based on immediate
Source:
// Escape the handler and create Watcher observers for the data
function createWatcher(
vm, / / vm object
expOrFn, / / example: "money"
handler, // Example: object corresponding to money in vue watch {deep: true, immediate: true, handler: ƒ}
options / / parameters
) {
if (isPlainObject(handler)) { // Determine if it is an object
options = handler;
handler = handler.handler; Handler. Handler is a function
}
if (typeof handler === 'string') { // Check whether handler is a string. If so, it is key
handler = vm[handler]; // The value vm is the function in the outermost layer of Vue
}
// Escape the handler and create Watcher observers for the data
return vm.$watch(
expOrFn,// Key value or function
handler, / / function
options / / parameters
)
ƒ handler(newVal, oldVal), deep: true, depIds: Set(1) {3}, DEps: [Dep], dirty: false, expression: "money", getter: ƒ (obj), ID: 2, lazy: false, newDepIds: Set(0) {}, newDeps: [], sync: false, user: true, value: 100, vm: Vue {_uid: 0, _isVue: true, $options: {...}, _renderProxy: Proxy, _self: Vue,...}, *}] */
}
Copy the code
3-8-2. $watch
Function:
Whether to execute the watch function immediately according to immediate to uninstall the observer and remove the listener
Execution process:
- If cb is an object, recursively execute createWatcher (which returns vm.$watch)
- Set the user flag to true, and the user manually listens, which is the customized watch in options
- Create a new Watcher
- If the immediate attribute is true, the callback triggers the function immediately
- Finally, execute watcher.teardown() to uninstall the listener
Associated documents:
If (options.immediate) {cb.call(vm, watcher.value); if (options.immediate) {cb.call(vm, watcher.value); }
Source:
// Execute before new Vue
function stateMixin(Vue) {... Vue.prototype.$watch =function (
expOrFn, // Watch name, e.g. "money"
cb, ƒ Handler (newVal, oldVal)
options // Watch parameters: example: {deep: true, immediate: true, handler: ƒ}
) {
debugger
var vm = this;
If it is an object, recursively deep listen until it is not an object
if (isPlainObject(cb)) {
// Escape the handler and create Watcher observers for the data
return createWatcher(
vm,
expOrFn,
cb,
options
)
}
options = options || {};
options.user = true; // The user manually listens, which is a customized watch in options
var watcher = new Watcher(
vm,
expOrFn,
cb,
options
);
ƒ Handler (newVal, oldVal), deep: true, depIds: Set(1) {3}, deps: [Dep], dirty: False, expression: "money", getter: ƒ (obj), ID: 2, lazy: false, newDepIds: Set(0) {}, newDeps: [], sync: false, user: True, value: 100, vm: Vue {_uid: 0, _isVue: true, $options: {... },... }, __proto__: Object } */
// Immediate if the value of the attribute is true, the function is triggered immediately
if (options.immediate) {
cb.call(vm, watcher.value);
}
return function unwatchFn() { // Unmount the observer
// Remove self from the subscriber list of all dependencies.watcher.teardown(); }}; }Copy the code
3-8-3. Watcher
Function:
The Watcher constructor
Execution process:
Source:
var Watcher = function Watcher(
vm, //vm dom
expOrFn, // Get the value of the function, or update the viwe attempt function
cb, // The callback value is given to the callback function
options, / / parameters
isRenderWatcher// Whether to render the viewer
) {
this.vm = vm;
// Whether the viewer is already rendered
if (isRenderWatcher) { // Assign the current Watcher object to vm._watcher
vm._watcher = this;
}
The current Watcher is added to the vUE instance
vm._watchers.push(this);
// options
if (options) { // If there are parameters
this.deep = !! options.deep;/ / the actual
this.user = !! options.user;/ / user
this.lazy = !! options.lazy;// Lazy SSR rendering
this.sync = !! options.sync;// If it is synchronous
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
this.cb = cb; // The callback function
this.id = ++uid$1; // uid for batching UID indicates the id of the batch listener
this.active = true; / / activation
this.dirty = this.lazy; // For lazy watchers
this.deps = []; // Observer queue
this.newDeps = []; // New observer queue
// An array object whose contents cannot be repeated
this.depIds = new _Set();
this.newDepIds = new _Set();
// Make the function a string
this.expression = expOrFn.toString();
// Parse the expression for the getter
if (typeof expOrFn === 'function') {
// The function that gets the value
this.getter = expOrFn;
} else {
// If it is a keepAlive component it will go here
//path is a routing address
// If (bailre.test (path)) {var bailRE = /[^\w.$]/; // Match true if not alphanumeric underscore $symbol
// return
// }
// // does not match path in the split point
// var segments = path.split('.');
// return function (obj) {
//
// for (var i = 0; i < segments.length; i++) {
// // returns true if there are arguments
// if (! obj) {
// return
/ /}
// // assigns a key value to an object equal to segments using an array of dots as the key of obj
// obj = obj[segments[i]];
/ /}
// // otherwise returns an object
// return obj
// }
// Match true if not alphanumeric underscore $symbol
this.getter = parsePath(expOrFn);
if (!this.getter) { // An empty array is given if none exists
this.getter = function () {};"development"! = ='production' && warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.', vm ); }}this.value = this.lazy ? // Lazy is true only if it is a component
undefined :
this.get(); // Compute getters and re-collect dependencies. Get the value
};
Copy the code
3-8-4. Watcher.prototype.get
Function:
Compute getters and re-collect dependencies. To obtain the value value
Execution process:
Source:
Watcher.prototype.get = function get() {
// Add a dep target
pushTarget(this);
var value;
var vm = this.vm;
try {
If an error is reported, catch is executed
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\" "));
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
// "touch" each property so that they are all traced as
// Rely on deep observation
if (this.deep) {
// // if val has an __ob__ attribute
// if (val.__ob__) {
// var depId = val.__ob__.dep.id;
// // Seen whether the depId attribute or method is available
// if (seen.has(depId)) {
// return
/ /}
// // If no, add it
// seen.add(depId);
// }
// Deeply collect the key from val for seenObjects
traverse(value);
}
// Display a pushTarget
popTarget();
// Clean up the dependency collection.
this.cleanupDeps();
}
/ / the return value
return value
};
Copy the code
3-8-5. traverse
Source:
function traverse(val) {
// Add depId to seen
/ / seenObjects set object
// Deeply collect the key from val for seenObjects
_traverse(val, seenObjects);
// Clear the object and empty it
seenObjects.clear();
}
function _traverse(val, seen) {
// de_traverse
var i, keys;
// Check if it is an array
var isA = Array.isArray(val);
The isFrozen method checks whether an object isFrozen.
// Whether val is instantiated by VNode
if((! isA && ! isObject(val)) ||Object.isFrozen(val) || val instanceof VNode) {
return
}
// If val has an __ob__ attribute
if (val.__ob__) {
var depId = val.__ob__.dep.id;
// Seen contains a depId attribute or method
if (seen.has(depId)) {
return
}
// Seen is seenObjects = new _Set(); Add is the add method in the set object
// If not, add it
seen.add(depId);
}
// If it is an array
if (isA) {
i = val.length;
// then loop check the callback recursion
while(i--) { _traverse(val[i], seen); }}else {
keys = Object.keys(val);
i = keys.length;
// Loop recursively if it is an object
while(i--) { _traverse(val[keys[i]], seen); }}}Copy the code
4. defineReactive
Object (obj, obj, obj, obj, obj, obj, obj, obj, obj, obj, obj, obj) ¶
Execution process:
- Initialize a dependency object deP (e.g. {id: 3, subs: Array(0)}), a list of empty observers in the object to place dependencies that use this property in.
- Exit the function if the corresponding key passed to the object cannot be modified
- If the property has no get or set, and only two arguments are passed, set val as the value of the property passed in
- If value has an __ob__ instantiation dep object, add an __ob__ attribute to the deP object
- Setting properties with Object.defineProperty becomes getters and setters
Source:
Observer.prototype.walk = function walk(obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); }};function defineReactive(
obj, Obj = {money: 100, num: 12, arryList: Array(1), __ob__: Observer}
key,// Object key key = "money"
val, // Return data val = undefined
customSetter, // Shallow = undefined
shallow // Whether to add the __ob__ attribute shallow = undefined
) {
// Initializes a dependency object with a list of empty observers to store all dependencies that use the property.
var dep = new Dep(); // dep = Dep {id: 3, subs: Array(0)}
/ getOwnPropertyDescriptor methods: * * * * to determine whether this property key has its own properties, obj as the incoming Object, for example: * Object in getOwnPropertyDescriptor (obj, 'money'), the results are as follows: * {value: 100, writable: true, enumerable: true, configurable: If the property can not be modified, the c64X works without any additional control system. If the property can not be modified, the c64X directly exits the function */
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// Get the implemented getter /setter methods
var getter = property && property.get;
var setter = property && property.set;
// If the property has no get or set and only two arguments are passed, set val to the value of the property passed in
// arguments is the entry to defineReactive. As you can see from the example, the other values are undefined
// Only 2 arguments have values, so arguments are 2
if((! getter || setter) &&arguments.length === 2) {
val = obj[key]
}
debugger
/** * Determine if value has an __ob__ instantiated dep object, get the deP object, add an __ob__ attribute to value * recursively add val to the Observer */ Return the instantiated object of the new Observer */
varchildOb = ! shallow && observe(val);debugger
console.log(Dep.target)
// Properties become getters and setters
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter() {
// The value of the property is returned, and dependencies are collected to record where the property is used.
/** * getter = property.get * To see if the property has a get method (e.g. 'money' has a get method via getOwnPropertyDescriptor) * If it does, call the get method * If it doesn't, Take the corresponding key */ on obj
//
var value = getter ? getter.call(obj) : val;
The dep. target global variable points to the Watcher generated by Complie, which is currently parsing the directive
// Execute to dep.addSub(dep.target) to add Watcher to the Watcher list of the DEP object
if (Dep.target) { // dep. target Static flag indicates that the Dep has added an object instantiated by Watcher
// Add a dep
dep.depend(); Function depend() then function addDep(dep)
if (childOb) { // Add a dep if the child node exists
childOb.dep.depend();
if (Array.isArray(value)) { // Check if it is an array
dependArray(value); // Add deP to the array}}}return value
},
set: function reactiveSetter(newVal) {
/** * the new value, newVal, is assigned to the old val, if there is a previous setter, the old setter is used, if there is no setter, return. Finally, the new value adds an __ob__ attribute, which then triggers the dependency of that attribute, notifying them to change */
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */ will not be executed if the old and new values are the same
if(newVal === value || (newVal ! == newVal && value ! == value)) {return
}
/* eslint-enable no-self-compare * when not in production * */
if ("development"! = ='production' && customSetter) {
customSetter();
}
if (setter) {
The set method sets the new value
setter.call(obj, newVal);
} else {
// The new value is given directly to him
val = newVal;
}
//observe Add an observerchildOb = ! shallow && observe(newVal);// If the data is reassigned, call Dep's notify method to notify all watchersdep.notify(); }}); }Copy the code
In fact, defineReactive has already been performed several times during initialization in New Vue, such as initRender, defineReactive
defineReactive(
vm, / / the Vue instance
'$attrs',
parentData && parentData.attrs || emptyObject, / / {}
function () {
!isUpdatingChildComponent && warn("$attrs is readonly.", vm);
},
true
);
defineReactive(
vm, '$listeners',
options._parentListeners || emptyObject,
function () {
!isUpdatingChildComponent && warn("$listeners is readonly.", vm);
},
true
);
Copy the code
It mounts $attrs, $Listeners, and so on to Vue.
Let’s analyze the flow from Observe to defineReactive:
observe()=>new Observer=>function Observer()=>this.walk()=> defineReactive(obj, keys[i])=>function defineReactive()
At this point, the defineReactive is passed as an argument
function defineReactive(
obj, Obj = {money: 100, num: 12, arryList: Array(1), __ob__: Observer}
key,// Object key key = "money"
val, // Return data val = undefined
customSetter, // Shallow = undefined
shallow // Whether to add the __ob__ attribute shallow = undefined
)
Copy the code
Shallow is empty, so var childOb =! shallow && observe(val); Observe (val). It goes back to the observe function, which was explained in the previous section. Note that val = obj[key] has been reset to 100.
function observe(
value, / / 100
asRootData // undefined
){
// Value is not an object or an instantiated VNode
if(! isObject(value) || valueinstanceof VNode) {
return}}Copy the code
In this case, value does not meet the condition, and the childOb is still undefined.
Going back to defineReactive, it sets the get and set methods on the key of the passed object, and the properties become getters and setters.
4-1. Dep
Function: One DEP corresponds to one obj.key
The watcher is responsible for collecting dependencies for each DEP (or obj.key) dependency as it reads reactive data
Responsible for notifying the Watcher in the DEP to perform the update method during reactive data updates
Execution process:
- Deps default to ids (make sure each Dep has a unique ID) and subs (an array to hold Watcher objects)
- Several methods are mounted to the prototype:
- AddSub: Adds a dependency Watcher to the subs array
- RemoveSub: Removes dependencies
- Depend: Sets a Watcher dependency
- Notify: Notifies all Watcher objects to update the view
- AddSub: Adds a dependency Watcher to the subs array
Source:
var uid = 0;
// Dep is the data dependency of subscriber Watcher, which loads all watches into its own subs array
var Dep = function Dep() {
// Each Dep has a unique ID
this.id = uid++;
/* An array of Watcher objects */
this.subs = [];
};
// Add dependency Watcher to subs array
Dep.prototype.addSub = function addSub(sub) {
this.subs.push(sub);
};
// Remove dependencies
Dep.prototype.removeSub = function removeSub(sub) {
remove(this.subs, sub);
};
// Set a Watcher dependency
// Dep. Target is a constructor call from Watcher
Watcher's this.get call is not a normal call
Dep.prototype.depend = function depend() {
// Add a dep target Watcher dep is the deP object
if (Dep.target) {
// Add dependencies like directives
Dep.target.addDep(this); }};/* Notify all Watcher objects to update the view */
Dep.prototype.notify = function notify() {
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); }}; Dep.target =null;
// Target stack
var targetStack = [];
// Place the observer that deP exists in the targetStack for unified management, and then update dep. target to the incoming observer
function pushTarget(_target) {
//target is the Watcher dep object
if (Dep.target) {
targetStack.push(Dep.target);
}
Dep.target = _target;
}
Copy the code
4-2. queueWatcher
Pushes observer to queue to filter duplicate ids except when * refreshed
Watcher.prototype.update = function update() {
// Watcher lazy defaults to false
if (this.lazy) {
this.dirty = true; // Lazy person flag
} else if (this.sync) { // If it is synchronous
// Update data
this.run();
} else {
// If there are multiple observers
queueWatcher(this); // The observer in the queue}};function queueWatcher(watcher) {
debugger
var id = watcher.id;
//has is the object that records the observer's ID: {2: true}
// If the watcher id passed in does not exist in HAS, it needs to be added to HAS
// If it already exists, the current watch is a duplicate, exit the function directly
if (has[id] == null) {
has[id] = true;
// The default is false, and the flushSchedulerQueue function waits for flags
if(! flushing) { queue.push(watcher);Queue is an array of observer queues
} else {
// If it is refreshed, concatenate it according to the monitor id
// If its ID is already passed, next will be run immediately.
var i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
// Insert the concatenation into the array according to the id size
queue.splice(i + 1.0, watcher);
}
// queue the flush
if(! waiting) { waiting =true;
// Collect the queue CB function for callbacks and whether to fire the callbacks queue function based on pending status
debugger
nextTick(
flushSchedulerQueue// Update observer Runs the observer watcher.run() function and calls the component's updated and activated hooks); }}}Copy the code
4-3. nextTick
Collect the queue CB function for callbacks and whether to fire the Callbacks queue function based on pending status
function nextTick(cb, ctx) {
debugger
//cb callback function
// CTX this points to
var _resolve;
// Add a callback to the queue
callbacks.push(function () {
if (cb) {
// execute if cb exists and is a function
try {
cb.call(ctx);
} catch (e) {
// If it is not a function, an error is reported
handleError(e, ctx, 'nextTick'); }}else if (_resolve) {
//_resolve If it exists_resolve(ctx); }});if(! pending) { pending =true;
// Perform asynchronous macro tasks
if (useMacroTask) {
macroTimerFunc(); // Asynchronously trigger or implement observer trigger functions in the Callbacks queue
} else {
microTimerFunc(); // Asynchronously trigger or implement observer trigger functions in the Callbacks queue}}if(! cb &&typeof Promise! = ='undefined') {
// Declare a Promise function if the callback does not exist
return new Promise(function (resolve) { _resolve = resolve; }}})Copy the code
4-4. macroTimerFunc
Common Macro tasks in the browser environment include setTimeout, MessageChannel, postMessage, and setImmediate. Common Micro Tasks include MutationObsever and Promise.then. Macro Task implementation in Vue:
- Preempt whether native setImmediate is supported, a feature only supported by advanced versions of IE and Edge
- If not, check to see if native MessageChannel is supported.
- If it is not supported, setTimeout 0 is degraded.
var microTimerFunc; // Micro timer function
var macroTimerFunc; // Macro timer function
var useMacroTask = false; // Use macro tasks
if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
// Assign the function expression to macroTimerFunc
macroTimerFunc = function () {
debugger
setImmediate(flushCallbacks);
};
} else if (typeofMessageChannel ! = ='undefined' && (
isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
/** * creates a communication pipe with two ports, each of which can send data via postMessage, and one port can receive data from the other as long as it is bound to the onMessage callback method. * /
var channel = new MessageChannel();
var port = channel.port2;
// Set the receiver function for port 1 to flushCallbacks
channel.port1.onmessage = flushCallbacks;
// Port 2 pushes information to port 1
macroTimerFunc = function () {
debugger
port.postMessage(1);
};
} else {
macroTimerFunc = function () {
debugger
setTimeout(flushCallbacks, 0);
};
}
Copy the code
Source to ask questions
1. Please explain the principle of reactive data
Vue2 — Core point: Object.defineProperty — Modifies each attribute
- By default, when Vue initializes data, it uses Object.defineProperty for attributes in data, intercepts those acquired and set, and redefines all attributes.
- Dependency collection (collecting the watcher for the current component) occurs when the page fetches the corresponding property.
- If a property changes, the dependency is notified to update it.
Rely on the collection and distribution of updates:
Without this, every data update would render the page, which would be a huge performance drain. Add this operation to listen for related data changes, add to the queue, and render together when all changes are done.
Vue3 — Core point: Proxy — handles objects directly
Vue2 solved the problem of object recursion and array processing
Principle:
Responsive schematic diagram
2. How does vUE detect array changes?
Overrides array methods using function hijacking.
Vue rewrites the prototype chain of the array in data, pointing to the array prototype method defined by itself. This allows dependency updates to be notified when the array API is called. If the array contains a reference type, the reference type in the array is monitored again.
Object.create(Array.prototype)
copyArray
The prototype chain is the new object;- Intercepts the execution of the array’s seven methods and makes them responsive:
push
.pop
.shift
.unshift
.splice
.sort
.reverse
; - Execute when the array calls each of the seven methods
ob.dep.notify()
Distribute noticesWatcher
Update;
Principle:
For details, see [3-6-1 def].
3. Why does VUE use asynchronous rendering?
Vue is a component-level update, and if asynchronous updates are not used, the current component is re-rendered each time the data is updated. For performance reasons, VUE updates views asynchronously after this round of data updates.
Principle:
- Dep. Notify: [3-6-1 def]
- Subs [I].update(): [4-1 Dep]
- QueueWatcher: [4-2 queueWatcher]
- [4-3 nextTick]
Blog.csdn.net/weixin_4097…
4. How does nextTick work?
NextTick uses macro and micro tasks and defines an asynchronous method. Calling nextTick multiple times will queue the method, and the asynchronous method will clear the current queue, so nextTick is an asynchronous method.
5. Characteristics of computed data in VUE
Computed by default is also a Watcher, with a cache that updates the view only when the dependent properties change.
- InitComputed: [3-7 initComputed]
- DefineComputed: [3-7-1. DefineComputed]
- CreateComputedGetter: [3-7-2 createComputedGetter]
- [4-3 nextTick]
6. How is deep:true implemented in watch?
When the user sets the deep attribute in watch to true, if the monitored attribute is array type at that time, each item in the object will be evaluated, and the current Watcher will be stored in the dependency of the corresponding attribute, so that the data update will be notified when the object in the array changes. The internal principle is recursion, cost performance
- initWatch
- createWatcher
- Vue.prototype.$watch
- New Watcher: enclosing the get ();
- Prototype. Get: if (this.deep) {// traverse(value)}
- traverse
7. Why is data a function?
When the same component is reused multiple times, multiple instances are created. These instances use the same constructor, and if data is an object, all components share the same object. To ensure data independence, each component must return an object as its state through the data function.