1. What is your understanding of bidirectional binding in Vue?

I. What is two-way binding?

Let’s start with unidirectional binding. Unidirectional binding is very simple, we bind the Model to the View, and when we update the Model with JavaScript code, the View will be updated automatically. Bidirectional binding is very easy to imagine, based on unidirectional binding, the user updates the View, the Model data is automatically updated, So this is two-way binding for example

When the user fills in the form, the state of the View is updated. If the state of the Model can be automatically updated at this time, it is equivalent to making a bidirectional binding between the Model and the View

Two, what is the principle of two-way binding

As we all know, Vue is a framework for bidirectional data binding. Bidirectional binding consists of three important parts

  • Data layer (Model) : the data and business logic of the application
  • View: The presentation of the application, various UI components
  • Business Logic Layer (ViewModel) : The core of the framework’s encapsulation, which is responsible for associating data with views

The layered architectural solution above can be called a technical term: MVVM, where the core function of the control layer is “data bidirectional binding.” Naturally, we only need to understand what it is to understand how data binding works

Understand the ViewModel

Its main responsibilities are:

  • Update the view when data changes
  • Update the data when the view changes

Of course, it has two main components

  • Observer: Listens for all data attributes
  • Parser (Compiler) : Scans and parses instructions for each element node, replacing data according to the instruction template, and binding the corresponding update function

Three, the realization of two-way binding

Let’s take Vue as an example. What is the two-way binding process in Vue

  1. new Vue()First of all, do the initialization. YeahdataReactive processing is performed, and this process occursObserveIn the
  2. At the same time, the template is compiled to find the dynamically bound data fromdataGets and initializes the view inCompileIn the
  3. Define both an update function andWatcher, when the corresponding data changes in the futureWatcherThe update function is called
  4. Due to thedataOne of thekeyIt can occur multiple times in a view, so eachkeyThey all need a butlerDepTo manage multipleWatcher
  5. Once the data in the future data changes, it will first find the correspondingDep, informing allWatcherExecute the update function

The flowchart is as follows:

implementation

We start with a constructor that performs initialization and responds to data

class Vue { constructor(options) { this.$options = options; this.$data = options.data; Observe (this.$data); // Observe (this.$data); // Proxy (this); New Compile(options.el, this); }}Copy the code

Perform responsive actions on the DATA option

function observe(obj) {  
  if (typeof obj !== "object" || obj == null) {  
    return;  
  }  
  new Observer(obj);  
}  
  
class Observer {  
  constructor(value) {  
    this.value = value;  
    this.walk(value);  
  }  
  walk(obj) {  
    Object.keys(obj).forEach((key) => {  
      defineReactive(obj, key, obj[key]);  
    });  
  }  
}  
Copy the code

compileCompile

The instructions for each element node are scanned and parsed, the data is replaced according to the instruction template, and the corresponding update function is bound

class Compile { constructor(el, vm) { this.$vm = vm; this.$el = document.querySelector(el); If (this.$el) {this.compile(this.$el); } } compile(el) { const childNodes = el.childNodes; Array.from(childNodes).foreach ((node) => {// Iterate over the child element if (this.isElement(node)) {// determine whether the node is console.log(" compile the element "+ node.nodeName); } else if (this.isinterpolation (node)) {console.log(" compile interpolate text "+ node.textContent); {{}}} if (node.childnodes && Node.childnodes. Length > 0) {this.compile(node); // Iterate through the child elements recursively}}); } isElement(node) { return node.nodeType == 1; } isInterpolation(node) { return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent); }}Copy the code

Depend on the collection

The view uses a key in the data, which is called a dependency. The same key may appear multiple times, and each time it needs to be collected and maintained by a Watcher. This process is called dependency collection. Multiple Watchers need a Dep to manage them, and the Dep notifies them uniformly when they need to be updated

Implementation approach

  1. defineReactiveIs each of themkeyTo create aDepThe instance
  2. When initializing a viewkey, e.g.name1, create awatcher1
  3. Because the triggername1thegetterMethod, then willwatcher1Added to thename1In the corresponding Dep
  4. whenname1Update,setterWhen triggered, it can pass the correspondingDepInform its management of allWatcherupdate
// Responsible for updating the view
class Watcher {  
  constructor(vm, key, updater) {  
    this.vm = vm  
    this.key = key  
    this.updaterFn = updater  
  
    // When creating an instance, specify the current instance to the dep. target static property
    Dep.target = this  
    // Read the key and trigger get
    vm[key]  
    / / empty
    Dep.target = null  
  }  
  
  // The future dom update function is called by dep
  update() {  
    this.updaterFn.call(this.vm, this.vm[this.key])  
  }  
}  
Copy the code

The statement Dep

class Dep {  
  constructor() {  
    this.deps = [];  // Dependency management
  }  
  addDep(dep) {  
    this.deps.push(dep);  
  }  
  notify() {   
    this.deps.forEach((dep) = >dep.update()); }}Copy the code

The getter is fired when the Watcher is created

class Watcher {  
  constructor(vm, key, updateFn) {  
    Dep.target = this;  
    this.vm[this.key];  
    Dep.target = null; }}Copy the code

Dependency collection, creating Dep instances

function defineReactive(obj, key, val) {  
  this.observe(val);  
  const dep = new Dep();  
  Object.defineProperty(obj, key, {  
    get() {  
      Dep.target && dep.addDep(Dep.target);// dep. target is the Watcher instance
      return val;  
    },  
    set(newVal) {  
      if (newVal === val) return;  
      dep.notify(); // Notify deP to execute the update method}}); }Copy the code

What’s the difference between v-show and V-if in Vue?

V-show and V-IF

We all know that v-show and V-if have the same effect in vUE (without v-else). They both control whether or not an element is displayed on the page

The same is true in usage

<Model v-show="isShow" />
<Model v-if="isShow" />
Copy the code
  • When the expression istrueTakes up space on the page
  • When both expressions are zerofalse, will not occupy the page position

The difference between v-show and V-if

  • Different control methods
  • Different compilation processes
  • Different compilation conditions

Control: v-show hides by adding CSS to the element –display: None, the DOM element is still there. V-if show hide is to add or remove an entire DOM element

Compile process: V-if switch has a local compile/unload process, during which internal event listeners and subcomponents are destroyed and rebuilt appropriately; V-show is simply a CSS based switch

Compile conditions: V-if is a true condition rendering, which ensures that event listeners and child components within the condition block are properly destroyed and rebuilt during the switch. Only if the render condition is false, no action is taken until it is true

  • V-show changing from false to true does not trigger the component life cycle

  • When v-if changes from False to true, the beforeCreate, create, beforeMount, and Mounted hooks of the component are triggered. When v-if changes from true to False, the beforeCreate, create, beforeMount, and Mounted hooks of the component are triggered. When V-if changes from true to False, the beforeDestory and deStoryed methods of the component are triggered

Performance cost: V-IF has higher switching cost; V-show has higher initial render consumption;

Three, V-SHOW and V-IF principle analysis

The specific analysis process is not expanded here, the general process is as follows

  • The templatetemplatetoastThe structure of theJSobject
  • withastTo get theJSObjects are assembledrenderandstaticRenderFnsfunction
  • renderandstaticRenderFnsFunction is called after generating virtualVNODENode that contains the creationDOMNode Information
  • vm.patchFunction via virtualDOMAlgorithm usingVNODENode creation realityDOMnode

V – show principle

Elements will always be rendered regardless of the initial conditions, okay

Let’s see how this is implemented in VUE. Okay

The code is easy to understand. If we have transition, we execute transition, and if we don’t, we just set the display property

// https://github.com/vuejs/vue-next/blob/3cd30c5245da0733f9eb6f29d220f39c46518162/packages/runtime-dom/src/directives/vSho w.ts
export const vShow: ObjectDirective<VShowElement> = {
  beforeMount(el, { value }, { transition }) {
    el._vod = el.style.display === 'none' ? ' ' : el.style.display
    if (transition && value) {
      transition.beforeEnter(el)
    } else {
      setDisplay(el, value)
    }
  },
  mounted(el, { value }, { transition }) {
    if (transition && value) {
      transition.enter(el)
    }
  },
  updated(el, { value, oldValue }, { transition }) {
    // ...
  },
  beforeUnmount(el, { value }) {
    setDisplay(el, value)
  }
}
Copy the code

V – if principle

V-if is much more complicated to implement than V-show, because there are other conditions to deal with, such as else else-if, here we also extract a small part of the source code dealing with V-IF

The render function uses the value of the expression to determine whether to generate the DOM

// https://github.com/vuejs/vue-next/blob/cdc9f336fd/packages/compiler-core/src/transforms/vIf.ts
export const transformIf = createStructuralDirectiveTransform(
  /^(if|else|else-if)$/.(node, dir, context) = > {
    return processIf(node, dir, context, (ifNode, branch, isRoot) = > {
      // ...
      return () = > {
        if (isRoot) {
          ifNode.codegenNode = createCodegenNodeForBranch(
            branch,
            key,
            context
          ) as IfConditionalExpression
        } else {
          // attach this branch's codegen node to the v-if root.
          const parentCondition = getParentCondition(ifNode.codegenNode!)
          parentCondition.alternate = createCodegenNodeForBranch(
            branch,
            key + ifNode.branches.length - 1,
            context
          )
        }
      }
    })
  }
)
Copy the code

Iv. Application scenarios of V-show and V-IF

Both V-IF and V-show control how dom elements are displayed on the page

V-if is more expensive than V-show (direct operation of DOM node addition and removal)

If you need to switch very frequently, use v-show

3. Why is v-if and V-for in Vue not recommended?

A,

The V-if directive is used to render a piece of content conditionally. This content will only be rendered if the directive’s expression returns true

The V-for directive renders a list based on an array. The V-for directive requires special syntax in the form item in items, where items is an array or object of source data, and item is an alias for the array element being iterated over

In the case of V-for, it is recommended to set key values and ensure that each key value is unique, which facilitates diff algorithm optimization

Both in usage

<Modal v-if="isShow" />

<li v-for="item in items" :key="item.id">
    {{ item.label }}
</li>
Copy the code

Second, priority

V-if and V-for are directives in the VUE templating system

When the vue template is compiled, it converts the instruction system into an executable render function

The sample

Write a p tag that uses both v-if and V-for

<div id="app">
    <p v-if="isShow" v-for="item in items">
        {{ item.title }}
    </p>
</div>
Copy the code

Create a vue instance to store the isShow and items data

const app = new Vue({
  el: "#app".data() {
    return {
      items: [{title: "foo" },
        { title: "baz"}}},computed: {
    isShow() {
      return this.items && this.items.length > 0}}})Copy the code

The code for the template directive is generated in the render function. App.$options

ƒ anonymous() {
  with (this) { return 
    _c('div', { attrs: { "id": "app" } }, 
    _l((items), function (item) 
    { return (isShow) ? _c('p', [_v("\n" + _s(item.title) + "\n")]) : _e() }), 0)}}Copy the code

_l is vue’s list rendering function, which makes an if judgment internally

Preliminary conclusion: The priority of V-for is higher than that of V-IF

Put v-for and V-IF on different labels

<div id="app">
    <template v-if="isShow">
        <p v-for="item in items">{{item.title}}</p>
    </template>
</div>
Copy the code

Output the render function

ƒ anonymous() {
  with(this){return 
    _c('div', {attrs: {"id":"app"}},
    [(isShow)?[_v("\n"),
    _l((items),function(item){return _c('p',[_v(_s(item.title))])})]:_e()],2)}}Copy the code

In this case, we can see that v-for and V-if are used for different tags, which are judged before rendering the list

We look at the next vUE source code

Source location: \vue-dev\ SRC \compiler\codegen\index.js

export function genElement (el: ASTElement, state: CodegenState) :string {
  if (el.parent) {
    el.pre = el.pre || el.parent.pre
  }
  if(el.staticRoot && ! el.staticProcessed) {return genStatic(el, state)
  } else if(el.once && ! el.onceProcessed) {return genOnce(el, state)
  } else if(el.for && ! el.forProcessed) {return genFor(el, state)
  } else if(el.if && ! el.ifProcessed) {return genIf(el, state)
  } else if (el.tag === 'template'&&! el.slotTarget && ! state.pre) {return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    return genSlot(el, state)
  } else {
    // component or element. }Copy the code

When making an if judgment, v-for precedes V-if

Final conclusion: V-FOR has a higher priority than V-IF

3. Matters needing attention

  1. Never setv-ifv-forUse it on the same element at the same time, resulting in performance wastage (the condition is iterated before each render)
  2. If this is avoided, it is nested in the outer layertemplatePage rendering is not generateddomNode), the V-if judgment is performed at this level, and then the V-for loop is performed internally
<template v-if="isShow">
    <p v-for="item in items">
</template>
Copy the code
  1. If the condition occurs inside the loop, the attribute can be computedcomputedFilter out items that do not need to be displayed in advance
computed: {
    items: function() {
      return this.list.filter(function (item) {
        return item.isShow
      })
    }
}
Copy the code

4. Why is the data attribute in Vue a function and not an object?

The difference between instance and component definition data

A vue instance defines the data attribute as either an object or a function

const app = new Vue({
    el:"#app".// Object format
    data: {foo:"foo"
    },
    // Function format
    data(){
        return {
             foo:"foo"}}})Copy the code

Component defines the data attribute, which can only be a function

If data is directly defined as an object for the component

Vue.component('component1', {template:Components ` < div > < / div > `.data: {foo:"foo"}})Copy the code

Gets a warning message

Warning: The data returned should be a function on each component instance

The data component defines the difference between functions and objects

The data component must be a function. Have you ever wondered why?

When we define a component, vue will eventually form an instance of the component via vue.extend ()

Here we define the data attribute in the form of an object, mimicking the component constructor

function Component(){
 
}
Component.prototype.data = {
	count : 0
}
Copy the code

Create two component instances

const componentA = new Component()
const componentB = new Component()
Copy the code

Example Change the data attribute value of componentA. The value in componentB is changed as well

console.log(componentB.data.count)  / / 0
componentA.data.count = 1
console.log(componentB.data.count)  / / 1
Copy the code

This is the reason that the two share the same memory address, componentA modified content, also has an impact on componentB

If we take the form of a function, this does not happen (the object returned by the function is not in the same memory address)

function Component(){
	this.data = this.data()
}
Component.prototype.data = function (){
    return {
   		count : 0}}Copy the code

Example Change the data attribute value of componentA. The values in componentB are not affected

console.log(componentB.data.count)  / / 0
componentA.data.count = 1
console.log(componentB.data.count)  / / 0
Copy the code

The vue component may have many instances and uses a function to return a new data form so that the data of each instance object is not contaminated by the data of the other instance objects

Three, principle analysis

First, look at the code vue uses to initialize data, which can be defined as a function or an object

Source location: vue/dev/SRC/core/instance/state. Js

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
    ...
}
Copy the code

Data can be either object or function, so why the warning above?

Don’t worry, read on

When a component is created, options are merged

Source location: vue/dev/SRC/core/util/options. Js

Custom components go to mergeOptions for option merging

Vue.prototype._init = function (options? :Object) {...// merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    ...
  }
Copy the code

Defining data will perform data validation

Source location: vue/dev/SRC/core/instance/init. Js

In this case, the VM instance is undefined, and the if judgment is entered. If the data type is not function, a warning message is displayed

strats.data = function (parentVal: any, childVal: any, vm? : Component): ?Function {
  if(! vm) {if (childVal && typeofchildVal ! = ="function") { process.env.NODE_ENV ! = ="production" &&
        warn(
          'The "data" option should be a function ' +
            "that returns a per-instance value in component " +
            "definitions.",
          vm
        );

      return parentVal;
    }
    return mergeDataOrFn(parentVal, childVal);
  }
  return mergeDataOrFn(parentVal, childVal, vm);
};
Copy the code

Four, conclusion

  • Root instance objectdataIt can be an object or a function (the root instance is a singleton) without data contamination
  • Component instance objectdataMust be a function, to prevent multiple component instance objects from sharing onedata, resulting in data pollution. In the form of a function,initDataWill return brand new as a factory functiondataobject

5. Talk about the principle of key in VUE

What is the Key

Before we begin, let’s revert to two actual work scenarios

  1. When we are usingv-for, you need to add to the unitkey
<ul>
    <li v-for="item in items" :key="item.id">.</li>
</ul>
Copy the code
  1. with+new Date()Generated timestamp askeyTo manually force a re-render
<Comp :key="+new Date()" />
Copy the code

So what’s the logic behind this? What does key do?

In a word

Key is a unique ID given to each Vnode. It is also an optimization strategy of DIFF. According to key, the corresponding Vnode can be found more accurately and quickly

The logic behind the scene

When we use v-for, we need to add a key to the unit

  • Instead of using a key, Vue uses the in-place principle of minimizing the movement of an element and tries to patch or reuse the same type of element in the right place to the best of its ability.

  • If keys are used, Vue records the elements in the order of keys. Elements that once had keys will be removed or destoryed if they are no longer present

Manually force a re-render with the timestamp generated by +new Date() as the key

  • When the rerender with the new value is the key and the Comp with the new key appears, the old key Comp is removed and the new key Comp triggers rendering

2. The difference between setting key and not setting key

Here’s an example:

Create an instance and insert data into the Items array after 2 seconds

<body>
  <div id="demo">
    <p v-for="item in items" :key="item">{{item}}</p>
  </div>
  <script src=".. /.. /dist/vue.js"></script>
  <script>
    // Create an instance
    const app = new Vue({
      el: '#demo'.data: { items: ['a'.'b'.'c'.'d'.'e'] },
      mounted () {
        setTimeout(() = > { 
          this.items.splice(2.0.'f')  // 
       }, 2000); }});</script>
</body>
Copy the code

Without a key, vue will do something like this:

Analyze the overall process:

  • Compare nodes A, A, of the same type, proceedpatch, but the data is the same, does not happendomoperation
  • Compare nodes B, B, of the same type, and proceedpatch, but the data is the same, does not happendomoperation
  • Compare C, F, nodes of the same type, proceedpatch, the data is different, happensdomoperation
  • Compare D, C, and nodes of the same typepatch, the data is different, happensdomoperation
  • Compare E, D, nodes of the same type, proceedpatch, the data is different, happensdomoperation
  • The loop ends and E is inserted intoDOMIn the

There were three updates and one insert

In the case of key: Vue will do something like this:

  • Compare nodes A, A, of the same type, proceedpatch, but the data is the same, does not happendomoperation
  • Compare nodes B, B, of the same type, and proceedpatch, but the data is the same, does not happendomoperation
  • Compare nodes C, F, of different types
    • Compare E, E, and nodes of the same typepatch, but the data is the same, does not happendomoperation
  • Compare D, D, and nodes of the same typepatch, but the data is the same, does not happendomoperation
  • Compare C, C, and nodes of the same typepatch, but the data is the same, does not happendomoperation
  • The loop ends, inserting F before C

There were 0 updates and 1 insert

Through the above two small examples, you can see that setting the key can greatly reduce the PAGE DOM operation, improve the efficiency of diff

Does setting a key necessarily improve diff efficiency?

That’s not true, and the document makes it clear

When vue. js is updating a list of rendered elements with v-for, it defaults to a “reuse in place” policy. If the order of the data items is changed, Vue will not move the DOM elements to match the order of the data items, but will simply reuse each element here and make sure that it shows each element that has been rendered under a specific index

This default mode is efficient, but only applies to list rendering output that does not depend on the child component state or temporary DOM state (for example, form input values)

It is recommended that you provide a key whenever possible when using V-for, unless it is simple to iterate through the output DOM content, or you are deliberately relying on the default behavior for performance gains

Three, principle analysis

Source location: core/vdom/patch.js

The first thing we’re going to do to see if it’s the same key, is we’re going to see if the keys are equal if we don’t set key, then key is undefined, and undefined is the identity of undefined

function sameVnode (a, b) {
    return (
        a.key === b.key && (
            (
                a.tag === b.tag &&
                a.isComment === b.isComment &&
                isDef(a.data) === isDef(b.data) &&
                sameInputType(a, b)
            ) || (
                isTrue(a.isAsyncPlaceholder) &&
                a.asyncFactory === b.asyncFactory &&
                isUndef(b.asyncFactory.error)
            )
        )
    )
}
Copy the code

The new and old VNodes are diff in the updateChildren method, and the results are used to update the real DOM

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {...while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        if (isUndef(oldStartVnode)) {
            ...
        } else if (isUndef(oldEndVnode)) {
            ...
        } else if (sameVnode(oldStartVnode, newStartVnode)) {
            ...
        } else if (sameVnode(oldEndVnode, newEndVnode)) {
            ...
        } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right. }else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left. }else {
            if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
            idxInOld = isDef(newStartVnode.key)
                ? oldKeyToIdx[newStartVnode.key]
                : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
            if (isUndef(idxInOld)) { // New element
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
            } else {
                vnodeToMove = oldCh[idxInOld]
                if (sameVnode(vnodeToMove, newStartVnode)) {
                    patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
                    oldCh[idxInOld] = undefined
                    canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
                } else {
                    // same key but different element. treat as new element
                    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
                }
            }
            newStartVnode = newCh[++newStartIdx]
        }
    }
    ...
}
Copy the code

This article is from: github.com/febobo/web-… If there is infringement, contact delete

The last

There are more than 500 questions in front of the interview, covering HTML, CSS, JS, React, Vue, Node, etc. TypeScript, front-end security, algorithms, performance optimization, Design patterns, engineering, computer fundamentals, and more, with online answer tests. Finally, I wish the partner who will be interviewed to be the offer harvester ~