Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

preface

The purpose of this article is clear 🐱🐉, in view of the important characteristics of Vue framework, the principle of the framework in the form of a summary of questions.

So where does that “past experience” come from?

Yes, I’m going to tell a short story again, but this time it’s a sequel.

Story 1: CSS Preprocessor, Do you still only nest? Story 2: [Adaptive] Px to REM, do you still count?

Why to say is sequel, because these are same big guy ask, thank big guy here, sky descend material 🤣.

Story sequel

Big guy: have you read Vue source code?

Me: Yeah, yeah.

Big Man: What about the underlying implementation of nextTick?

Me: I paused for about 10 seconds and said I forgot. (Not straight and still strong)

Big guy: Oh, oh, it’s okay. (Heart has probably given up on my knowledge)

Because it is a video interview, the awkwardness of pretending to be confident overflows from the screen, which is probably ordinary and confident 🤦♂️? Take a lesson from X’s failures and forget the interview results of the sequel.

This interview blows or quite big, inspect comprehensive and detail. After the interview, I have been sorting out the knowledge points related to Vue, so I will not write the nextTick implementation separately, but just include it in the questions below. Take this article as a test to see if you are comfortable with these topics.

The content of this article is summarized on the shoulders of many big names (see reference), and the trivial knowledge of Vue that has not been involved in the follow-up will be updated in this article.

The title

Pros and cons of Vue

advantages

  1. A lightweight Web application framework for creating single-page applications
  2. Simple and easy to use
  3. Two-way data binding
  4. The idea of componentization
  5. Virtual DOM
  6. Data-driven view
  7. Have mature build tool (VUE-CLI), run fast

disadvantages

– IE8 is not supported (at this stage, it can only be scraped together so much 😂)

The understanding of the SPA

SPA stands for single-page-application, which translates as single-page app. Load Html, Javascript, and Css together when the WEB page is initialized. Once the page is loaded, SPA does not reload or jump the page due to user actions. Instead, THE ROUTING mechanism is used to transform the Html content.

advantages

  1. Good user experience, content changes without reloading the page.
  2. Based on the above point, SPA has less pressure than the service side.
  3. The responsibilities of the front and back end are separated and the structure is clear.

disadvantages

  1. As a single-page WEB application, JavaScript and Css files need to be requested when loading the rendered page, so it takes more time.
  2. Due to front-end rendering, search engines will not parse JS, can only capture the home page unrendered template, not conducive to SEO.
  3. Because single-page apps display all content on one page, browser forward and backward is not supported by default.

Weakness 3, I think someone and I have the same question.

Through search engines, it is the front-end routing mechanism that solves the problem that a single page application cannot move forward or backward. In Hash mode, Hash changes are recorded by the browser (onHashChange event). In History mode, pushState and replaceState methods are added to H5 to change the browser History stack.

What does new Vue(Options) do

As shown in the following Vue constructor, the this._init(options) method is primarily executed, which is registered in the initMixin function.

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '.. /util/index'

function Vue (options) {
  if(process.env.NODE_ENV ! = ='production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')}/ / the Vue. Prototype. _init method
  this._init(options)
}

// the _init method is registered in initMixin
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue
Copy the code

See the implementation of the initMixin method. The implementation of other functions can be seen by yourself, but it is not posted here.

let uid = 0
export function initMixin() {
  Vue.prototype._init = function(options) {
    const vm = this
    vm._uid = uid++
    vm._isVue = true
   
    // Process component configuration items
    if (options && options._isComponent) {
       /** * If it is a subcomponent, the current if branch is used to optimize performance: put all the deep methods on the prototype chain into vm.$options, reducing the access on the prototype chain */   
      initInternalComponent(vm, options)
    } else {
      /** * If it is the root component, go to the current else branch * to merge the global configuration of Vue into the root component. Such as Vue.com ponent registered global components incorporated into the root component of the components of the option * child components in the option combination in two places * 1. Vue.com ponent method registered global components at the time of registration to do option merger * 2. { Local components registered with the Component: {xx}} method do option merging */ when executing the compiler generated render function  
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
  
    if(process.env.NODE_ENV ! = ='production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }

    vm._self = vm
    $parent $root $children $refs */ initializes the component instance relationship properties
    initLifecycle(vm)
    /** * Initializes custom events * < Component@click="handleClick"></component> * Events registered on the component, listener is not the parent, but the child component itself */
    initEvents(vm)
    $slot = vm.$slot = vm.$createElement = h * /
    initRender(vm)
    /** * Execute the beforeCreate lifecycle function */
    callHook(vm, 'beforeCreate')
    /** * Parse the inject configuration item, get the configuration object result[key] = val, do the reactive processing and proxy to the vm strength */
    initInjections(vm) 
    /** * Responsive processing core, processing props, methods, data, computed, watch */
    initState(vm)
    /** * parse the provide object and mount it to the VM instance */
    initProvide(vm) 
    /** * Executes the Created lifecycle function */
    callHook(vm, 'created')

    // If el is selected, $mount is automatically executed
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
Copy the code

The understanding of the MVVM

MVVM stands for model-view-ViewModel. The Model represents the data layer and allows you to define and modify data and write business logic. View represents the View layer, which is responsible for rendering data into pages. The ViewModel is responsible for monitoring data changes at the data layer and controlling the behavior interaction at the view layer. In short, it is to synchronize objects at the data layer and the view layer. ViewModel connects View and Model layer through bidirectional binding, and synchronization work without human intervention, so that developers only focus on business logic, do not need to frequently manipulate DOM, do not need to pay attention to the synchronization of data state.

How to implement v-Model

The V-model directive is used to implement bidirectional binding of form elements such as input and SELECT. It is a syntax sugar.

Native input elements of type TEXT/Textarea use the value attribute and input event. Native input elements of type Radio /checkbox use the Checked attribute and change event. Native SELECT element, using the value attribute and the change event.

Using v-model on input elements is equivalent to

<input :value="message" @input="message = $event.target.value" />
Copy the code

Implement v-models for custom components

Custom component V-models use prop values for value and input events. For radio/checkbox types, you need to use model to resolve that the native DOM uses the Checked attribute and change event, as shown below.

/ / the parent component<template>
  <base-checkbox v-model="baseCheck" />
</template>
Copy the code
/ / child component<template>
  <input type="checkbox" :checked="checked" @change="$emit('change', $event.target.checked)" />
</template>
<script>
export default {
  model: {
    prop: 'checked'.event: 'change'
  },
  prop: {
    checked: Boolean}}</script>
Copy the code

How to understand Vue one-way data flow

Vue official documentation Prop menu has a sub-menu called Single Data Flow.

We often talk about Vue’s bidirectional binding, but it’s a one-way binding that adds input/change events to elements to dynamically modify the view. Data transfer between Vue components is still monomial, that is, parent component passes to child component. A child component can internally define the values in the props, but cannot modify the data passed by the parent component. This prevents the child component from accidentally changing the state of the parent component and making the application data flow difficult to understand.

If you change prop directly within a child component, you get a warning.

Both definitions depend on the values in props

  1. Properties are defined through Data with prop as the initial value.
<script>
export default {
  props: ['initialNumber'].data() {
    return {
      number: this.initailNumber
    }
  }
}
</script>
Copy the code
  1. Depend on the value of prop to define computed properties
<script>
export default {
  props: ['size'].computed: {
    normalizedSize() {
      return this.size.trim().toLowerCase()
    }
  }
}
</sciprt>
Copy the code

Vue responsive principle

Core source location: vue/SRC/core/observer/index. Js

The responsivity principle consists of three steps: data hijacking, dependency collection, and distribution of updates.

There are two types of data: objects and arrays.

object

Traverse the Object, setting the getter/setter for each property via Object.defineProperty, and do data hijacking. Getter functions are used for dependency collection when data is read, storing the Watcher in the corresponding DEP. Setters are the ones that tell all watchers to update when the data is updated.

Handling the source code

function defineReactive(obj, key, val, shallow) {
  // instantiate a deP, one key for one DEP
  const dep = new Dep()
 
  // Get the attribute descriptor
  const getter = property && property.get
  const setter = property && property.set
  if((! getter || setter) &&arguments.length === 2) {
    val = obj[key]
  }

  // Handle the case where val is an object recursively, i.e. handle nested objects
  letchildOb = ! shallow && observe(val)Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.// Intercepts obj.key for dependency collection
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      // Dep.target is the watcher for the current component rendering
      if (Dep.target) {
        // Add deP to watcher
        dep.depend()
        if (childOb) {
          // Nested objects depend on collection
          childOb.dep.depend()
          // The value is an array
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // Get the old value
      const value = getter ? getter.call(obj) : val
      // Check whether the old and new values are consistent
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      if(process.env.NODE_ENV ! = ='production' && customSetter) {
        customSetter()
      }

      if(getter && ! setter)return
      // If the value is new, replace the old value with the new value
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // The new value is processed responsivelychildOb = ! shallow && observe(newVal)// When reactive data is updated, rely on notification updates
      dep.notify()
    }
  })
}
Copy the code

An array of

Override the default array method on the original property with array enhancement. Enables watcher to be notified via DEP to update data when it is added or deleted.

Handling the source code

const arrayProto = Array.prototype
// Create a new object based on the array prototype object
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]

methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  // Define seven methods on the arrayMethods object
  def(arrayMethods, method, function mutator (. args) {
    // Execute the native method first
    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
    }
    // Respond to new elements
    if (inserted) ob.observeArray(inserted)
    // Data is updated whether it is added or deleted
    ob.dep.notify()
    return result
  })
})
Copy the code

Handwritten observer model

let uid = 0
class Dep {
  constructor() {
    this.id = uid++
    // Store all the watcher
    this.subs = []
  }
  addSub(sub) {
    this.subs.push(sub)
  }
  removeSub(sub) {
    if(this.subs.length) {
      const index = this.subs.indexOf(sub)
      if(index > -1) return this.subs.splice(index, 1)}}notify() {
    this.subs.forEach(sub= > {
      sub.update()
    })
  }
}

class Watcher {
  constructor(name) {
    this.name = name
  }
  update() {
    console.log('update')}}Copy the code

Handwritten publish and subscribe model

Similar to the observer pattern, the difference is that publishers and subscribers are decoupled, with a central dispatch center communicating with publishers and subscribers. The Vue responsive principle is closer to the publish-and-subscribe model, where the Observer is the publisher, the Watcher is the subscriber, and the Dep is the dispatch center.

class EventEmitter {
  constructor() {
    this.events = {}
  }
  on(type, cb) {
    if(!this.events[type]) this.events[type] = []
    this.events[type].push(cb)
  }
  emit(type, ... args) {
    if(this.events[type]) {
      this.events.forEach(cb= >{ cb(... args) }) } }off(type, cb) {
    if(this.events[type]) {
      const index = this.events[type].indexOf(cb)
      if(index > -1) this.events[type].splice(index, 1)}}}Copy the code

Observable Vue. Observable

Ue. Observable makes objects responsive. The returned objects can be used directly in rendering functions and computed properties, and can trigger updates when changes occur. Can also be used as a minimal cross-component state store.

In Vue 2.x, the object passed in is the same object as the object returned. Vue 3.x is not an object, and the source object does not have responsive functionality.

Application scenario: Vue. Observable can be used to replace eventBus and Vuex scenarios in projects where there is not a lot of non-parent component communication.

Use the following

// store.js
import Vue from 'vue'
export const state = Vue.observable({
  count: 1
})
export const mutations = {
  setCount(count) {
    state.count = count
  }
} 

/ / the vue file
<template>
  <div>{{ count }}</div>
</template>
<script>
import { state, mutation } from './store.js'
export default {
  computed: {
    count() {
      return state.count
    }
  }
}
</script>
Copy the code

The principle part is the same function as the reactive principle processing component data, instantiating an Observe to hijack data.

Why is data a function in a component

The object is stored in the stack address, the function is to privatize the property, to ensure that the modification of data component does not affect other reusable components.

Vue life cycle

The life cycle describe
beforeCreate After vUE instance initialization, data Observer and before event configuration. Data, computed, Watch, and Methods are inaccessible.
created Called immediately after the vUE instance is created to access Data, computed, Watch, and Methods. $el, $ref cannot be accessed without DOM mounted.
beforeMount Called before DOM mounting begins.
mounted The Vue instance is mounted to the DOM.
beforeUpdate Called before data is updated and occurs before the virtual DOM is patched.
updated Called after data update.
beforeDestroy Called before instance destruction.
destroyed Called after instance destruction.

Invoking asynchronous requests can be invoked in the Created, beforeMount, and Mounted lifecycles because the related data is already created. The best option is to call it in Created.

In Mounted, $ref is used to obtain the DOM.

Vue parent and child component lifecycle execution order

Loading the rendering process

Child component update process

Parent component update process

Destruction of the process

How does a parent component listen for a child component’s lifecycle hook function

$emit implementation

Take Mounted as an example.

/ / the parent component<template>
  <div class="parent">
    <Child @mounted="doSomething"/>
  </div>
</template>
<script>
export default {  
  methods: {
    doSomething() {
      console.log(Parent component listens to child component mounted hook function)}}}</script>/ / child component<template>
  <div class="child">
  </div>
</template>
<script>
export default {
  mounted() {
    console.log('Triggers mounted events... ')}}</script>
Copy the code

@ hook to achieve

/ / the parent component<template>
  <div class="parent">
    <Child @hook:mounted="doSomething"/>
  </div>
</template>
<script>
export default {  
  methods: {
    doSomething() {
      console.log(Parent component listens to child component mounted hook function)}}}</script>/ / child component<template>
  <div class="child">
  </div>
</template>
<script>
export default {
  mounted() {
    console.log('Triggers mounted events... ')}}</script>
Copy the code

Communication between Vue components

Parent-child component communication

  1. Props and $emit
  2. $parent and $children

Intergenerational component communication

  1. $attrs and $listeners
  2. Dojo.provide and inject

Communication between father and son, brother and other generation components

  1. EventBus
  2. Vuex

V-on listens for multiple methods

<button v-on="{mouseenter: onEnter, mouseleave: onLeave}"> mouse in1</button>`
Copy the code

A commonly used modifier

Form modifier

  1. Lazy: Synchronizes information after losing focus
  2. Trim: Automatically filters the first and last Spaces
  3. Number: The input value is converted to a numeric type

Event modifier

  1. Stop: Stops bubbles
  2. Prevent: Prevents the default behavior
  3. Self: Only the binding element itself fires
  4. “Once” : triggers only once

Mouse button modifier

  1. Left: the left mouse button
  2. Right: Right mouse button
  3. “Middle” : the middle mouse button

How to bind class and style dynamically

Classes and styles can be dynamically bound using object syntax and array syntax

Object to write

<template>
  <div :class="{ active: isActive }"></div>
  <div :style="{ fontSize: fontSize }">
</template>
<script>
export default {
  data() {
    return {
      isActive: true.fontSize: 30}}}</script>
Copy the code

An array of writing

<template>
  <div :class="[activeClass]"></div>
  <div :style="[styleFontSize]">
</template>
<script>
export default {
  data() {
    return {
      activeClass: 'active'.styleFontSize: {
        fontSize: '12px'}}}}</script>
Copy the code

V-show is different from V-if

Common ground: Control elements show and hide. Difference:

  1. V-show controls the element’s CSS (display); V-if is the addition or deletion of the control element itself.
  2. The component life cycle is not triggered when v-show changes from false to true. The component is triggered when v-if changes from false to truebeforeCreate,create,beforeMount,mountedA hook that changes from true to false to trigger the componentbeforeDestory,destoryedMethods.
  3. V-if has a higher performance cost than V-show.

Why can’t V-if be used with V-for

Performance waste, every render must loop before making conditional judgments, consider using computed attributes instead.

In Vue2. X, V-for has a higher priority than V-if.

In Vue3. X, v-if has a higher priority than V-for.

Differences between computed and Watch and application scenarios

In essence, both computed and Watch are implemented by instantiating Watcher, and the biggest difference is that they are applied in different scenarios.

computed

Evaluates properties, depends on other property values, and values have caching properties. The next fetched value is recalculated only if the value of the attribute it depends on changes.

Applies to numerical calculations and depends on other attributes. Because you can take advantage of the caching feature, you don’t have to recalculate every time you get a value.

watch

Observe properties and listen for changes in property values. Whenever the property value changes, the corresponding callback is executed.

This method is suitable for asynchronous or expensive operations when data changes.

Slot slot

Slot Indicates that slot is prepositioned in the component template. When a component is reused and a slot label is used, the content in the slot label is automatically replaced by the slot label in the component template as an outlet for the distribution content.

The main function is to reuse and expand components, do some customized components processing.

There are three main types of slots

  1. The default slot
/ / child component<template>
  <slot>
    <div>Default slot alternative content</div>
  </slot>
</template>/ / the parent component<template>
  <Child>
    <div>Replace the default slot content</div>
  </Child>
</template>
Copy the code
  1. A named slot

If the slot tag does not have a name attribute, it is the default slot. If the slot has the name attribute, it is a named slot

/ / child component<template>
  <slot>The location of the default slot</slot>
  <slot name="content">Slot content</slot>
</template>/ / the parent component<template>
   <Child>
     <template v-slot:default>The default...</template>
     <template v-slot:content>Content...</template>
   </Child>
</template>
Copy the code
  1. Scope slot

Properties bound to the scope by the child to pass information about the component to the parent. These properties are hung on objects accepted by the parent.

/ / child component<template>
  <slot name="footer" childProps="Subcomponent">Scope slot contents</slot>
</template>/ / the parent component<template>
  <Child v-slot="slotProps">
    {{ slotProps.childProps }}
  </Child>
</template>
Copy the code

Vue.$delete differs from delete

Delete is to change the deleted element to undefined, the other elements of the same key value. Vue.$delete deletes the element directly, changing the array length.

Vue.$set does not respond to new attributes of an object

Vue.$set occurs because of a limitation of Object.defineProperty: Additions or deletions of Object attributes cannot be detected.

Source location: vue/SRC/core/instance/index. Js

export function set(target, key, val) {
  / / array
  if(Array.isArray(target) && isValidArrayIndex(key)) {
    // Change the array length to avoid splice errors caused by indexes larger than the array length
    target.length = Math.max(target.length, key)
    // Use the array splice to trigger the response
    target.splice(key, 1, val)
    return val
  }
  // The key already exists
  if(key intarget && ! (keyin Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = target.__ob__
  // Target is not reactive data, directly assigned
  if(! ob) { target[key] = valreturn val
  }
  // Handle attributes responsively
  defineReactive(obj.value, key, val)
  // Send updates
  ob.dep.notify()
  return val
}
Copy the code

Implementation principle:

  1. In the case of an array, use the array splice method directly to trigger the response.
  2. If so, determine whether the attribute exists and whether the object is responsive.
  3. If none of the above is satisfied, the attribute is processed responsively through defineReactive.

Vue asynchronous update mechanism

The core of Vue asynchronous update mechanism is realized by using asynchronous task queue of browser.

When reactive data is updated, dep.notify is triggered to notify all collected Watchers to perform the update method.

Dep class notify method

notify() {
  // Get all the watcher
  const subs = this.subs.slice()
  // Go through the watcher stored in the DEP and execute watcher.update
  for(let i = 0; i < subs.length; i++) {
    subs[i].update()
  }
}
Copy the code

Watcher. Update puts itself on the global Watcher queue and waits for execution.

Update method of the Watcher class

update() {
  if(this.lazy) {
    // Lazy execution goes the current if branch, such as computed
    // The identifier here is mainly used for computed cache reuse logic
    this.dirty = true
  } else if(this.sync) {
    // The watch option is synchronized to the current branch
    // If true, execute watcher.run() without filling the asynchronous update queue
    this.run()
  } else {
    // Update the current else branch normally
    queueWatcher(this)}}Copy the code

QueueWatcher method, discovering the familiar nextTick method. You can skip to nextTick first, and then go back. 😊

function queueWatcher(watcher) {
  const id = watcher.id
  // Check whether the user is in the queue according to the Watcher ID. If the user is in the queue, the user does not join the queue repeatedly
  if (has[id] == null) {
    has[id] = true
    // The global queue is not refreshed. Watcher can join the queue
    if(! flushing) { queue.push(watcher)// The global queue is refreshed
    // Find the current id position in monotonically increasing sequence and insert
    } else {
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1.0, watcher)
    }
   
    if(! waiting) { waiting =true
      // Execute logic synchronously
      if(process.env.NODE_ENV ! = ='production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      // Put the callback function flushSchedulerQueue into the callbacks array
      nextTick(flushSchedulerQueue)
    }
  }
}
Copy the code

The nextTick function actually ends up executing the flushCallbacks function, which is the callback passed in by running the flushSchedulerQueue callback and calling nextTick in the project.

What does handling the source code for flushSchedulerQueue look like

/** * If flushing is true, the watcher added to the queue must be inserted into the queue in ascending order. ** * If flushing is true, the watcher added to the queue must be ordered in ascending order. ** If flushing is true, the watcher added to the queue must be ordered in ascending order. Execute watcher. Before and watcher. Run in order to clear the cached watcher */
function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  // Indicates that the queue is being refreshed
  flushing = true
  let watcher, id

  queue.sort((a, b) = > a.id - b.id)
  // The length is not cached because watcher may be added when watcher is executed
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    // Clear the cached watcher
    has[id] = null
    // Trigger an update function, such as updateComponent, or perform the user's watch callback
    watcher.run()
  }

  
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()
  
  // Waiting = flushing = false to add the next flushCallbacks to the browser task queue
  resetSchedulerState()
 
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

  if (devtools && config.devtools) {
    devtools.emit('flush')}}Copy the code

Let’s take a look at what watcher.run does. First it calls the get function.

/** * Performs the second argument passed by the watcher instantiation, such as updateComponent * updates the old value to a new value * the third argument passed by the watcher instantiation, the user-passed watcher callback */
run () {
  if (this.active) {
    / / call the get
    const value = this.get()
    if( value ! = =this.value ||
      isObject(value) ||
      this.deep
    ) {
      // Update the old value to the new value
      const oldValue = this.value
      this.value = value
      // If the project passes in a watcher, the instantiated callback is executed.
      if (this.user) {
        const info = `callback for watcher "The ${this.expression}"`
        invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
      } else {
        this.cb.call(this.vm, value, oldValue)
      }
    }
  }
}
/** * Execute this.getter and re-collect dependencies. * Recollect dependencies because only reactive observations are made in setters that trigger update, but no dependencies are collected. * So, when the page is updated, the Render function is re-executed, during which the read operation is triggered, and dependency collection takes place. * /
get () {
  // Dep.target = this
  pushTarget(this)
  let value
  const vm = this.vm
  try {
    // Execute the callback function, such as updateComponent, to enter the patch phase
    value = this.getter.call(vm, vm)
  } catch (e) {
    if (this.user) {
      handleError(e, vm, `getter for watcher "The ${this.expression}"`)}else {
      throw e
    }
  } finally {
    // watch is deep
    if (this.deep) {
      traverse(value)
    }
    // Dep. Target is null
    popTarget()
    this.cleanupDeps()
  }
  return value
}
Copy the code

The principle of Vue. $nextTick

NextTick: Executes a deferred callback after the next DOM update loop ends. It is used to retrieve the updated DOM after modifying the data.

Vue/SRC /core/util/next-tick.js

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'

// Whether to use the microtask identifier
export let isUsingMicroTask = false

// Callback function queue
const callbacks = []
/ / asynchronous lock
let pending = false

function flushCallbacks () {
  // The next flushCallbacks can enter the browser's task queue
  pending = false
  // To prevent problems when nextTick is included in the callback queue, copy the backup in advance and empty the callback queue before executing it
  const copies = callbacks.slice(0)
  // Empty the callbacks array
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

let timerFunc

// Check browser capability
// The purpose of using macro tasks or microtasks is that they must be executed after the synchronization code has finished, ensuring that the DOM is finally rendered.
// Macro tasks take longer than microtasks. Microtasks are preferred when supported by browsers.
// There is also a gap in efficiency in macro tasks, the lowest being setTimeout
if (typeof Promise! = ='undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () = > {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () = > {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
  timerFunc = () = > {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () = > {
    setTimeout(flushCallbacks, 0)}}export function nextTick (cb? :Function, ctx? :Object) {
  let _resolve
  // Wrap the nextTick callback in a try catch for exception catching
  // Place the wrapped function in callback
  callbacks.push(() = > {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')}}else if (_resolve) {
      _resolve(ctx)
    }
  })
  // If pengding is false, timerFunc is executed
  if(! pending) {/ / off locked
    pending = true
    timerFunc()
  }
  if(! cb &&typeof Promise! = ='undefined') {
    return new Promise(resolve= > {
      _resolve = resolve
    })
  }
}

Copy the code

Conclusion:

  1. Using the concept of an asynchronous lock, only one flushCallbacks are in the task queue at any one time. When pengding is false, there is no flushCallbacks in the browser task queue. When pengding is true, the browser task queue is already loaded with flushCallbacks. When the flushCallback function is executed, pengding is set to false again, indicating that the next flushCallbacks can enter the task queue.
  2. Environment capability detection, select the most efficient (macro task/micro task) for packaging execution, ensure that the synchronization code is completed before performing operations such as DOM modification.
  3. FlushCallbacks are copied before flushing. To prevent nextTick from nesting nextTick, the loop does not end.

Implementing the virtual DOM

The advent of the virtual DOM solves browser performance problems. The virtual DOM is a DOM structure object (Vnode) simulated by JS. It is used to update the DOM not immediately after frequent DOM changes. Instead, it compares the old and new VNodes, updates the latest Vnode, and finally maps it into the real DOM once. The reason for this is that manipulating JS objects in memory is much faster than manipulating DOM objects.

Take the 🌰 of the real DOM

<div id="container">
  <p>real dom </p>
  <ul>
    <li class="item">item 1</li>
    <li class="item">item 2</li>
    <li class="item">item 3</li>
  </ul>
</div
Copy the code

Using JS to simulate THE DOM node to achieve virtual DOM

function Element(tagName, props, children) {
  this.tageName = tagName
  this.props = props || {}
  this.children = children || []
  this.key = props.key
  let count = 0
  this.children.forEach(child= > {
    if(child instanceif Element) count += child.count
    count++
  })
  this.count = count
}
const tree = Element('div', { id: container }, [
  Element('p', {},'real dom'])
  Element('ul', {}, [
    Element('li', { class: 'item'},'item1']),
    Element('li', { class: 'item'},'item2']),
    Element('li', { class: 'item'},'item3']]]])Copy the code

The virtual DOM becomes a real node

Element.prototype.render = function() {
  let el = document.createElement(this.tagName)
  let props = this.props
  for(let key in props) {
    el.setAttribute(key, props[key])
  }
  let children = this.children || []
  children.forEach(child= > {
    let child = (child instanceof Element) ? child.render() : document.createTextNode(child)
    el.appendChild(child)
  })
  return el
}
Copy the code

Principles of Diff in Vue

Core source: vue/SRC/core/vdom/patch. Js

Move the patch function entry for comparing the new and old nodes

/** * If oldVnode is a real element, it is the first time to render, create a new node, insert the body, and remove the node. * If oldVnode is not a real element, it is the update phase. Perform patchVnode * /
function patch(oldVnode, vnode) {
  // The new Vnode does not exist, the old Vnode exists, destroy the old node
  if(isUndef(vnode)) {
    if(isDef(oldVnode)) invokeDestroyHook(oldVnode) 
    return 
  }
  
  // The new Vnode exists, but the old Vnode does not
  // <div id="app"><comp></comp></div>
  // The com component is first rendered using the current if logic
  if(isUndef(oldVnode)) { 
    isInitialPatch = true
    createElm(vnode, insertedVnodeQueue)
  } else {
      const isRealElement = isDef(oldVnode.nodeType)
      // New and old nodes are the same, more refined comparison
      if(! isRealElement && sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode) }else {
        // Is the real element, render the root component
        if(isRealElement) { 
          // Mount to real elements and handle server rendering
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if(process.env.NODE_ENV ! = ='production') {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '<p>, or missing <tbody>. Bailing hydration and performing ' +
                'full client-side render.')}}// Create a vNode based on the real node
          oldVnode = emptyNodeAt(oldVnode)
       }
       // Get the real element of the old node
       const oldElm = oldVnode.elm
       // Get the parent element of the old node, body
       const parentElm = nodeOps.parentNode(oldElm)
       
       // Create an entire DOM tree based on the new vNode and insert it under the body element
       creatElm(
         vnode, 
         insertedVnodeQueue, 
         oldElm._leaveCb ? null : parentElm, 
         nodeOps.nextSibling(oldElm)
       )
       
       // Update the parent placeholder node element recursively
       if(isDef(vnode.parent)) {
         ...
       }
       
       // Remove the old node
       if(isDef(parentEle)) {
       
       } else if(isDef(oldVnode.tag)) {
       
       }
    }
  }
}
Copy the code

Moved the source code of patchVnode and deleted some source code.

/** * Update the node * If both the old and new nodes have children, recursively execute updateChildren * If the new node has children and the old node has no children, add the children of the new node * if the old node has children and the new node has no children, delete the children of the old node * update the text node */
function patchVnode(oldVnode, vnode) {
  // If the new and old nodes are the same, return directly
  if(oldVnode === vnode) return 
  
  // Get the child node of the old and new node
  const oldCh = oldVnode.children
  const ch = vnode.children
  
  // The new node is not a text node
  if(isUndef(vnode.text)) {
    // If both the old and new nodes have children, perform recursive updateChildren
    if(isDef(oldCh) && isDef(ch) && oldCh ! == ch) {// oldVnode and vnode children are inconsistent, update children
      updateChildren(oldCh,ch)
    // If the new node has children and the old node has no children, these children of the new node are added
    } else if(isDef(ch)) { 
      if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
    // If the old node has children and the new node has no children, delete those children from the old node
    } else if(isDef(oldCh)) { 
      removeVnodes(oldCh, 0, oldCh.length - 1)
    // Old node text exists, new node text does not exist, clear the text
    } else if(isDef(oldVnode.text)){
      nodeOps.setTextContent(elm, ' ')}// The new and old text nodes are text nodes, and the text changes, the text node is updated
  } else if(oldVnode.text ! == vnode.text) { nodeOps.setTextContent(elm, vnode.text) } }Copy the code

Move updateChildren source code.

function updateChildren(oldCh, ch) {
   // const oldCh = [n1, n2, n3, n4]
   // const ch = [n1, n2, n3, n4, n5]
   // Start index of the old node
   let oldStartIdx = 0 
   // Start index of the new node
   let newStartIdx = 0 
   // The old node ends the index
   let oldEndIdx = oldCh.length - 1 
   // The new node ends the index
   let newEndIdx = newCh.length - 1 
   while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
     const newStartVnode = ch[newStartIdx]
     const oldStartVnode = oldCh[oldStartIdx]
     const newEndVnode = ch[newEndIdx]
     const oldEndVnode = oldCh[oldEndIdx]
     // If a node is moved, it may not exist on the current index, detect this, and adjust the index if the node does not exist
     if(isUndef(oldStartVnode)) {
       oldStartVnode = oldCh[++oldStartIdx]
     } else if(isUndef(oldEndVnode)) {
       oldEndVnode = oldCh[--oldEndIdx]
     // The new start node is the same as the old start node
     } else if(sameVnode(oldStartNode, newStartNode)) { 
       patchVnode(oldStartNode , newStartNode)
       oldStartIdx++
       newStartIdx++
     // The new start node is the same as the old end node
     } else if(sameVnode(oldEndNode, newEndNode)) { 
       patchVnode(oldEndNode, newEndNode)
       oldEndIdx--
       newEndIdx--
     // The old start and the new end are the same node
     } else if(sameVnode(oldStartNode, newEndNode)) { 
       patchVnode(oldStartNode, newEndNode)
       oldStartIdx++
       newEndIdx--
     // The old end and the new beginning are the same node
     } else if(sameVnode(oldEndNode, newStartNode)) { 
       patchVnode(oldEndNode, newStartNode)
       oldEndIdx--
       newStartIdx++
     } else {
       // If none of the above assumptions is true, find the index position in the new start node and the old node by traversing
       
       {key: idx}}
       if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
       // Find the index position of the new start node on the old node
       idxInOld = isDef(newStartVnode.key)
         ? oldKeyToIdx[newStartVnode.key]
         : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
       
       // If no element is found, it is a newly created element
       if (isUndef(idxInOld)) { 
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        } else {
          // Find the new start node in the relational mapping table
          vnodeToMove = oldCh[idxInOld]
          // If the node is the same, run patch
          if (sameVnode(vnodeToMove, newStartVnode)) {
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
            // Set the old node to undefined after the patch is complete
            oldCh[idxInOld] = undefined
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } else {
          // In the last case, the node is found, but the two nodes are not the same node, the new element is considered, and the creation is performed
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
          }
        }
        // Move the new node back one bit
        newStartVnode = newCh[++newStartIdx]
     }
     if(newStartIdx < newEndIdx) {} // The old node is traversed first, and the remaining new nodes are added to the DOM
     if(oldStartIdx < oldEndIdx) {} // The new node is traversed, and the remaining old nodes are deleted}}Copy the code

The role of keys in Vue

Key is the unique token of vnode in Vue, and is used in sameVnode and updateChildren in diff’s algorithm.

SameVnode is used to determine whether the node is the same. A common service scenario is a list. If the key value is a list index, local reuse will occur when the node is added or deleted. So the unique key value ensures that diff is more accurate.

In updateChildren, if none of the four hypotheses matches, a relational mapping table needs to be created based on the keys and indexes of the old node, and the new node’s keys are used to search for indexes in the relational mapping table for update, which ensures a faster DIff algorithm.

What is a Vue dynamic component

Dynamic components are implemented through the IS feature. Suitable for data-based, dynamically rendered scenarios where the component type is uncertain.

Take an example of a news detail page, as shown below.

However, the order of the components in the detail page of each news story may be different, so we need to dynamically render the components from the data rather than writing out the order of each component.

<template>
  <div v-for="val in componentsData" :key="val.id">
    <component :is="val.type">
  </div>
</template>
<script>
import CustomTitle from './CustomTitle'
import CustomText from './CustomText'
import CustomImage from './CustomImage'

export default {
  data() {
    return {
      componentsData: [{
        id: 1.type: 'CustomTitle'}, {id: 2.type: 'CustomText'}, {id: 3
        type: 'CustomImage'}}}}]</script>
Copy the code

Has vue. directive been written? What are the application scenarios?

Vue.directive can register both global and local directives.

The instruction definition function provides the following hook functions

  1. Bind: called when the directive is first bound to an element (only called once)
  2. Inserted: Used when a bound element is inserted into a parent (called if the parent exists)
  3. Update: Called when the template to which the bound element belongs is updated, regardless of whether the binding value changes. By comparing the binding values before and after the update.
  4. ComponentUpdated: Called when the template to which the bound element belongs completes an update cycle.
  5. Unbind: Called only once, when an instruction is unbound from an element.

In the project, such as anti-shake, lazy loading of pictures, one-click copy, permission control can be controlled by command, which greatly simplifies our workload.

I recommend sharing 8 very useful Vue custom commands

Vue filter

Vue filters can be used in two places: double curly brace interpolation and V-bind expressions, a feature deprecated in Vue3.

Can be registered locally in the component

<template>
  <div>{{ message | formatMessage }}</div>
</template>
<script>
export default {
  filters: {
    formatMessage: function(value) {
      // Some processing can be done based on the source value
      return value
    }
  }
}
</script>
Copy the code

Global filters can also be registered

Vue.filter('formatMessage'.function(value) {
  // Some processing can be done based on the source value
  return value
})
Copy the code

Filters can be executed in series from left to right, with the second filter input value being the output value of the first filter.

<div>{{ message | formatMessage1 | formatMessage2 }}</div>
Copy the code

What are the application scenarios of Vue mixins

Definition: Mixins, which provide a very flexible way to distribute reusable functionality in Vue components.

Mixin includes global mixin and local mixin. Global mixin is not recommended, which affects the subsequent creation of each Vue instance. Local mixing can extract similar code between components for logical reuse.

If all pages have popovers, the data and logic of popovers can be encapsulated through mixins.

// mixin.vue
<template>
  <div></div>
</template>
<script>
export default {
  data() {
    return {
      visible: false}},methods: {
    toggleShow() {
      this.visible = !this.visible
    }
  }
}
</script>// Components to be imported<template>
  <div></div>
</template>
<script>
import MixinItem from './mixin.vue'
export default {
  mixins: [MixinItem],
}
</script>
Copy the code

Let me introduce you to Keep-alive

Keep-alive is a component built into Vue that caches the state of the component to avoid repeated rendering and improve performance.

The keep-Alive built-in component has three properties

  1. Include: string or regular expression. Components whose names match are cached.
  2. Exclude: a string or regular expression. Components whose names match will not be cached.
  3. Max: indicates the threshold of the number of cache components

Setting the keep-alive component adds two health hooks (activated/deactivated).

Enter components for the first time: BeforeCreate -> created -> beforeMount -> Mounted -> activated Triggers deactivated from the component because the component cache is not destroyed. BeforeDestroy and Destroyed life hooks are not triggered. Re-enter the component directly from the Activated Life hook.

Common service scenario: Enter the details page on page 2 of the list page, and return to the details page. The details page remains on page 2 without re-rendering. But to get to the list page from another page, you still need to re-render.

Vuex uses an array to store the name of a list page. The beforeRouteLeave hook is used to determine whether the list page needs to be cached.

Use the following position in the router-view tag

<template>
  <keep-alive :include="cacheRouting">
    <router-view></router-view>
  </keep-alive>
</template>
<script>
export default {
  computed: {
    cacheRouting() {
      return this.$store.state.cacheRouting
    }
  }
}
</script>
Copy the code

The list page is used below

<template>
  <div></div>
</template>
<script>
export default {
  beforeRouteLeave(to, from, next) {
    if(to.name === 'Details Page') {
      / /... Adds a list page to the global cache route array
      next()
    } else {
      / /... Delete the list page from the global cache route array
      next()
    }
  }
}
</script>
Copy the code

Keep – the realization of the alive

Core source: vue/SRC/core/components/keep – alive. Js

LRU replacement strategy: Find the most unused data in memory and replace it with new data.

/** * Traverses the cache to remove unwanted caches from the cache */
function pruneCache (keepAliveInstance, filter) {
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    const cachedNode = cache[key]
    if (cachedNode) {
      const name = getComponentName(cachedNode.componentOptions)
      if(name && ! filter(name)) { pruneCacheEntry(cache, key, keys, _vnode) } } } }/** * Delete virtual DOM */ from cache
function pruneCacheEntry (cache, key, keys, current) {
  const entry = cache[key]
  if(entry && (! current || entry.tag ! == current.tag)) {// Executes the destroy hook for the component
    entry.componentInstance.$destroy()
  }
  // Set the virtual DOM corresponding to components in the cache to null
  cache[key] = null
  // Delete the key that caches the virtual DOM
  remove(keys, key)
}

export default {
  name: 'keep-alive'.abstract: true.props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String.Number]
  },

  created () {
    // Cache the virtual DOM
    this.cache = Object.create(null) 
    // Cache the key set of the virtual DOM
    this.keys = [] 
  },

  destroyed () {
    // Delete all cached contents
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {
    // Listen for include and exclude parameters, and call pruneCache to modify cached data in the cache
    this.$watch('include'.val= > {
      pruneCache(this.name= > matches(val, name))
    })
    this.$watch('exclude'.val= > {
      pruneCache(this.name= >! matches(val, name)) }) },// The render function determines the render result
  render () {
    const slot = this.$slots.default
    // Get the first child virtual DOM
    const vnode: VNode = getFirstComponentChild(slot)
    // Get the configuration parameters of the virtual DOM
    const componentOptions: ? VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      // Get the component name
      constname: ? string = getComponentName(componentOptions)const { include, exclude } = this
      // If it is not included in include or exclude, exit the cache mechanism
      if( (include && (! name || ! matches(include, name))) || (exclude && name && matches(exclude, name)) ) {return vnode
      }

      const { cache, keys } = this
      // Get the component key
      constkey: ? string = vnode.key ==null
        ? componentOptions.Ctor.cid + (componentOptions.tag ? ` : :${componentOptions.tag}` : ' ')
        : vnode.key
      // Hit cache
      if (cache[key]) {
        // Get the cached instance Settings from the cache onto the current component
        vnode.componentInstance = cache[key].componentInstance
        // Delete the existing key and place it at the end
        remove(keys, key)
        keys.push(key)
      // Failed to hit the cache
      } else {
        // Cache the current virtual node
        cache[key] = vnode
        // Add the current component key
        keys.push(key)
        // If the cache component exceeds the Max value, LRU replaces
        if(this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }
      // Set the current component keep-alive to true
      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])}}Copy the code

Vue-router Configures the 404 page

* indicates a wildcard. If you place it before any route, it will be matched first, which leads to an error page. Therefore, you need to place it last.

{
  path: The '*'.name: '404'
  component: () = > import('./404.vue')}Copy the code

What kind of navigation guard does vue-router have

Global front guard

Triggered before a route jump, you can do some logon authentication logic before executing the next method.

const router = new VueRouter({})

router.beforeEach((to, from, next) = >{...// The next method must be executed to trigger a route jump
  next() 
})
Copy the code

Global parsing guard

Similar to beforeEach, this is triggered before a route jump, except that it is called after the guard and asynchronous route components have been resolved within all components, i.e., after the beforeRouteEnter within the component.

const router = new VueRouter({})

router.beforeResolve((to, from, next) = >{...// The next method must be executed to trigger a route jump
  next() 
})
Copy the code

Global post-hook

Unlike the guards, these hooks do not accept the next function or change the navigation itself.

router.afterEach((to, from) = > {
  // ...
})
Copy the code
  1. Route exclusive guard

You can define beforeEnter directly on the route configuration

const router = new VueRouter({
  routes: [{path: '/home'.component: Home,
      beforeEnter: (to, from, next) = >{}}]})Copy the code

Guards within components

The following route navigation guards can be defined directly within the component

const Foo = {
  template: `... `.beforeRouteEnter(to, from, next) {
    // Cannot get component instance this
    // When the guard executes, the component instance has not been created
  },
  beforeRouteUpdate(to, from, next) {
    // Called when the current route changes but the component is being reused
    // Access instance this
  },
  beforeRouteLeave(to, from, next) {
    // is called when navigation leaves the component}}Copy the code

What is the complete navigation parsing process of vue-Router

  1. Navigation triggered
  2. Call the beforeRouteLeave guard in the deactivated component
  3. Call the global beforeEach front guard
  4. The reused component invokes the beforeRouteUpdate guard
  5. Route configuration calls beforeEnter
  6. Parse the asynchronous routing component
  7. Call the beforeRouteEnter guard in the activated component
  8. Call the global beforeResolve
  9. Navigation confirmed
  10. Call the global afterEach
  11. Triggering DOM updates
  12. Call the callback passed to Next in the beforeRouteEnter guard, and the created component instance is passed in as an argument to the callback.

How many modes does vue-router have? What’s the difference?

Vue-router has three routing modes: hash, history, and Abstract.

Hash pattern

By default, the Vue-Router works in hash mode. Based on the onHashchange event of the browser, when the address changes, it obtains the hash value of the address using window.location.hash. Matches the component content of the Routes object based on the hash value.

The characteristics of

  1. The hash value is stored in the URL and carried#Changes to the hash value do not reload the page.
  2. The hash change is triggeredonhashchangeEvent, which can be logged by the browser to enable the browser to move forward or backward.
  3. Hash parameters are urL-based and have volume limitations when passing complex parameters.
  4. Good compatibility, support browsers of earlier versions and Internet Explorer.

Realize the principle of

<div class="main">
  <a href="#/home">The home page</a>
  <a href="#/detail">The detail page</a>
  <div id="content"></div>
</div>
<script>
const routers = [{
  path: '/home'.component: '
       
I'm Home page
'
}, { path: '/detail'.component: '
I'm the Detail page
'
}] function Router(routers) { this.routers = {} // Initial routers are generated routers.forEach((router) = > { this.routers[router] = () = > { document.getElementById("content").innerHTML = item.compontent; }})this.updateView = function(e) { let hash = window.location.hash.slice(1) | |'/' this.routers[hash] && this.routers[hash]() } // Route loading triggers view updates window.addEventListener('load'.this.updateView.bind(this)) // Route changes trigger view updates window.addEventListener('hashchange'.this.updateView.bind(this))}
</script> Copy the code

The history mode

New HTML5-based implementations of pushState and replaceState manipulate browser history without refreshing it. The former is a new history, the latter is a direct replacement history.

The characteristics of

  1. Urls do not carry#PushState and replaceState are used to redirect urls without reloading the page.
  2. A URL change triggers an HTTP request. So add a candidate resource on the server that covers all cases: if the URL does not match any static resource, it should return the sameindex.html. This is the page your app depends on.
// nginxServer configuration location / {try_files $uri $uri/ /index.html;
}
Copy the code
  1. Compatibility ie 10 +

Realize the principle of

<div class="main">
  <a href="javascript:;" path="/home">The home page</a>
  <a href="javascript:;" path="/detail">The detail page</a>
  <div id="content"></div>
</div>
<script>
const routers = [{
  path: '/home'.component: '
       
I'm Home page
'
}, { path: '/detail'.component: '
I'm the Detail page
'
}] function Router(routers) { this.routers = {} // Initial routers are generated routers.forEach((router) = > { this.routers[router] = () = > { document.getElementById("content").innerHTML = item.compontent; }})const links = [...document.getElementsByTagName('a')] links.forEach(link= > { link.addEventListener('click'.() = > { window.history.pushState({}, null, link.getAttribute('path')) this.updateView() }) }) this.updateView = function() { let url = window.location.pathname || '/' this.routers[url] && this.routers[url]() } // Route loading triggers view updates window.addEventListener('load'.this.updateView.bind(this)) // Route changes trigger view updates window.addEventListener('popstate'.this.updateView.bind(this))}
</script> Copy the code

The abstract model

All JS modes are supported. Vue-router verifies the environment itself. If no browser API is found, the route is automatically forced to enter abstract mode. Abstract mode is also used in the mobile native environment.

Vue Route parameter transmission mode

There are three ways to transmit parameters in a Vue route

  1. Plan a
// Route configuration
{
  path: '/detail/:id'.name: 'Detail'.component: () = > import('./Detail.vue')}// Route jump
let id = 1
this.$router.push({ path: '/detail/${id}'})
// Get parameters
this.$route.params.id
Copy the code
  1. Scheme 2
// Route configuration
{
  path: '/detail'.name: 'Detail'.component: () = > import('./Detail.vue')}// Route jump
let id = 1
this.$router.push({ name: 'Detail'.params: { id: id } })
// Get parameters
this.$route.params.id
Copy the code
  1. Plan 3
// Route configuration
{
  path: '/detail'.name: 'Detail'.component: () = > import('./Detail.vue')}// Route jump
let id = 1
this.$router.push({ name: 'Detail'.query: { id: id } })
// Get parameters
this.$route.query.id
Copy the code

Understanding and using Vuex

Vuex is a state management mode developed specifically for vuue.js applications. It uses centralized storage to manage the state of all components of the application.

It mainly solves the following two problems

  1. Multiple views depend on the same state.
  2. Actions from different views need to change the same state.

It contains the following modules, move the official website map

State: Defines and initializes the global State. Getter: depends on the State in the State, for secondary wrapping, does not affect the State source data. Mutation: A function that changes the State, which must be synchronous. Action: for submission Mutation, which can contain any asynchronous operation. Module: If the application is complex, the Store will be bloated with a large object. Module allows us to manage the Store modularized.

Of course, if the application is simple and there are few shared states, vue. observe can be used instead of Vuex. It is also good to install a library.

How to deal with data loss after Vuex refresh

Persistent cache: localStorage, sessionStorage

How does Vuex know whether State is modified by Mutation or externally?

The only way to change state in Vuex is to execute the commit method (this._withcommit (fn)) and set the _case identifier to true (true) to change the state (false). The identification bit cannot be set for external modification, so watch monitors state changes to judge the validity of the modification.

Vue SSR

Vue SSR project has not been used for the time being, the follow-up will write a separate Demo written. Move the other answers over here.

SSR server rendering, the HTML rendering work on the server is completed, the HTML back to the browser side.

Pros: SSR has better SEO, faster first screen loading. Disadvantages: Heavy server load.

If it is an internal system, SSR is not necessary. For external projects, maintaining a highly available Node server can be difficult.

What is the difference between Vue2 and Vue3? What are the optimizations for Vue3?

Self-produced and self-sold: what are the “differences” of Vue3 compared with Vue2?

Vue performance optimization

  1. Freeze non-responsive data by using Object.freeze
  2. The nesting level should not be too deep
  3. Computed and watch are used differently
  4. V-if and v-show are used differently
  5. Do not use v-for together with V-if, and the binding key value must be unique
  6. Too much list data uses paging or virtual list
  7. Clear timers and events after component destruction
  8. Lazy loading of images
  9. Route lazy loading
  10. Anti-shake and throttling
  11. Bring in external libraries as needed
  12. Keep-alive cache is used
  13. Server-side rendering and pre-rendering

conclusion

Ten thousand words long summary, if feel helpful, along with the praise, attention, collection. 😘

reference

Front-end knowledge 3+1 front-end interview questions recommended VUE source code very clear troll: Li Yongning