关于前端:阿里前端常考vue面试题汇总

1次阅读

共计 29511 个字符,预计需要花费 74 分钟才能阅读完成。

Vuex 中 actions 和 mutations 有什么区别

题目剖析

  • mutationsactionsvuex带来的两个独特的概念。老手程序员容易混同,所以面试官喜爱问。
  • 咱们只需记住批改状态只能是 mutationsactions 只能通过提交 mutation 批改状态即可

答复范例

  1. 更改 Vuexstore 中的状态的惟一办法是提交 mutationmutation 十分相似于事件:每个 mutation 都有一个字符串的类型 (type)和一个 回调函数 (handler)。Action 相似于 mutation,不同在于:Action能够蕴含任意异步操作,但它不能批改状态,须要提交 mutation 能力变更状态
  2. 开发时,蕴含异步操作或者简单业务组合时应用 action;须要间接批改状态则提交mutation。但因为dispatchcommit是两个 API,容易引起混同,实际中也会采纳对立应用dispatch action 的形式。调用 dispatchcommit两个 API 时简直齐全一样,然而定义两者时却不甚雷同,mutation的回调函数接管参数是 state 对象。action则是与 Store 实例具备雷同办法和属性的上下文 context 对象,因而个别会解构它为 {commit, dispatch, state},从而不便编码。另外dispatch 会返回 Promise 实例便于解决外部异步后果
  3. 实现上 commit(type) 办法相当于调用 options.mutations[type](state)dispatch(type) 办法相当于调用options.actions[type](store),这样就很容易了解两者应用上的不同了

实现

咱们能够像上面这样简略实现 commitdispatch,从而分别两者不同

class Store {constructor(options) {this.state = reactive(options.state)
        this.options = options
    }
    commit(type, payload) {
        // 传入上下文和参数 1 都是 state 对象
        this.options.mutations[type].call(this.state, this.state, payload)
    }
    dispatch(type, payload) {
        // 传入上下文和参数 1 都是 store 自身
        this.options.actions[type].call(this, this, payload)
    }
}

如何在组件中批量应用 Vuex 的 getter 属性

应用 mapGetters 辅助函数, 利用对象开展运算符将 getter 混入 computed 对象中

import {mapGetters} from 'vuex'
export default{
    computed:{...mapGetters(['total','discountTotal'])
    }
}

Vue 组件之间通信形式有哪些

Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信 父子组件通信 隔代组件通信 兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信

组件传参的各种形式

组件通信罕用形式有以下几种

  • props / $emit 实用 父子组件通信

    • 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的
  • ref$parent / $children(vue3 废除) 实用 父子组件通信

    • ref:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例
    • $parent / $children:拜访拜访父组件的属性或办法 / 拜访子组件的属性或办法
  • EventBus($emit / $on) 实用于 父子、隔代、兄弟组件通信

    • 这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件
  • $attrs / $listeners(vue3 废除) 实用于 隔代组件通信

    • $attrs:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 (classstyle 除外 )。当一个组件没有申明任何 prop时,这里会蕴含所有父作用域的绑定 (classstyle 除外 ),并且能够通过 v-bind="$attrs" 传入外部组件。通常配合 inheritAttrs 选项一起应用
    • $listeners:蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过 v-on="$listeners" 传入外部组件
  • provide / inject 实用于 隔代组件通信

    • 先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。provide / inject API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系
  • $root 实用于 隔代组件通信 拜访根组件中的属性或办法,是根组件,不是父组件。$root 只对根组件有用
  • Vuex 实用于 父子、隔代、兄弟组件通信

    • Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state )
    • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
    • 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。

依据组件之间关系探讨组件通信最为清晰无效

  • 父子组件:props/$emit/$parent/ref
  • 兄弟组件:$parent/eventbus/vuex
  • 跨层级关系:eventbus/vuex/provide+inject/$attrs + $listeners/$root

上面演示组件之间通信三种状况: 父传子、子传父、兄弟组件之间的通信

1. 父子组件通信

应用 props,父组件能够应用props 向子组件传递数据。

父组件 vue 模板father.vue:

<template>
  <child :msg="message"></child>
</template>

<script>
import child from './child.vue';
export default {
  components: {child},
  data () {
    return {message: 'father message';}
  }
}
</script>

子组件 vue 模板child.vue:

<template>
    <div>{{msg}}</div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      required: true
    }
  }
}
</script>

回调函数(callBack)

父传子:将父组件里定义的 method 作为 props 传入子组件

// 父组件 Parent.vue:<Child :changeMsgFn="changeMessage">
methods: {changeMessage(){this.message = 'test'}
}
// 子组件 Child.vue:<button @click="changeMsgFn">
props:['changeMsgFn']

子组件向父组件通信

父组件向子组件传递事件办法,子组件通过 $emit 触发事件,回调给父组件

父组件 vue 模板father.vue:

<template>
    <child @msgFunc="func"></child>
</template>

<script>
import child from './child.vue';
export default {
    components: {child},
    methods: {func (msg) {console.log(msg);
        }
    }
}
</script>

子组件 vue 模板child.vue:

<template>
    <button @click="handleClick"> 点我 </button>
</template>

<script>
export default {
    props: {
        msg: {
            type: String,
            required: true
        }
    },
    methods () {handleClick () {
          //........
          this.$emit('msgFunc');
        }
    }
}
</script>

2. provide / inject 跨级拜访先人组件的数据

父组件通过应用 provide(){return{}} 提供须要传递的数据

export default {data() {
    return {
      title: '我是父组件',
      name: 'poetry'
    }
  },
  methods: {say() {alert(1)
    }
  },
  // provide 属性 可能为前面的后辈组件 / 嵌套的组件提供所须要的变量和办法
  provide() {
    return {
      message: '我是先人组件提供的数据',
      name: this.name, // 传递属性
      say: this.say
    }
  }
}

子组件通过应用 inject:[“参数 1”,”参数 2”,…] 接管父组件传递的参数

<template>
  <p> 曾孙组件 </p>
  <p>{{message}}</p>
</template>
<script>
export default {
  // inject 注入 / 接管先人组件传递的所须要的数据即可 
  // 接管到的数据 变量 跟 data 外面的变量一样 能够间接绑定到页面 {{}}
  inject: ["message","say"],
  mounted() {this.say();
  },
};
</script>

3. $parent + $children 获取父组件实例和子组件实例的汇合

  • this.$parent 能够间接拜访该组件的父实例或组件
  • 父组件也能够通过 this.$children 拜访它所有的子组件;须要留神 $children 并不保障程序,也不是响应式的
<!-- parent.vue -->
<template>
<div>
  <child1></child1>   
  <child2></child2> 
  <button @click="clickChild">$children 形式获取子组件值 </button>
</div>
</template>
<script>
import child1 from './child1'
import child2 from './child2'
export default {data(){
    return {total: 108}
  },
  components: {
    child1,
    child2  
  },
  methods: {funa(e){console.log("index",e)
    },
    clickChild(){console.log(this.$children[0].msg);
      console.log(this.$children[1].msg);
    }
  }
}
</script>
<!-- child1.vue -->
<template>
  <div>
    <button @click="parentClick"> 点击拜访父组件 </button>
  </div>
</template>
<script>
export default {data(){
    return {msg:"child1"}
  },
  methods: {
    // 拜访父组件数据
    parentClick(){this.$parent.funa("xx")
      console.log(this.$parent.total);
    }
  }
}
</script>
<!-- child2.vue -->
<template>
  <div>
    child2
  </div>
</template>
<script>
export default {data(){
    return {msg: 'child2'}
  }
}
</script>

4. $attrs + $listeners 多级组件通信

$attrs 蕴含了从父组件传过来的所有 props 属性

// 父组件 Parent.vue:<Child :name="name" :age="age"/>

// 子组件 Child.vue:<GrandChild v-bind="$attrs" />

// 孙子组件 GrandChild
<p> 姓名:{{$attrs.name}}</p>
<p> 年龄:{{$attrs.age}}</p>

$listeners蕴含了父组件监听的所有事件

// 父组件 Parent.vue:<Child :name="name" :age="age" @changeNameFn="changeName"/>

// 子组件 Child.vue:<button @click="$listeners.changeNameFn"></button>

5. ref 父子组件通信

// 父组件 Parent.vue:<Child ref="childComp"/>
<button @click="changeName"></button>
changeName(){console.log(this.$refs.childComp.age);
    this.$refs.childComp.changeAge()}

// 子组件 Child.vue:data(){
    return{age:20}
},
methods(){changeAge(){this.age=15}
}

6. 非父子, 兄弟组件之间通信

vue2中废除了 broadcast 播送和散发事件的办法。父子组件中能够用 props$emit()。如何实现非父子组件间的通信,能够通过实例一个 vue 实例 Bus 作为媒介,要互相通信的兄弟组件之中,都引入 Bus,而后通过别离调用 Bus 事件触发和监听来实现通信和参数传递。Bus.js 能够是这样:

// Bus.js

// 创立一个地方工夫总线类  
class Bus {constructor() {this.callbacks = {};   // 寄存事件的名字  
  }  
  $on(name, fn) {this.callbacks[name] = this.callbacks[name] || [];  
    this.callbacks[name].push(fn);  
  }  
  $emit(name, args) {if (this.callbacks[name]) {this.callbacks[name].forEach((cb) => cb(args));  
    }  
  }  
}  

// main.js  
Vue.prototype.$bus = new Bus() // 将 $bus 挂载到 vue 实例的原型上  
// 另一种形式  
Vue.prototype.$bus = new Vue() // Vue 曾经实现了 Bus 的性能  
<template>
    <button @click="toBus"> 子组件传给兄弟组件 </button>
</template>

<script>
export default{
    methods: {toBus () {this.$bus.$emit('foo', '来自兄弟组件')
    }
  }
}
</script>

另一个组件也在钩子函数中监听 on 事件

export default {data() {
    return {message: ''}
  },
  mounted() {this.$bus.$on('foo', (msg) => {this.message = msg})
  }
}

7. $root 拜访根组件中的属性或办法

  • 作用:拜访根组件中的属性或办法
  • 留神:是根组件,不是父组件。$root只对根组件有用
var vm = new Vue({
  el: "#app",
  data() {
    return {rootInfo:"我是根元素的属性"}
  },
  methods: {alerts() {alert(111)
    }
  },
  components: {
    com1: {data() {
        return {info: "组件 1"}
      },
      template: "<p>{{info}} <com2></com2></p>",
      components: {
        com2: {
          template: "<p> 我是组件 1 的子组件 </p>",
          created() {this.$root.alerts()// 根组件办法
            console.log(this.$root.rootInfo)// 我是根元素的属性
          }
        }
      }
    }
  }
});

8. vuex

  • 实用场景: 简单关系的组件数据传递
  • Vuex 作用相当于一个用来存储共享变量的容器
  • state用来寄存共享变量的中央
  • getter,能够减少一个 getter 派生状态,(相当于 store 中的计算属性),用来取得共享变量的值
  • mutations用来寄存批改 state 的办法。
  • actions也是用来寄存批改 state 的办法,不过 action 是在 mutations 的根底上进行。罕用来做一些异步操作

小结

  • 父子关系的组件数据传递抉择 props$emit进行传递,也可抉择ref
  • 兄弟关系的组件数据传递可抉择 $bus,其次能够抉择$parent 进行传递
  • 先人与后辈组件数据传递可抉择 attrslisteners或者 ProvideInject
  • 简单关系的组件数据传递能够通过 vuex 寄存共享的变量

Vue 为什么须要虚构 DOM?优缺点有哪些

因为在浏览器中操作 DOM是很低廉的。频繁的操作 DOM,会产生肯定的性能问题。这就是虚构 Dom 的产生起因。Vue2Virtual DOM 借鉴了开源库 snabbdom 的实现。Virtual DOM 实质就是用一个原生的 JS 对象去形容一个 DOM 节点,是对实在 DOM 的一层形象

长处:

  • 保障性能上限:框架的虚构 DOM 须要适配任何下层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;然而比起粗犷的 DOM 操作性能要好很多,因而框架的虚构 DOM 至多能够保障在你不须要手动优化的状况下,仍然能够提供还不错的性能,即保障性能的上限;
  • 无需手动操作 DOM:咱们不再须要手动去操作 DOM,只须要写好 View-Model 的代码逻辑,框架会依据虚构 DOM 和 数据双向绑定,帮咱们以可预期的形式更新视图,极大进步咱们的开发效率;
  • 跨平台:虚构 DOM 实质上是 JavaScript 对象, 而 DOM 与平台强相干,相比之下虚构 DOM 能够进行更不便地跨平台操作,例如服务器渲染、weex 开发等等。

毛病:

  • 无奈进行极致优化:尽管虚构 DOM + 正当的优化,足以应答绝大部分利用的性能需求,但在一些性能要求极高的利用中虚构 DOM 无奈进行针对性的极致优化。
  • 首次渲染大量 DOM 时,因为多了一层虚构 DOM 的计算,会比 innerHTML 插入慢。

虚构 DOM 实现原理?

虚构 DOM 的实现原理次要包含以下 3 局部:

  • JavaScript 对象模仿实在 DOM 树,对实在 DOM 进行形象;
  • diff 算法 — 比拟两棵虚构 DOM 树的差别;
  • pach 算法 — 将两个虚构 DOM 对象的差别利用到真正的 DOM 树。

说说你对虚构 DOM 的了解?答复范例

思路

  • vdom是什么
  • 引入 vdom 的益处
  • vdom如何生成,又如何成为dom
  • 在后续的 diff 中的作用

答复范例

  1. 虚构 dom 顾名思义就是虚构的 dom 对象,它自身就是一个 JavaScript 对象,只不过它是通过不同的属性去形容一个视图构造
  2. 通过引入 vdom 咱们能够取得如下益处:
  3. 将实在元素节点形象成 VNode,无效缩小间接操作 dom 次数,从而进步程序性能

    • 间接操作 dom 是有限度的,比方:diffclone 等操作,一个实在元素上有许多的内容,如果间接对其进行 diff 操作,会去额定 diff 一些没有必要的内容;同样的,如果须要进行 clone 那么须要将其全部内容进行复制,这也是没必要的。然而,如果将这些操作转移到 JavaScript 对象上,那么就会变得简略了
    • 操作 dom 是比拟低廉的操作,频繁的 dom 操作容易引起页面的重绘和回流,然而通过形象 VNode 进行两头解决,能够无效缩小间接操作 dom 的次数,从而缩小页面重绘和回流
  • 不便实现跨平台

    • 同一 VNode 节点能够渲染成不同平台上的对应的内容,比方:渲染在浏览器是 dom 元素节点,渲染在 Native(iOS、Android)变为对应的控件、能够实现 SSR、渲染到 WebGL 中等等
    • Vue3 中容许开发者基于 VNode 实现自定义渲染器(renderer),以便于针对不同平台进行渲染
  • vdom如何生成?在 vue 中咱们经常会为组件编写模板 – template,这个模板会被编译器 – compiler编译为渲染函数,在接下来的挂载(mount)过程中会调用 render 函数,返回的对象就是虚构 dom。但它们还不是真正的dom,所以会在后续的patch 过程中进一步转化为dom
  1. 挂载过程完结后,vue程序进入更新流程。如果某些响应式数据发生变化,将会引起组件从新 render,此时就会生成新的vdom,和上一次的渲染后果diff 就能失去变动的中央,从而转换为最小量的 dom 操作,高效更新视图

为什么要用 vdom?案例解析

当初有一个场景,实现以下需要:

[{ name: "张三", age: "20", address: "北京"},    
  {name: "李四", age: "21", address: "武汉"},    
  {name: "王五", age: "22", address: "杭州"},
]

将该数据展现成一个表格,并且轻易批改一个信息,表格也跟着批改。用 jQuery 实现如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <div id="container"></div>
  <button id="btn-change"> 扭转 </button>

  <script src="https://cdn.bootcss.com/jquery/3.2.0/jquery.js"></script>
  <script>
    const data = [{
        name: "张三",
        age: "20",
        address: "北京"
      },
      {
        name: "李四",
        age: "21",
        address: "武汉"
      },
      {
        name: "王五",
        age: "22",
        address: "杭州"
      },
    ];
    // 渲染函数
    function render(data) {const $container = $('#container');
      $container.html('');
      const $table = $('<table>');
      // 重绘一次
      $table.append($('<tr><td>name</td><td>age</td><td>address</td></tr>'));
      data.forEach(item => {
        // 每次进入都重绘
        $table.append($(`<tr><td>${item.name}</td><td>${item.age}</td><td>${item.address}</td></tr>`))
      })
      $container.append($table);
    }

    $('#btn-change').click(function () {data[1].age = 30;
      data[2].address = '深圳';
      render(data);
    });
  </script>
</body>
</html>
  • 这样点击按钮,会有相应的视图变动,然而你审查以下元素,每次改变之后,table标签都得从新创立,也就是说 table 上面的每一个栏目,不论是数据是否和原来一样,都得从新渲染,这并不是现实中的状况,当其中的一栏数据和原来一样,咱们心愿这一栏不要从新渲染,因为 DOM 重绘相当耗费浏览器性能。
  • 因而咱们采纳 JS 对象模仿的办法,将 DOM 的比对操作放在 JS 层,缩小浏览器不必要的重绘,提高效率。
  • 当然有人说虚构 DOM 并不比实在的 DOM 快,其实也是有情理的。当上述 table 中的每一条数据都扭转时,显然实在的 DOM 操作更快,因为虚构 DOM 还存在 jsdiff算法的比对过程。所以,上述性能劣势仅仅实用于大量数据的渲染并且扭转的数据只是一小部分的状况。

如下 DOM 构造:

<ul id="list">
    <li class="item">Item1</li>
    <li class="item">Item2</li>
</ul>

映射成虚构 DOM 就是这样:

{
  tag: "ul",
  attrs: {id: "list"},
  children: [
    {
      tag: "li",
      attrs: {className: "item"},
      children: ["Item1"]
    }, {
      tag: "li",
      attrs: {className: "item"},
      children: ["Item2"]
    }
  ]
} 

应用 snabbdom 实现 vdom

这是一个繁难的实现 vdom 性能的库,相比 vuereact,对于vdom 这块更加繁难,适宜咱们学习 vdomvdom 外面有两个外围的 api,一个是h 函数,一个是 patch 函数,前者用来生成 vdom 对象,后者的性能在于做虚构 dom 的比对和将 vdom 挂载到实在 DOM

简略介绍一下这两个函数的用法:

h('标签名', {属性}, [子元素])
h('标签名', {属性}, [文本])
patch(container, vnode) // container 为容器 DOM 元素
patch(vnode, newVnode)

当初咱们就来用 snabbdom 重写一下方才的例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="container"></div>
  <button id="btn-change"> 扭转 </button>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.min.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
  <script>
    let snabbdom = window.snabbdom;

    // 定义 patch
    let patch = snabbdom.init([
      snabbdom_class,
      snabbdom_props,
      snabbdom_style,
      snabbdom_eventlisteners
    ]);

    // 定义 h
    let h = snabbdom.h;

    const data = [{
        name: "张三",
        age: "20",
        address: "北京"
      },
      {
        name: "李四",
        age: "21",
        address: "武汉"
      },
      {
        name: "王五",
        age: "22",
        address: "杭州"
      },
    ];
    data.unshift({name: "姓名", age: "年龄", address: "地址"});

    let container = document.getElementById('container');
    let vnode;
    const render = (data) => {let newVnode = h('table', {}, data.map(item => {let tds = [];
        for(let i in item) {if(item.hasOwnProperty(i)) {tds.push(h('td', {}, item[i] + ''));
          }
        }
        return h('tr', {}, tds);
      }));

      if(vnode) {patch(vnode, newVnode);
      } else {patch(container, newVnode);
      }
      vnode = newVnode;
    }

    render(data);

    let btnChnage = document.getElementById('btn-change');
    btnChnage.addEventListener('click', function() {data[1].age = 30;
      data[2].address = "深圳";
      //re-render
      render(data);
    })
  </script>
</body>
</html>

你会发现,只有扭转的栏目才闪动,也就是进行重绘,数据没有扭转的栏目还是放弃原样,这样就大大节俭了浏览器从新渲染的开销

vue 中应用 h 函数 生成虚构 DOM 返回

const vm = new Vue({
  el: '#app',
  data: {user: {name:'poetry'}
  },
  render(h){// h()
    // h(App)
    // h('div',[])
    let vnode = h('div',{},'hello world');
    return vnode
  }
});

</details>

怎么缓存以后的组件?缓存后怎么更新

缓存组件应用 keep-alive 组件,这是一个十分常见且有用的优化伎俩,vue3keep-alive 有比拟大的更新,能说的点比拟多

思路

  • 缓存用keep-alive,它的作用与用法
  • 应用细节,例如缓存指定 / 排除、联合 routertransition
  • 组件缓存后更新能够利用 activated 或者beforeRouteEnter
  • 原理论述

答复范例

  1. 开发中缓存组件应用 keep-alive 组件,keep-alivevue 内置组件,keep-alive包裹动静组件 component 时,会缓存不流动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,避免反复渲染DOM
<keep-alive>
  <component :is="view"></component>
</keep-alive>
  1. 联合属性 includeexclude能够明确指定缓存哪些组件或排除缓存指定组件。vue3中联合 vue-router 时变动较大,之前是 keep-alive 包裹 router-view,当初须要反过来用router-view 包裹keep-alive
<router-view v-slot="{Component}">
  <keep-alive>
    <component :is="Component"></component>
  </keep-alive>
</router-view>
  1. 缓存后如果要获取数据,解决方案能够有以下两种
  2. beforeRouteEnter:在有 vue-router 的 我的项目,每次进入路由的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){
  next(vm=>{console.log(vm)
    // 每次进入路由执行
    vm.getData()  // 获取数据})
},
  • actived:在 keep-alive 缓存的组件被激活的时候,都会执行 actived 钩子
activated(){this.getData() // 获取数据
},
  1. keep-alive是一个通用组件,它外部定义了一个 map,缓存创立过的组件实例,它返回的渲染函数外部会查找内嵌的component 组件对应组件的 vnode,如果该组件在map 中存在就间接返回它。因为 componentis属性是个响应式数据,因而只有它变动,keep-aliverender 函数就会从新执行

vue-router 动静路由是什么

咱们常常须要把某种模式匹配到的所有路由,全都映射到同个组件。例如,咱们有一个 User 组件,对于所有 ID 各不相同的用户,都要应用这个组件来渲染。那么,咱们能够在 vue-router 的路由门路中应用“动静门路参数”(dynamic segment) 来达到这个成果

const User = {template: "<div>User</div>",};

const router = new VueRouter({
  routes: [
    // 动静门路参数 以冒号结尾
    {path: "/user/:id", component: User},
  ],
});

问题: vue-router 组件复用导致路由参数生效怎么办?

解决办法:

  1. 通过 watch 监听路由参数再发申请
watch: { // 通过 watch 来监听路由变动
 "$route": function(){this.getData(this.$route.params.xxx);
 }
}
  1. :key 来阻止“复用”
<router-view :key="$route.fullPath" />

答复范例

  1. 很多时候,咱们须要将给定匹配模式的路由映射到同一个组件,这种状况就须要定义动静路由
  2. 例如,咱们可能有一个 User 组件,它应该对所有用户进行渲染,但用户 ID 不同。在 Vue Router中,咱们能够在门路中应用一个动静字段来实现,例如:{path: '/users/:id', component: User},其中 :id 就是门路参数
  3. 门路参数 用冒号 : 示意。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params 的模式裸露进去。
  4. 参数还能够有多个,例如 /users/:username/posts/:postId;除了 $route.params 之外,$route 对象还公开了其余有用的信息,如 $route.query$route.hash

参考 前端进阶面试题具体解答

watch 原理

watch 实质上是为每个监听属性 setter 创立了一个 watcher,当被监听的属性更新时,调用传入的回调函数。常见的配置选项有 deepimmediate,对应原理如下

  • deep:深度监听对象,为对象的每一个属性创立一个 watcher,从而确保对象的每一个属性更新时都会触发传入的回调函数。次要起因在于对象属于援用类型,单个属性的更新并不会触发对象 setter,因而引入 deep 可能很好地解决监听对象的问题。同时也会引入判断机制,确保在多个属性更新时回调函数仅触发一次,防止性能节约。
  • immediate:在初始化时间接调用回调函数,能够通过在 created 阶段手动调用回调函数实现雷同的成果

组件通信

组件通信的形式如下:

(1)props / $emit

父组件通过 props 向子组件传递数据,子组件通过 $emit 和父组件通信

1. 父组件向子组件传值
  • props只能是父组件向子组件进行传值,props使得父子组件之间造成了一个单向上行绑定。子组件的数据会随着父组件不断更新。
  • props 能够显示定义一个或一个以上的数据,对于接管的数据,能够是各种数据类型,同样也能够传递一个函数。
  • props属性名规定:若在 props 中应用驼峰模式,模板中须要应用短横线的模式
// 父组件
<template>
  <div id="father">
    <son :msg="msgData" :fn="myFunction"></son>
  </div>
</template>

<script>
import son from "./son.vue";
export default {
  name: father,
  data() {msgData: "父组件数据";},
  methods: {myFunction() {console.log("vue");
    },
  },
  components: {son},
};
</script>
// 子组件
<template>
  <div id="son">
    <p>{{msg}}</p>
    <button @click="fn"> 按钮 </button>
  </div>
</template>
<script>
export default {name: "son", props: ["msg", "fn"] };
</script>
2. 子组件向父组件传值
  • $emit绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过 v-on 监听并接管参数。
// 父组件
<template>
  <div class="section">
    <com-article
      :articles="articleList"
      @onEmitIndex="onEmitIndex"
    ></com-article>
    <p>{{currentIndex}}</p>
  </div>
</template>

<script>
import comArticle from "./test/article.vue";
export default {
  name: "comArticle",
  components: {comArticle},
  data() {return { currentIndex: -1, articleList: ["红楼梦", "西游记", "三国演义"] };
  },
  methods: {onEmitIndex(idx) {this.currentIndex = idx;},
  },
};
</script>
// 子组件
<template>
  <div>
    <div
      v-for="(item, index) in articles"
      :key="index"
      @click="emitIndex(index)"
    >
      {{item}}
    </div>
  </div>
</template>

<script>
export default {props: ["articles"],
  methods: {emitIndex(index) {this.$emit("onEmitIndex", index); // 触发父组件的办法,并传递参数 index
    },
  },
};
</script>

(2)eventBus 事件总线($emit / $on

eventBus事件总线实用于 父子组件 非父子组件 等之间的通信,应用步骤如下:(1)创立事件核心治理组件之间的通信

// event-bus.js

import Vue from 'vue'
export const EventBus = new Vue()

(2)发送事件 假如有两个兄弟组件firstComsecondCom

<template>
  <div>
    <first-com></first-com>
    <second-com></second-com>
  </div>
</template>

<script>
import firstCom from "./firstCom.vue";
import secondCom from "./secondCom.vue";
export default {components: { firstCom, secondCom} };
</script>

firstCom 组件中发送事件:

<template>
  <div>
    <button @click="add"> 加法 </button>
  </div>
</template>

<script>
import {EventBus} from "./event-bus.js"; // 引入事件核心

export default {data() {return { num: 0};
  },
  methods: {add() {EventBus.$emit("addition", { num: this.num++});
    },
  },
};
</script>

(3)接管事件 secondCom 组件中发送事件:

<template>
  <div> 求和: {{count}}</div>
</template>

<script>
import {EventBus} from "./event-bus.js";
export default {data() {return { count: 0};
  },
  mounted() {EventBus.$on("addition", (param) => {this.count = this.count + param.num;});
  },
};
</script>

在上述代码中,这就相当于将 num 值存贮在了事件总线中,在其余组件中能够间接拜访。事件总线就相当于一个桥梁,不必组件通过它来通信。

尽管看起来比较简单,然而这种办法也有不变之处,如果我的项目过大,应用这种形式进行通信,前期保护起来会很艰难。

(3)依赖注入(provide / inject)

这种形式就是 Vue 中的 依赖注入 ,该办法用于 父子组件之间的通信。当然这里所说的父子不肯定是真正的父子,也能够是祖孙组件,在层数很深的状况下,能够应用这种办法来进行传值。就不必一层一层的传递了。

provide / inject是 Vue 提供的两个钩子,和 datamethods 是同级的。并且 provide 的书写模式和 data 一样。

  • provide 钩子用来发送数据或办法
  • inject钩子用来接收数据或办法

在父组件中:

provide() { 
    return {num: this.num};
}

在子组件中:

inject: ['num']

还能够这样写,这样写就能够拜访父组件中的所有属性:

provide() {
 return {app: this};
}
data() {
 return {num: 1};
}

inject: ['app']
console.log(this.app.num)

留神: 依赖注入所提供的属性是 非响应式 的。

(3)ref / $refs

这种形式也是实现 父子组件 之间的通信。

ref:这个属性用在子组件上,它的援用就指向了子组件的实例。能够通过实例来拜访组件的数据和办法。

在子组件中:

export default {data () {
    return {name: 'JavaScript'}
  },
  methods: {sayHello () {console.log('hello')
    }
  }
}

在父组件中:

<template>
  <child ref="child"></component-a>
</template>
<script>
import child from "./child.vue";
export default {components: { child},
  mounted() {console.log(this.$refs.child.name); // JavaScript
    this.$refs.child.sayHello(); // hello},
};
</script>

(4)$parent / $children

  • 应用 $parent 能够让组件拜访父组件的实例(拜访的是上一级父组件的属性和办法)
  • 应用 $children 能够让组件拜访子组件的实例,然而,$children并不能保障程序,并且拜访的数据也不是响应式的。

在子组件中:

<template>
  <div>
    <span>{{message}}</span>
    <p> 获取父组件的值为: {{parentVal}}</p>
  </div>
</template>

<script>
export default {data() {return { message: "Vue"};
  },
  computed: {parentVal() {return this.$parent.msg;},
  },
};
</script>

在父组件中:

// 父组件中
<template>
  <div class="hello_world">
    <div>{{msg}}</div>
    <child></child>
    <button @click="change"> 点击扭转子组件值 </button>
  </div>
</template>

<script>
import child from "./child.vue";
export default {components: { child},
  data() {return { msg: "Welcome"};
  },
  methods: {change() {
      // 获取到子组件
      this.$children[0].message = "JavaScript";
    },
  },
};
</script>

在下面的代码中,子组件获取到了父组件的 parentVal 值,父组件扭转了子组件中 message 的值。须要留神:

  • 通过 $parent 拜访到的是上一级父组件的实例,能够应用 $root 来拜访根组件的实例
  • 在组件中应用 $children 拿到的是所有的子组件的实例,它是一个数组,并且是无序的
  • 在根组件 #app 上拿 $parent 失去的是 new Vue() 的实例,在这实例上再拿 $parent 失去的是 undefined,而在最底层的子组件拿$children 是个空数组
  • $children 的值是 数组 ,而$parent 是个 对象

(5)$attrs / $listeners

思考一种场景,如果 A 是 B 组件的父组件,B 是 C 组件的父组件。如果想要组件 A 给组件 C 传递数据,这种隔代的数据,该应用哪种形式呢?

如果是用 props/$emit 来一级一级的传递,的确能够实现,然而比较复杂;如果应用事件总线,在多人开发或者我的项目较大的时候,保护起来很麻烦;如果应用 Vuex,确实也能够,然而如果仅仅是传递数据,那可能就有点节约了。

针对上述情况,Vue 引入了$attrs / $listeners,实现组件之间的跨代通信。

先来看一下 inheritAttrs,它的默认值 true,继承所有的父组件属性除props 之外的所有属性;inheritAttrs:false 只继承 class 属性。

  • $attrs:继承所有的父组件属性(除了 prop 传递的属性、class 和 style),个别用在子组件的子元素上
  • $listeners:该属性是一个对象,外面蕴含了作用在这个组件上的所有监听器,能够配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)

A 组件(APP.vue):

<template>
  <div id="app">
    // 此处监听了两个事件,能够在 B 组件或者 C 组件中间接触发
    <child1
      :p-child1="child1"
      :p-child2="child2"
      @test1="onTest1"
      @test2="onTest2"
    ></child1>
  </div>
</template>
<script>
import Child1 from "./Child1.vue";
export default {components: { Child1},
  methods: {onTest1() {console.log("test1 running");
    },
    onTest2() {console.log("test2 running");
    },
  },
};
</script>

B 组件(Child1.vue):

<template>
  <div class="child-1">
    <p>props: {{pChild1}}</p>
    <p>$attrs: {{$attrs}}</p>
    <child2 v-bind="$attrs" v-on="$listeners"></child2>
  </div>
</template>
<script>
import Child2 from "./Child2.vue";
export default {props: ["pChild1"],
  components: {Child2},
  inheritAttrs: false,
  mounted() {this.$emit("test1"); // 触发 APP.vue 中的 test1 办法
  },
};
</script>

C 组件 (Child2.vue):

<template>
  <div class="child-2">
    <p>props: {{pChild2}}</p>
    <p>$attrs: {{$attrs}}</p>
  </div>
</template>
<script>
export default {props: ["pChild2"],
  inheritAttrs: false,
  mounted() {this.$emit("test2"); // 触发 APP.vue 中的 test2 办法
  },
};
</script>

在上述代码中:

  • C 组件中能间接触发 test 的起因在于 B 组件调用 C 组件时 应用 v-on 绑定了$listeners 属性
  • 在 B 组件中通过 v -bind 绑定 $attrs 属性,C 组件能够间接获取到 A 组件中传递下来的 props(除了 B 组件中 props 申明的)

(6)总结

(1)父子组件间通信

  • 子组件通过 props 属性来承受父组件的数据,而后父组件在子组件上注册监听事件,子组件通过 emit 触发事件来向父组件发送数据。
  • 通过 ref 属性给子组件设置一个名字。父组件通过 $refs 组件名来取得子组件,子组件通过 $parent 取得父组件,这样也能够实现通信。
  • 应用 provide/inject,在父组件中通过 provide 提供变量,在子组件中通过 inject 来将变量注入到组件中。不管子组件有多深,只有调用了 inject 那么就能够注入 provide 中的数据。

(2)兄弟组件间通信

  • 应用 eventBus 的办法,它的实质是通过创立一个空的 Vue 实例来作为消息传递的对象,通信的组件引入这个实例,通信的组件通过在这个实例上监听和触发事件,来实现音讯的传递。
  • 通过 $parent/$refs 来获取到兄弟组件,也能够进行通信。

(3)任意组件之间

  • 应用 eventBus,其实就是创立一个事件核心,相当于中转站,能够用它来传递事件和接管事件。

如果业务逻辑简单,很多组件之间须要同时解决一些公共的数据,这个时候采纳下面这一些办法可能不利于我的项目的保护。这个时候能够应用 vuex,vuex 的思维就是将这一些公共的数据抽离进去,将它作为一个全局的变量来治理,而后其余组件就能够对这个公共数据进行读写操作,这样达到理解耦的目标。

组件中写 name 属性的益处

能够标识组件的具体名称不便调试和查找对应属性

// 源码地位 src/core/global-api/extend.js

// enable recursive self-lookup
if (name) {Sub.options.components[name] = Sub // 记录本人 在组件中递归本人  -> jsx
}

vue3.0 个性你有什么理解的吗?

Vue 3.0 正走在公布的路上,Vue 3.0 的指标是让 Vue 外围变得更小、更快、更弱小,因而 Vue 3.0 减少以下这些新个性:

(1)监测机制的扭转

3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言笼罩的反馈性跟踪。这打消了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限度:

  • 只能监测属性,不能监测对象
  • 检测属性的增加和删除;
  • 检测数组索引和长度的变更;
  • 反对 Map、Set、WeakMap 和 WeakSet。

新的 observer 还提供了以下个性:

  • 用于创立 observable 的公开 API。这为中小规模场景提供了简略轻量级的跨组件状态治理解决方案。
  • 默认采纳惰性察看。在 2.x 中,不论反应式数据有多大,都会在启动时被察看到。如果你的数据集很大,这可能会在利用启动时带来显著的开销。在 3.x 中,只察看用于渲染应用程序最后可见局部的数据。
  • 更准确的变更告诉。在 2.x 中,通过 Vue.set 强制增加新属性将导致依赖于该对象的 watcher 收到变更告诉。在 3.x 中,只有依赖于特定属性的 watcher 才会收到告诉。
  • 不可变的 observable:咱们能够创立值的“不可变”版本(即便是嵌套属性),除非零碎在外部临时将其“解禁”。这个机制可用于解冻 prop 传递或 Vuex 状态树以外的变动。
  • 更好的调试性能:咱们能够应用新的 renderTracked 和 renderTriggered 钩子准确地跟踪组件在什么时候以及为什么从新渲染。

(2)模板

模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会从新渲染,而 3.0 把作用域插槽改成了函数的形式,这样只会影响子组件的从新渲染,晋升了渲染的性能。

同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来不便习惯间接应用 api 来生成 vdom。

(3)对象式的组件申明形式

vue2.x 中的组件是通过申明的形式传入一系列 option,和 TypeScript 的联合须要通过一些装璜器的形式来做,尽管能实现性能,然而比拟麻烦。3.0 批改了组件的申明形式,改成了类式的写法,这样使得和 TypeScript 的联合变得很容易。

此外,vue 的源码也改用了 TypeScript 来写。其实当代码的性能简单之后,必须有一个动态类型零碎来做一些辅助治理。当初 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外裸露的 api 更容易联合 TypeScript。动态类型零碎对于简单代码的保护的确很有必要。

(4)其它方面的更改

vue3.0 的扭转是全面的,下面只波及到次要的 3 个方面,还有一些其余的更改:

  • 反对自定义渲染器,从而使得 weex 能够通过自定义渲染器的形式来扩大,而不是间接 fork 源码来改的形式。
  • 反对 Fragment(多个根节点)和 Protal(在 dom 其余局部渲染组建内容)组件,针对一些非凡的场景做了解决。
  • 基于 treeshaking 优化,提供了更多的内置性能。

v-show 与 v-if 有什么区别?

v-if 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是 惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简略得多——不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS 的“display”属性进行切换。

所以,v-if 实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show 则实用于须要十分频繁切换条件的场景。

什么是作用域插槽

插槽

  • 创立组件虚构节点时,会将组件儿子的虚构节点保存起来。当初始化组件时,通过插槽属性将儿子进行分类{a:[vnode],b[vnode]}
  • 渲染组件时会拿对应的 slot 属性的节点进行替换操作。(插槽的作用域为父组件)
<app>
    <div slot="a">xxxx</div>
    <div slot="b">xxxx</div>
</app> 

slot name="a" 
slot name="b"

作用域插槽

  • 作用域插槽在解析的时候不会作为组件的孩子节点。会解析成函数,当子组件渲染时,会调用此函数进行渲染。(插槽的作用域为子组件)
  • 一般插槽渲染的作用域是父组件,作用域插槽的渲染作用域是以后子组件。
// 插槽

const VueTemplateCompiler = require('vue-template-compiler'); 
let ele = VueTemplateCompiler.compile(` 
    <my-component> 
        <div slot="header">node</div> 
        <div>react</div> 
        <div slot="footer">vue</div> 
    </my-component> `
)

// with(this) { 
//     return _c('my-component', [_c('div', {//         attrs: { "slot": "header"},
//         slot: "header" 
//     }, [_v("node")] // _文本及诶点 )
//     , _v(" "), 
//     _c('div', [_v("react")]), _v(""), _c('div', {//         attrs: { "slot": "footer"},
//         slot: "footer" }, [_v("vue")])]) 
// }

const VueTemplateCompiler = require('vue-template-compiler');
let ele = VueTemplateCompiler.compile(` 
    <div>
        <slot name="header"></slot> 
        <slot name="footer"></slot> 
        <slot></slot> 
    </div> `
);

with(this) {return _c('div', [_v("node"), _v(""), _t(_v("vue")])]), _v(" "), _t("default")], 2) 
}
//  _t 定义在 core/instance/render-helpers/index.js
// 作用域插槽:
let ele = VueTemplateCompiler.compile(` <app>
        <div slot-scope="msg" slot="footer">{{msg.a}}</div> 
    </app> `
);

// with(this) { 
//     return _c('app', { scopedSlots: _u([{ 
//         // 作用域插槽的内容会被渲染成一个函数 
//         key: "footer", 
//         fn: function (msg) {//             return _c('div', {}, [_v(_s(msg.a))]) } }]) 
//         })
//     } 
// }

const VueTemplateCompiler = require('vue-template-compiler');
VueTemplateCompiler.compile(` <div><slot name="footer" a="1" b="2"></slot> </div> `);

// with(this) {return _c('div', [_t("footer", null, { "a": "1", "b": "2"})], 2) }

什么是 MVVM?

Model–View–ViewModel(MVVM)是一个软件架构设计模式,由微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动编程形式。由 John Gossman(同样也是 WPF 和 Silverlight 的架构师)于 2005 年在他的博客上发表

MVVM 源自于经典的 Model–View–Controller(MVC)模式,MVVM 的呈现促成了前端开发与后端业务逻辑的拆散,极大地提高了前端开发效率,MVVM 的外围是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易治理和应用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口申请进行数据交互,起呈上启下作用

(1)View 层

View 是视图层,也就是用户界面。前端次要由 HTML 和 CSS 来构建。

(2)Model 层

Model 是指数据模型,泛指后端进行的各种业务逻辑解决和数据操控,对于前端来说就是后端提供的 api 接口。

(3)ViewModel 层

ViewModel 是由前端开发人员组织生成和保护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换解决,做二次封装,以生成合乎 View 层应用预期的视图数据模型。须要留神的是 ViewModel 所封装进去的数据模型包含视图的状态和行为两局部,而 Model 层的数据模型是只蕴含状态的,比方页面的这一块展现什么,而页面加载进来时产生什么,点击这一块产生什么,这一块滚动时产生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 能够残缺地去形容 View 层。

MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展示在 View 层,前端开发者再也不用低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架曾经把最脏最累的一块做好了,咱们开发者只须要解决和保护 ViewModel,更新数据视图就会主动失去相应更新。这样 View 层展示的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就齐全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端拆散计划施行的重要一环。

咱们以下通过一个 Vue 实例来阐明 MVVM 的具体实现,有 Vue 开发教训的同学应该高深莫测:

(1)View 层

<div id="app">
    <p>{{message}}</p>
    <button v-on:click="showMessage()">Click me</button>
</div>

(2)ViewModel 层

var app = new Vue({
    el: '#app',
    data: {  // 用于形容视图状态   
        message: 'Hello Vue!', 
    },
    methods: {  // 用于形容视图行为  
        showMessage(){
            let vm = this;
            alert(vm.message);
        }
    },
    created(){
        let vm = this;
        // Ajax 获取 Model 层的数据
        ajax({
            url: '/your/server/data/api',
            success(res){vm.message = res;}
        });
    }
})

(3)Model 层

{
    "url": "/your/server/data/api",
    "res": {
        "success": true,
        "name": "IoveC",
        "domain": "www.cnblogs.com"
    }
}

v-model 的原理?

咱们在 vue 我的项目中次要应用 v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,咱们晓得 v-model 实质上不过是语法糖,v-model 在外部为不同的输出元素应用不同的属性并抛出不同的事件:

  • text 和 textarea 元素应用 value 属性和 input 事件;
  • checkbox 和 radio 应用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

以 input 表单元素为例:

<input v-model='something'>

相当于

<input v-bind:value="something" v-on:input="something = $event.target.value">

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

父组件:<ModelChild v-model="message"></ModelChild>

子组件:<div>{{value}}</div>

props:{value: String},
methods: {test1(){this.$emit('input', '小红')
  },
},

v-if 和 v -for 哪个优先级更高

  • 实际中不应该把 v-forv-if放一起
  • vue2 中,v-for的优先级是高于 v-if,把它们放在一起,输入的渲染函数中能够看出会先执行循环再判断条件,哪怕咱们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比拟节约;另外须要留神的是在vue3 中则齐全相同,v-if的优先级高于 v-for,所以v-if 执行时,它调用的变量还不存在,就会导致异样
  • 通常有两种状况下导致咱们这样做:

    • 为了过滤列表中的我的项目 (比方 v-for="user in users" v-if="user.isActive")。此时定义一个计算属性 (比方 activeUsers),让其返回过滤后的列表即可(比方users.filter(u=>u.isActive)
    • 为了防止渲染本应该被暗藏的列表 (比方 v-for="user in users" v-if="shouldShowUsers")。此时把 v-if 挪动至容器元素上 (比方 ulol)或者外面包一层 template 即可
  • 文档中明确指出永远不要把 v-ifv-for 同时用在同一个元素上,显然这是一个重要的注意事项
  • 源码外面对于代码生成的局部,可能清晰的看到是先解决 v-if 还是 v-for,程序上vue2vue3正好相同,因而产生了一些症状的不同,然而不管怎样都是不能把它们写在一起的

vue2.x 源码剖析

在 vue 模板编译的时候,会将指令系统转化成可执行的 render 函数

编写一个 p 标签,同时应用 v-ifv-for

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

创立 vue 实例,寄存 isShowitems数据

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

模板指令的代码都会生成在 render 函数中,通过 app.$options.render 就能失去渲染函数

ƒ 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) }
}
  • _lvue 的列表渲染函数,函数外部都会进行一次 if 判断
  • 初步失去论断:v-for优先级是比v-i f 高
  • 再将 v-forv-if置于不同标签
<div id="app">
  <template v-if="isShow">
    <p v-for="item in items">{{item.title}}</p>
  </template>
</div>

再输入下 render 函数

ƒ 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)}
}

这时候咱们能够看到,v-forv-if 作用在不同标签时候,是先进行判断,再进行列表的渲染

咱们再在查看下 vue 源码

源码地位:\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
    ...
}

在进行 if 判断的时候,v-for是比 v-if 先进行判断

最终论断:v-for优先级比 v-if

虚构 DOM 的优劣如何?

长处:

  • 保障性能上限: 虚构 DOM 能够通过 diff 找出最小差别, 而后批量进行 patch, 这种操作尽管比不上手动优化, 然而比起粗犷的 DOM 操作性能要好很多, 因而虚构 DOM 能够保障性能上限
  • 无需手动操作 DOM: 虚构 DOM 的 diff 和 patch 都是在一次更新中主动进行的, 咱们无需手动操作 DOM, 极大进步开发效率
  • 跨平台: 虚构 DOM 实质上是 JavaScript 对象, 而 DOM 与平台强相干, 相比之下虚构 DOM 能够进行更不便地跨平台操作, 例如服务器渲染、挪动端开发等等

毛病:

  • 无奈进行极致优化: 在一些性能要求极高的利用中虚构 DOM 无奈进行针对性的极致优化, 比方 VScode 采纳间接手动操作 DOM 的形式进行极其的性能优化

为什么 Vue 采纳异步渲染

Vue 是组件级更新,如果不采纳异步更新,那么每次更新数据都会对以后组件进行从新渲染,所以为了性能,Vue 会在本轮数据更新后,在异步更新视图。核心思想 nextTick

源码相干

dep.notify() 告诉 watcher进行更新,subs[i].update 顺次调用 watcherupdatequeueWatcherwatcher 去重放入队列,nextTickflushSchedulerQueue)在下一tick 中刷新 watcher 队列(异步)

update () { /* istanbul ignore else */ 
    if (this.lazy) {this.dirty = true} 
    else if (this.sync) {this.run() 
    } 
    else {queueWatcher(this); // 当数据发生变化时会将 watcher 放到一个队列中批量更新 
    }
}

export function queueWatcher (watcher: Watcher) { 
    const id = watcher.id // 会对雷同的 watcher 进行过滤 
    if (has[id] == null) {has[id] = true 
        if (!flushing) {queue.push(watcher) 
        } else { 
            let i = queue.length - 1 
            while (i > index && queue[i].id > watcher.id) {i--}
            queue.splice(i + 1, 0, watcher) 
        }
        // queue the flush 
        if (!waiting) { 
            waiting = true 
            if (process.env.NODE_ENV !== 'production' && !config.async) {flushSchedulerQueue() 
                return 
            }
            nextTick(flushSchedulerQueue) // 调用 nextTick 办法 批量的进行更新 
        } 
    } 
}

你有应用过 vuex 的 module 吗?

const moduleA = {state: () => ({...}),
  mutations: {...},
  actions: {...},
  getters: {...}
}
const moduleB = {state: () => ({...}),
  mutations: {...},
  actions: {...}
}
const store = createStore({
  modules: {
    a: moduleA,
    b: moduleB
  }
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
store.getters.c // -> moduleA 里的 getters
store.commit('d') // -> 能同时触发子模块中同名 mutation
store.dispatch('e') // -> 能同时触发子模块中同名 action
  • 用过 module,我的项目规模变大之后,独自一个store 对象会过于宏大臃肿,通过模块形式能够拆分开来便于保护
  • 能够按之前规定独自编写子模块代码,而后在主文件中通过 modules 选项组织起来:reateStore({modules:{...}})
  • 不过应用时要留神拜访子模块状态时须要加上注册时模块名:store.state.a.xxx,但同时 gettersmutationsactions又在全局空间中,应用形式和之前一样。如果要做到齐全拆分,须要在子块加上 namespace 选项,此时再拜访它们就要加上命名空间前缀。
  • 很显然,模块的形式能够拆分代码,然而毛病也很显著,就是应用起来比拟繁琐简单,容易出错。而且类型零碎反对很差,不能给咱们带来帮忙。pinia显然在这方面有了很大改良,是时候切换过来了

父子组件生命周期调用程序(简略)

渲染程序:先父后子,实现程序:先子后父

更新程序:父更新导致子更新,子更新实现后父

销毁程序:先父后子,实现程序:先子后父

说说 vue 内置指令

正文完
 0