preface
This paper shares 12 interview questions on Vue high frequency principle, covering the core implementation principle of Vue. In fact, it is impossible to finish an article on the implementation principle of a framework. I hope that through these 12 questions, readers can have a certain understanding of their Vue Mastery (B number), so as to make up for their shortcomings and better master Vue ❤️
1. Vue responsive principle
vue-reactive
Core implementation class:
Observer: its function is to add getter s and setter s to the object's properties for dependency collection and distribution of updates
Dep: used to collect the dependencies of the current responsive object. Each responsive object, including child objects, has a dep instance (where subs is the watcher instance array). When the data changes, each Watcher will be notified through dep.notify().
Watcher: observer object. The instances are divided into render watcher, computed watcher and listener watcher
Relationship between Watcher and Dep
Dep is instantiated in the watcher and subscribers are added to dep.subs. Dep traverses dep.subs through notify to notify each watcher of updates.
Dependency collection
-
initState: when initializing the computed attribute, the computed watcher dependency collection is triggered
-
In initState, the user watcher dependency collection is triggered when the listening property is initialized
-
The render() process triggers the render watcher dependency collection
-
When re render, VM When render() is executed again, the subscription of watcer in all subs will be removed and re assigned.
Distribute updates
-
The data of the response is modified in the component to trigger the logic of the setter
-
Call dep.notify()
-
Traverse all subs (watcher instances) and call the update method of each watcher.
principle
When creating a vue instance, vue will traverse the attributes of the data option, using object Defineproperty adds getters and setters for properties to hijack data reading (getters are used to collect dependencies and setters are used to distribute updates), tracks dependencies internally, and notifies changes when properties are accessed and modified.
Each component instance will have a corresponding watcher instance. During component rendering, all dependent data attributes (dependency collection, calculated watcher and user watcher instances) will be recorded. Later, when the dependency is changed, the setter method will notify the watcher instance that depends on this data to recalculate (send updates), so as to make its associated components re render.
One sentence summary:
vue.js adopts data hijacking combined with publish subscribe mode through object Define property to hijack setters and getters of various properties, publish messages to subscribers when data changes, and trigger the listening callback of response
2. Implementation principle of computed
computed is essentially an inert evaluator.
A lazy watcher is implemented inside computed, that is, computed watcher. Computed watcher does not evaluate immediately and holds a dep instance at the same time.
Its internal through this The dirty property marks whether the calculated property needs to be re evaluated.
When the dependency state of computed changes, the lazy watcher will be notified,
computed watcher through this dep.subs. Length determines whether there are subscribers,
If yes, it will be recalculated and then compared with the old and new values. If it changes, it will be rendered again. (Vue wants to ensure that it is not only the value that the calculation attribute depends on that changes, but also that the rendering watcher will be triggered to re render when the final calculated value of the calculation attribute changes. It is essentially an optimization.)
If not, just put this dirty = true. (when the calculated attribute depends on other data, the attribute will not be recalculated immediately. It will be calculated only when the attribute needs to be read elsewhere later, that is, it has the lazy feature.)
3. What are the differences and application scenarios between computed and watch?
difference
Computed calculated attribute: it depends on other attribute values, and the computed value is cached. Only when the dependent attribute value changes, the computed value will be recalculated the next time the computed value is obtained.
watch listener: it is more of an "observation" function and has no caching. It is similar to the monitoring callback of some data. Whenever the monitored data changes, the callback will be executed for subsequent operations.
Application scenario
Application scenario:
When we need to perform numerical calculation and rely on other data, we should use computed, because we can use the cache feature of computed to avoid recalculation every time we get a value.
When we need to perform asynchronous or expensive operations when data changes, we should use watch. The watch option allows us to perform asynchronous operations (access an API), limits the frequency of our operations, and sets the intermediate state before we get the final result. These are things that computational properties cannot do.
4. Why in vue3 0 uses Proxy and discards object defineProperty?
Object.defineProperty itself has a certain ability to monitor the changes of array subscripts, but in Vue, this feature is greatly abandoned in consideration of performance / experience cost performance( Why can't Vue detect array changes ). In order to solve this problem, after vue internal processing, you can use the following methods to listen to the array
push(); pop(); shift(); unshift(); splice(); sort(); reverse(); Copy code
Because the hack processing is only carried out for the above seven methods, the properties of other arrays can not be detected, which still has some limitations.
Object.defineProperty can only hijack the properties of objects, so we need to traverse each property of each object. Vue 2. In X, the data is monitored through recursion + traversal of data objects. If the attribute value is also an object, it needs deep traversal. Obviously, it is a better choice to hijack a complete object.
Proxy can hijack the whole object and return a new object. Proxy can not only proxy objects, but also proxy arrays. You can also dynamically add attributes to agents.
5. What is the use of the key in Vue?
Key is the unique id given to each vnode. Depending on the key, our diff operation can be more accurate and faster (for simple list page rendering, diff nodes are also faster, but there will be some hidden side effects, such as no transition effect, or there will be state dislocation when there is bound data (form) state on some nodes.)
In the process of diff algorithm, the beginning and end of the old and new nodes will be cross compared first. When they cannot be matched, the key of the new node will be compared with the old node to find the corresponding old node
More accurate: because the key is not used locally, the local reuse can be avoided in the comparison of a.key === b.key in the sameNode function. Therefore, it will be more accurate. If you do not add a key, the state of the previous node will be retained and a series of bug s will be generated.
Faster: the uniqueness of the key can be fully utilized by the Map data structure. Compared with the time complexity O (n), the time complexity of the Map is only O(1). The source code is as follows:
function createKeyToOldIdx(children, beginIdx, endIdx) { let i, key; const map = {}; for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key; if (isDef(key)) map[key] = i; } return map; } Copy code
6. Talk about the principle of nextTick
JS operation mechanism
JS execution is single threaded and is based on event loops. The event cycle is roughly divided into the following steps:
- All synchronization tasks are executed on the main thread to form an execution context stack.
- In addition to the main thread, there is also a "task queue". As long as the asynchronous task has run results, an event will be placed in the "task queue".
- Once all synchronization tasks in the "execution stack" are executed, the system will read the "task queue" to see what events are in it. Those corresponding asynchronous tasks end the waiting state, enter the execution stack and start execution.
- The main thread repeats the third step above.
event-loop
The execution process of the main thread is a tick, and all asynchronous results are scheduled through the "task queue". Tasks are stored in the message queue. According to the specification, tasks are divided into two categories: macro tasks and micro tasks. After each macro task is completed, all micro tasks must be cleared.
for (macroTask of macroTaskQueue) { // 1. Handle current MACRO-TASK handleMacroTask(); // 2. Handle all MICRO-TASK for (microTask of microTaskQueue) { handleMicroTask(microTask); } } Copy code
In the browser environment:
Common macro task s include setTimeout, MessageChannel, postMessage, and setImmediate
Common micro task s include mutationobserver and promise then
Asynchronous update queue
You may not have noticed that Vue executes asynchronously when updating the DOM. Whenever a data change is heard, Vue will open a queue and buffer all data changes that occur in the same event loop.
If the same watcher is triggered multiple times, it will only be pushed into the queue once. This de duplication in buffering is important to avoid unnecessary calculations and DOM operations.
Then, in the next event loop "tick", Vue refreshes the queue and performs the actual (de duplicated) work.
Vue internally attempts to use native promise. Com for asynchronous queues Then, MutationObserver and setImmediate. If the execution environment does not support it, setTimeout(fn, 0) will be used instead.
In vue2 In the source code of 5, the degradation schemes of macrotask are: setImmediate, MessageChannel and setTimeout
Implementation principle of vue's nextTick method:
-
vue uses asynchronous queue to control DOM update and nextTick callback execution successively
-
Because of its high priority, microtask can ensure that the micro tasks in the queue are executed before an event cycle
-
Considering the compatibility problem, vue made a degradation scheme from microtask to macrotask
7. How does Vue mutate array methods?
Let's look at the source code first
const arrayProto = Array.prototype; export const arrayMethods = Object.create(arrayProto); const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse" ]; /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function(method) { // cache original method const original = arrayProto[method]; def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args); const ob = this.__ob__; let inserted; switch (method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = args.slice(2); break; } if (inserted) ob.observeArray(inserted); // notify change ob.dep.notify(); return result; }); }); /** * Observe a list of Array items. */ Observer.prototype.observeArray = function observeArray(items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); } }; Copy code
In short, Vue rewrites the seven methods of the array through prototype interception. First, it obtains the ob of the array, that is, its Observer object. If there is a new value, it calls observeArray to listen for the new value, and then manually calls notify, notifies render watcher and executes update
8. Why must Vue component data be a function?
In the new Vue() instance, data can be an object directly. Why must data be a function in the vue component?
Because components can be reused and objects in JS are reference relationships, if component data is an object, the data attribute values in sub components will pollute each other and produce side effects.
Therefore, the data option of a component must be a function, so each instance can maintain an independent copy of the returned object. The instance of new Vue will not be reused, so the above problems do not exist.
9. Talk about Vue event mechanism, handwritten $on,$off,$emit,$once
Vue event mechanism is essentially an implementation of publish subscribe mode.
class Vue { constructor() { //Event channel dispatching center this._events = Object.create(null); } $on(event, fn) { if (Array.isArray(event)) { event.map(item => { this.$on(item, fn); }); } else { (this._events[event] || (this._events[event] = [])).push(fn); } return this; } $once(event, fn) { function on() { this.$off(event, on); fn.apply(this, arguments); } on.fn = fn; this.$on(event, on); return this; } $off(event, fn) { if (!arguments.length) { this._events = Object.create(null); return this; } if (Array.isArray(event)) { event.map(item => { this.$off(item, fn); }); return this; } const cbs = this._events[event]; if (!cbs) { return this; } if (!fn) { this._events[event] = null; return this; } let cb; let i = cbs.length; while (i--) { cb = cbs[i]; if (cb === fn || cb.fn === fn) { cbs.splice(i, 1); break; } } return this; } $emit(event) { let cbs = this._events[event]; if (cbs) { const args = [].slice.call(arguments, 1); cbs.map(item => { args ? item.apply(this, args) : item.call(this); }); } return this; } } Copy code
10. Talk about Vue's rendering process
render
- Call the compile function to generate the render function string. The compilation process is as follows:
-
parse function parses template and generates ast (abstract syntax tree)
-
The optimize function optimizes the static nodes (marks the contents that do not need to be updated every time, and the diff algorithm directly skips the static nodes, thus reducing the comparison process and optimizing the performance of patch)
-
The generate function generates a render function string
-
Call the new Watcher function to listen for data changes. When the data changes, the Render function generates a vnode object
-
Call the patch method, compare the old and new vnode objects, and add, modify and delete Real DOM elements through the DOM diff algorithm
11. Talk about the implementation principle and cache strategy of keep alive
export default { name: "keep-alive", abstract: true, //Abstract component attribute, which will be ignored when establishing parent-child relationship between component instances, occurs in the process of initLifecycle props: { include: patternTypes, //Cached component exclude: patternTypes, //Components not cached max: [String, Number] //Specify cache size }, created() { this.cache = Object.create(null); //Cache this.keys = []; //Cached VNode key }, destroyed() { for (const key in this.cache) { //Delete all caches pruneCacheEntry(this.cache, key, this.keys); } }, mounted() { //Listen for cached / uncached components this.$watch("include", val => { pruneCache(this, name => matches(val, name)); }); this.$watch("exclude", val => { pruneCache(this, name => !matches(val, name)); }); }, render() { //Gets the vnode of the first child element const slot = this.$slots.default; const vnode: VNode = getFirstComponentChild(slot); const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions; if (componentOptions) { //name is not in inlcude or in exclude} and returns vnode directly // check pattern const name: ?string = getComponentName(componentOptions); const { include, exclude } = this; if ( // not included (include && (!name || !matches(include, name))) || // excluded (exclude && name && matches(exclude, name)) ) { return vnode; } const { cache, keys } = this; //Get the key, and get the name field of the component first, otherwise it is the tag of the component const key: ?string = vnode.key == null ? // same constructor may get registered as different local components // so cid alone is not enough (#3269) componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : "") : vnode.key; //Hit the cache, directly get the component instance of vnode , from the cache, and readjust the order of , key , to the last if (cache[key]) { vnode.componentInstance = cache[key].componentInstance; // make current key freshest remove(keys, key); keys.push(key); } //If the cache is not hit, set {vnode} into the cache else { cache[key] = vnode; keys.push(key); // prune oldest entry //If {max} is configured and the cache length exceeds} this max, also delete the first one from the cache if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode); } } //keepAlive flag bit vnode.data.keepAlive = true; } return vnode || (slot && slot[0]); } }; Copy code
principle
-
Get the first sub component object wrapped by keep alive and its component name
-
Match the conditions according to the set include/exclude (if any) to determine whether to cache. If not, return the component instance directly
-
Generate the cache key according to the component ID and tag, and find out whether the component instance has been cached in the cache object. If it exists, directly take out the cache value and update the key in this Location in keys (updating the location of keys is the key to realizing LRU replacement strategy)
-
In this Store the component instance in the cache object and save the key value. Then check whether the number of cached instances exceeds the setting value of max. if it exceeds the setting value, delete the most recently unused instance (that is, the key with subscript 0) according to the LRU replacement policy
-
Finally, the keepAlive attribute of the component instance is set to true, which will be used in rendering and executing the hook function of the wrapped component, which will not be described in detail here
LRU cache elimination algorithm
LRU (Least recently used) algorithm eliminates data according to the historical access record of data. Its core idea is that "if the data has been accessed recently, the probability of being accessed in the future is higher".
LRU
The implementation of keep alive uses the LRU policy to push the recently accessed components to this Keys last, this Keys [0] is the component that has not been accessed for the longest time. When the cache instance exceeds the max setting, delete this keys[0]
12. vm.$ What is the implementation principle of set()?
Due to the limitations of modern JavaScript (and Object.observe has been abandoned), Vue cannot detect the addition or deletion of object properties.
Because Vue will perform getter/setter conversion on the attribute when initializing the instance, the attribute must exist on the data object for Vue to convert it into a responsive.
For instances that have been created, Vue does not allow dynamic addition of root level responsive properties. However, you can use Vue The set (object, propertyname, value) method adds responsive properties to nested objects.
So how does Vue solve the problem that new attributes of objects cannot respond?
export function set(target: Array<any> | Object, key: any, val: any): any { //target is an array if (Array.isArray(target) && isValidArrayIndex(key)) { //Modify the length of the array to avoid incorrect execution of splice() due to index > array length target.length = Math.max(target.length, key); //Using the splice mutation method of array to trigger the response formula target.splice(key, 1, val); return val; } //Target is the object, and the key is in target or target On prototype and must not be on object On prototype , direct assignment if (key in target && !(key in Object.prototype)) { target[key] = val; return val; } //If none of the above is true, start creating a new attribute for target //Get Observer instance const ob = (target: any).__ob__; //target itself is not responsive data, so it is assigned directly if (!ob) { target[key] = val; return val; } //Perform responsive processing defineReactive(ob.value, key, val); ob.dep.notify(); return val; } Copy code
-
If the target is an array, use the mutation method splice implemented by vue to implement the response
-
If the target is an object, judge the existence of the attribute, that is, it is a response type and assign a value directly
-
If the target itself is not responsive, assign a value directly
-
If the attribute is not responsive, the defineReactive method is called for responsive processing
By the way, recommend my novel website
These two novel websites include the hottest online novels at present, and provide the latest chapters of high-quality novels for free. They are the necessary novel reading networks for the majority of online novel lovers. Free online reading without pop-up window, no middleman to earn price difference, welcome to collect Yo