关于VUE

v-if和v-show的区别

v-if是真正的条件渲染,只有条件为true时才会渲染,v-show它只是通过display:none来设置显示隐藏,无论条件是否吻合都会渲染
v-if是条件渲染,条件为假的时候不会渲染,频繁变换的时候建议使用v-show,更利于性能优化
v-if可以用来做异步判断,v-if的首次渲染开销要小的多,有更高的切换开销
v-if有配套的v-else-if和v-else,而v-show没有
v-show只是简单的控制元素的display属性,不管条件真假都会渲染,有更高的首次渲染开销,切换开销较小

Vue.use()的作用

官方对 Vue.use() 方法的说明:通过全局方法 Vue.use() 使用插件,Vue.use 会自动阻止多次注册相同插件,它需要在你调用 new Vue() 启动应用之前完成,Vue.use() 方法至少传入一个参数,该参数类型必须是 Object 或 Function,如果是 Object 那么这个 Object 需要定义一个 install 方法,如果是 Function 那么这个函数就被当做 install 方法。在 Vue.use() 执行时 install 会默认执行,当 install 执行时第一个参数就是 Vue,其他参数是 Vue.use() 执行时传入的其他参数。就是说使用它之后调用的是该组件的install 方法。
简单来说Vue.use是用来注册使用插件的

Vue的实例方法、实例属性

  • 实例属性

vm. e l 获 取 V u e 实 例 使 用 的 根 D O M 元 素 v m . el 获取Vue实例使用的根DOM元素 vm. elVue使DOMvm.data 获取数据对象data
vm. o p t i o n s 获 取 自 定 义 属 性 和 调 用 自 定 义 方 法 v m . options 获取自定义属性和调用自定义方法 vm. optionsvm.refs 获取所有添加过ref属性的元素(一个对象,持有注册过ref特性的所有DOM元素和组件实例)

  • 实例方法

vm.$nextTick([callback])
将回调延迟到下次DOM更新循环后执行。在修改数据之后立即使用它,然后等待DOM更新。

vm.$mount()手动挂载Vue实例

vm.$destroy()销毁实例

vm.$set()向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新

vm.$delete()删除对象的属性

Vue常用指令

v-once:
只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能

v-show
根据表达式真假值,切换元素的display

v-if
根据表达式的值的truthiness来有条件地渲染元素。在切换时元素及它的数据绑定/组件被销毁并重建

v-for
列表循环

v-on
绑定事件监听器

v-bind
动态地绑定一个火多个attribute,或一个组件prop到表达式

v-model
在表单控件或组件上创建双向绑定

v-html
更新元素的innerHTML

v-text
更新元素的textContent

Vue自定义指令和过滤器

  • 自定义组件
// HelloWorld.vue组件
<template>
  <div>自定义组件</div>
</template>


<script>
export default {
  data() {
    return {
      key: "value",
    };
  },
  // 组件交互
};
</script>

<style scoped lang="less">
// 组件样式
</style>
  • 局部注册调用组件
// Test.vue
<template>
  <div>
    <HelloWorld />
  </div>
</template>
<script>
import HelloWorld from "./HelloWorld.vue";
export default {
  components: {
    HelloWorld,
  },
};
</script>
<style lang="less" scoped>
</style>

  • 全局注册使用

现在main.js中全局注册该组件

import Vue from 'vue'
import App from './App.vue'
//全局注册
import HelloWorld from './components/HelloWorld.vue'
Vue.component('hello-world', HelloWorld)
new Vue({
    render: h => h(App),
}).$mount('#app')

然后在需要使用公共组件的业务组件中,调用该组件

// Test.vue
<template>
	<div>
		<hello-world></hello-world>
	</div>
</template>
  • 过滤器
// 过滤器的本质 是一个有参数有返回值的方法
new VTTCue({
    filters:{
        myCurrency:function(myInput){
            return '处理后的数据'
        }
    }
})

watch和computed

  • 计算属性computed

1.支持缓存,只有依赖数据发生改变,才会重新进行计算
2.不支持异步,当computed内有异步操作时无效,无法监听数据的变化
3.computed属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
4.如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
5.如果computed属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的属性都有一个get和一个set方法,当数据变化时,调用set方法

  • 侦听属性watch

1.不支持缓存,数据发生改变,会直接触发相应的操作
2.watch支持异步
3.监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值
4.当一个属性发生变化时,需要执行对应的操作,一对多
5.监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数
immediate:组件加载立即触发回调函数执行
deep:深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到

虚拟DOM和diff算法

Virtual DOM其实就是一棵以JavaScript对象(VNode节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实DOM的抽象。最终可以通过一系列操作使这棵树映射到真实的DOM上。
主要还是因为DOM操作太耗费性能,才有了虚拟DOM的出现。

  • diff算法

1.用JavaScript对象结构表示DOM树的结构;然后用这个树构建一个真正的DOM树,插到文档当中
2.当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较(diff),记录两棵树差异
3.把2所记录的差异应用到步骤1所构建的真正的DOM树上(patch),视图就更新

diff算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,是一种相当高效的算法,可以大幅度节约性能

vue生命周期

  • 生命周期是什么

Vue实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载DOM->渲染、更新->渲染、卸载等一系列过程,我们称为Vue的生命周期

  • 各个生命周期阶段及其钩子函数

第一阶段:数据挂载阶段
把配置项data中的属性,赋给了vue对象本身,并做了数据劫持。该阶段前后两个钩子函数:beforeCreated和created

第二阶段:模板渲染阶段:把vue对象的数据渲染到模板上
该阶段前后的两个钩子函数:beforeMount和mounted

第三阶段:组件更新阶段
当数据发生变化时,组件会进行重新渲染,准确的说是组件重新渲染阶段
该阶段的两个钩子函数:beforeUpdate和updated

第四阶段:组件销毁阶段:组件销毁
钩子函数:beforeDestroy和destroyed

当使用keep-alive包裹组件时,会有组件激活和停用之说,这两个钩子函数分别是activited和deactivated

  • 发送请求在哪个阶段,为什么
    (如果组件的初始数据来自后端)发送请求建议在created里,这个钩子函数里是最快,也能有助于一致性。

为什么不在beforeCreated里?

一般来说数据从后端返回回来后,都会赋给vue中的属性(data挂载的),在beforeCreated钩子函数里,data的数组还没有挂载到vue对象本身上,所以不能在这个钩子函数里使用。而created钩子函数里,数据已经挂载到vue对象了

为什么不可以是beforeMoumt,mounted

1.在这两个钩子函数里,发送请求有点晚,会增加页面loading的时间
2.vue的SSR不支持beforeMount、mounted钩子函数,所以放在created中有助于一致性

vue定义创建路由的步骤

如果使用模块化机制编程,导入Vue和VueRouter并调用
定义(路由组件)
定义路由,每个路由应该映射一个组件
创建router实例,然后传routes配置
创建和挂载根实例
router-view设置路由出口

v-model原理

  • 什么是v-model

v-model本质上是一个语法糖,可以看成是value+input方法的语法糖。可以通过model的prop属性和event事件来进行自定义。
v-model是vue的双向绑定的指令,能将页面上控件输入的值同步更新到相关绑定的data属性,也会在更新data绑定属性的时候,更新页面上输入控件的值

vue的双向绑定是由数据劫持结合发布者 - 订阅者模式实现的,那么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想做的事情

我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发生变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:

1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者
2.实现一个订阅者Watcher,每一个Watcher都绑定一个更新函数,watcher可以收到属性的变化通知并执行相应的函数,从而更新视图
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher)
4.vue2.0里面用Object.defineProperty 3.0里面用new Proxy 一个监听每个属性 一个监听整个对象

vue动态权限绑定渲染列表(权限列表渲染)

  1. 首先请求服务器,获取当前用户的权限数据,比如请求this.$http.get(‘rights/list’)
  2. 获取到权限数据之后,在列表中使用v-if v-if-else的组合来展示不同的内容
<template>
    <div>
        <!-- 面包屑导航区 -->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{path: '/home' }">首页</el-breadcrumb-item>
            <el-breadcrumb-item>权限管理</el-breadcrumb-item>
            <el-breadcrumb-item>权限列表</el-breadcrumb-item>
        </el-breadcrumb>
        <!-- 卡片视图 -->
        <el-card>
            <el-table :data="rightsList" border stripe>
                <el-table-column type="index" label="#"></el-table-column>
                <el-table-column label="权限名称" prop="authName"></el-table-column>
                <el-table-column label="路径" prop="path"></el-table-column>
                <el-table-column label="权限等级" prop="level">
                    <template slot-scope="scope">
                        <el-tag v-if="scope.row.level === '0'">一级</el-tag>
                        <el-tag type="success" v-else-if="scope.row.level === '1'">二级</eltag>
                            <el-tag type="danger" v-else>三级</el-tag>
                    </template>
                </el-table-column>
            </el-table>
        </el-card>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                // 权限列表
                rightsList: []
            };
        },
        created() {
            this.getRightsList();
        },
        methods: {
            async getRightsList() {
                //获取权限列表数据
                const { data: res } = await this.$http.get("rights/list");
                if (res.meta.status !== 200) {
                    return this.$message.error("获取权限列表失败!");
                }
                this.rightsList = res.data;
            }
        }
    };
</script>
<style lang='less' scoped>
</style>

Vue用的哪种设计模式

发布者订阅模式,在vue中使用observer和definereactive两个方法的结合对数据进行递归劫持,然后通过watch这个类来对属性进行订阅,Dep类用于解耦合,当数据变更的时候先触发数据的set方法,然后调用Dep.notiify通知视图更新

vue操作真实DOM性能瓶颈

vue性能瓶颈的几种情况

  1. 一次渲染大量的数据的时候,存在大量数据并且都是复杂类型的时候,会导致vue对数据的劫持时间和渲染时间变长,js连续执行时间过长,会导致页面长时间无法交互,而且渲染时间太慢,用户一次交互反馈的时间过长。

优化方案:可以使用 requestAnimation 这个方法,将数据进行分割,分批次渲染,减少了 js 的连续运行时间,并且加快了渲染时间,利用加长总运行时间换取了渲染时间,用户既能快速得到反馈,而且不会因为过长时间的 js 运行而无法与页面交互。

  1. 当页面中存在大量数据,只是修改了一小部分导致页面卡顿,因为vue的更新以组件为粒度进行更新的,只要修改了当前组件中所使用的数据,组件就会整个去进行更新,造成大量的时间浪费

优化方案:将不同的模块划分成不同的组件,这样有效降低虚拟DOM的diff运算时间过长的问题,比如将大量数据的模块单独放一个组件,其他放一个组件,由于vue是以组件为粒度更新,修改其他组件的情况下不会导致table的重新diff,提升页面的响应速度高达几百倍

3.动态插槽作用域或者静态插槽的更新

使用欧冠插槽作用域来替换这两种操作方式,一样能提升性能,因为使用插槽作用域之后,插槽内容会被封装到一个函数中,被子组件渲染,而不是在父组件

vue中如何获取DOM,操作DOM,更新DOM

在vue中提供了一种特别的方式来获取DOM,即给DOM加上个ref属性,那么久可以通过this.$refs.name来获取都该DOM元素

通过refs.name就可以拿到对应的真实DOM,然后就可以用原生JS进行操作和更新。当然vue框架本身就是不需要DOM操作的,通过修改相应的数据并再配合指令,模板语法就可以轻松的操作和更新DOM

Vue双向数据绑定原理

在Vue2.x中,双向数据绑定是通过数据劫持结合发布订阅模式的方式来实现的,也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变。核心:Object.defineProperty()方法
Vue3.x则是用ES6的语法Proxy对象来实现的

  • Object.defineProperty()的缺点:

1.只能监听对象(Object),不能监听数组的变化,无法触发push,pop,shift,unshift,splice,sort,reverse
2.必须遍历对象的每个属性
3.只能劫持当前对象属性,如果想深度劫持,必须深层遍历嵌套的对象

  • Proxy的优点

1.Proxy可以直接监听对象而非属性
2.Proxy可以直接监听数组的变化
3.Proxy有多达13种拦截方法,不限于apply,ownKeys,deleteProperty,has等等是Object.defineProperty不具备的
4.Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改。
5.Proxy作为新标准将收到浏览器厂商重点持续的性能优化,也就是 传说中的新标准的性能红利。

 let arr = [];
    let proxy = new Proxy(arr, {
        get: function (obj, prop) {
            return obj[prop]
        },
        set: function (obj, prop, value) {
            obj[prop] = value /* 可以被坚挺到变化 */
            return true;
        }
    })
    setTimeout(() => {
        proxy.push(1)
    }, 2000)

mvvm框架是什么

MVVM是Model-View-ViewModel的简写。它本质上就是MVC(Model-View-Controller)的改进版。在开发过程中,由于需求的变更或添加,项目的复杂度越来越高,代码量越来越大,此时我们会发现MVC维护起来有些吃力,尤其Controller控制层非常的厚重,非常的庞大,难以维护。

所以有人想到把Controller的数据和逻辑处理部分从中抽离出来,用一个专门的对象去管理,这个对象就是ViewModel。ViewModel是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的Model数据进行转换处理,做二次封装,以生成符合View层使用预期的视图数据模型

由于实现了双向绑定,ViewModel的内容会实时展现在View层,这是激动人心的,因为前端开发者再也不必低效又麻烦地通过操纵DOM去更新视图,MVVM框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护ViewModel,更新数据视图就会自动得到相应更新,真正实现数据驱动开发
在这里插入图片描述

Vue的token存储

在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下:

1.第一次登录的时候,前端调后端的登录接口,发送用户名和密码
2.后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token
3.前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面
4.前端每次跳转路由,就判断localStorage中有无token,没有就跳转到登录页面,有则跳转到对应路由页面
5.每次调后端接口,都要在请求头中加token
6.后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401
7.如果前端拿到状态码为401,就清除token信息并跳转到登录页面

nextTick的理解

当你设置vm.message = 'new messa’,该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环‘tick’中更新。多数情况我们不需要关心这个过程,但如果你想基于更新后的DOM状态来做点什么,这就可能会有些棘手。虽然Vue.js通常鼓励开发人员使用‘数据驱动’的方式思考,避免直接接触DOM,但是有时候我们必须要这么做。为了在数据变化之后等待Vue完成更新DOM,可以在数据变化之后立即使用Vue.nextTick(callback)。这样回调函数将在DOM更新完成后被调用。

    <div id="example">
        {{ message }}
    </div>
    var vm = new Vue({
        el: "#example",
        data: {
            message: 'old message'
        }
    })
    vm.message = 'new message' /* 更改数据 */
    vm.$el.textContent
    Vue.nextTick(function () {
        vm.$el.textContent /* 'new message' */
    })

nextTick和setTimeout区别

首先Vue在更新DOM时是异步执行的,也就是说数据变了,DOM不会立即改变,那么我们是如何知道DOM什么时候会改变呢,也就是说如何知道异步后的触发时机呢?

可以通过nextTick方法,这个方法在源码内,先监听是否具备Promise.then,利用promise来监听,如果当前环境不支持promise,那么就降级采用MutationObserver,如果MutationObserver不支持的话,那么就降级采用setImmediate,如果setImmediate不支持的话,那么就使用setTimeout(fn,0)。

所以nextTick和setTimeout区别就是nextTick会先尝试使用promise,MutationObserver,setImmediate这些技术去监听,如果都不支持才会采用setTimeout

vue中为什么用虚拟dom而不操作真实dom

起初我们在使用欧冠JS/JQuery时,不可避免的会大量操作DOM,而DOM的变化又会引发回流或重绘,从而降低页面渲染性能。那么怎样来减少对DOM的操作呢?此时虚拟DOM应用而生,所以虚拟DOM出现的主要目的就是为了减少频繁操作DOM而引起回流重绘所引发的性能问题

虚拟DOM(Virtual Dom),起始本质上就是一个JS对象,当数据发生变化时,我们不直接操作真实DOM,因为很昂贵,我们去操作这个JS对象,就不会触发大量回流重绘操作,再加上diff算法,可以找到两次虚拟DOM之间改变的部分,从而最小量的去一次性更新真实DOM,而不是频繁操作DOM,性能得到了大大的提升
在这里插入图片描述
虚拟DOM还有一个好处,可以渲染到DOM以外的平台,实现SSR,同构渲染这些高级特性,Weex等框架应用的就是这一特性

Vue如何进行组件传值

父向子组件传值,可以利用prop方式
子向父组件传值,可以利用自定义事件$emit方式
在这里插入图片描述
多层组件传值,可以使用provide/inject
在这里插入图片描述
无关系的组件传值,利用vuex状态管理
在这里插入图片描述

vue父子通信

父传子:通过prop向子组件传递数据,子组件通过props属性来接收

    <blog-post title='My journey with Vue'></blog-post>
    Vue.component('blog-post', {
        props: ['title'],
        template: '<h3>{{title}}</h3>' /* 获取父组件的值 */
    })

子传父:父组件自定义事件,子组件利用$emit来完成

    // 拿到子组件传递的数据$event即0.1
    <blog-post v-on:enlarge-text='postFontSize += $event'></blog-post>
    Vue.component('blog-post', {
        props: ['title'],
        template: '<h3 v-on:click="$emit('enlarge- text',0.1)">{{title}}</h3>'
    })

在这里插入图片描述

vue组件通信

四大类:父与子 子与父 兄弟组件 跨层级组件

  1. props和 e m i t 父 组 件 向 子 组 件 传 递 数 据 是 通 过 p r o p 传 递 的 , 子 组 件 传 递 数 据 给 父 组 件 是 通 过 emit 父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过 emitpropemit触发事件
  2. a t t r s 和 attrs和 attrslisteners
  3. 中央事件总线 bus
    新建一个Vue事件bus对象,通过bus. e m i t 触 发 事 件 , b u s . emit触发事件,bus. emitbus.on监听触发的事件。
  4. provide和inject
    父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用
  5. v-model
    父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input’,val)自动修改v-model的绑定的值
  6. p a r e n t 和 parent和 parentchildren
  7. boradcast和dispatch
  8. vuex
    如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离处理,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的

vue中key值的作用

key是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以更准确,更快速
diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后超出差异
diff过程可以概括为:oidCh和newCh,各有两个头尾的变量StartIdx和EndIdx,它们的两个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就回用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完了,就会结束比较,这四种比较方式就是首,尾,旧尾新头,旧头新尾
准确: 如果不加key,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug
快速:key的唯一性导致可以被Map数据结构充分利用,相比于遍历查找的时间复杂度0(n),Map的时间复杂度仅仅为0(1)

vue3相关

  1. ref、toRefs、toRef、isRef

ref用于定义响应式变量、快捷DOM访问
基本语法:const a = ref(1) // {value:1}
基础使用:一般用于定义String、Number、Boolean这种基本数据类型,外在表现统一使用.value访问
补充:ref还可以访问DOM对象或者组件实例对象,可做DOM操作
toRef、toRefs用于把一个object的变量,编程响应式的变量
基本语法:const msg = toRef(obj, key) //把obj[key]变成响应式的
基本语法:const {msg} = toRefs(obj) //把整个obj变成响应式的
unref返回一个变量的值
基本语法:const x = unref(x) //如果x是ref变量则返回x.value,如果不是ref变量则直接返回x
isRef用于判断一个变量是不是ref响应式变量
基本语法:const bol = isRef(x)

    function useUpdBoxStyle() {
        const el = ref(null)
        const updateStyle = color => {
            el.value.style.color = color
        }
        return [el, updateStyle]
    }
  1. shallowRef、triggerRef

shallowRef用于性能优化,只对对象的第一层进行proxy
基本语法:const obj = shallowRef( { a : 1 , b : { c : { d : { e:2 } } } } )
triggerRef用于手动触发那些shallowRef的变量进行更新视图
基本语法:triggerRef(obj) //当obj.value.b.c.d发生变化,triggerRef(obj)强制更新视图
customRef自定义ref,把ref变量拆成get/set的写法
基本语法:customRef( ( track, trigger ) => ( {get, set } )

    function useObj() {
        const obj = { a: 1, b: { c: { d: 2 } } }
        const obj1 = ref(obj)
        const obj2 = shallowRef(obj)
        // console.log('obj1',obj1);
        // console.log('obj2',obj2);
        const changeObj = (obj, newD) => {
            obj1.value.b.c.d = newD
            triggerRef(obj)
        }
        return [[obj1, obj2], changeObj]
    }
  1. reactive、readonly

reactive用于定义响应式变量(引用数据类型)
基本语法:const arr = reactive( [ ] ) // {value : [ ] }
ref和reactive的关系:ref背后是使用reactive来实现的
shallowReactive用于性能优化,只对对象的一层进行proxy
基本语法:const c = shallowReactive( { a: { b: { c :100 } } } ) //只对这个对象的第一层进行proxy
readonly把响应式变量编程‘只读的’,如果修改就报警告
基本语法:const user = readonly( { name : 1 , age : 2 } )
isReadonly用于判断一个变量是否是readonly的,返回布尔值
基本语法:const bol = isReadonly(x)
isProxy用于判断一个变量是否是响应式的,返回布尔值
基本语法:const bol = isProxy(X)
isReactive用于判断一个变量是否是reactive响应式变量,返回布尔值
基本语法:const bol = isReactive(x)

    function useUser() {
        const user = readonly(reactive({ name: 'list', age: 30 }))
        console.log('user', user);
        // setTimeout(()=>user.age=40,2000)
        const x = 1
        const y = readonly({ a: 1, b: { c: 3 } })
        console.log('是否被proxy拦截过', isProxy(user), isProxy(x), isProxy(y.b));
        return user
    }
  1. toRaw、markRaw

toRaw用于返回一个响应式变量的原始值
基本语法:const a3 = toRaw( reactive(a1) ) //a1===a3是true
markRaw用于把一个普通变量标记成“不可proxy”的
基本语法:const b2 = markRaw( b1 ) //b2是无法被reactive的

    function useRaw() {
        const a1 = { title: 100 }
        const a2 = reactive(a1)
        const a3 = toRaw(a2)
        console.log('toRaw(a2)===a1', a3 === a1);
        console.log('a2===a1', a2 === a1);
        return [a1, a2, a3]
    }
  1. computed、watch、watchEffect

computed用于对响应式变量进行二次计算,当它依赖的响应式变量发生变化时会重新计算
基本语法:const c = computed( ( ) => c1.value*c2.value) //只读
基本语法: const stop = watch( [x,y], ( [ newX,newY],[ oldX,oldY ] )=>{ } )
watchEffect用于监听响应式变量的变化,组件初始化会执行
基本语法:const stop = watchEffect( ( ) =>ajax( { cate , page , size } ) )

    export default function useWatchComputed() {
        const c1 = ref(10)
        const c2 = ref(20)
        const c3 = computed(() => c1.value * c2.value) // 只读
        // 可读也可写
        const c4 = computed({
            get: () => c1.value * c2.value,
            set: (newVal) => {
                c1.value = parseInt(newVal) / c2.value
            }
        })
        const stop1 = watch(c4, (newC4, oldC4) => console.log('c4变了', newC4, oldC4))
        const stop2 = watch([c1, c2], ([newC1, newC2], [oldC1, oldC2]) => {
            console.log('[c1,c2] 新值:', [newC1, newC2])
            console.log('[c1,c2] 旧值:', [oldC1, oldC2])
        })
        const stop3 = watchEffect(() => { console.log('watch effect', c1.value, c2.value) })
        const update = (c, v) => c.value = v
        return [[c1, c2, c3, c4], [stop1, stop2, stop3, update]]
    }
  1. diff算法更快
    vue2.0是需要全局去比较每个节点的,若发现有节点发生变化后,就去更新该节点
    vue3.0是在创建虚拟DOM中,会根据DOM的内容会不会发生变化,添加静态标记,谁有标记就比较谁
  2. 静态提升
    vue2中无论元素是否参与更新,每次都会重新创建,然后再渲染vue3中on对于不参与更新的元素,会做静态提升,只被创建一次,在渲染时直接复用即可
  3. 事件侦听缓存
    默认情况下,onclick为动态绑定,所以每次都会追踪它的变化,但是因为是同一函数,没有必要追踪变化,直接缓存复用即可
    在之前会添加静态标记,会把点击事件当做动态属性,会进行diff算法比较,但是在事件监听缓存之后就没有静态标记了,就会进行缓存复用
    9.性能比vue2.x快1.2-2倍
    10.支持tree-shaking,按需编译,体积比vue2.x更小
    11.支持组合API
    12.更好的支持TS
    13.更先进的组件

vue组件如何与iframe通信

vue组件内嵌一个iframe,现在想要在iframe内获取父组件内信息,采用的是H5新特性PostMessage来解决跨域问题
采用postMessage内涵两个API:
onMessage:消息监听
postMessage:消息发送

    < div class="mapbox" >
        <iframe name="map" src="http://localhost:8083/setposition.html?add='add'">
        </iframe>
    </div >
    clearMap(){
    let map = document.getElementsByName("map")[0].contentWindow
    map.postMessage("clearMap", "*")
    }
    // iframe内:
    window.addEventListener('message', function (evt) {
    if (evt.data == 'clearMap') {
        clearMap()
    }
    //event.data获取传过来的数据
    });

keep-alive

keep-alive的作用是可以在组件切换时,保存其包裹的组件的状态,使其不被销毁,防止多次渲染。

keep-alive会比平常的组件多两个生命周期钩子函数,分别是activated和deactivated。

使用keep-alive包裹的组件在切换时不会被销毁,而是缓存到内存中并执行deactivated钩子函数,再次渲染后会执行activated钩子函数。如果再一次跳回显示组件的时候可以在activated中做处理

vue导航守卫

vue的导航守卫一般指的是路由导航守卫,作用是在页面跳转的时候可以执行一个钩子函数。
导航守卫使用最多的是全局守卫router.beforeEach,主要是用来验证用户的登录状态。它接收三个参数to,from,next

  • to:即将要进入的路由对象
  • from:当前导航要离开的路由
  • next:一个回调函数,一定要调用这个方法,不然路由不会继续往下

vue中的插槽

  • 默认插槽
    默认插槽就是指组件之间的内容,类似于占位符
//定义一个全局子组件
Vue.component('child', {
    template: '<div><slot></slot></div>',
});
var vm = new Vue({
    el: '#root',
});
//引用child组件
<div id="root">
    <child>
        <span>我是占位符</span>
    </child>
</div>

上述的子组件child里定义的slot被span标签给代替了,如果子组件里没有定义slot,则span标签会被直接忽略,且一个子组件里只能定义一个单个插槽

  • 具名插槽
    可以通过设置name属性,指定显示的位置
<div class="container">
	<header>
		<slot name="header"></slot>
	</header>
	<main>
		<slot></slot>
	</main>
	<footer>
		<slot name="footer"></slot>
	</footer>
</div>


<base-layout>
	<template v-slot:header>
		<h1>Here might be a page title</h1>
	</template>
	
	<p>A paragraph for the main content.</p>
	<p>And another one.</p>

	<template v-slot:footer>
		<p>Here's some contact info</p>
	</template>
</base-layout>
  • 作用域插槽
    父组件替换插槽标签,但是内容由子组件类提供

<body>
    <div id="root">
        <child>
            <!--定义一个插槽,该插槽必须放在template标签内-->
            <template slot-scope="props">
                <li>{{props.value}}</li>
            </template>
        </child>
        <!--!--定义不同的渲染方式-->
        <child>
            <!--slot-scope="props"作用是使props指向子组件中定义的<slot>-->
            <template slot-scope="props">
                <h1>{{props.value}}</h1>
            </template>
        </child>
    </div>
</body>
<script>
    //定义一个全局子组件
    Vue.component('child', {
        data: function () {
            return {
                list: [1, 2, 3, 4],
            };
        },
        template:
            '<div><ul><slot v-for="value in list" :value=value></slot></ul></div>',
    });
    var vm = new Vue({
        el: '#root',
    });
</script>

vue组件懒加载,图片懒加载

  • 组件懒加载
// 1.结合路由插件使用的时候用import方式实现
// 第一步注释import导入的文件
// import About from '../components/About.vue';
// 第二步将引入组件的方式以箭头函数的方式异步引入
const routes = [
    {
        path: '/about'component: () => import( /* webpackChunkName: 'about' */ '../components/About.vue'
        )
    }
]

// 2.引入组件的时候使用回调函数的方式引入,比如

// 组件懒加载
const IconList = () => import('components/base/icon-list');
export default {
    components: {
        IconList,
    },
};
  • 图片懒加载

在加载页面的时候,如果页面中的图片过多,可以使用占位符的方式替换没有在可视区域内的图片,只加载当前需要实现的图片。监听滚动条的位置,当图片标签出现在可视区域的时候,重置图片的路径为真实路径,然后展示图片地址,一般在实际开发的时候都直接使用图片懒加载插件实现。还有一种解决方案就是使用页面骨架屏效果,也是类似站位显示,当数据加载完成之后替换掉占位显示的内容

vue 的组件封装

复用性

  • 预留插槽slot,多次调用如果子组件视图结构不一样,呢么就要在子组件template预留号插槽(单个插槽,具名插槽,作用域插槽)
  • 考虑数据传递,定义props组件接收父组件传递的数据,同时需要注意单向数据流,props不能直接修改,$emit自定义事件,父组件修改
  • 业务逻辑不要在子组件中处理,子组件在不同父组件中调用时,业务处理代码不同,切记不要直接在子组件中处理业务,应该子组件$emit自定义事件,将数组传递给父组件,父组件处理业务

vuex的管理操作

vuex是vue的一个状态管理插件,采用集中式管理方式,来管理项目中多个组件的公共状态。
vuex有一个仓库概念,将组件公共的state存储在仓库的state属性中,state是只读的,组件只能使用,不能直接修改,修改需要通过仓库中的mutations模块来修改,这样的好处是当数据修改便于溯源,且不会因为多个组件直接修改数据,导致组件间数据的互相影响,同时,当我们仓库中有一个state数据需要请求数据接口才能获取时,vuex设计了一个action模块,在action模块中发送异步请求,得到数据后,提交mutation来修改state。当state发生改变后组件自动刷新,在组建中可以commit,mutation或者dispatch action来修改state
在这里插入图片描述

vuex的工作流程

vuex的仓库有5个模块,分别是state,mutations,actions,getters,modules
组件的公共状态定义在vuex仓库的state中,state是只读的,无法直接修改,必须调动仓库中的某个mutation才能修改状态,getters可以理解为vuex中的计算属性,当我们在某个组件中使用vuex中的某个state时,不是直接使用原值,而是需要派生出一个新的值,就可以定义getters,可以在组件中获取。当依赖的state发生改变,此时getters会重新计算得到新值,同时action中可以发送异步请求,得到数据后,commit,mutation来给state赋值

  • 仓库代码
const store = new Vuex.Store({
    state: {
        items: [] // 定义一个公共的购物车数据
    },
    getters: {
        // 可以基于已有的state 派生新的状态
        selectedItems(state) {
            // 过滤购物车中未选中的商品
            return state.items.filter(item => item.selected)
        }
    },
    mutations: {
        // 定义mutation来修改state
        INIT_ITEMS(state, items) {
            state.items = items
        }
    },
    actions: {
        // action可以发送异步请求,得到数据后commit mutation将请求结果传入
        FETCH_ITEMS({ commit }, params = {}) {
            // 调用封装好的 接口函数
            fetchItem(params).then(res => {
                if (res.data.code === 200) {
                    commit('INIT_ITEMS', res.data.data)
                }
            })
        }
    }
})

  • 组件中使用vuex
// 获取state
this.$store.state.items // 直接获取
{
    computed: {
...mapState(['items']) // 助手函数获取
    }
}
// 获取getters
this.$store.getters.selectedItems // 直接获取
{
    computed: {
...mapGetters(['selectedItems']) // 助手函数获取
    }
}
// 组件中提交action
this.$store.dispatch('FETCH_ITEMS', { token: 'xxx' })
{
    methods: {
...mapActions(['FETCH_ITEMS']) // 助手函数 直接调用this.FETCH_ITEMS(params)触发
    }
}
// 组件中也可以直接commit mutation
this.$store.commit('INIT_ITEMS'[, 参数])
{

    methods: {
        ...mapMutations(['INIT_ITEMS']) // 助手函数 直接调用this.INIT_ITEMS(参数)
    }
}

vuex工作原理

  • 原则

中小型项目中,如果组件的公共状态不多的情况下,不建议使用vuex,反而会增加代码复杂度,想要组件通信,直接通过event bus即可,中大型项目中,多个组件公共状态较多情况下,建议使用vuex

  • 工作流程
    在仓库state中定义公共状态,action中发送异步请求,得到数据后调用mutation,赋值给state,组件中使用state,也可以在组件中dispatch action 和触发mutation来修改state,视图刷新
// 仓库代码
const store = new Vuex.Store({
    state: {
        items: [] // 定义一个公共的购物车数据
    },
    getters: {
        // 可以基于已有的state 派生新的状态
        selectedItems(state) {
            // 过滤购物车中未选中的商品
            return state.items.filter(item => item.selected)
        }
    },
    mutations: {
        // 定义mutation来修改state
        INIT_ITEMS(state, items) {
            state.items = items
        }
    },
    actions: {
        // action可以发送异步请求,得到数据后commit mutation将请求结果传入
        FETCH_ITEMS({ commit }, params = {}) {
            // 调用封装好的 接口函数
            fetchItem(params).then(res => {
                if (res.data.code === 200) {
                    commit('INIT_ITEMS', res.data.data)
                }
            })
        }
    }
})
// 组件中使用vuex
// 获取state
this.$store.state.items // 直接获取
{
    computed: {
...mapState(['items']) // 助手函数获取
    }
}
// 获取getters
this.$store.getters.selectedItems // 直接获取
{
    computed: {
...mapGetters(['selectedItems']) // 助手函数获取
    }
}
// 组件中提交action
this.$store.dispatch('FETCH_ITEMS', { token: 'xxx' })
{
    methods: {
...mapActions(['FETCH_ITEMS']) // 助手函数 直接调用this.FETCH_ITEMS(params)触发
    }
}
// 组件中也可以直接commit mutation
this.$store.commit('INIT_ITEMS'[, 参数])
{
    methods: {
...mapMutations(['INIT_ITEMS']) // 助手函数 直接调用this.INIT_ITEMS(参数)
    }
}

vuex中处理异步

异步处理需要在仓库的actions中定义
action类似于mutation

  • action提交的mutation,而不是直接变更状态
  • action可以包含任意异步操作
    我们可以在action中发送异步请求,成功后触发mutation,将结果传入,在mutation赋值给state
const store = new Vuex.Store({
    state: {
        items: [] // 定义一个公共的购物车数据
    },
    mutations: {
        // 定义mutation来修改state
        INIT_ITEMS(state, items) {
            state.items = items
        }
    },
    actions: {
        // action可以发送异步请求,得到数据后commit mutation将请求结果传入
        FETCH_ITEMS({ commit }, params = {}) {
            // 调用封装好的 接口函数
            fetchItem(params).then(res => {
                if (res.data.code === 200) {
                    commit('INIT_ITEMS', res.data.data)
                }
            })
        }
    }
})

对vuex的理解

vuex是转为vue设计的状态管理工具,可用于父子组件和非父子组件的全局组件通信。应用的状态集中放在store中,改变状态必须要经过commit,同步改变状态是提交mutations,异步是先通过actions再通过mutations
五大模块

  • state

存放状态

  • getters

就像计算属性一样,getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算
可以对state中的数据做一些处理

  • mutations

更改vuex的store中的状态的唯一方法是提交mutation,通过store.commit提交到mutations模块

  • actions

actions是异步的改变state中状态的方法,通过store.dispatch来提交到mutations模块,再通过提交commit来更改state中的状态

  • modules

vuex允许我们将store分割成模块(module)。每个模块拥有自己的state,mutation,action,getter,甚至是嵌套子模块从上至下进行同样方式的分割

vuex一个模块中改变state中数据,其他模块如何获取

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得 非常复杂时,store对象就有可能变得相当臃肿。
为了解决以上问题,Vuex允许我们将store分割成模块(module)。每个模块拥有自己的state、mutation、action、getter,甚至是嵌套子模块–从上至下进行统一的方式分割
vuex模块化后,需要另一个模块的state变化,可以在这个模块中定义getters获取

// 模块a
const modulea = {
  namespaced: true,
  state: {
    num: 10
  },
  mutations: {
    ADD_NUM(state, n) {
      state.num += n
    }
  }
}
// 模块b
const moduleb = {
  namespaced: true,
  state: {
    num: 10
  },
  getters: {
    // 在这里拿到 模块a numstate
    moduleaNum(state, getters, rootState, rootGetters) {
      // 模块下的getter有四个参数分别是当前模块的state,当前模块的getters,以及根state个根
      getters可以通过rootState获取其他模块的state
      return rootState.modulea.num
    }
  },
  mutations: {
    ADD_NUM(state, n) {
      state.num += n
    }
  }
}

vuex怎么改变状态

vuex的状态储存在仓库的state属性中,state是只读的,无法直接修改,必须调用mutation才能修改

const store = new Vuex.Store({
  state: {
    num: 10
  },
  mutations: {
    ADD_NUM(state, n) {
      state.num += n
    }
  }
})
// 在组件中直接出发mutaion
this.$store.commit('ADD_NUM', 10)
// 或者助手函数 提交 mutation
{
  methods: {
  ...mapMutations(['ADD_NUM'])
  }
}
// 直接调用即可
this.ADD_NUM(10)

vuex的应用场景

中小型项目中,如果组件的公共状态不多的情况下,不建议使用vuex,反而会增加代码复杂度,想要组件通信,直接通过event bus 即可,中大型项目中,多个组件公共状态较多情况下,建议使用vuex
在项目中,多个组件的公共状态可以存储的vuex中,比如电商网站的购物车数据,可以储存在vuex中,后台管理角色鉴权中的不同角色的侧边栏数据,以及不同角色可以访问的路由数据可以存储的vuex中,拿到数据储存

vuex的数据丢失怎么解决

可以利用缓存,将vuex中的state,在缓存中备份一下,当状态发生改变时,同步缓存的备份。
刷新时,将缓存中的备份给state赋值
实际开发中一般利用vuex的一个插件来实现:vuex-persistedstate
安装

npm i vuex-persistedstate -S

使用

import Vuex from "vuex";
import createPersistedState from "vuex-persistedstate";
const store = new Vuex.Store({
  // ...
plugins: [createPersistedState()
  ],
});

vuex原理

vuex是一个专门为vue构建的状态管理工具,主要是为了解决多组件之间状态共享问题。强调的是集中式管理,(组件与组件之间的关系变成了组件与仓库之间的关系)
vuex的核心包括:state(存放状态)、mutations(同步的更改状态)、actions(发送异步请求,拿到数据)、getters(根据之前的状态派发新的状态)、modules(模块划分)
state发布一条新的数据,在getters里面根据状态派发新的状态,actions发送异步请求获取数据,然后再mutations里面同步的更改数据
应用场合:购物车的数据共享、登入注册

vuex仓库数据很多,怎么管理

使用modules模块划分和文件拆分来管理数据
我们可以在modules中进行模块划分,用户模块放入user中,文章放入article中等等

modules: {
  user: { //跟用户相关的数据放这
  state: {},
  geeters: {},
  mutations: {},
  actions: {},
  },
  article: { //跟文章相关的保存在这里
  state: {},
  geeters: {},
  mutations: {},
  actions: {},
  }
}

vuex做数据集中管理,mutations和actions分别是做什么的,为什么不能用mutations处理异步数据

  • mutations和actions分别是做什么的

mutations和action是都是用来改变Vuex store的状态的;mutations提供的回调函数是同步的;而actions提供的方法是异步的,此外,actions的方法最终还是通过调用mutations的方法来实现修改vuex的状态的。

  • 为什么不能用mutations处理异步数据

官方文档说明:“在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你能调用了两个
包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢?这就是为什么
我们要区分这两个概念。在 Vuex 中,我们将全部的改变都用同步方式实现。我们将全部的异步操
作都放在Actions中。”

actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。事实上在 vuex 里
面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要
最后触发 mutation 就行。异步竞态怎么处理那是用户自己的事情。vuex 真正限制你的只有 mutation 必
须是同步的这一点(在 redux 里面就好像 reducer 必须同步返回下一个状态一样)。同步的意义在于这样
每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个
snapshot 存下来,然后就可以随便 time-travel 了。如果你开着 devtool 调用一个异步的 action,你可以
清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态。其实我有个
点子一直没时间做,那就是把记录下来的 mutations 做成类似 rx-marble 那样的时间线图,对于理解应用
的异步状态变化很有帮助。

原生JS

react

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值