Bootstrap

Vue 2 - 常见面试题汇总大全

一 基础知识

1.vue 的生命周期

1-1 钩子函数

创建

beforecreate:

第一个钩子,这个阶段的data,methods,computed以及watch的数据和方法不能被访问

created:

是实例创建完成后发生的。这个阶段完成数据观测,可以使用数据,更改数据,
无法与Dom进行交互,想要的话可以通过nextTick来访问 ,nexttick延迟回调,更新数据后立即操作dom

挂载

beforeMount:

发生在页面渲染之前,当前阶段虚拟Dom已经创建完成,即将开始渲染。
在此时也可以对数据进行更改,不会触发updated。

mounted:

在挂载完成后发生,在当前阶段,真实的Dom挂载完毕,数据完成双向绑定,
可以访问到Dom节点,使用$refs属性对Dom进行操作。

补:第一次页面加载就会触发 创建和挂载钩子

更新

beforeUpdate:

发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,
你可以在当前阶段进行更改数据,不会造成重渲染。

updated:

发生在更新完成之后,当前阶段组件Dom已完成更新。
要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。

补充

activited:

keep-alive专属,组件被激活时调用

deactivited:  
 
keep-alive专属,组件被销毁时调用

销毁

beforeDestroy:

发生在实例销毁之前,在当前阶段实例完全可以被使用,
我们可以在这时进行善后收尾工作,比如清除计时器

destroyed:

发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。

1-2 钩子函数的使用方法

1.beforecreate:  data 和 $el 都没有初始化 全部为 undefined,
可以在加个loading事件,在加载实例时触发 ,

2.created:  初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用

3.mounted: 挂载元素,获取到dom节点

4.beforeUpdate 可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。

5.updated: 可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,
因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。

6.beforeDestroy : 可以做一个确认停止事件的确认框

7.destroyed 可以执行一些优化操作,清空定时器,解除绑定事件

1-3 父子组件生命周期构造函数执行顺序

加载渲染过程

父 beforecreate,created,beforeMount 
子 beforecreate,created,beforeMount
子 mounted 
父 mounted 

子组件更新过程

父 beforeUpdate
子 beforeUpdate
子 Updated
父 Updated

销毁过程

父 beforeDestroy
子 beforeDestroy
子 destroyed
父 destroyed

如果常用的数据如果需要传递到子组件的话,最好在created 或者 beforemount,把数据请求回来,然后传递给子组件

2 v-show 与 v-if v-for

2-1.v-show 与 v-if 的区别

共同:
根据真假切换元素的显示状态

不同:

v-if : 通过操控DOM值来 实现 显示隐藏

(不适合频繁切换. 数据多不建议用if.每一次切换则重新消耗性能.乱) 
v-if是惰性的,只有当条件为真时才会真正渲染标签;如果初始条件不为真,则v-if不会去渲染标签。

回流

v-show :修改元素的display,实现显示隐藏

(适合频繁切换,在第一次渲染的时候已经消耗了性能)
v-show则无论初始条件是否成立,都会渲染标签,它仅仅做的只是简单的CSS切换

重绘

补:
在这里插入图片描述

2-2 v-if 和v-for的优先级

当v-if 与 v-for 一起使用时,v-for 比v-if 优先级高,如果连用的话会把 v-if 给每个元素都添加一下,会造成性能问题

所以不推荐v-if和v-for在同一个标签中同时使用。

原因:先有循环才能渲染

解决方法:
1、ul 和 li 搭配使用,或者是渲染父级标签下的子标签。

<ul v-if = "state">
  <li v-for = "(item,id) in list" :key="id"></li>
</ul>

2,将v-if写成和v-for同级

<div v-for='(item,id) in list' :key='id' v-if='item.userRank'>

3、使用过滤器将v-if中的判断转移到computed的计算属性中。

<ul>
   <li v-for="(item,id) in list" :key = "id"></li>
</ul>
computed: {
  formList: function(){
     return this.list.filter(function(item){
        return item.state;
    })
  }
}

补:过滤器
filter()方法 对原数组的元素进行过滤,返回到一个新的数组中去。不影响原始的数组。、callback需要返回布尔值

为true的时候,对应的元素留下来,
为false的时候,对应的元素过滤掉

3.组件通信有哪些方式?

3-1.父传子
思路:

1.传递:在父组件调用子组件的位置 绑定属性
(属性名:需要发送的变量名)

2.接收:子组件通过 **props**接收
**(props: [属性名])**
type: Number, 数据类型
required: true , 必须传值
default: 100 默认携带的数字

  props: {
    provinceNumId: {
      type: String,
      required: true
    },
    list: {
      type: Array,
      default() {
        return [];
      }
    },
    studentModalData: {
      type: Object,
      default() {
        return {}
      }
    }
 }

3-2.子传父

思路:

1.**传递:** 在调用子组件的位置 添加自定义事件 使用$emit 发送数据

2.**接收:** 通过created 接收数据

3-3.兄弟传值

思路:

1.定义中央事件总线  **let bus = new Vue();**

2.**传递**:  触发自定义事件 并发送 **bus.emit** (要出发的事件名,要发送的数据) 

3.**接收**:  触发mounted  并接收 **bus.on**(要出发的事件名,要接收的数据) 

4.**销毁**:  在beforeDestroy中 **bus.$off** 进行销毁,否则会出现重复触发事件的问题

3-4.公共传递

思路:

1.**传递:**在父组件内部添加 **provide**:{命名:'内容'} 并赋明需要具体传递的值,

2.**接收:**在子或者子子 内部 **inject:**["命名"] 接收

3.在相对应页面中可直接获取

补:组件传值代码参考:
https://blog.csdn.net/weixin_55042716/article/details/114434354

4 computed和watch ,有什么区别

4-1.computed 计算属性

1. 支持缓存,只在依赖的数据发生变化时,才会重新计算,否则当多次调用**computed**属性时,调用的其实是缓存;
2. 不支持异步,当**computed**内有异步操作时无效,无法监听数据的变化
3. 如果一个数据需要经过复杂计算就用 **computed**
4. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,(某个属性受多个属性影响)
var var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

4-2.watch 侦听器

1. 不支持缓存,每调用一次就计算一次;
2. **watch**支持异步;
3. 如果一个数据需要被监听并且对数据做一些操作就用 watch
4. 当一个属性发生变化时,需要执行对应的操作;一对多(监听多个数据)
<div id="app">
    <input type="text" v-model="num">
</div>
<script>
    new Vue({
        el: '#app',
        data: {
            num: ''
        },
        watch: {
        	firstName: {
    			handler(newName, oldName) {
      				this.fullName = newName + ' ' + this.lastName;
    			},
   				 // 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法
    			immediate: true,
    			// 开启深度监听:只要firstName中的任何一个属性发生改变,都会触发相应的代码
          		deep: true
  			},
            num(newVal, oldVal) {
            // 监听 num 属性的数据变化
    		// 作用 : 只要 num 的值发生变化,这个方法就会被调用
    		// 第一个参数 : 新值
    		// 第二个参数 : 旧值,之前的值
                console.log('oldVal:',oldVal)
                console.log('newVal:',newVal)
            }
        }
    })

</script>

4-2-1. 问:watch怎么开启深度监听,具体使用场景

Deep:
监听的是对象类型,当手动修改对象的某个属性时,发现是无效的。
设置deep:true 就可以发现对象内部值得变化,所以需要深度监听

Immediate:
immediate:true,代表watch里面声明了之后会立马执行handler里面的函数。

补4-3.methods 方法

1. 不支持缓存, 只要使用该方法便进行调用。

5 Vue中的 data 为什么必须是函数

vue组件中data值不能为对象,因为对象是引用类型,每个组件的data都是内存的同一个地址,如果修改一个对象里面的数据,其他的也会被影响到,
data 如果是一个函数,每一个函数都有自己的局部作用域,他改变的话不会影响到其他的数据。

6 Vue中的 key 有什么作用?

列表渲染时的唯一性

页面上标签都对应具体的虚拟dom对象(JS对象),循环中,如果没有唯一的key,页面上删除一条标签,由于不知道删除的是哪一条,需要吧全部的虚拟dom重新渲染,如果知道key,为标签被删除掉,只需要吧渲染的dom为标签删除即可

6-1.添加减少值
例:
[1,2,3,4,5,]
添加一个a
[1,2,a,3,4,5,]

不加key  则 3,4,5 会重新渲染一遍      a成3 ,3成4, 4成5

加key   则只单独渲染新插入的值

减少
[1,2,4,5,]

加key,如果有一项移出,后面的不会错位,用于删除操作

6-2 条件渲染中的key
不加key:
vue无法判断每个组件的不同,则会重复使用相同的组件,会拿到上一个组件的信息,

加key:     
对于vue来说,每个组件都是不同的,切换时,都会重新渲染,
(如果不重新渲染的话,不会触发生命周期函数,没法请求新的数据)

6-3 总结:
无:key属性时,状态默认绑定的是位置; 删除所有,重新渲染,
有:key属性时,状态根据key的属性值绑定到了相应的数组元素。删除某一条不会重新渲染,节省性能,

6-4 建议:
不使用index作为key的值,使用唯一id作为标识.(后台提供或自己创建)

7 vue 路由

通过互联的网络把信息从 (源地址) 传输到 (目的地址) 的活动
映射表:地址和具体某一台电脑之间的关系

7-1 vue-router 路由模式有几种?

1.hash路由 – 默认模式

在地址栏会有一个#号,
因为hash发生变化的url都会被浏览器记录下来,浏览器的前进后退都可以用了,同时点击后退时,页面字体颜色也会发生变化。
这样一来,尽管浏览器没有请求服务器,但是页面状态和url一一关联起来,
后来人们给它起了一个霸气的名字叫前端路由,成为了单页应用标配。

2.history路由
有的back、forward、go 方法

history ——利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法。
提供了对历史记录进行修改的功能。它能让开发人员在不刷新网页的情况下改变站点的 URL

History 有 URL 重定向问题,需要在服务端去配置 url 重定向,否则会报 404 错误。

404

history模式下,**前端的url必须和实际向后端发起请求的url 一致**,
如 http://www.abc.com/book/id 。如果后端缺少对/book/id 的路由处理,将返回404错误。

3.Abstract

abstract 模式针对的是没有浏览器环境的情况,比如 Weex 客户端开发,内部是没有浏览器 API 的,
那么 Vue-Router 自身会对环境做校验,强制切换到 abstract 模式,
如果默认在 Vue-Router 的配置项中不写 mode 的值,
在浏览器环境下会默认启用 Hash 模式,在移动客户端下使用 abstract 模式。

4.将hash模式修改为history模式

const router = new VueRouter({
	routes,
	mode:'history'
})

7-3 router和route的区别

1 $router

用来操作路由  只写对象
提供了一些方法 go back 

2 $route

用来获取路由的信息  只读对象
存放路径 path params参数  query 
//操作 路由跳转
this.$router.push({
      name:'hello',
      params:{
          name:'word',
          age:'11'
     }
})
//读取 路由参数接收
this.name = this.$route.params.name;
this.age = this.$route.params.age;

7-4 params传参 跟query传参

7-4-1 query :
this.$route.query.xxx

query  传参使用path,name来引入路由。

query  是拼接在url后面的参数,没有在路由后面添加参数名也没关系。

query  相当于get请求,页面跳转的时候,可以在地址栏看到请求参数,

7-4-2 params :
this.$route.params.xxx

params 只能用name来引入路由

params 是路由的一部分,必须要在路由后面添加参数名。

params 相当于post请求,参数不会再地址栏中显示。

刷新页面之后参数会消失
路由里就要这样写

   this.$router.push({name:'Detail',params:{pro_id:pro_id}});

router - index.js 路由设置,参数名保持一致。

	{
      path: '/Detail/:pro_id', //配置参数,防止刷新数据丢失
      name: 'Detail',
      component: Detail
    },

7-4-3 props

在路由对象内,通过props属性 开启传参功能
在组件对象内 通过props接收
原因: 为了降低路由和组件的耦合度,

路由页面,设置   props:true
页面   props:['id']
在子detial中设置
 {
  path:'/detial:id',
  name:'detail',
  components:{
    default:()=> import(/* webpackChunkName: "pro" */'@/views/detail/detail.vue')
  },
  props:{
    default:true
  }
 }

7-5 路由跳转

7-5-1导航模式

<router-link to="path">xxx</router-link>

    to 要跳转到的路由规则 string|object 
    to="users" 
    :to="{path:'path'}"  
    tag  可以指定渲染成什么组件
    replace 不会留下history记录,浏览器返回前进按钮不能用
    

7-5-2 导航模式
可以跟点击事件一起用

this.$router.push("/login"); 
this.$router.push({ path:"/login" }); 
this.$router.push({ path:"/login",query:{username:"jack"} });
this.$router.push({ name:'user' , params: {id:123} }); 
this.$router.go( n );//n为数字 负数为回退

7-5-3 路由重定向

概念:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面

实现: 通过路由规则的 **redirect** 属性,指定一个新的路由地址,可以很方便地设置路由的重定向

7-5-4 嵌套路由

在路由下 添加子路由

在父路由组件的模板内容中添加子路由链接和子路由填充位(占坑),同时在路由规则处为父路由配置children属性指定子路由规则:

routes: [ 
 { 
    path: "/user", 
    component: User, //这个不能丢 
    // 通过children属性为/user添加子路由规则 
    children:[
        { path: "index", component: Index }, 
        { path: "add", component: Add }, 
    ] 
  } 
]
需要在 User组件中定义一个router-view 用于嵌套路由的渲染显示
<router-view></router-view>

7-6 路由守卫

导航守卫 就是路由跳转过程中的一些 钩子函数 ,这个过程中触发的这些函数能让你操作一些其它事情时,可以进行过滤操作,这就是导航守卫。

to: 目标路由
from: 当前路由
next() 跳转 一定要调用
next(false);//不让走
next(true);//继续前行
next(/login’)//走哪
next({path:/login’,params:{},query:{}})//带点货

全局前置守卫 beforEach

   router.beforeEach((to, from, next) => {
        document.title = to.meta.title || '卖座电影';
        if (to.meta.needLogin && !$store.state.isLogin) {
            next({
                path: '/login'
            })
        } else {
            next()
        }
    })

路由独享的守卫 beforeEnter
使用方法与全局守卫相同 不同的是:全局守卫可以作用于全局,路由独享守卫只作用于被设置守卫的路由

//登录模块
    path: '/login',
    component: () => import('@/views/login'),
    beforeEnter: (to, from, next) => {
       if (to.meta.needLogin && !$store.state.isLogin) {
           next({
               path: '/login'
           })
        } else {
           next()
        }
     }

组件路由守卫

//组件内部钩子
beforeRouteEnter (to, from, next) {//前置
  // 不!能!获取组件实例 `this`
  // 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
  // 在当前路由改变,但是该组件被复用时调用
  // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
  // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
  // 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {//后置
  // 导航离开该组件的对应路由时调用
  // 可以访问组件实例 `this`
}

注意: 路由独享守卫,守的是path 组件内部守卫,守的是component

8 路由懒加载

8-1.为什么需要懒加载?

像vue这种单页面应用,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,
需要加载的内容过多,时间过长,会出啊先长时间的白屏,即使做了loading也是不利于用户体验,

而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,
减少首页加载用时

8-2.如何实现懒加载

方法一 resolve

这一种方法较常见。它主要是使用了resolve的异步机制,用require代替了import,实现按需加载,下面是代码示例

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

export default new Router({
  routes: [
//     {
//       path: '/',
//       name: 'HelloWorld',
//       component: HelloWorld
//     }
//   更改
        {
          path: '/',
          name: 'HelloWorld',
          component: resolve => require(['@/components/HelloWorld'], resolve)
        }
  ]
}) 

方法二.import按需加载(官方写法)

能够被webpack自动代码分割允许将不同的组件打包到一个异步块中,使用命名chunk(特殊注释语法)。

Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。

const comA = () => import('url')
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

方法三 webpack提供的require.ensure()
vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。
这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。

{  
    path: '/home',   name: 'home',   
    component: r =>require.ensure([], () => r(require('@/components/home')), 'demo')
 }

9 vue的内置组件 keep-alive -优化

页面切换的时候会进行销毁,当我们不想它被销毁的话就需要用keep-alive包裹着组件
在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间 及 性能消耗,提高用户体验性

使用:将要被缓存的组件或可能会被重复渲染的组件,使用 **keep-alive**包裹即可

渲染场景 例:

首页下拉刷新,加载过的数据,切换其他页面后,再返回首页,没有记录,则数据重新请求,
添加 keep-alive 包裹后,记录数据,不会再重复加载。 切换另一个页面,上一个页面依旧保持活跃,
数据继续保持

应用 :
根据条件缓存页面

利用路由的元数据meta 添加

APP.js页面

 <keep-alive>
    <!-- 添加meta的则添加 keep-alive -->
    <router-view v-if="$route.meta.keep"></router-view>
 </keep-alive>
    <!-- 不添加 -->
    <router-view v-if="!$route.meta.keep"></router-view>
路由页面给需要的添加meta
{
    path: '/',
    name: 'home',
    alias: '/home',
    components:{ home },
    meta:{
      keep:true //需要缓存
    }
  },
  

补充:

生命周期函数
  1. activated
     在 keep-alive 组件激活时调用
     该钩子函数在服务器端渲染期间不被调用

  2. deactivated
在 keep-alive 组件停用时调用   
该钩子在服务器端渲染期间不被调用   
被包含在 keep-alive 中创建的组件,会多出两个生命周期的钩子:activated与deactivated   
使用 keep-alive 会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,
需要在 activated 阶段获取数据,承担原来 created 钩子函数中获取数据的任务。  
 
注意: 只有组件被 keep-alive 包裹时,这两个生命周期函数才会被调用,
如果作为正常组件使用,是不会被调用的,以及在 2.1.0 版本之后,使用 exclude 排除之后,
就算被包裹在 keep-alive 中,这两个钩子函数依然不会被调用!
另外,在服务端渲染时,此钩子函数也不会被调用。

10 vue 的混入 mixin -优化

公共配置
多个组件中都需要的数据或功能,混入对象内部的选项等同于组件对象的选项,但是混入对象并不是真正的组件对象,必须添加到组件对象内部后才能执行,

重复方法或重复数据

--- 对象类合并   
--- 函数类  将多个函数放在数组中,都执行   
--- 重复的数据 以组件内部的为准,    

语法:

---定义mixin 对象
--- 引入 mixin
--- 使用
 <div id="app">
     <button @click='hello'>测试</button>
 </div>
    
<script>
var mymixin ={
   methods:{
      hello:function(){
        console.log('进入了mymixin了')
      }
   }
}

//进入 vue编程中
 const app = new Vue({
    el:'app',
    mixins:[mymixin],
    data:{}
 })
</script>

11-1. solt 插槽

组件的最大特性就是 重用 ,而用好插槽能大大提高组件的可重用能力
插槽的作用:父组件向子组件传递内容。

image.png

通俗的来讲,插槽无非就是在 子组件 中挖个坑,坑里面放什么东西由 父组件 决定

11-2 匿名插槽
匿名插槽一般就是使用单个插槽

//子组件内部
<div class='banner'>
    <img v-for='item in imgs' :src='item.src' :key='item.bannerId'>
    //占了个位,
    <solt>
    //插槽内部设置了默认值。如果将来插槽没有被插入组件或者元素,就用自己内部的元素来渲染
       <button>组件内部</button>
    </solt>
</div>
//父组件 
<banner>
//如果这里没有设置值,则显示插槽中的内容,
//设置例了值则显示此处的值
   <button> &lt; </button>
<banner>

11–3 具名插槽

slot 元素可以用一个特殊的特性 name 来进一步配置如何分发内容。多个插槽可以有不同的名
字,具名插槽将匹配内容片段中有对应 slot 特性的元素。

具名插槽存在的意义就是为了解决在单个页面中同时使用多个插槽。
如果没有起名的话,默认为default

//子组件
<div class='banner'>
    <img v-for='item in imgs' :src='item.src'>
    <solt name="but">
       <button>组件内部</button>
    </solt>
</div> 
//父组件
<banner>
   <template v-solt:but>
  //简写 <template #but>
       <button> &lt; </button>
   </template>
<banner>

11–4 作用域插槽

应用场景:父组件对子组件的内容进行加工处理
作用域插槽是一种特殊类型的插槽,作用域插槽会绑定了一套数据,父组件可以拿这些数据来用,
于是,情况就变成了这样:样式父组件说了算,但父组件中内容可以显示子组件插槽绑定的数据。

//子组件
 <div>
    <slot text="我是子组件中的内容"></slot>
 </div>
//父组件
<banner> 
    <div slot-scope="props">
        <div>父组件</div>
        <h3>{{ props.text }}</h3>
    </div>
</banner>

作用域插槽 相对于匿名插槽 加上 匿名插槽里面携带的信息

12. nextTick

在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,
(就是当页面出来的时候,可能静态资源还没配置完成,这个时候要用nexttick延迟回调,更新数据后立即操作dom)

1)在**created**生命周期中进行dom操作,例如created中通过**$refs**获取子组件的属性或方法。
(相当于mounted里操作)

2)在某个数据改变引起的dom更新后 再进行的操作

总结:在下一次dom更新循环结束之后执行延迟回调,在修改数据之后使用这个方法,立即更新dom.

13.对vuex的理解

集中式的存储管理 应用程序中所有组件的状态
需要多个组件共享的变量全部存储在一个对象里面    
然后,将这个对象放在顶层的vue实例中,让其他组件可以使用 

13-1 为什么使用它?

1. vue单向数据流 父级 prop 的更新会向下流动到子组件中,如果组件嵌套过深 通过props层层往下传比较繁琐,
在传的过程中容易出现对数据的更改,导致数据紊乱

3. 如果在一个项目很庞大各个,组件会共用一个状态,所以用到vuex,即可在整个Vue项目的组件中使用。

13-2.五大核心属性
概念:

state  是状态管理器的状态中心,里面是初始化的数据  不可以直接操作Store 中的数据

mutation 同步操作,改变store的数据    变更Store 数据,
通过commit 提交方法,this.$store.commit('xxx')

action 异步操作,改变store的数据,  让motation中的方法能在异步操作中起作用   
通过store.dispatch 来分发actions 中通过commit提交mutations,进行修改数据

getter 是状态管理器中的计算属性  主要用来过滤一些数据,可以在多组件之间复用

module  将 store 分割成模块,每个模块都具有state,mutation、action、getter、甚至是嵌套子模块


state 是状态管理器的状态中心,里面是初始化的数据

export default new Vuex.store({
    state:{
        count:0
    }   
})
//使用页面:
<h1>{{ this.$store.state.count }}</h1>

mutation 是唯一改变数据的方式 这里面写 同步代码

export default new Vuex.store({
  state:{
      count:0
    },
  mutations:{
    add(state,step){
       state.count =state.count+step
    }
  }
})

//使用页面
<button @click='add1'> mutations测试</button>
methods:{
   add1(){
     this.$store.commit('add',1)
   }
}
// 每次点击 增加 1

actions 就是处理异步操作,让motation中的方法能在异步操作中起作用

export default new Vuex.store({
  state:{
     count:0
  },
  mutations:{
    add(state,step){
      state.count=state.count+step
    }
  },
 actions:{
   act(context,step){
     setTimeout(() =>{
       context.commit('add',step)
     },3000)
   }
 }
})

//使用
<button @click='add2'> actions测试</button>

methods:{
   add2(){
     this.$store.dispatch('act',1)
   }
}
// 每次点击 增加1  三秒后执行

getter 是状态管理器中的计算属性

export default new Vuex.store({
  state:{
     count:0
  },
  getters:{
    powerCounter(state){
       return state.count * state.count
   }
 }
})

使用:
<h>{{ $store.getters.powerCounter}}</h>

module 具体去分割模块

export default new Vuex.store({
  modules :{
    a:{
      state:{
          name:'zs'
      },
      mutations:{},
      actions:{},
      getters:{}
    },
    b:{
      state:{},
      mutations:{},
      actions:{},
      getters:{}
   }
 }
})

<h1>{{$store.state.a.name}}</h1>

13-3.哪些会用到?
登录状态,用户名称,头像,购物车的数量,收藏信息,地理位置。所有需要用户信息的地方验证登陆状态。

例: vuex登录注册使用场景
点击登录按钮的时候,发送axios请求,从后端调用的借口获取数据,返回成功的状态码时,调用vuex里面的mutation方法,动态改变用户和密码的内容,把用户名渲染在用户页面上

13-4 解决vuex页面刷新数据丢失问题
原因:vuex里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,vuex里面的数据就会被清空。

解决:

办法一:将vuex中的数据直接保存到浏览器缓存中(sessionStorage、localStorage、cookie)   
办法二:在页面刷新的时候再次请求远程数据,使之动态更新vuex数据   
办法三:在父页面向后台请求远程数据,并且在页面刷新前将vuex的数据先保存至sessionStorage
(以防请求数据量过大页面加载时拿不到返回的数据)   
  //===========================下面是解决刷新页面丢失vuex数据
  created() {
    //在页面加载时读取sessionStorage里的状态信息
    if (sessionStorage.getItem('store')) {
      this.$store.replaceState(Object.assign({}, this.$store.state, JSON.parse(sessionStorage.getItem('store'))));
    }

    //在页面刷新时将vuex里的信息保存到sessionStorage里
    window.addEventListener('beforeunload', () => {
      sessionStorage.setItem('store', JSON.stringify(this.$store.state));
    });
  }

14 vue中import和require的用法

1,require是CommonJS规范的模块化语法,import是ECMAScript 6规范的模块化语法;   
2,require是运行时加载,import是编译时加载;    
3,require可以写在代码的任意位置,import只能写在文件的最顶端且不可在条件语句或函数作用域中使用;    
4,require通过module.exports导出的值就不能再变化,import通过export导出的值可以改变;    
5;require通过module.exports导出的是exports对象,import通过export导出是指定输出的代码;    
6,require运行时才引入模块的属性所以性能相对较低,import编译时引入模块的属性所所以性能稍高。

15 父组件可以监听到子组件的生命周期吗?

比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:

// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
  this.$emit("mounted");
}

以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:

//  Parent.vue
<Child @hook:mounted="doSomething" ></Child>
doSomething() {
   console.log('父组件监听到 mounted 钩子函数 ...');
},
//  Child.vue
mounted(){
   console.log('子组件触发 mounted 钩子函数 ...');
},     
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ... 

当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。

16.直接给一个数组项赋值,Vue 能检测到变化吗

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。

16-1 对于对象

对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用   

Vue.set(object, propertyName, value) 
this.$set(this.someObject,'b',2) 

16-2 问$set能解决什么。

当生成vue实例后,当再次给数据赋值时,有时候并不会自动更新到视图上去;

vue.js不能监听对象属性的添加和删除,因为在vue组件初始化的过程中,会调用getter和setter方法,
所以该属性必须是存在在data中,视图层才会响应该数据的变化

16-3 对于数组:
Vue.set(vm.items, indexOfItem, newValue) vm.items.splice(indexOfItem, 1, newValue)
或者
vm.$set(vm.items, indexOfItem, newValue)
16-4 数组方法

push()
pop()
shift()
unshift()
splice()  
sort()
reverse()

16-5 $forceUpdate

vue中的$forceUpdate是强制更新的意思

数据的绑定都不用我们操心,例如在data中有一个name的变量,你修改它页面的内容就会自动发生变化。

但是如果对于一个复杂的对象,例如一个对象数组,你直接去给数组上某一个元素增加属性,
vue就无法知道发生了改变。

可参考:
https://blog.csdn.net/qq_38280242/article/details/102807862

17 为何Vue 采用异步渲染

因为如果不采用异步更新,那么每次更次更新数据都会对当前组件进行重新渲染,所以为了性能考虑
vue 会在本轮更新后,再去异步更新视图

18 scoped

当一个style标签拥有scoped属性时,它的CSS样式就只能作用于当前的组件,

也就是说,该样式只能适用于当前组件元素。使组件之间的样式不互相污染。

补:
但是,在做项目中,会遇到这么一个问题,即:引用了第三方组件,需要在组件中局部修改第三方组件的样式,而又不想去除scoped属性造成组件之间的样式污染。那么有哪些解决办法呢?

1.创建两个style标签

<style lang="scss">
  /*添加要覆盖的样式*/
</style>
<style lang="scss" scoped>
  /* local styles */
</style>
<!--vue官网中提到:一个 .vue 文件可以包含多个style标签。所以上面的写法是没有问题的。-->

2.穿透scoped >>>或/deep/

<template>
 <div class="box">
  <dialog></dialog>
 </div>
</template>
<!--使用 >>>或者 /deep/ 操作符(Sass 之类的预处理器无法正确解析 >>>,可以使用/deep/)-->
<style lang="scss" scoped>
.box {
 /deep/ input {
  width: 166px;
  text-align: center;
 }
}
</style>
或者
<style lang="scss" scoped>
.box >>> input {
  width: 166px;
  text-align: center;
 }
}
</style>

二.原理部分

1 MVVM?

M:(model)对数据修改的业务逻辑  
V:(view)视图    前端展示页面 
VM:(ViewModel)监听数据变化,同时对视图控制的一个行为

MVVM 模式 采用双向绑定     将Model 的改变实时的反应到View 中,另一方面它实现了DOM监听    
当DOM 发生一些事件(点击 滚动 touch 等)时, 可以监听到,并在需要的情况下改变对应的Data
数据和视图不能直接通信,vm数据发生修改是通知视图进行修改。对视图操作也需要对数据修改,(通过VM实现v和m之间的数据通信。)

优点

低耦合

  视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,
  当View变化的时候Model可以不变,当Model变化的时候View也可以不变。    

可重用性

你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。   

可测试

界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。

2.vue响应式原理

采⽤数据劫持结合发布者-订阅者模式的⽅式,通过**Object.defineProperty()** 来劫持各个属性的**setter, getter** 
在数据变动时发布消息给订阅者,触发相应监听回调,,在属性被访问和修改时通知变化

new Vue() 会传入一个el , 和数据data , 如果传入的是data ,会被传入一个Observer对象中 ,通过Object.defineProperty 监听劫持的数据data中的各个属性的 set 和get ,给每个属性创建一个Dep 对象,然后添加观察者; 如果传入的是el, 会被传入Compile 对象中解析el 模板中的指令, 然后会初始化到view层中而且每个属性都会创建一个watcher,然后把watcher加入到Dep 对象中;当改变某个属性中的值后,就被监听到这个属性发生改变,就会调用该属性的Dep对象中notify 函数,然后遍历所有的观察者watcher 调用update,去更新view 层
Object.defineProperty() 此方法会直接在一个对象上定义一个新的属性,或者修改一个对象的现有属性,并返回此对象
get set 能时刻观察页面变化,然后通过组件渲染函数去改变页面的显示

Object.defineProperty(obj, prop, descriptor)   
obj  要定义属性的对象。
prop  观察的属性 。
descriptor 要定义选项get set函数。
get读    set设置

白话理解:
修改数据视图会更新。创建对象的时候,会遍历data里面每个JS对象,遍历时会通过 Object.defineProperty() 给每个对象添加getter/setter。给这个对象赋值调setter(改),给其他对象赋值调用getter(读)其他视图来获取数据的时候,调用JS对象的getter。再去记录谁获取直接数据,对数据修改调用setter,通知观察者进行页面更新。

原生JS代码简单实现

<body>
	<div id="msg"></div>
	<input type="text" name="" id="ipt" oninput="change(this)">
</body>
<script>
//1.定义源数据
	var data={
		msg:"hello world"
	}
// 4.定义 watcher(干活的,可实现通用性,其他页面也可实现) 
var obj = {}
 Object.defineProperty(obj, "msg", {
	get (){
		// 获取msg的值
		return data.msg
	},
	set (datas){
		// 设定msg的值,更新页面
		data.msg = datas
		// 更新值
		document.getElementById("msg").innerHTML = datas
		return true
	}
 })  
// 2.将数据显示在页面中,(一展示页面就显示)
document.getElementById("msg").innnerHTML = obj.msg
document.getElementById("ipt").value = data.msg
//3.编写oninput 方法处理函数
 function changeVal(ele){
 // 获取到改变之后的新值
	const inputVal = ele.value
	// 更新div中的值
	//document.getElementById("msg").innerHTML = inputVal
	obj.msg = inputVal
}
</script>

2-2 v-model 双向数据绑定

v-model是 :value=“msg” @input=“msg=$event.target.value” 的语法糖,

其中  :value="msg" 是绑定了数据,   
value就是input输入框里的值;   

@input="msg=$event.target.value" 就是监听input输入框里值的变化,然后改变值。   

一句话概括就是,绑定数据并且监听数据改变

<div>
	<input type='text' :value='msg' @input='aaa($event)'>
	<p>{{msg}}</p>
</div>
<script>
  var app = new Vue({
	el:'#app',
	methods:{
	  aaa(eve){
		this.msg = eve.target.value
	  }
	}
  })
</script>

3.虚拟DOM 原理

3-1 虚拟DOM原理
虚拟dom是对真实dom的抽象,本质是JS对象,这个对象就是更加轻量级的对dom的描述

在渲染页面之前(真实的dom树挂载之前) vue会自己根据真实的dom构建一个虚拟dom,当里面某个节点发生变动时,与旧节点对比一下,发现不一样的地方直接修改在真实对的dom上,就是在虚拟dom上比较新旧节点的同时直接在真实dom上修补

真实DOM
<div class="red">
    <span></span>
    <span></span>
</div>
虚拟DOM
const app = {
    tag: "div", // 标签名或组件名
    data: {
        class: "red", // 标签上的属性
        on: {
            click: () => {} // 事件
        }
    },
    children: [ // 子元素节点
      {......}
    ],
}
如何创建虚拟 DOM
h('div', {
    class: 'red',
    on: {
        click: () => {}
    },
}, [h('span', {}, 'span1'), h('span', {}, 'span2')])

3-2 优点:
在某些情况下,虚拟 DOM 比真实 DOM 快,因为虚拟 DOM 能减少不必要的 DOM 操作

虚拟 DOM 可以减少 DOM 操作

将多次操作合并一次操作,比如在页面里添加1000个div,DOM可能要操作1000次,
但是虚拟DOM只需要操作一次(把数据添加到一个数组里然后操作);

还可以减少DOM操作的范围

虚拟DOM借助DOM diff可以把多余的操作省掉,比如添加1000个div,通过对比区分出哪些是新增的、哪些是重复的,
如果只有10个是新增的就只渲染这10个。

虚拟DOM可以跨平台渲染

虚拟DOM不仅可以变成DOM,还可以变成小程序、IOS或者安卓应用,虚拟DOM本质上 只是一个JS对象

3-3 缺点:
需要额外的创建函数来创建虚拟DOM,但是可以通过JSX语法来简化成XML写法,但是这也有缺点,就是严重依赖打包工具,要添加额外的构建过程
当数据规模不大(比如有几千个节点的时候),虚拟DOM的确更快,但是如果规模达到一定程度(十万+)的话,虚拟DOM的稳定性不如原生DOM。不过一般也达不到这个规模,所以多数情况下不必考虑。

4 单页面应用(SPA)

单页面应用(SPA)

页面发生变化时,主要是通过前端的JS来进行控制的,不需要每次访问后台服务器。
js会感知到 url 的变化,可以使用js动态把当前页面清楚掉,然后显示新的组件。
把页面的切换放到前端来控制,而不需要每次都请求后台。

多页面(MPA)

每次页面变化时,都会访问后台服务器,通过后台服务器进行控制页面的切换。每一次页面跳转,
服务器都会返回一个不同的文档,这种便称为多页面的应用,每次都返回新的HTML页面。

5 vue渲染过程

5-1 渲染过程
Vue 推荐在绝大多数情况下使用 template 来创建你的 HTML。但是模板毕竟是模板,不是真实的dom节点。从模板到真实dom节点还需要经过一些步骤

1 把模板编译为**render**函数(或在开发环境已完成,vue-loader)  
2 实例进行挂载, 根据根节点render函数的调用,递归的生成虚拟dom   
3 对比虚拟dom,渲染到真实dom   
4 组件内部data发生变化,组件和子组件引用data作为props重新调用render函数,生成虚拟dom, 返回到步骤3

5-1-2 这样有什么好处呢?
一个组件对象,如果内部的data发生变化,触发了render函数,重新生成了VNode虚拟节点。那么就可以直接找到所对应的节点,然后直接替换。那么这个过程只会在本组件内发生,不会影响其他的组件。于是组件与组件是隔离的。

5-2 更新过程

1,修改data,触发setter(此前在getter中已被监听)  
2,重新执行render函数,生成newVnode  
3,patch(vnode,newVnode)

6.预编译器less 和 sass

CSS预处理器,可嵌套class、减少很多重复的选择器,提高样式代码的可维护性。大大提高了我们的开发效率。

Less 是在客户端处理的。
Sass 是在服务器端处理的。

less变量 @变量名:值
例:@color:pink
使用:

body{
		background-color:@color	
}

less 编译

vs code安装easy less
出现my.css
link 引入

less嵌套

子元素嵌套,直接写到父元素里面
添加伪元素 &:hover

伪类,交集选择器都用&

less运算

加减乘除

@border:5px (空格) + (空格) 5

注:运算符左右两侧 敲空格隔开 
如果运算符前后两个都有单位,以第一个单位为准

三 项目优化

(1)代码层面的优化

v-if 和 v-show 区分使用场景   
computed 和 watch  区分使用场景   
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if   
长列表性能优化   
事件的销毁    
图片资源懒加载    
路由懒加载    
第三方插件的按需引入    
优化无限列表性能    
服务端渲染 SSR or 预渲染

(2)Webpack 层面的优化

Webpack 对图片进行压缩-------先引入npm install image-webpack-loader --save-dev,然后在 webpack.config.js 中配置   
减少 ES6 转为 ES5 的冗余代码   
提取公共代码   
模板预编译   
提取组件的 CSS   
优化 SourceMap   
构建结果输出分析   
Vue 项目的编译优化   

(3)基础的 Web 技术的优化

开启 gzip 压缩   
浏览器缓存   
CDN 的使用   
使用 Chrome Performance 查找性能瓶颈   
;