vue这些小细节我知道吗

338 阅读17分钟

前言

本文整理了前端高频vue考点,如果对答案有不一样见解的同学欢迎评论区补充讨论,当然有问题,也欢迎在评论区指出。

来源 juejin.cn/post/698421…

详细版vue笔记 segmentfault.com/a/119000001…

juejin.cn/post/684490… 补充

1.vue优点

  • 轻量级框架:只关注视图层,大小只有几十kb

  • 简单易学:国人开发,中文文档,易于理解和学习;

  • 双向数据绑定:保留了angular的特点,在数据操作方面更为简单;

  • 组件化:保留了react的优点,实现了html的封装和复用,利于构建单页面应用;

  • 视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;

  • 虚拟DOM:dom操作非常耗费性能,改用VDom+diff

  • 运行速度更快: 相比较与react而言,同样是操作虚拟dom,vue性能更好。

缺点:单页面不利于seo,首次加载主页面时间长(ssr解决)

2.vue是渐进式框架的理解

就是你可以只用我的一部分,而不是用了我这一点就必须用我的所有部分(例如:你想用component就用,不用也行,你想用vuex就用,不用也可以)

3. Vue跟React的异同点

相同点:

  • 1.都使用了虚拟dom
  • 2.组件化开发
  • 3.都是单向数据流(父子组件之间,不建议子修改父传下来的数据)
  • 4.都支持服务端渲染
区别vuereact
监听数据变化Vue 通过defineproperty对数据劫持,监听数据变化。Vue 强调可变数据React 不精确监听数据变化,因为而React更强调数据的不可变
生命周期不同
数据状态管理vuexredux
dom操作很方便 ,for指令 if指令代码更加美观
数据流的不同组件和DOM之间是双向数据流(v-model),父子之间是单向数据流(props)父子之间(props),组件和dom之间(state)单向数据流

4. MVVM和MVC区别

MVC

  • Model(模型):负责从数据库中取数据
  • View(视图):负责展示数据的界面
  • Controller(控制器):用户交互的地方,例如点击事件等等
  • 思想:Controller将Model的数据展示在View上

MVVM

  • VM(视图模型):实现数据的双向绑定,(1)将后端传递的数据转化成所看到的页面。(2)将所看到的页面转化成后端的数据
  • 思想:Vue数据驱动,即数据改变后对应的view层显示自动改变
  • vue不是严格符合MVVM,因为MVVM规定Model和View不能直接通信,而Vue的ref可以做到这点

7. 为什么data是个函数并且返回一个对象呢?

data之所以只一个函数,是因为一个组件可能会多处调用,而每一次调用就会执行data函数并返回新的数据对象,这样可以避免多处调用之间的数据污染

8. 使用过哪些Vue的修饰符呢?

可以看这篇文章「百毒不侵」面试官最喜欢问的13种Vue修饰符

截屏2021-07-11 下午9.56.53.png

9. 使用过哪些Vue的内部指令呢?

image.png

  • v-once一旦绑定,数据就不会改变
<template>
    <div>
        <h3>v-once</h3>
        <input type="text" v-model="voncetext" />   输入    测试v-once123
        <p>{{voncetext}}</p>  测试v-once123
        <p v-once>{{voncetext}}</p>   测试v-once
    </div>
</template>
​
<script>
    export default({
        name:"voncetest",
        data(){
            return{
                voncetext:"测试v-once"
            }
        }
    })
</script>

10. 组件之间的传值方式有哪些(通信)?

  • 父组件传值给子组件,子组件使用props进行接收
  • 子组件传值给父组件,子组件使用$emit+事件对父组件进行传值
  • 组件中可以使用$parent$children获取到父组件实例和子组件实例,进而获取数据
//父组件
mounted(){
  console.log(this.$children) 
  //可以拿到 一级子组件的属性和方法
  //所以就可以直接改变 data,或者调用 methods 方法
}
​
//子组件
mounted(){
  console.log(this.$parent) //可以拿到 parent 的属性和方法
}
  • 使用$attrs$listeners,在对一些组件进行二次封装时可以方便传值,例如A->B->C
1.attrs 场景:如果父传子有很多值,那么在子组件需要定义多个 props 解决:attrs场景:如果父传子有很多值,那么在子组件需要定义多个props解决
相对应的如果子组件定义了 props,打印的值就是剔除定义的属性
props: {
  width: {
    type: String,
    default: ''
  }
},
mounted() {
  console.log(this.$attrs) //{title: "这是标题", height: "80", imgUrl: "imgUrl"}
},
​
2.listeners场景:子组件需要调用父组件的方法解决:父组件的方法可以通过v−on="listeners" 传入内部组件——在创建更高层次的组件时非常有用
// 父组件
<home @change="change"/>
​
// 子组件
mounted() {
  console.log(this.$listeners) //即可拿到 change 事件
}
  • 使用$refs获取组件实例,进而获取数据
// 父组件
<home ref="home"/>
​
mounted(){
  console.log(this.$refs.home) //即可拿到子组件的实例,就可以直接操作 data 和 methods
}
  • 使用Vuex进行状态管理store
state:定义存贮数据的仓库 ,可通过this.$store.state 或mapState访问.可以在这里设置默认的初始状态
getter:允许组件从 Store 中获取数据,可认为是 store 的计算属性,可通过this.$store.getter 或
       mapGetters访问(computed)
mutation:同步改变 store 值,为什么会设计成同步,因为mutation是直接改变 store 值,vue 对操作进行了记录,如果是异步无法追踪改变.可通过mapMutations调用。是唯一更改 store 中状态的方法
action:异步调用函数执行mutation,进而改变 store 值,可通过 this.$dispatch或mapActions访问
modules:模块,如果状态过多,可以拆分成模块,最后在入口通过...解构引入
  • 使用eventBus进行跨组件触发事件,进而传递数据
@method $on 事件订阅, 监听当前实例上的自定义事件。必须在事件广播($emit)前注册;
@method $off 取消事件订阅,移除自定义事件监听器。必须跟事件订阅($on)成对出现
@method $emit 事件广播, 触发当前实例上的事件
@method $once 事件订阅, 监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器。 
//event-bus.js创建事件总线并将其导出,方便调用
import Vue from 'vue'
export const $EventBus = new Vue()
​
// main.js全局事件总线
Vue.prototype.$EventBus = new Vue()
​
如果你只想监听一次事件的发生,可以使用 EventBus.$once(channel: string, callback(payload1,…))
​
A组件
beforeDestroy () {
    //事件广播
    this.$EventBus.$emit('testing', color)
}
B组件
created () {
//事件订阅
    this.$EventBus.$on('testing', (res) => { console.log(res) })
},
beforeDestroy () {
    this.$EventBus.$off('testing')
}
  • $root
// 父组件
mounted(){
  console.log(this.$root) //获取根实例,最后所有组件都是挂载到根实例上
  console.log(this.$root.$children[0]) //获取根实例的一级子组件
  console.log(this.$root.$children[0].$children[0]) //获取根实例的二级子组件
}
  • 修饰符.sync
// 父组件
<home :title.sync="title" />
//编译时会被扩展为
<home :title="title"  @update:title="val => title = val"/>// 子组件 
// 所以子组件可以通过$emit 触发 update 方法改变
mounted(){
  this.$emit("update:title", '这是新的title')
}
  • 使用provideinject,官方建议我们不要用这个,我在看ElementUI源码时发现大量使用
主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中; 
  • 使用浏览器本地缓存,例如localStorage

12. 如何设置动态class,动态style?

  • 动态class对象:<div :class="{ 'is-active': true, 'red': isRed }"></div>
  • 动态class数组:<div :class="['is-active', isRed ? 'red' : '' ]"></div>
  • 动态style对象:<div :style="{ color: textColor, fontSize: '18px' }"></div>
  • 动态style数组:<div :style="[{ color: textColor, fontSize: '18px' }, { fontWeight: '300' }]"></div>

13. v-if和v-show有何区别,v-for和v-if不能一块使用

  • 1.v-if是通过控制dom元素的删除和生成来实现显隐,显隐决定了组件的生成和销毁
  • 2.v-show是通过控制dom元素的css样式来实现显隐,不会销毁
  • 3.频繁或者大数量显隐使用v-show,否则使用v-if

v-for和v-if不应该一起使用,必要情况下应该替换成computed属性。

  • 原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。

vue高阶面试题

1.<keep-alive></keep-alive>的作用是什么?

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件 保留状态,或避免重新渲染。

keep-alive给我们提供了三个属性:

  • include:接受值为字符串或者正则表达式,只有匹配的才被缓存
  • exclude:接受值为字符串或者正则表达式,被匹配的路由将不会被缓存;
  • max:缓存组件最大值

对组件的缓存

//只缓存组件name为a或者b的组件
<keep-alive include="a,b">
 <component :is="currentView"/>
</keep-alive>
​
//组件名为c的组件不缓存
<keep-alive exclude="c">
 <component :is="currentView"/>
</keep-alive>
​
// 如果同时使用include,exclude,那么exclude优先于include, 下面的例子也就是只缓存a组件
<keep-alive include="a,b" exclude="b">
 <component :is="currentView"/>
</keep-alive>
​
// 如果缓存的组件超过了max设定的值5,那么将删除第一个缓存的组件
<keep-alive exclude="c" max="5">
 <component :is="currentView"/>
</keep-alive>

对路由组件的缓存

使用keep-alive可以将所有路径匹配到的路由组件都缓存起来,包括路由组件里面的组件。

<keep-alive> //若有些组件不需要缓存,跟上面方法一样,添加exclude属性
 <router-view />
</keep-alive>

也可以实现对部分路由的缓存

  1. 通过在meta属性中添加keepAlive属性实现
{
    path: '/login',
    name: 'login',
    meta: {
        keepAlive: false //设置不缓存
    }
},
{
    path: '/argu',
    name: 'argu',
    component: () => import('@/views/argu.vue'),
    meta: {
        keepAlive: true //设置缓存
    }
},
  1. 在app.vue中设置,通过$route来获取meta中的keepAlive属性进而判断是否缓存。
<template>
  <div id="app">
    <keep-alive>
      <router-view v-if="$route.meta.keepAlive"></router-view> 
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive"></router-view>
  </div>
</template>
  1. 当组件被包裹在keep-alive组件中时,会多出两个钩子函数:activated()和deactivated()
  • activated(): 这个钩子函数在组件第一次渲染时候被调用,之后在每次缓存组件被激活时候调用。

  • deactivated():组件被停用(离开路由)的时候调用。

  • 使用了keep-alive的组件是不会触发beforeDestroy()和destroyed()这两个钩子函数的,因为组件并没有被销毁,而是被缓存了起来。

  • 从进入一个缓存组件钩子函数的调用顺序很重要:

    • 进入一个缓存组件:deactivated(如果是从另一个缓存组件中进入则有这个钩子,否则无)——>mounted——>activated 进入缓存组件——> 执行beforeRouteLeave 回调。
    • 离开一个缓存组件:beforeRouteLeave ——> 全局前置守卫beforeEach——>deactivated 离开当前组件 ——> activated(如果进入的还是一个缓存组件则有,否则无)

2.如何获取dom?

在组件内设置ref="domName" ------ 用法:this.$refs.domName

3.v-model的使用。

v-model用于表单数据的双向绑定,其实它就是一个语法糖,这个背后就做了两个操作:

  • v-bind绑定一个value属性;
  • v-on指令给当前元素绑定input事件。

4.请说出vue-cli项目中src目录每个文件夹和文件的用法?

  • assets文件夹是放项目中template需要的样式文件js文件等,走打包,减少体积
  • components是放组件;
  • router是定义路由相关的配置;
  • app.vue是一个应用主组件;
  • main.js是入口文件。

static/ 目录下的文件并不会被 Webpack 处理,在打包时,它们会直接被复制到最终目录(默认是dist/static)。

  • 存放项目中引入的第三方的资源文件如iconfoont.css等文件
  • 任何放在 static/ 中文件需要以绝对路径的形式引用:/static/[filename]。这是通过在 config.js 文件中的 build.assetsPublicPath 和 build.assetsSubDirectory 连接来确定的。

5.分别简述computed和watch和filter的使用场景

computed:

当一个属性受多个属性影响的时候就需要用到computed,适用于复杂的数据转换、统计等场景

  • 最典型的栗子: 购物车商品结算的时候
  • computed 属性具有缓存能力,如下:index由0变成1,那么会触发视图更新,这时候methods会重新执行一次,而computed不会,因为computed依赖的两个变量num和price都没变
<div>
    <div>{{howMuch1()}}</div>
    <div>{{howMuch2()}}</div>
    <div>{{index}}</div>
</div>
​
data: () {
    return {
         index: 0
       }
     }
methods: {
    howMuch1() {
        return this.num + this.price
    }
  }
computed() {
    howMuch2() {
        return this.num + this.price
    }
  }
watch:

当一条数据影响多条数据的时候就需要用watch。栗子:搜索数据

  • 当我们监听一个基本数据类型时:
watch: {
    value () {
        // do something
    }
}
  • 当我们监听一个引用数据类型时:
docData: {//对象
            'doc_id': 1,
            'tpl_data': 'abc'
        }
​
watch: {
    docData: {
       handler () { // 执行回调
           // do something
       },
       deep: true, // 是否进行深度监听   当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变
       immediate: true // 是否初始组件时就执行handler函数
    }
}
filter:

filter 无法缓存,调用频率高,适用于格式化输出场景,比如日期格式化

  1. 全局过滤器

    • 通过插值表达式使用单个过滤器

      {{msg | upper}}

    • 通过插值表达式叠加使用多个过滤器

      {{msg | upper | lower}}
      进行了两次的数据格式化,先是变成大小,再变成小写,最后呈现小写的结果

    • // main.js
      import Vue from "vue"
      import App from "./App.vue"Vue.filter('upper',function(val){
          return val.charAt(0).toUpperCase()+val.slice(1)
      })
      Vue.filter('lower',function(val){
          return val.charAt(0).toLowerCase()+val.slice(1)
      })
      ​
      new Vue({
          el:"#app",
          render(h){
              return h(App);
          }
      })
      
    • 通过使用过滤器来格式化属性绑定的值

      <div v-bind:id="id | formatId">Test</div>

  2. 局部过滤器

<template>
  <div>
    <input type="text" v-model="msg">
    <div>{{msg|upper}}</div>
    <button @click="handle(test)">测试methods方法调用filter</button>
  </div>
</template>
​
<script>
  export default {
    data(){
      return {
        msg:'hzy',
        test:'hello'
      }
    },
    filters:{
      upper(val){
        return val.charAt(0).toUpperCase()+val.slice(1)
      }
    },
    methods:{
      handle(val){
        //使用this.$options.filters[]方式获取本地过滤器
        let localFilter = this.$options.filters['upper'];
        //调用本地过滤器格式化
        localFilter(val);
      }
    }
  }
</script>

6.v-on可以监听多个方法吗?

可以,栗子:<input type="text" v-on="{ input:onInput,focus:onFocus,blur:onBlur, }">

7. 不需要响应式的数据应该怎么处理?

在我们的Vue开发中,会有一些数据,从始至终都未曾改变过,这种死数据,既然不改变,那也就不需要对他做响应式处理了,不然只会做一些无用功消耗性能,比如一些写死的下拉框,写死的表格数据,这些数据量大的死数据,如果都进行响应式处理,那会消耗大量性能。

// 方法一:将数据定义在data之外
data () {
    this.list1 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list2 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list3 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    return {}
 }
    
// 方法二:Object.freeze()
data () {
    return {
        list1: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list2: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list3: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
    }
 } 

8. 父子组件生命周期顺序

父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

9. 对象新属性无法更新视图,删除属性无法更新视图,为什么?怎么办?

  • 原因:Object.defineProperty没有对对象的新属性进行属性劫持

  • 对象新属性无法更新视图:使用Vue.$set(obj, key, value),组件中this.$set(obj, key, value)

  • 删除对象属性无法更新视图:使用Vue.$delete(obj, key),组件中this.$delete(obj, key)

    • delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。

    • Vue.delete 直接删除了数组 改变了数组的键值。

    • arr[0,1,2,3],使用delete删除第二个元素,剩下的元素键值和原数组一样。arr[0]=0;arr[2]=2;arr[3]=3;此处a[1]是empty

    • 使用vue.delete删除会自动往前填充 为:arr[0]=0;arr[1]=2;arr[2]=3

10. 自定义指令

建议看这篇文章8个非常实用的Vue自定义指令

11. 插槽的使用以及原理?

建议看我这篇文章「Vue源码学习」你真的知道插槽Slot是怎么“插”的吗

A.匿名插槽(也叫默认插槽): 没有命名,有且只有一个;

// 父组件调用子组件
<todo-list> 
    <template v-slot:default>
       任意内容
       <p>我是匿名插槽 </p>
    </template>
</todo-list> 
​
// 子组件todoList.vue
<slot>我是默认值</slot>
//v-slot:default写上感觉和具名写法比较统一,容易理解,也可以不用写

B.具名插槽: 相对匿名插槽组件slot标签带name命名的;

// 父组件,调用的子组件
<todo-list> 
    <template v-slot:todo>
       任意内容
       <p>我是匿名插槽 </p>
    </template>
</todo-list> 
​
//子组件
<slot name="todo">我是默认值</slot>

C.作用域插槽: 父组件可以拿到子组件的数据

// 父组件
<todo-list>
 <template v-slot:todo="slotProps" >
   {{slotProps.user.firstName}}
 </template> 
</todo-list> 
//slotProps 可以随意命名
//slotProps 接取的是子组件标签slot上属性数据的集合所有v-bind:user="user"// 子组件
<slot name="todo" :user="user" :test="test">
    {{ user.lastName }}
</slot> 
data() {
    return {
      user:{
        lastName:"Zhang",
        firstName:"yue"
      },
      test:[1,2,3,4]
    }
  },
// {{ user.lastName }}是默认数据  v-slot:todo 当父页面没有(="slotProps")

12. 为什么不建议用index做key,为什么不建议用随机数做key?

举个例子:

<div v-for="(item, index) in list" :key="index">{{item.name}}</div>
list: [
    { name: '小明', id: '123' },
    { name: '小红', id: '124' },
    { name: '小花', id: '125' }
]
渲染为
<div key="0">小明</div>
<div key="1">小红</div>
<div key="2">小花</div>
​
现在我执行 list.unshift({ name: '小林', id: '122' })
渲染为
<div key="0">小林</div>
<div key="1">小明</div>
<div key="2">小红</div>
<div key="3">小花</div>
​
新旧对比
<div key="0">小明</div>  <div key="0">小林</div>
<div key="1">小红</div>  <div key="1">小明</div>
<div key="2">小花</div>  <div key="2">小红</div>
                         <div key="3">小花</div>
可以看出,如果用index做key的话,其实是更新了原有的三项,并新增了小花,虽然达到了渲染目的,但是损耗性能
​
现在我们使用id来做key,渲染为
<div key="123">小明</div>
<div key="124">小红</div>
<div key="125">小花</div>
现在我执行 list.unshift({ name: '小林', id: '122' }),渲染为
<div key="122">小林</div>
<div key="123">小明</div>
<div key="124">小红</div>
<div key="125">小花</div>
​
新旧对比
                           <div key="122">小林</div>
<div key="123">小明</div>  <div key="123">小明</div>
<div key="124">小红</div>  <div key="124">小红</div>
<div key="125">小花</div>  <div key="125">小花</div>
可以看出,原有的三项都不变,只是新增了小林这个人,这才是最理想的结果

index和用随机数都是同理,随机数每次都在变,做不到专一性,也很消耗性能

13. nextTick的用处

当你修改了data的值然后马上获取这个dom元素的值,是不能获取到更新后的值,你需要使用$nextTick这个回调,让修改后的data值渲染更新到dom元素之后在获取,才能成功。

<div ref="testDiv">{{name}}</div>
​
name: '小林'this.name = '林三心'   //修改变量
console.log(this.$refs.testDiv.innerHTML) // 这里是啥呢

答案是“小林”,前面说了,Vue是异步更新,所以数据一更新,视图却还没更新,所以拿到的还是上一次的旧视图数据(dom更新前)

this.name = '林三心'
this.$nextTick(() => {
    console.log(this.$refs.testDiv.innerHTML) // 林三心
})

14. Vue的SSR是什么?有什么好处?

  • SSR就是服务端渲染
  • 基于nodejs服务环境开发,所有html代码在服务端渲染
  • 将渲染后的数据(html)返回给前端,然后前端进行“激活”,即可成为浏览器识别的html代码
  • SSR优化了首次主页面加载、防止爬虫

15.Vue中双向数据绑定是如何实现的?

详细请看这里:Vue中的观察者与发布订阅

要保持数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;

  1. 数据监听器observer:采用数据劫持,通过Object.defineProperty()的getter函数将各个属性用Watcher类来监听,每个Watcher对应一个属性,将所有的Watcher类添加到Dep类(一个容器,用于消息管理)。当某个属性改变时,触发setter函数,对属性值进行修改,并重新将新的属性值添加Watcher类添加到Dep类中,然后dep.notify()执行compiler绑定的更新函数
  2. 指令解析器compiler:对每一个元素节点的指令进行解析,根据指令模板替换数据,以及绑定相应的更新函数
  3. watcher:作为observer和compiler的桥梁,能够订阅并收到每个属性的变动通知,执行指令绑定的相应回调函数,从而更新视图
class Dep{
    constructor(){
        this.subs = [] //存放所有的 watcher
    }
    //订阅
    addSub(watcher){ //添加 watcher
        this.subs.push(watcher)
    }
    //发布
    notify(){
        this.subs.forEach(watcher=>watcher.updata());
    }
}
class Observer{
    constructor(data) {
        this.observer(data);
    }
    observer(data){
        //如果是对象才观察
        if(data && typeof data === 'object'){
            
            for (let key in data) { //循环 data 中的所有子项
                this.defineReactive(data,key,data[key]);
            }
        }
    }
    //实现数据劫持
    defineReactive(obj,key,value){
        this.observer(value); //如果传进来的参数是对象,就回调一下这个函数,就是一个递归函数
        
        let dep = new Dep(); //给每一个属性都添加一个具有发布和订阅的功能
        
        Object.defineProperty(obj,key,{
            get(){
                //创建watcher时,会获取到对应的内容 并且把watcher放到了全局上
                Dep.target && dep.addSub(Dep.target);
                return value;
            },
            set: (newVal)=>{
                if(value !== newVal){
                    this.observer(newVal); //给设置的新值也加上 get set 方法
                    value = newVal;
                    dep.notify(); //执行观察者更新时的函数
                }
                
            }
        })
    }
}

16.单页面应用和多页面应用区别及优缺点

单页面应用(SPA),通俗说就是指只有一个主页面的应用

  • 浏览器一开始要加载所有必须的 html, js, css,初次加载耗时多。
  • 所有的页面内容都包含在这个所谓的主页面中。
  • 单页面的页面跳转,由路由程序动态载入,仅刷新局部资源。
  • 多应用于pc端。

单页面首次加载主页面慢:可通过安装动态懒加载所需插件;使用CDN资源。

单页面应用(SinglePage Web Application,SPA)多页面应用(MultiPage)
组成一个主页面多个完整页面构成
资源共用(css,js)共用,浏览器一开始要加载所有必须的 html, js, css不共用,每个页面都有自己的css、js
刷新方式页面局部刷新或更改整页刷新
url 模式a.com/#/pageone a.com/#/pagetwoa.com/pageone.html a.com/pagetwo.html
转场动画容易实现、页面片段间切换快,用户体验良好无法实现,页面切换加载缓慢,流畅度不够,用户体验比较差
数据传递容易,组件之间传值依赖 url传参、或者cookie 、localStorage等
搜索引擎优化(SEO)需要单独方案、实现较为困难、不利于SEO检索 可利用服务器端渲染(SSR)优化实现方法简易
试用范围高要求的体验度、追求界面流畅的应用适用于追求高度支持搜索引擎的应用
开发、维护成本开发成本较高,常需借助专业的框架,维护相对容易开发成本较低 ,但页面重复代码多,维护相对复杂

17.Vue-router跳转和location.href有什么区别

使用location.href='/url'来跳转,简单方便,但是刷新了页面;

使用location.href实现页面div块的快速定位
location.href='#divClass'//<div id = "divClass"></div>,通过事件直接跳转到该div
location.href可直接获取当前路径
parent.location.href跳转至上一层页面
top.location.href跳转至最外层页面

使用history.pushState('/url'),无刷新页面,静态跳转;需要服务端配合

18.请说下封装 vue 组件的过程?

  1. 建立组件的模板,先把架子搭起来,写写样式,考虑好组件的基本逻辑。
  2. 准备好组件的数据输入。即分析好逻辑,定好 props 里面的数据、类型。
  3. 准备好组件的数据输出。即根据组件逻辑,做好暴露出来的方法(this.$emit)。

19.vue初始化页面闪动问题

  • 加载时遇到{{value.name}}闪烁,是因为你在渲染时是这么写的<p>{{value.name}}</p>
  • 加载时遇到一个空的盒子里边什么也没有,是因为你在渲染时是这么写的<p v-html="value.name"></p>

解决办法

v-cloak并不需要添加到每个标签,只要在el挂载的标签上添加就可以,这是最简单有效的办法

//app.vue
<div class="#app" v-cloak>
    <p>{{value.name}}</p>
</div>
//css
[v-cloak] {
    display: none;
}
//这样就可以防止页面闪烁了。

但是有的时候会不起作用,可能的原因有二:

  • v-cloak的display属性被层级更高的给覆盖掉了,所以要提高层级
[v-cloak] {
    display: none !important;
}
  • 样式放在了@import引入的css文件中(传统的开发方式) v-cloak的这个样式放在@import 引入的css文件中不起作用,可以放在link引入的css文件里或者内联样式中

冷门的知识点

1. 如果子组件改变props里的数据会发生什么

  • 改变的props数据是基本类型

如果修改的是基本类型,则会报错

props: {
    num: Number,
  }
created() {
    this.num = 999
  }

0458e2ff1538ee85d42953cec9a94ca.png

  • 改变的props数据是引用类型
props: {
    item: {
      default: () => {},
    }
  }
created() {
    // 不报错,并且父级数据会跟着变
    this.item.name = 'sanxin';
    
    // 会报错,跟基础类型报错一样
    this.item = 'sss'
  },
复制代码

2. props怎么自定义验证

props: {
    num: {
      default: 1,
      validator: function (value) {
          // 返回值为true则验证不通过,报错
          return [
            1, 2, 3, 4, 5
          ].indexOf(value) !== -1
    }
    }
  }

3. watch监听一个对象时,如何排除某些属性的监听

下面代码是,params发生改变就重新请求数据,无论是a,b,c,d属性改变

data() {
    return {
      params: {
        a: 1,
        b: 2,
        c: 3,
        d: 4
      },
    };
  },
watch: {
    params: {
      deep: true,
      handler() {
        console.log('属性变化执行的内容')
      },
    },
  }

但是如果我只想要a,b改变时重新请求,c,d改变时不重新请求呢?

mounted() {
  const handler = function(){
    console.log('属性变化执行的内容')
  }
  Object.keys(this.params)
    .filter(item1=> !["c","d"].includes(item1))//不包含c,d属性
    .forEach(item2=> {
      this.$watch(vm => vm.params[item2], handler, {
        deep: true
      });
    });
}

5. 审查元素时发现data-v-xxxxx,这是什么

image.png

这是在标记vue文件中css时使用scoped标记产生的,因为要保证各文件中的css不相互影响,给每个component都做了唯一的标记,所以每引入一个component就会出现一个新的'data-v-xxx'标记

6. computed如何实现传参?

// html
<div>{{ total(3) }}
​
// js
computed: {
    total() {
      return function(n) {
          return n * this.num
         }
    },
}

7. vue的hook的使用

  • 同一组件中使用

这是我们常用的使用定时器的方式

juejin.cn/post/698722…

export default{
  data(){
    timer:null  
  },
  mounted(){
      this.timer = setInterval(()=>{
      //具体执行内容
      console.log('1');
    },1000);
  }
  beforeDestory(){
    clearInterval(this.timer);
    this.timer = null;
  }
}

缺点:

  • clearInterval 后没有清空 timer 为 null。
  • 开启定时器和清除定时器的代码分散开在两个地方,有损可读性/维护性,用尤大大的话说,这使得我们比较难于程序化地清理我们建立的东西。
  • timer 被定义在 data 里,实际上 timer 不需要什么响应式操作,定义在 data 里是没必要的,反而造成性能浪费。
使用 hook 监听 beforeDestroy 生命周期,这样 timer 只需被定义在生命周期里
export default{
  methods:{
    fn(){
      const timer = setInterval(()=>{
        //具体执行代码
        console.log('1');
      },1000);
      this.$once('hook:beforeDestroy',()=>{
        clearInterval(timer);
        timer = null;
      })
    }
  }
}

问题:在后台系统中,我们常常会设置页面缓存,而当路由被 keep-alive 缓存时是不走 beforeDestroy 生命周期的。借助 activated 和 deactivated 这两个生钩子

export default {
  data() {
    return {
    }
  },
  
  mounted() {
    let timer = setInterval(() => {
      console.log('setInterval')
    }, 2000)
    this.$on('hook:activated', () => {
      if (timer === null) { // 避免重复开启定时器
        timer = setInterval(() => {
          console.log('setInterval')
        }, 2000)
      }
    })
    this.$on('hook:deactivated', () => {
      clearInterval(timer)
      timer = null
    })
  }
}
由于缓存原因,所以需要用 $on 而不是 $once,不然执行一次后就不会再触发了。
  • 7.2 父子组件使用

如果子组件需要在mounted时触发父组件的某一个函数,平时都会这么写:

//父组件
<rl-child @childMounted="childMountedHandle"/>
method () {
  childMountedHandle() {
  // do something...
  }
},
​
// 子组件
mounted () {
  this.$emit('childMounted')
},

使用hook的话可以更方便:

//父组件
<rl-child @hook:mounted="childMountedHandle"/>
method () {
  childMountedHandle() {
  // do something...
  }
},

8. provide和inject是响应式的吗

作用:用于父组件向子孙组件传递数据

使用方法:provide在父组件中返回要传给下级的数据,inject在需要使用这个数据的子辈组件或者孙辈等下级组件中注入数据。

// 祖先组件
provide(){
    return {
   // keyName: { name: this.name }, // value 是对象才能实现响应式,也就是引用类型
      keyName: this.changeValue // 通过函数的方式也可以[注意,这里是把函数作为value,而不是this.changeValue()]
   // keyName: 'test' value 如果是基本类型,就无法实现响应式
    }
  },
data(){
  return {
    name:'张三'
}
  },
  methods: {
    changeValue(){
        this.name = '改变后的名字-李四'
    }
  }  
  
  // 后代组件
  inject:['keyName']
  create(){
    console.log(this.keyName) // 改变后的名字-李四
}

9.Vue的el属性和$mount优先级?

比如下面这种情况,Vue会渲染到哪个节点上

new Vue({
  router,
  store,
  el: '#app',
  render: h => h(App)
}).$mount('#ggg')

这是官方的一张图,可以看出el$mount同时存在时,el优先级 > $mount

image.png

10. 动态指令和参数使用过吗?

<template>
    ...
    <aButton @[someEvent]="handleSomeEvent()" :[someProps]="1000" />...
</template>
<script>
  ...
  data(){
    return{
      ...
      someEvent: someCondition ? "click" : "dbclick",
      someProps: someCondition ? "num" : "price"
    }
  },
  methods: {
    handleSomeEvent(){
      // handle some event
    }
  }  
</script>

11. 相同的路由组件如何重新渲染?

开发人员经常遇到的情况是,多个路由解析为同一个Vue组件。问题是,Vue出于性能原因,默认情况下共享组件将不会重新渲染

const routes = [
  {
    path: "/a",
    component: MyComponent
  },
  {
    path: "/b",
    component: MyComponent
  },
];

如果依然想重新渲染,怎么办呢?可以使用key

<template>
    <router-view :key="$route.path"></router-view>
</template>

12. 如何将获取data中某一个数据的初始状态?

在开发中,有时候需要拿初始状态去计算

可以通过this.$options.data().xxx来获取初始值

data() {
    return {
      num: 10
  },
mounted() {
    this.num = 1000
  },
methods: {
    howMuch() {
        // 计算出num增加了多少,那就是1000 - 初始值
        console.log(1000 - this.$options.data().num)
    }
  }

总结

觉得写得好的,对你有帮助的,可以分享给身边人,知识越分享越多,千万不要吝啬呀

后续更新前端其它知识总结,请关注我,整理好,分享给你们,我们一起学前端