In an interview, it’s not uncommon to say that a performance optimization has been performed on the xyz project, resulting in a significant improvement in performance. If the interviewer asks, “Tell me about the optimizations you made,” it’s time to clearly state what you did.

This article will teach you how to say optimization in the interview with my own Vue project experience, if there are mistakes, there is insufficient, please give advice, don’t hesitate to give advice, progress together, find a better job. If you find this article helpful, give it a thumbs up.

1. Opening Remarks

I personally believe that performance optimization can be carried out from three aspects, one is the code level optimization, the second is the optimization of project packaging, and the third is the optimization of project deployment.

Keep your introduction brief and give an overview of what you are going to say. Don’t make it long. The interviewer doesn’t have much time for bullshit.

Here are some common optimizations to start with. In addition, all the optimization is a principle, I hope you can catch the interviewer’s in-depth investigation.

Second, code level optimization

1. Use V-if and V-show to reduce the performance overhead of initial rendering and switching rendering

When the page is loaded, v-if is used to control the component to render only when it is used for the first time to reduce the initial rendering, and v-show is used to control the component to show and hide to reduce the performance overhead of switching rendering.

Perhaps the above description is not clear enough, but let’s use an example to reinforce the impression.

Iview pop-up components you use it that often, pop-up component v – show is used to control the show and hide, so when the page is loaded, pop-up components (including content) will be initialized rendering, if one page is only a popup window components, will not affect performance, but if a page has dozens of pop-up components, Will it affect performance, you can do a right-click menu to try.

For the first rendering of the Element popover, the contents inside the body of the popover will not be rendered.

Let’s do it in code.

<template> <div> <Button type="primary" @click.native="add"> </Button> <add v-model="add.show" v-bind="add"></add> </div> </template> <script> export default{ data(){ return{ add:{ show:false, init:false } } }, components:{ add:() =>import('./add.vue') }, methods:{ add(){ this.add.show=true; this.add.init=true } } } </script>Copy the code
<template> <div V-if ="init"> <Modal V-model ="show" title=" add "@on-cancel="handleClose"></Modal> </div> </template> <script> export default{ props:{ value:{ type:Boolean, default:false }, init:{ type:Boolean, default:false } }, data(){ return{ show:false, } }, watch:{ value(val){ if(val){ this.show = val; } } }, methods:{ handleClose(val) { this.$emit('input', val); }, } } </script>Copy the code

Principle:

When the v-if binding value is false, its conditional block will not be rendered during initial rendering.

The v-if binding value, when toggles between true and false, destroys and rerenders its conditional block.

Whether the v-show binding value is true or false, its conditional block will always be rendered during initial rendering.

The v-show binding value, when toggles between true and false, does not destroy and re-render its conditional block, but only hides it with the display: None style.

2. Distinguish the use scenarios of computed, Watch and Methods

Computed, Watch, and Methods all work for some needs, but you need to differentiate your usage scenarios. Use the wrong scenario to achieve functionality but affect performance.

  • computed:
    • One data affected by more than one data.
    • This data can be exploited if it requires expensive performance calculations, such as traversing a large array and doing a lot of calculations to get itcomputedIt only recalculates if the data on which it relies is found to have changed, otherwise it returns the cached value directly.
  • watch:
    • One data affecting multiple data.
    • When data changes and asynchronous or expensive operations need to be performed. Request an interface if the data changes.
  • methods:
    • You want the data to be updated in real time, without caching.

3. Deal with the data in advance to solve the problem that V-IF and V-for must be at the same level

Because v-for has a higher priority than V-if when Vue processes instructions, it means that V-IF will be repeated separately in each V-for loop.

In computed, you can filter v-IF items in advance for v-FOR data.

//userList.vue
<template>
    <div>
        <div v-for="item in userList" :key="item.id" v-if="item.age > 18">{{ item.name }}</div>
    </div>
</template>
Copy the code
//userList.vue
<template>
    <div>
        <div v-for="item in userComputedList" :key="item.id">{{ item.name }}</div>
    </div>
</template>
export default {
    computed:{
        userComputedList:function(){
            return this.userList.filter(function (item) {
                return item.age > 18
            })
        }
    }
}
Copy the code

Why else might the interviewer give v-for a higher priority than V-if? This question touches on the level of principle, and it would be a plus if you could answer it.

Vue renders the page using the render function, first printing the render generated by compiling the component.

//home.vue
<script>
import userList from './userList'
console.log(userList.render)
</script>
Copy the code

The printout looks like this

var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h
  return _c(
    "div",
    _vm._l(_vm.userList, function(item) {
      return item.age > 18
        ? _c("div", { key: item.id }, [_vm._v(_vm._s(item.name))])
        : _vm._e()
    }),
    0
  )
}
var staticRenderFns = []
render._withStripped = true
export { render, staticRenderFns }
Copy the code

Where _l is the renderList method generated by the genFor directive, item.age > 18? Is the code for the ternary operator generated by the v-if directive via the genIf function. The _v method is used to create the text node and the _e method is used to create the empty node. Is it clear by now that v-if runs in every V-for.

In the final analysis, v-for has a higher priority than V-if in generating the render function. Let’s go to the render function generation process to see.

Vue provides two versions, one is Runtime + Compiler, one is Runtime only, the former contains the compiled code, you can put the compilation process at Runtime, the latter does not contain the compiled code, The template needs to be pre-compiled into the render function with webpack’s vue-loader.

There is no research vue – loader, so use the Runtime + Compiler to study, also use the CDN is introduced into the vue. Js, at this point the vue entrance in SRC/platforms/web/entry – the Runtime – with – Compiler. Js.

const vm = new Vue({
    render: h => h(App)
}).$mount('#app')
Copy the code

The Vue instance is mounted to the DOM via $mount. Look for the $mount method in the entry file and the Render field in its method and find the following code

const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV ! == 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFnsCopy the code

The render function is generated from compileToFunctions, so find where the compileToFunctions are.

CompileToFunctions method in SRC/platforms/web/compiler/index defined js.

const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }
Copy the code

The compileToFunctions method is generated by the createCompiler method, continue looking for the createCompiler method.

The createCompiler method is defined in SRC/Compiler /index.js.

export const createCompiler = createCompilerCreator( function baseCompile(template,options) { const ast = parse(template.trim(), options) if (options.optimize ! == false) { optimize(ast, options) } const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } })Copy the code

As you can see in the code above, render is the render of code, which is generated by the generate method.

As a side note, what is an AST, a grammar generated by tempalte, that executes the following logic before the generate method

  • Parsing the template string generates an ASTconst ast = parse(template.trim(), options).
  • Optimized syntax treeoptimize(ast, options).
  • Generate the render function codeconst code = generate(ast, options).

Continue to look for ways to generate, its in the SRC/compiler/codegen/index defined js.

export function generate (ast,options){
    const state = new CodegenState(options)
    const code = ast ? genElement(ast, state) : '_c("div")'
    return {
        render: `with(this){return ${code}}`,
        staticRenderFns: state.staticRenderFns
    }
}
Copy the code

Find that code is generated by the genElement method, and continue to look for the genElement method. In fact, the root cause has been solved here, with a few lines of key code.

export function genElement(el,state){ if(){ //... }else if (el.for && ! el.forProcessed) { return genFor(el, state) } else if (el.if && ! el.ifProcessed) { return genIf(el, state) } }Copy the code

As can be seen from the above code, el.for is v-for, el.if is V-if, el.for is executed before el.if, so V-for has a higher priority than V-if.

In addition, the genFor method will continue to call the genElement method at the end of the process, forming a hierarchical execution.

return `${altHelper || '_l'}((${exp}),` +
    `function(${alias}${iterator1}${iterator2}){` +
    `return ${(altGen || genElement)(el, state)}` +
'})'
Copy the code

The whole idea of looking for reasons to write out, is to let a small partner in the interview questions do not memorize, to understand, you can follow the above ideas, to read the source code to find the reason. After all, the ability to read source code can also be a plus in an interview.

4. Add key to v-for cycle item to improve diff calculation speed

Once you’ve talked to the interviewer about this optimization, be prepared to be asked in-depth questions like these two.

  • Why adding a key increases the speed of diff calculation.

    After four times of cross comparison, no node worthy of comparison is matched, if the new node has a key. If there is no key, it is necessary to use the sameVnode method to determine whether the new node is worth comparing with the old node by cycling through the old node array. If it is, the old node’s index is returned. It is obviously faster to compute through a map than through a loop array.

  • What is diff computation?

    For watcher, vm._update(vm._render(), hydrating) is invoked, and vm.__patch__ is called in the vm._undata method, which points to the patch method. Diff calculation means that the patch method is called at the beginning, and the sameVnode method is used to judge whether the node is worthy of comparison. If it is not worthy, the old node is directly replaced by the new node. Worth the contrast into patchVnode method, respectively with the several circumstances, if the old and the new node has a text node, a new node under the text nodes directly replaces the old text node under the node, if the new node has child nodes, the old node no children, so the new node directly to get old parent node, if there is no child node, a new node If the old node has children, all children of the old node are deleted. If both the old and new nodes have child nodes, enter the updateChildren method and cross-compare the old nodes with new nodes, old nodes with new nodes, old nodes with new nodes, old nodes with new nodes, old nodes with new nodes, and old nodes with new nodes. If the comparison is worthwhile, enter the patchVnode method again. No key loops through old nodes to get old nodes worth comparing. When all the new nodes are compared and the old nodes are not compared, delete the old nodes that have not been compared. When all the old nodes are compared and the new node is not compared, the new node is added after the last new node compared to complete the diff calculation.

These two questions can be asked back-to-back, and once you have answered the first question, you may be moved on to the second.

The following is a detailed process that you can’t possibly describe in an interview. It’s just for you to understand.

First, I will introduce what diff calculation is. Diff calculation is to compare the old and new virtual DOM (Virtual DOM). Virtual DOM is to extract the data of the real DOM and simulate the tree structure in the form of objects.

When the diff algorithm is used to compare the old and new nodes, the comparison will only be performed at the same level, not across levels.Step diagram first, you can see the diagram first, and then read the text introduction The logic for each comparison is roughly as follows

  • 1. In the patch method, sameVnode is used to judge whether the old and new nodes are worth comparing.
  • 2. If the comparison is not worthwhile, directly add the new node to the parent of the old node and then delete the old node to exit the comparison.
  • 3. If it is worth comparing, call patchVnode method.
  • 4. If the new and old nodes are equal, exit the comparison.
  • 5. If not, find the corresponding real DOM, denoted as EL.
  • 6. If both the old and new nodes have text nodes and are not equal, set the text node of EL to the text node of the new node and exit the comparison.
  • 7. If the new node has child nodes and the old node has no child nodes, add the child nodes of the new node to the EL after generating real DOM to exit the comparison.
  • 8. If the new node has no child node and the old node has child node, delete the child node of EL and exit the comparison.
  • 9. If both the new node and the old node have children, start comparing their children using the updateChildren method.
  • 10. Mark the children of the old node asoldChIs an array, and its header is usedoldCh[oldStartIdx]Get down tooldStartVnode.oldStartIdxThe initial value is 0. Its tail witholdCh[oldEndIdx]Get down tooldEndVnode.oldEndIdxFor the initialoldCh.length - 1.
  • 11. Mark the children of the old node asnewChIs an array, and its header is usednewCh[newStartIdx]Get down tonewStartVnode.newStartIdxThe initial value is 0. Its tail withnewCh[newEndIdx]Get down tonewEndVnode.newEndIdxFor the initialnewCh.length - 1.
  • 12. Use sameVnode to determine whether the old and new child heads are worth comparing.
  • 13. If the comparison is worthwhile, call patchVnode method and repeat step 3. At the same time witholdCh[++oldStartIdx]Retrieve the old child node header with thenewCh[++newStartIdx]Retrieves the new child node head.
  • 14. If it is not worth comparing, use sameVnode to judge whether it is worth comparing the tail of the old child node and the tail of the new child node, referred to as the old tail and the new tail.
  • 15. If the comparison is worthwhile, call patchVnode method and repeat step 3. At the same time witholdCh[--oldEndIdx]Retrieves the tail of the old child node and uses it againnewCh[--newEndIdx]Gets the tail of the new child node.
  • 16. If it is not worthy of comparison, use sameVnode to determine whether the head of the old child node and the tail of the new child node, referred to as the old head and new tail, are worthy of comparison.
  • 17. If the comparison is worthwhile, call patchVnode method and perform step 3 again. At the same time put the old child node’s headoldStartVnodeThe corresponding real DOM moves to the end of the old child nodeoldEndVnodeCorresponding to the real DOM. At the same time witholdCh[++oldStartIdx]Retrieve the old child node header with thenewCh[--newEndIdx]Retrieve the tail of the new child node.
  • 18. If it is not worth comparing, use sameVnode to judge whether it is worth comparing the tail of the old child node and the head of the new child node, referred to as the old tail and new head.
  • 19. If the comparison is worthwhile, call patchVnode method and repeat step 3. Also put the tail of the old child nodeoldEndVnodeThe corresponding real DOM is moved to the head of the old child nodeoldStartVnodeCorresponding to the real DOM. At the same time witholdCh[--oldEndIdx]Retrieve the tail of the old child node withnewCh[++newStartIdx]Retrieves the new child node head.
  • 20. If the comparison is not worthwhile, use createKeyToOldIdx to obtain a map structure based on the old child’s key and its subscript valuesoldKeyToIdx.
  • 21, if the new child node headnewStartVnodeIt has the key attribute and passes directlyoldKeyToIdx[newStartVnode.key]Gets the corresponding subscriptidxInOld.
  • 22, if the new child node headnewStartVnodeWithout the key attribute, use the findIdxInOld method to find the subscripts of the old child nodes that are worth comparingidxInOld.
  • After a search. ifidxInOldDoes not exist. Call the createElm method to generate it directlynewStartVnodeCorresponding to the actual DOM insertionoldStartVnodeCorresponds to the front of the real DOM.
  • 24, ifidxInOldExist, then use througholdCh[idxInOld]Note that the Vnode is obtainedvnodeToMoveandnewStartVnodeUse sameVnode to determine if the comparison is worthwhile.
  • 25. If the comparison is worthwhile, call patchVnode and repeat step 3. At the same time to performoldCh[idxInOld] = undefinedSo as not to be compared repeatedly. At the same time willvnodeToMoveThe corresponding real DOM is moved to the head of the old child nodeoldStartVnodeCorresponding to the actual DOM.
  • 26. If the comparison is not worthwhile, call createElm directlynewStartVnodeCorresponding to the actual DOM insertionoldStartVnodeCorresponds to the front of the real DOM.
  • 27,newCh[++newStartIdx]Retrieves the new child node head
  • If meetoldStartIdx <= oldEndIdx && newStartIdx <= newEndIdxGo to Step 9.
  • 29 and ifoldStartIdx > oldEndIdxCreateElm method is used to generate the corresponding real DOM for any new child nodesnewCh[newEndIdx + 1]Corresponding to the real DOM.
  • 30, ifnewStartIdx > newEndIdx, indicating that all new child nodes are compared, and the remaining old child nodes are deleted.

5. Use V-once to handle elements or components that will only be rendered once

Render elements and components only once. In subsequent re-rendering, the element/component and all its child nodes are treated as static and skipped. This can be used to optimize update performance.

For example, if a page is a contract model, most of the content in the page is obtained from the server and is fixed, only the name, product, amount and other content will change. At this point, v-Once can be added to elements that wrap fixed content. When generating a new contract, the fixed content can be skipped and only the name, product, amount, and so on can be re-rendered.

When used together with v-if, v-once does not take effect. Used on an element or component within a V-for loop, the key must be added.

In case the interviewer asks you how does V-Once render an element or component only once?

When it comes to rendering, it’s up to the render function to find out where it was generated.

In the SRC/compiler/codegen/index. Js, find genElement method

else if (el.once && ! El.onceprocessed) {if v-once is set, call genOnce() return genOnce(el, state)}Copy the code

Let’s look at the genOnce method

function genOnce(el, state){ el.onceProcessed = true if (el.if && ! El.ifprocessed) {// If the v-if directive is defined // } else if (el.staticInFor) {// If (el.staticInFor) { return `_o(${genElement(el, state)},${state.onceId++},${key})` } else { return genStatic(el, state) } }Copy the code

If the v-if directive is defined, the genStatic method will still be called if the value of the V-if directive does not exist. Let’s look at the genStatic method

function genStatic(el, state) {
	//...
    state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)
    return `_m(${state.staticRenderFns.length - 1}${el.staticInFor ? ',true' : ''})`
}
Copy the code

SRC \core\instance\render-helpers\render- helpers\render- helpers\render-static.js renderStatic is the key to the V-once implementation of rendering elements or components only once.

function renderStatic(index,isInFor){ const cached = this._staticTrees || (this._staticTrees = []) let tree = cached[index] if (tree && ! isInFor) { return tree } tree = cached[index] = this.$options.staticRenderFns[index].call(this._renderProxy,null,this) return tree }Copy the code

Cached is the cache of the virtual DOM node generated by the rendering of the element or component with V-once. If the cache of a virtual DOM node exists and the virtual DOM node is not returned directly in v-for, if the virtual DOM node has no cache, Then there is a rendering function in the staticRenderFns array in the call to genStatic method, rendering the virtual DOM node and storing cached, so that the virtual DOM node can be directly returned next time without re-rendering. At the same time, the markOnce method is called to add isOnce flag to the virtual DOM node. The value is true.

If I define v minus for, The final call is _o(${genElement(el, SRC \core\instance\render-helpers\render- helpers\render-static.js ${key}) IsOnce is used to add the isOnce flag to the generated virtual DOM node. True indicates that the virtual DOM node is a static node. During patch, whether vNode. isOnce is judged will be true.

6. Use Object.freeze() to freeze data that does not require responsive changes

During Vue initialization, data is passed into the Observe function for data hijacking, and all data in data is converted into responsive data.Copy the code

Call defineReactive inside the observe function to process data, configure getter/setter properties, and convert to response. if you freeze some data using object.freeze (), This sets its freely configurable property to false.

The defineReactive () function checks whether the specified key is configured with a false 6464x property. If so, the 6464X function returns the configured getter/setter property.

export function defineReactive(obj,key,val,customSetter,shallow){ //... const property = Object.getOwnPropertyDescriptor(obj, If (property && property.64x === false) {return} // works without any additional information. }Copy the code

Freeze () can be used to freeze data that does not need to be changed in response to the project. It can skip the initialization data hijacking step and greatly improve the initial rendering speed.

Filter out non-essential data in advance and optimize the data structure in the Data option

When Vue is initialized, the option data is passed to the observe function for data hijacking.

initData(vm){
    let data = vm.$options.data
    //...
    observe(data, true)
}
Copy the code

The observe function is called

observe(value,asRootData){
   //...
   ob = new Observer(value);
}
Copy the code

In the Observer prototype, the defineReactive function processes the data, configures getter/setter properties, and transitions to responsiveness

walk (obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
        defineReactive(obj, keys[i])
    }
}
Copy the code

DefineReactive passes the value of the data back to the Observe function

export function defineReactive(obj,key,val,customSetter,shallow){ //... if (arguments.length === 2) { val = obj[key] } let childOb = observe(val); / /... }Copy the code

Observe has code that passes data into the Observer class.

export function observe(value,asRootData){
  //...
  ob = new Observer(value)
  //...
  return ob
}
Copy the code

This constitutes a recursive call.

When receiving data from the server, there will be some data that will not be used when rendering the page. The convention on the server side is to prefer too much to too little.

Therefore, filter out the data from the server that is not used for rendering the page first. Then assign to the data option. You can avoid hijacking data that is not needed for a rendered page, reducing loops and recursive calls, and improving rendering speed.

8. Avoid reading array type data in data in v-for loop

export function defineReactive(obj,key,val,customSetter,shallow){ const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) const getter = property && property.get; const setter = property && property.set; if ((! getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = ! shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value } }) } function dependArray (value: Array<any>) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { dependArray(e) } } } export function observe (value, asRootData){ if (! isObject(value) || value instanceof VNode) { return } //... }Copy the code

The reason to avoid reading array type data in data in v-for loops is because defineReactive is called in data hijacking. Getter is a function that references DEP and childOb and forms a closure. Therefore, DEP and childOb always exist in memory (getter function for each data), DEP is the dependency collection container for each data, and childOb is the data after reactive processing.

In the process of rendering the view, using watch listening, and using calculated properties, when reading data, dep. target is assigned a value of Watcher (dependency). For example, when reading data during rendering the view, dep. target is renderWatcher.

We then call dep.depend() to collect the dependencies for ourselves, and if val (its own value) is not an object, childOb is false. If val is an object, collect dependencies with childob.dep.depend (). If value is an array, recurse on each item to collect dependencies.

DependArray (value) recursively collects dependencies on each item of a v-for loop if value is an array

For a simple example, each row in the table has two fields for driver and phone numbers. This is how the code works.

<template> <div class="g-table-content"> <el-table :data="tableData"> <el-table-column prop="carno" </el-table-column> <el-table-column prop=" carType "label=" cartype" ></el-table-column> <el-table-column <template slot-scope="{row,column,$index}"> <el-input V-model ="driverList[$index]. Name "></el-input> </template> </el-table-column> <el-table-column label=" phone "> <template slot-scope="{row,column,$index}"> <el-input v-model="driverList[$index].phone"></el-input> </template> </el-table-column> </el-table> </div> </template>Copy the code

If the table has 500 entries, read the driverList 500 times. Each read of the driverList will enter a dependArray(value) loop 500*500= 250 thousand times. If there is pagination, the loop will be at least 250 thousand times.

What if after we get the data from the service, we do the following preprocessing and assign it to this.tableData?

res.data.forEach(item =>{
    item.name='';
    item.phone='';
})
Copy the code

The template is implemented like this

<template> <div class="g-table-content"> <el-table :data="tableData"> <el-table-column prop="carno" </el-table-column> <el-table-column prop=" carType "label=" cartype" ></el-table-column> <el-table-column Label =" driver ">< template slot-scope="{row}"> <el-input V-model ="row.name"></el-input> </template> </el-table-column> <el-table-column label=" phone">< template slot-scope="{row,column,$index}"> <el-input V-model ="row.phone"></el-input> </template> </el-table-column> </el-table> </div> </template>Copy the code

A dependArray(value) will not be applied to the rendering process and 250,000 unnecessary cycles will not be created. Greatly improved performance.

9, shake and throttle

Anti-shake and throttling are optimized for user operation. First, understand the concept of anti-shake and throttling.

  • Anti – shake: The event will be executed only once within the specified time after the event is triggered. In simple terms, it is to prevent hand shaking, short time operation for many times.
  • Throttling: Events are executed only once in a specified period of time.
  • Application scenario: Throttling executes only one event within a specified period of time, regardless of whether the event is triggered or frequently triggered. Anti-shake executes only one event when the event is triggered within a specified period of time and the event is triggered for the last time. Throttling can be used in cases where events need to be timed but other operations also allow events to be executed. If an event does not need to be executed regularly but only needs to be triggered and cannot be executed for multiple times in a short period of time, you can use shake prevention in this scenario.

In a Vue project built with Vue Cli scaffolding, you can reference the Debounce and Throttle-throttling functions in the Lodash library.

import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
export default{
	methods:{
    	a: debounce(function (){
        	//...
        },200,{
            'leading': false,
            'trailing': true
        }),
        b: throttle(function (){
        	//...
        },200,{
            'leading': false,
            'trailing': true
        })
    }
}
Copy the code
  • Debounce (func, [wait=0], [options={}]) creates an anti-shock function that calls the func method after waiting milliseconds since the last time it was called. DebounceFn returns a stabilization function, debounceFn, debounce. Cancel cancels the stabilization, debounce. Flush immediately calls the func.

    • options.leadingWhen true, func is called before the delay starts.
    • options.trailingWhen true, func is called after the delay starts and ends.
    • options.maxWaitSet the maximum amount of delay allowed by func.
  • Throttle (func, [wait=0], [options={}]) creates a throttling function that executes func at most once in wait seconds. Return a throttleFn function, throttlefn. cancel cancels throttling, and throttlefN. flush immediately calls func.

    • options.leadingWhen true, func is called before throttling begins.
    • options.trailingWhen true, func is called after throttling ends.
    • leadingandtrailingBoth are true and func is called multiple times during the wait.

Image size optimization and lazy loading

To optimize the image size, you can use image-webpack-loader to compress the image, which is configured in the Webpack plug-in, as described in this article.

Lazy loading of images can be implemented with vue-lazyload plug-in.

Run NPM install vue-lazyload –save to install vue-lazyload. Introduce the configuration in main.js

import VueLazyload from 'vue-lazyload'; Vue.use(VueLazyload, {preLoad: 1.3,// preLoad height ratio error: 'dist/error. PNG ',// Failed to load 'dist/loading.gif',// attempt: 1,// attempts})Copy the code

For use in projects

<img v-lazy="/static/img/1.png">
Copy the code

Optimize the white screen problem by using the feature that mount nodes will be replaced

import Vue from 'vue'
import App from './App.vue'
new Vue({
    render: h => h(App)
}).$mount('#app')
Copy the code

If the render function is present in the Vue option, the Vue constructor does not compile the render function from the HTML template option or from the mount element specified by the EL option.

A drawback of Vue projects is that the first render will have a blank screen for a while because a bunch of resources such as JS, CSS, images need to be loaded on the first render. The ultimate goal of many optimization strategies is to increase the loading speed of these resources. But if the network is slow, no matter to the extreme optimization or need a certain load time, then there will be a white screen phenomenon.

The first page to load is the index.html page, which has no content and will display a blank screen. If there is something inside

, there will be no white screen. So we can add the first static page in

After the actual first screen is loaded, the

structure will be replaced, giving a visual error, so there will be no blank screen.

11. Introduction of component libraries on demand

The methods that component libraries introduce on demand are generally documented.

Such as the Element UI library, with the babel-plugin-Component plug-in for on-demand import.

Run NPM install babel-plugin-component –save-dev to install the plug-in.

In the. Babelrc. js file in the root directory, do as follows

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}
Copy the code

LibraryName indicates the name of the component library, while styleLibraryName indicates the name of the folder where the style is stored after the component library is packed. It can be imported on demand in main.js.

import Vue from 'vue';
import { Button, Select } from 'element-ui';
Vue.use(Button)
Vue.use(Select)
Copy the code

The babel-plugin-component plugin was adapted by Element with the babel-plugin-import plugin for Element UI. The general component library is still the babel-plugin-import plug-in implementation introduced on demand.

Run NPM install babel-plugin-import –save-dev to install the plug-in.

In the. Babelrc. js file in the root directory, do as follows

{
  "plugins": [
    ["import", {
      "libraryName": "vant",
      "libraryDirectory": "es",
      "style": true
    }]
  ]
}
Copy the code

Where libraryName is the name of the component library, libraryDirectory is the name of the main or module entry file from the package.json library, otherwise the default is lib.

In the introductionstyleOptions before configuration. Take a look at the structure and content of the generated file after the Vant component library is packaged.

The contents of the index.js file are as follows

The contents of the less-. js file are as follows

When style is true, index.js from the corresponding style file is imported into the project as needed.

When style is CSS, less-.js from the corresponding style file is imported into the project as needed.

With the style Function, babel-plugin-import will automatically import files whose file path is equal to the value returned by the Function. This is useful for component library developers. You can see my other article Vue CLI3 build component library and implement on demand to introduce actual operation.

3. Optimization of project packaging

Before we get to that, let’s be clear about what packing is. In plain English, you package a project into js files, CSS files, etc., and then add them to the index. HTML file, which you can look at in the dist folder of your project.

In the red box below is a resource packaged by a project. In fact, optimization is to optimize these resources. So how do you optimize these resources?In the early days without Webpack, these resources were handled and introduced by developers according to team specifications. And through optimization to achieve the fastest, the most reasonable download of these resources from the server. The optimization in this period is mainly reflected in:

  • Js and CSS code are introduced as required.
  • Js, CSS code common code extraction.
  • Minimizes compression of JS and CSS code.
  • Image resource compression.

Projects are now packaged with Webpack and can be optimized by configuring Webpack.

If your Vue project is built with Vue Cli3, you can create a new vue.config.js file in the root directory and configure Webpack in this file to optimize these resources.

Optimization is still mentioned in the above four points. Here are five optimizations, two of which are already optimized by default in production environments, but are worth looking at.

Optimise before and after. Install webpack-bundle-Analyzer, which helps you visualize the size of the packaged resources.

npm install webpack-bundle-analyzer --save-dev
Copy the code

Introduce this plug-in in vue.config.js

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports={
    configureWebpack:config =>{
        return {
            plugins:[
                new BundleAnalyzerPlugin()
            ]
        }
    }
}
Copy the code

Execute the commandnpm run build, opens a package analysis diagram in your browser, as shown below.

1. Use the import() asynchronous import component to import on demand

When we talk about import(), we naturally think of route lazy loading, where components are introduced asynchronously with import().

Resolve =>require([‘ address to load component ‘],resolve)

component: () =>import('views/home.vue'),
component: resolve =>require(['views/home.vue'],resolve)
Copy the code

Resolve =>require([‘ address of the component to be loaded ‘],resolve) The js file is loaded when the component is loaded, which is lazy loading.

After importing components asynchronously with import() and executing the command NPM run build, take a look at the package analysis diagram.

After comparison, it was found that a 1.42MB JS file was missing, which was split into many small JS files such as 32.55KB and 31.69KB. These small JS files are loaded only when the corresponding component is loaded, which is lazy loading.

If you look at these JS file names and feel confused, they will not match the components in the project. Here is a tip. WebpackChunkName: indicates the name of the chunk file. [request] Indicates the file name that is actually parsed.

function load(component) {
    return () => import(/* webpackChunkName: "[request]" */ `views/${component}`)
}
Copy the code

Run the command NPM run build to see the package analysis diagram.

The js file in the red box is a package of the views/flow_card_manage/flow_card_list/index.vue component.

Open the project in your browser and use F12 to capture and searchflow_card_manage-flow_card_list.67de1ef8.jsThis file. On the home page, before the routing component is loaded. This js file is loaded, it just prefetch, no content is returned. The goal is to tell the browser to load this JS file for me when it’s free. Until the route component is actually loaded, the js file is loaded again. If the browser has loaded the js file, it returns the content. If the browser has not loaded the JS file, it requests the js file from the server, and returns the content. This is lazy loading, loading on demand. Principle:Take a look at my other article🚩 four years front end brings you to understand the principle of lazy load routing

2. Use externals to extract third-party dependencies and introduce them into CDN

As you can see from the package analysis diagram, chunk-vendors. Js and Chunk-80f6b79a.js are still large files. These two files contain element-UI, jquery, XLSX and other third party dependent resources.

The externals configuration option in Webpack avoids packaging third-party dependencies and instead gets them externally at project runtime.

See my other article Webpack for a detailed explanation of how to use externals.

Execute the commandnpm run buildTake a look at the package analysis diagram.Chunk-vendors. Js and chunk-80f6b79a.js have significantly smaller file sizes than they did before.

Note The golden mean when extracting third-party dependencies using externals. Although the ultimate goal is to reduce the size of HTTP request resources, overdoing it will increase the number of HTTP requests.

Use SplitChunks to extract common JS code and split JS code

Even with Webpack, many resources are repeatedly packaged into js files, which can be further optimized with SplitChunks to reduce the overall size of the package generation file.

In addition, CDN is a third party, which is unstable. In case of sudden failure of CDN, the system will collapse, with certain risks. SplitChunks can also be used to implement the effect of externals configuration, so that third-party dependencies are kept on the server to reduce risks.

For details, see SplitChunks in my other Webpack article

4. Extract CSS styles using MiniCssExtractPlugin

In a Vue project built with Vue Cli3, css.extract is used to control whether the MiniCssExtractPlugin is enabled or not. Although in production, css.extract defaults to true. This means the MiniCssExtractPlugin plugin is enabled. But familiarize yourself with the MiniCssExtractPlugin in case the interviewer asks.

For details, please refer to the MiniCssExtractPlugin plugin in my other article Webpack.

5. Use the OptimizeCssnanoPlugin to compress and de-duplicate CSS style files

By default, the OptimizeCssnanoPlugin is used in the Vue Cli3 project to compress and remove CSS style files.

Here’s how to use the plugin. Install the OptimizeCssnanoPlugin

cnpm install --save-dev @intervolga/optimize-cssnano-plugin
Copy the code

This is configured in vue.config.js

const OptimizeCssnanoPlugin = require('@intervolga/optimize-cssnano-plugin');
module.exports={
    configureWebpack:config =>{
        return {
            plugins:[
                new OptimizeCssnanoPlugin({
                    sourceMap: false,
                    cssnanoOptions: {
                        preset: [
                            'default',
                            {
                              mergeLonghand: false,
                              cssDeclarationSorter: false
                            }
                        ]
                    },
                }),
            ]
        }
    }
}
Copy the code

CssnanoOptions can be seen here.

MergeLonghand :false disables CSS style attribute merges such as margin, padding and border.

.box { margin-top: 10px; margin-right: 20px; margin-bottom: 10px; margin-left: 20px; } // after compression. Box {margin: 10px 20px; }Copy the code

CssDeclarationSorter: false, said shut down according to the CSS productname of CSS properties.

body { animation: none; color: #C55; border: 0; } // Body {animation: none; border: 0; color: #C55; }Copy the code

6, open optimization. Minimize to compress JS code

Optimization. The minimize option has two values, true to enable compressed JS code and false to disable compressed JS code.

The default is true in production and false in development.

If you do not need to debug code in your development environment, you can also set true to compress JS code to speed up page loading.

This is configured in vue.config.js

module.exports={
    configureWebpack:config =>{
        return {
            optimization:{
                minimize: true
            }
        }
    }
}
Copy the code

The TerserPlugin plugin is used to compress JS code by default in Vue Cli3, where the configuration is already optimal.

If you want to use another plug-in to compress JS code, you can add it in the optimization.minimizer option, and the value is an array.

Add it with chainWebpack, where WebpackPlugin is the plug-in name and args is the plug-in parameter.

Module. Exports = {chainWebpack: const WebpackPlugin = require(plug-in name) module.exports = {chainWebpack: config =>{ config.optimization .minimizer(name) .use(WebpackPlugin, args) }, }Copy the code

7. Use image-webpack-loader to compress images

In the Vue project built with Vue Cli3, images are directly processed by URL-loader and file-loader without compression.

Use image-webpack-loader to compress images for url-loader and file-loader.

In Vue Cli3 has configured the loader for image processing, to modify it, the specific method can see my other article Webpack loader configuration details.

Install the image first – webpack – loader

cnpm install image-webpack-loader --save-dev
Copy the code

Then configure this in vue.config.js

module.exports = {
    chainWebpack: config =>{
        config.module
            .rule('images')
            .use('imageWebpackLoader')
            .loader('image-webpack-loader')
    },
}
Copy the code

Before adding image-webpack-loader, and after packing homebg.png, the following image is shown

After image-webpack-loader is added, the image of homebg.png is packaged as follows

You can see that the image size has been reduced from 251KB to 110KB.

Image-webpack-loader supports compression of PNG, JPEG, GIF, SVG and WEBP images. The following describes its common parameters.

  • BypassOnDebug ture/false, defaults to false, disables compressed images when true, used in [email protected].

  • Disable ture/false, defaults to false, disables compressed images when true, used in [email protected] and later. You can disable compressed images in your development environment to make them compile faster.

    module.exports = {
        chainWebpack: config =>{
            config.module
                .rule('images')
                .use('imageWebpackLoader')
                .loader('image-webpack-loader')
                .options({
                    disable: process.env.NODE_ENV === 'development',
                })
        },
    }
    Copy the code
  • Mozjpeg: Controls the configuration for compressing JPEG images, enabled by default. The parameter value is an object, and the commonly used sub-parameters are:

    • Quality Compression quality, ranging from 0 (worst) to 100 (best).
  • Optipng: Controls the configuration of compressed PNG images, enabled by default. The parameter value is an object, and the commonly used sub-parameters are:

    • OptimizationLevel OptimizationLevel. Select an OptimizationLevel between 0 and 7. The higher the value is, the better the compression quality is, but the slower the compression speed is.
  • Pngquant: Controls the configuration of compressed PNG images, enabled by default. The parameter value is an object, and the commonly used sub-parameters are:

    • Speed Compression speed. The value ranges from 1 to 11. A higher value indicates a higher compression speed. A value of 10 reduces mass by 5% but is 8 times faster than the default speed.
    • Quality Indicates the compression quality. The value is an array, for example[0, 1], the minimum is 0 (worst) and the maximum is 1 (perfect).
  • Gifsicle: Controls the configuration of compressed GIF images, enabled by default. -OptimizationLevel specifies the OptimizationLevel. An OptimizationLevel is selected between 1 and 3. The OptimizationLevel determines the OptimizationLevel. Higher levels take longer, but may have better results.

  • Webp: Compress JPG and PNG images into WebP. This function is disabled by default and can be enabled only after configuration. After enabled, JPG and PNG images can be compressed to output smaller images, but more time-consuming than mozJPEG, OpTIPng, pngQuant compression, will affect the compilation and packaging speed, you need to choose their own.

    The parameter value is an object, and the commonly used subparameters are

    • Quality Quality factor. The value is between 0 and 100. The default value is 75.
    • Lossless Indicates whether lossless compression is enabled. The default value is false.
    • NearLossless is lossless encoded using an additional lossy preprocessing step, with a quality factor between 0 (maximum preprocessing) and 100 (equal to Lossless).
module.exports = { chainWebpack: config =>{ config.module .rule('images') .use('imageWebpackLoader') .loader('image-webpack-loader') .options({ disable: process.env.NODE_ENV === 'development', mozjpeg:{ quality:75 }, optipng:{ OptimizationLevel:3 }, pngquant:{ speed:4, Quality :[0.2,0.5]}, gifsicle:{OptimizationLevel:1}, webp:{quality:75, lossless:true, nearLossless:75}},}Copy the code

Iv. Optimization of project deployment

Here is only a relatively common and simple optimization means, gzip compression. There are other optimizations, involving the server side, that can backfire if the interviewer delves into them.

1. Determine whether gzip compression is enabled

Content-encoding: gzip: Response headers (content-encoding: gzip)

2. Enable gzip compression on Nginx

In nginx/conf/nginx.conf

http {
    gzip  on;
    gzip_min_length 1k;
    gzip_comp_level 5;
    gzip_types application/javascript image/png image/gif image/jpeg text/css text/plain;
    gzip_buffers 4 4k;
    gzip_http_version 1.1;
    gzip_vary on;
}
Copy the code
  • gzip: on | off, the default is off and on for open gzip, off to close the gzip.
  • gzip_min_length: number: indicates the starting point of compression. The size of the file must be larger than the size of the file. The unit is byte by default.
  • gzip_comp_level: Compression level: the value ranges from 1 to 9. A larger number indicates a smaller size after compression, and consumes more CPU resources and takes longer.
  • gzip_types: Indicates the type of the file to be compressed. Type go to Response Headers and see the content-type attribute.
  • gzip_buffers: number size, sets the number of units of cache the system obtains to store the compressed gzip result data stream.

For example, 4 4K indicates that the memory size is four times the original data size in 4K. If the original data size is 17K, apply for (17/4) x 4 = 17K memory.

  • gzip_http_version: Set the HTTP version later than gzip compression.
  • gzip_vary: on | off, whether in the HTTP header to add than: Accept – Encoding, add on. Vary: accept-encoding Tells the proxy server to cache two versions of the resource: Compression and uncompression, avoid a browser does not support compressed resources, and first request the server, the server cache uncompressed resources, and then a browser supports compressed resources, and then request the server, the result is uncompressed resources, but decompress it, the result will be an error. Therefore, you are advised to set it to ON.

Before enabling Gzip compressionAfter gzip compression is enabled

By contrast, the optimization effect is very obvious. You can also try it out locally. See my article on how to deploy Vue projects with Nginx.

3. Enable gzip compression in Webpack

Use the CompressionWebpack plug-in to achieve gzip compression.

Start by installing the CompressionWebpack plug-in

npm install compression-webpack-plugin --save-dev
Copy the code

Then configure this in vue.config.js

const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
    configureWebpack: config =>{
        return {
            plugins: [
                new CompressionPlugin()
            ],
        }
    }
}
Copy the code

performnpm run buildAfter command, opendistFile, you will find many more files with the same name, but one of the files with the suffix.gzThis is the gzip compressed file.

4, Nginx and Webpack compression differences

  • Regardless of Nginx or Webpack compression, gzip compression must be enabled in Nginx, otherwise the browser will load or uncompressed resources.

    You can also add gzip_static on to Nginx; The configuration. If gzip_static is enabled, when a browser requests a resource, Nginx checks whether a. Gz file with the resource name is stored. If so, Nginx returns the content of the gz file, preventing Nginx from performing gzip compression on the resource and wasting server CPU.

  • Each time the browser requests a resource, Nginx compresses the resource in real time and returns the resource only after the compression is complete. If the resource is large or the compression level is set very high, it will take too long to return the resource, resulting in a poor user experience.

  • Using Webpack can make packing time longer. However, with CompressionPlugin compression, there is a cache, which can relatively reduce the packaging time.

  • It is recommended that both Nginx and Webpack compression be enabled and that gzip_static on be added to Nginx; To reduce the CPU usage of the server, of course, it depends on the actual situation of the project.

5, CompressionPlugin parameters detailed explanation

  • test: String | RegExp | Array < String | RegExp >,Name of resourceOnly those that meet the conditions will be compressed. The default value is undefined, that is, all will be compressed. For example, only js files will be compressed
    plugins: [
        new CompressionPlugin({
            test: /\.js(\?.*)?$/i,
        })
    ],
    Copy the code
  • include: String | RegExp | Array < String | RegExp >,Name of resourceThe default value is undefinedtestIs filtered within the range of parameterstestParameter, and is satisfiedincludeParameter condition resources will be compressed.
  • exclude: String | RegExp | Array < String | RegExp >, ruled out when compressedName of resourceThe qualified resource, which defaults to undefined, is intestThe parameters are excluded within the rangetestParameter conditions are not metexcludeParameter condition resources will be compressed.
  • algorithm: Compression algorithm/function, default gZIP, generally do not change.
  • compressionOptionsforalgorithmParameter Indicates the compression level. The value ranges from 1 to 9. A larger number indicates a smaller size after compression, occupies more CPU resources, and takes longer time.
    plugins: [
        new CompressionPlugin({
            compressionOptions: { level: 1 },
        })
    ],
    Copy the code
  • threshold: Number: sets the minimum size of the compressed resource, in bytes. The default is 0.
  • minRatio: Number: sets the compression ratio. The compression ratio = the size of the compressed resource/the compressed resource. The resource that is smaller than the compression ratio will be compressed. andthresholdThe argument is the relationship between and.
  • filenameType: String | Function, after set compression resource name, default value: [path]. Gz [query], [file] assets is replaced with the original file name. [path] Replaces the path of the original asset. [dir] replaces the directory of the original asset. [name] is replaced with the file name of the original asset. [ext] is replaced with the extension of the original asset. [query] is replaced by a query.

Now I’m going to print out all of these values using a function.

new CompressionPlugin({ filename(info) { console.log(info) return `${info.path}.gz${info.query}`; }})Copy the code

  • deleteOriginalAssets: Boolean, default is BooleanfalsefortrueTo delete the original resource file. This parameter is not recommended.
  • cache: Boolean | String, the default is zerotruefortrueTo enable file caching. The default path for the cache directory:node_modules/.cache/compression-webpack-plugin. When the value is String. Enable file caching and set the path to the cache directory.
new CompressionPlugin({
      cache: 'path/to/cache',
}),
Copy the code