前端面试基础题:Vue特性、虚拟DOM和Diff算法…

2023-07-0309:15:28WEB前端开发Comments1,155 views字数 27072阅读模式

✨Vue特性

  1. 数据驱动视图:在使用了Vue的页面中,Vue会监听数据的变化,从而自动渲染页面结构。(单向数据绑定)
  2. 双向数据绑定:在填写表单时,双向数据绑定可以辅助开发者在不操作DOM的前提下,自动把用户填写的内容同步到数据源中。

✨Vue2和Vue3的区别

  1. Vue2的响应式原理是通过Object.defineProperty()这个ES5的API来对数据进行劫持,并结合观察者模式的方式来实现的;Vue3中使用ES6的proxy API对数据代理,来监听属性变化,从而实现对数据的监控的。
  2. Vue3支持碎片,即组件可以拥有多个根节点。
  3. Vue2使用的是选项式API,Vue3可以使用组合式API。选项式API相同逻辑关注点的代码被拆分到不同的选项中,位于文件的不同位置。组合式API将同一个逻辑关注点相关的代码归为一组,增强可读性,降低重构成本。

✨MVC、MVP和MVVM

MVC文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • M(Model):模型层,负责存储页面的业务数据,以及对相应数据的操作,当Model层发生改变时,会通知View层更新页面;
  • V(View):视图层,负责页面的显示逻辑;
  • C(Controller):控制层,负责用户与应用的相应操作,当用户与页面产生交互时,Controller通过调用Model,完成对Model中数据的修改,然后Model再去通知View更新。
  • MVC是单向通信。View一般用Controller来和Model进行联系。
前端面试基础题:Vue特性、虚拟DOM和Diff算法…

MVP文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • M(Model):模型层,用于数据存储以及业务逻辑。
  • V(View):视图层,用于展示与用户实现交互的页面。
  • P(Presenter):表示器,连接M和V。
  • 各部分的通信是双向的,View层不能再直接访问Model层,必须通过Presenter提供的接口。
前端面试基础题:Vue特性、虚拟DOM和Diff算法…

MVVM文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • M(Model):表示当前页面渲染时所依赖的数据源;
  • V(View):表示当前页面渲染的DOM结构;
  • VM(ViewModel):表示Vue实例,当数据源发生变化时,会被VM监听到,VM会根据最新的数据源自动更新页面结构;当表单元素的值发生变化时,也会被VM监听到,VM会把变化后的值自动同步到Model数据源中。
前端面试基础题:Vue特性、虚拟DOM和Diff算法…

✨虚拟DOM和Diff算法

作用:减少操作真实DOM的次数,提高性能。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

虚拟DOM文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

虚拟DOM是利用JS描述元素与元素的关系,用JS对象来表示真实的 DOM 树结构,创建一个虚拟 DOM 对象。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

<ul id="test">
	<li class="item">哈哈</li>
	<li class="item">呵呵</li>
	<li class="item">嘻嘻</li>
</ul>
let newVDOM = { // 新虚拟DOM
	tagName: 'ul',  // 标签名
	props: {  // 标签属性
		id: 'list'
	},
	children: [  // 标签子节点
		{ tagName: 'li', props: { class: 'item' }, children: ['哈哈']},
		{ tagName: 'li', props: { class: 'item' }, children: ['呵呵']},
		{ tagName: 'li', props: { class: 'item' }, children: ['嘻嘻']}
	]
}

虚拟DOM作用:用JS对象模拟DOM节点的好处是,页面的更新可以先全部反应在JS对象(虚拟DOM)上,操作内存中的JS对象的速度显然要更快,等更新完成之后,再将最终的JS对象映射成真实的DOM,交由浏览器去绘制。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

Diff算法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • Diff算法是一种对比算法,主要是对比旧的虚拟DOM和新的虚拟DOM,找出发生更改的节点,并只更新这些节点,而不更新未发生变化的节点,从而准确的更新DOM,减少操作真实DOM的次数,提高性能。
  • Diff比较两个虚拟DOM只会在同层级之间进行比较,不会跨层级进行比较,时间复杂度O(n)
  • Diff是采用先序深度优先遍历的方式进行节点比较的,即当比较某个节点时,如果该节点存在子节点,那么会优先比较他的子节点,直到所有子节点全部比较完成,才会开始去比较该节点的下一个同层级节点。
前端面试基础题:Vue特性、虚拟DOM和Diff算法…

1. patch方法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

对比当前同层的虚拟节点是否为同一种类型的标签:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

    • 是:继续执行patchVnode方法进行深层比对
    • 否:没必要比对了,直接整个节点替换成新虚拟节点

是否为同一类型的标准(sameVnode): key值是否一样、标签名是否一样、是否都为注释节点、是否都定义了data、当标签为input时,type是否相同文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

2. patchVnode方法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • 找到对应的真实DOM,称为el
  • 判断newVnode和oldVnode是否指向同一个对象,如果是,那么直接return
  • 如果他们都有文本节点并且不相等,那么将el的文本节点设置为newVnode的文本节点。
  • 如果oldVnode有子节点而newVnode没有,则删除el的子节点
  • 如果oldVnode没有子节点而newVnode有,则将newVnode的子节点真实化之后添加到el
  • 如果两者都有子节点,则执行updateChildren函数比较子节点

3. updateChildren方法——首尾指针法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • oldS 和 newS 使用sameVnode方法进行比较,sameVnode(oldS, newS)
  • oldS 和 newE 使用sameVnode方法进行比较,sameVnode(oldS, newE)
  • oldE 和 newS 使用sameVnode方法进行比较,sameVnode(oldE, newS)
  • oldE 和 newE 使用sameVnode方法进行比较,sameVnode(oldE, newE)
  • 如果以上逻辑都匹配不到,再把所有旧子节点的 key 做一个映射到旧节点下标的 key -> index 表,然后用新 vnode 的 key 去找出在旧节点中可以复用的位置。
前端面试基础题:Vue特性、虚拟DOM和Diff算法…

✨发布订阅模式和观察者模式

发布订阅模式文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

基于一个事件中心,接收通知的对象是订阅者,需要先订阅某个事件,触发事件的对象是发布者,发布者通过触发事件,通知各个订阅者。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

class EventEmitter {
    constructor () {
        this.subs = Object.create(null);
    }
    // 注册事件
    $on (eventType, handler) {
        this.subs[eventType] = this.subs[eventType] || [];
        this.subs[eventType].push(handler);
    }
    // 触发事件
    $emit (eventType) {
        this.subs[eventType].forEach(handler => {
            handler();
        });
    }
}

let em = new EventEmitter();
// 注册事件(订阅消息)
em.$on('click', () => {
    console.log('click1');
});
em.$on('click', () => {
    console.log('click2');
});
// 触发事件(发布消息)
em.$emit('click');

// 输出:click1 click2

观察者模式文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

目标者对象和观察者对象有相互依赖的关系,观察者对某个对象的状态进行观察,如果对象的状态发生改变,就会通知所有依赖这个对象的观察者。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

观察者模式相比发布订阅模式少了个事件中心,发布订阅模式中订阅者和发布者不是直接关联的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

观察者 - Watcher文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • update():当事件发生时,具体要做的事情

目标者 - Dep文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • subs数组:存储所有的观察者
  • addSub():添加观察者
  • notify():当事件发生后调用所有观察者的update()
// 目标者 
class Dep {
  constructor() {
    // 记录所有订阅者
    this.subs = [];
  }
  // 添加观察者
  addSub(sub) {
    if (sub && sub.update) {
      this.subs.push(sub);
    }
  }
  // 发布通知
  notify() {
    this.subs.forEach(sub => {
      sub.update();
    });
  }
}

// 观察者
class Watcher {
  update() {
    console.log('update');
  }
}

let dep = new Dep();
let watcher1 = new Watcher();
let watcher2 = new Watcher();
dep.addSub(watcher1);
dep.addSub(watcher2);
dep.notify();
// 输出:update update

发布订阅模式和观察者模式的区别文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • 从结构上分析
    观察者模式里,只有两个角色:观察者和目标者
    发布订阅模式里,不仅仅有发布者和订阅者,还有一个事件中心
  • 从关系上分析
    观察者和目标者,是松耦合的关系
    发布者和订阅者,则完全不存在耦合
  • 从使用角度分析
    观察者模式,多用于单个应用内部(Vue中响应式数据变化就是观察者模式)
    发布订阅模式,则更多应用于跨应用的模式,比如我们常用的消息中间件

✨Vue响应式原理

响应式:数据改变,对应视图也会改变,这是通过追踪对象属性的读写实现的。JS中有两种劫持property访问的方式:getters/settersProxies文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

Vue 2.x文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • 底层原理:Object.defineProperty
  • 直接监听属性
  • 浏览器兼容IE8以上(不兼容IE8)

Vue的响应式数据分为两类:对象和数组文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

对象文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

遍历对象的所有属性,并为每个属性设置gettersetter,以便将来的获取和设置,如果属性的值也是对象,则递归为属性值上的每个key设置gettersetter文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

获取数据时:在dep中添加相关的watcher(收集依赖) 设置数据时:再由dep通知相关的watcher去更新(通知依赖)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

数组文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

覆盖了原有的7个改变了原数组的方法,并克隆了一份,然后在克隆的这一份上更改自身的原型方法,然后拦截对这些方法的操作文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

添加新数据时:需要进行数据响应式的处理,再由dep通知watcher去更新 删除数据时:也要由dep通知watcher去更新文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

Vue3.X文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • 底层原理:Proxy
  • 直接监听对象,而非属性
  • ES6中新增方法,不支持IE浏览器

Proxy配合Reflect使用,Reflect是ES6出现的新特性,代码运行期间用来设置或获取对象成员(操作对象成员),Reflect没有出现前使用Object的一些方法比如 Object.getPrototypeOf, Reflect也有对应的方法 Reflect.getPrototypeOf,两者都是一样的,不过Reflect更有语义。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

const p = new Proxy(target, handler) 创建一个对象的代理文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • target:要使用Proxy包装的目标对象;
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p的行为。

Proxy相比于defineProperty的好处文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • defineProperty需要对属性进行递归绑定;
  • defineProperty无法对新增属性做到响应式;
  • defineProperty需要重写数组方法。

✨Vue双向数据绑定(v-model的实现)

Vue通过v-model指令为组件添加上input事件处理和value属性的赋值(主要是实现view→model的数据绑定)。在自定义组件中,v-model 默认会利用名为 value 的 prop和名为 input 的事件。本质是一个父子组件通信的语法糖,通过prop和$.emit实现。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

<template>
	<input v-model='localValue'/>
</template>

<template>
	<!-- 这里添加了input事件的监听和value的属性绑定 -->
	<input @input='onInput' :value='localValue' />
	<span>{{localValue}}</span>
</template>
<script>
	export default{
		data(){
			return {
				localValue:'',
			}
		},
		methods:{
			onInput(v){
				//在input事件的处理函数中更新value的绑定值
				this.localValue=v.target.value;
				console.log(this.localValue)
			}
		}
	}
</script>

✨Vue指令

前端面试基础题:Vue特性、虚拟DOM和Diff算法…
  • v-text、v-html和{{}}的区别
    v-text会覆盖元素内的默认值,插值表达式{{}}可以解决v-text覆盖默认文本问题,v-text{{}}都只能渲染文本内容,不能识别HTML标签,而v-html可以将HTML渲染为页面的HTML元素。
  • 事件修饰符
    .prevent 阻止默认行为
    .stop 阻止事件冒泡
    例:<a href="https://www.baidu.com" @click.prevent="onLinkClick"></a>
  • v-model指令修饰符
修饰符作用使用方法
.number自动将用户输入值转换为数值类型<input v-model.number="age">
.trim自动过滤用户输入的首尾空白字符<input v-model.trim="msg">
.lazy在”change”时而非”input”时更新<input v-model.lazy=”msg”>
  • v-if和v-show的区别
    实现原理不同:v-if指令会动态创建和移除DOM元素,从而控制元素在页面上的显示与隐藏;v-show指令会动态的为元素添加或移除 style=”display: none;” 样式,从而控制元素在页面上的显示与隐藏。
    编译条件v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译。v-show是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且DOM元素保留;
    性能消耗不同:v-if有更高的切换开销,v-show有更高的初始渲染开销。
  • v-for中key值的作用
    key属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes; 在没有key的情况下(或者使用index),diff过程中,无法确定具体更新的节点,会导致删除的节点错误,所以需要一个唯一的key值,用于渲染更新; 而使用key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素;
  • 为什么不建议用index作为key?
    使用index作为key和没写基本上没区别,因为不管数组的顺序怎么颠倒,index都是 0, 1, 2...这样排列,导致Vue会复用错误的旧子节点,做很多额外的工作。
前端面试基础题:Vue特性、虚拟DOM和Diff算法…
前端面试基础题:Vue特性、虚拟DOM和Diff算法…
  • 为什么v-if和v-for不建议用在同一标签?
    v-for优先级高于v-if,每项都通过v-for渲染出来后再去通过v-if判断显隐,做了很多无用功。

✨Vue过滤器(Vue3中移除)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

前端面试基础题:Vue特性、虚拟DOM和Diff算法…

✨Vue侦听器

监听数据变化,针对数据的变化做特定的操作。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

const vm = new Vue({
    data: { username: '' },
    watch: {
        username (newVal, oldVal) {}
    }
}).$mount('#app');

const vm = new Vue({
    data: { info: { username: 'admin' } },
    watch: {
        info: {
            handle (newVal, oldVal) {},
                // 任何一个属性变化都会触发
                deep: true,
                // 表示页面初次渲染好之后就立即触发watch侦听器
                immediate: true
        }
    }
}).$mount('#app');

✨Vue计算属性

实时监听数据变化,并return一个新值用于DOM渲染使用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

定义时定义为方法,使用时作为属性使用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

var vm = new Vue({
    data: { r: 0, g: 0, b: 0 },
    computed: {
        rgb() { return `rgb(${this.r}, ${this.g}, ${this.b})` }
    }
    methods: {
        show () { console.log(this.rgb); }
    }
}).$mount('#app');

watch和computed的区别文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • 计算属性侧重于监听多个值的变化,最终计算并返回一个新值。具有缓存机制,依赖值不变的情况下,会复用计算值。
  • 侦听器侧重监听单个数据的变化,最终执行特定的业务处理,不需要有返回值。

computed和methods的区别文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • 相同点:将同样的函数定义为method和计算属性computed,二者最终的结果是一样的。
  • 不同点:computed会进行缓存,还有在相关依赖数据发生改变时才会计算。method调用总会执行该函数。

✨Vue组件

Vue组件由三部分组成:template(组件的模板结构)、script(组件的JS行为)、style(组件的样式)。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

<template></template>
<script>
export default {
    data () {},
    methods: {},
    watch: {},
    computed: {},
    filters: {}
}
</script>
<style lang=”less” scoped></style>
  • template是Vue提供的容器标签,不会渲染为真正的DOM元素,Vue2中template只能包含唯一的根节点,Vue3中可以包含多个。
  • 组件相关的data数据、methods方法等都需要定义到export default所导出的对象中。
  • vue组件的data必须是一个函数,不能直接指向一个数据对象,否则会导致多个组件共用一份数据的问题。(防止组件被多个页面使用,造成变量互相污染)。因为js的对象是按引用传递的,如果使用对象的话,会导致组件内数据无法保持唯一,可能会被其他组件数据修改,所以需要使用函数,每次都重新创建一个新的对象实现。
  • style中的scoped属性防止组件之间样式冲突问题(vue组件中的样式全局生效)。
// 私有组件
import Count from '@/component/Count.vue'
export default {
    component: { Count }
}

// 全局组件
import Count from '@/component/Count.vue'
Vue.component('MyCount', Count)

✨组件的props

主要用于组件的传值,接收外面传过来的数据。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

组件中封装的自定义属性props是只读的,不能直接修改props的值,要想修改props的值,可以把props的值转存到data中,但是修改data不会影响propsprops不变。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

export default {
    props: ['init', 'attr1', 'attr2', ...]
}

export default {
    props: {
        init: {
            default: 0,  // 定义默认值
            type: Number,  // 定义值类型
            required: true  // 必填校验项
        }
    }
}

<Count :init="message"></Count>

✨组件的生命周期

前端面试基础题:Vue特性、虚拟DOM和Diff算法…
  • Vue3中beforeDestorydestoryed变为beforeUnmountUnmounted
  • 父子组件生命周期顺序?
    • 加载渲染过程:父组件beforeCreate → 父组件Created → 父组件beforeMount → 子组件beforeCreate → 子组件Created → 子组件beforeMount → 子组件Mounted → 父组件Mounted
    • 更新过程:父组件beforeUpdate → 子组件beforeUpdate → 子组件updated → 父组件updated
    • 销毁过程:父组件beforeDestroy→ 子组件beforeDestroy→ 子组件destroyed→ 父组件destroyed

✨组件之间的数据共享

  • 父传子,子组件通过props接收
  • 子传父,子组件使用$emit对父组件进行传值
  • 父子之间通过$parent$chidren获取实例进而通信
  • 通过vuex进行状态管理
  • 通过eventBus进行跨组件值传递
  • provideinject,官方不建议使用
  • $ref获取实例,进而传值
  • 路由传值
  • localStorage、sessionStorage

1. props文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

父组件通过props向子组件传递数据文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

// 父组件
<template>
  <div id="father">
    <Son :msg="msgData" :fn="myFunction"></Son>
  </div>
</template><script>
import Son from './Son.vue';
export default {
  name: 'Father',
  data () {
    return { 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文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

子组件通过$emit派发事件,父组件中通过v-on接收该事件,拿到传递的数据。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

// 父组件
<template>
  <div id="father">
    <Son @numchange="getNewCount"></Son>
  </div>
</template><script>
import Son from './Son.vue';
export default {
  name: 'Father',
  data() {
    return { count: 0 };
  },
  methods: {
    getNewCount(val) {
      this.count = val;
    }
  },
  components: { Son }
}
</script>
​
// 子组件
<template>
  <div id="son">
    <button @click="add">+1</button>
  </div>
</template><script>
export default {
  name: 'Son',
  data () {
    return { count: 0 };
  },
  methods: {
    add() {
      this.count += 1;
      this.$emit('numchange', count);
    }
  }
}
</script>

3. eventBus文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

EventBus 是中央事件总线,不管是父子组件,兄弟组件,跨层级组件等都可以使用它完成通信操作。创建一个公共的vue实例来实现事件的注册和触发。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

// 公共Vue实例
import Vue from 'vue'
export const EventBus = new Vue();
// 两个兄弟组件
<template>
  <div>
    <FirstCom></FirstCom>
    <SecondCom></SecondCom>
  </div>
</template>

<script>
import FirstCom from './FirstCom.vue';
import SecondCom from './SecondCom.vue';
export default {
  components: { FirstCom, SecondCom }
}
</script>

// 数据发送方
<template>
  <div id="first">
    <button @click="add">+1</button>
  </div>
</template>

<script>
import { EventBus } from './eventBus.js';
export default {
  name: 'FirstCom',
  data () {
    return { num: 0 };
  },
  methods: {
    add () {
      EventBus.$emit('addition', { num: this.num++ });
    }
  }
}
</script>

// 数据接收方
<template>
  <div id="second">
    {{count}}
  </div>
</template>

<script>
import { EventBus } from './eventBus';
export default {
  name: 'SecondCom',
  data () {
    return { count: 0 };
  },
  mounted () {
    EventBus.$on('addition', param => {
      this.count = this.count + param.num;
    })
  }
}
</script>

4. provide/inject文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

父节点通过provide共享数据,子孙节点通过inject接收数据。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

// 父组件共享数据
export default {
    data () {
        return { color: 'red' }
    },
    provide () {
        return { color: 'red' }
    }
}

// 子孙组件接收数据
export default {
    inject: ['color']
}

父节点结合computed函数对外共享响应式数据,子孙节点以.values的形式进行使用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

// 父组件共享数据
export default {
    data () {
        return { color: 'red' }
    },
    provide () {
        return {
            color: computed(() => this.color)
        }
    }
}

// 子孙组件接收数据
export default {
    inject: ['color']  // 访问 color.value
}

5. ref/$refs文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

这种方式也是实现父子组件之间的通信。ref属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

// 父组件
<template>
  <div id="father">
    <Son ref="son"></Son>
  </div>
</template><script>
import Son from './Son.vue';
export default {
  name: 'Father',
  mounted() {
    console.log(this.$refs.son.msg);  // Hello
    this.$refs.son.sayHello();  // Hello
  },
  components: { Son }
}
</script>
​
// 子组件
<script>
export default {
  name: 'Son',
  data() {
    return { msg: 'Hello' };
  },
  methods: {
    sayHello() {
      console.log('Hello');
    }
  }
}
</script>

6. $parent / $children文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

使用$parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

使用$children可以让组件访问子组件的实例,但是,$children并不能保证顺序,并且访问的数据也不是响应式的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

// 父组件
<template>
  <div id="father">
    <div>{{msg}}</div>
    <Son></Son>
    <button @click="change">点击改变子组件值</button>
  </div>
</template><script>
import Son from './Son.vue'
export default {
  name: 'Father',
  components: { Son },
  data() {
    return {
      msg: 'Welcome'
    }
  },
  methods: {
    change() {
      // 获取到子组件
      this.$children[0].message = 'JavaScript'
    }
  }
}
</script>
​
// 子组件
<template>
  <div id="son">
    <span>{{message}}</span>
    <p>获取父组件的值为: {{parentVal}}</p>
  </div>
</template><script>
export default {
  name: 'Son'
  data() {
    return { message: 'Vue' }
  },
  computed:{
    parentVal(){
      return this.$parent.msg;
    }
  }
}
</script>

注意:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

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

✨ref引用

用于获取DOM元素或组件的引用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

// 声明
<h3 ref="myh3">MyRef组件</h3>
<my-counter ref="countRef"></my-counter>
​
// 使用
this.$ref.myh3.style.color = 'red';
this.$ref.countRef.add();

✨class和style的动态绑定

// 对象语法
<div v-bind:class="{active: isActive, 'text-danger': hasError}"></div>
data: {
    isActive: true,
    hasError: false
}
​
// 数组语法
<div v-bind:class="[isActive? activeClass: '', errorClass]"></div>
data: {
    activeClass: 'active',
    errorClass: 'text-danger'
}
// 对象语法
<div v-bind:style="{ color: activeColor, fontSize: fontSize }"></div>
data: {
    activeColor: 'red',
    fontSize: 30
}
​
// 数组语法
<div v-bind:style="[styleColor, styleSize]"></div>
data: {
    styleColor: {
        color: 'red'
    },
    styleSize: {
        fontSize: '23px'
    }
}

✨nextTick

因为 vue 采用的异步更新策略,当监听到数据发生变化的时候不会立即去更新DOM,而是开启一个任务队列,并缓存在同一事件循环中发生的所有数据变更;这种做法带来的好处就是可以将多次数据更新合并成一次,减少操作DOM的次数,如果不采用这种方法,假设数据改变100次就要去更新100次DOM,而频繁的DOM更新是很耗性能的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • nextTick 接收一个回调函数作为参数,并将这个回调函数延迟到DOM更新后才执行。
  • 使用场景:在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。
    例:组件切换之后,让组件中的文本框自动获得焦点:this.$nextTick(() => {this.$refs.ipt.focus()})

✨动态组件

动态切换组件的显示与隐藏。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

<keep-alive :include="MyLeft, MyRight">
    <component :is="comName"></component>
</keep-alive>
  • component标签使用绑定is属性,动态指定要渲染的组件。
  • 默认情况下,切换动态组件无法保持组件的状态,可以使用vue内置的 <keep-alive> 组件保持动态组件的状态。
  • <keep-alive> 的include属性指定名称匹配的组件会被缓存,exclude属性匹配的组件不会被缓存,二者使用一个即可。
  • 当组件被缓存时,会自动触发组件的deactivated生命周期函数;当组件被激活时,会自动触发activated生命周期函数。

✨插槽

在封装组件时,将不确定、希望用户指定的部分定义为插槽。(预留内容的占位符)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

// 声明
<template>
    <div>
        <header>
            <slot name="header"></slot>
        </header>
        <main>
            <slot>后备内容</slot>
        </main>
        <footer>
            <slot name="footer"></slot>
        </footer>
    </div>
</template>
​
// 使用
<my-com>
    <template v-slot:header>
        <h1>Header</h1>
    </template>
    <template v-slot:default>
        <h1>Main</h1>
    </template>
    <template #footer>
        <h1>Footer</h1>
    </template>
</my-com>
  • 封装组件时需要预留多个插槽节点时,需要为每个插槽指定具体的name,没有指定name的插槽会有隐含名称叫做”default”。(具名插槽)
  • v-slot的简写形式:#
  • 封装组件过程中可以为插槽绑定props数据(作用域插槽),可以使用v-slot:的形式接收作用域插槽对外提供的数据。将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
// 声明
<template>
    <div>
        <tbody>
            <slot v-for="item in list" :user="item"></slot>
        </tbody>
    </div>
</template>
​
// 使用
<my-com>
    <template #default="{user}">
        <tr>
            <td>{{user.id}}</td>
            <td>{{user.name}}</td>
            <td>{{user.state}}</td>
        </tr>
    </template>
</my-com>

✨自定义指令

// 私有自定义指令
export default {
    directives: {
        color: {
            bind (el, binding) {
                el.style.color = binding.value;
            },
            update (el, binding) {}
        }
    }
}

// 全局自定义指令
Vue.directive('color', function(el, binding) {
    el.style.color = binding.value;
}, function(el, binding) {});

// 使用
<h1 v-color="red"></h1>
  • el表示当前绑定的元素,binding.value获取动态参数值。
  • bind函数只会调用一次:当指令第一次绑定到元素时使用;update函数会在每次DOM更新时被调用(第一次不会调用)。如果bindupdate函数中逻辑完全相同,则对象格式的自定义指令可以简写为函数格式。

✨SPA单页面

SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

单页面应用(SPA)的核心之一是: 更新视图而不重新请求页面文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • 优点:
    用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染; 基于上面一点,SPA 相对对服务器压力小; 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
  • 缺点:
    初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
    前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
    SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

✨首页加载白屏问题

  • FP(全称“First Paint”,翻译为“首次绘制”) 是时间线上的第一个“时间点”,它代表浏览器第一次向屏幕传输像素的时间,也就是页面在屏幕上首次发生视觉变化的时间。
  • FCP(全称“First Contentful Paint”,翻译为“首次内容绘制”) ,顾名思义,它代表浏览器第一次向屏幕绘制 “内容” 。
  • FP 与 FCP 这两个指标之间的主要区别是:FP是当浏览器开始绘制内容到屏幕上的时候,只要在视觉上开始发生变化,无论是什么内容触发的视觉变化,在这一刻,这个时间点,叫做FP。相比之下,FCP指的是浏览器首次绘制来自DOM的内容。
  • FMP(全称“First Meaningful Paint”,翻译为“首次有效绘制”) 表示页面的“主要内容”开始出现在屏幕上的时间点。它是我们测量用户加载体验的主要指标。

解决方法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. SSR:服务端渲染可以解决首屏加载慢这个问题,因为服务端会把所有数据全部渲染完成再返回给客户端,但需要node层高并发的解决
  2. 路由懒加载:在路由中通常会定义很多不同的页面。如果不应用懒加载的话,很多页面都会打包到同一个js文件中,文件将会异常的大。造成进入首页时,需要加载的内容过多,时间过长,在浏览器中可能会出现短暂的空白页,从而降低用户体验,而运用路由懒加载是将各个模块分开打包,用户查看的时候再加载对应的模块,减少加载用时。const app = () =>import('')
  3. Gzip压缩:使用Gzip压缩,减少文件体积,加快首屏页面打开速度
  4. 骨架屏:骨架屏就是在进入项目的FP阶段,给它来一个类似轮廓的东西,当页面加载完成之后就消失
  5. loading:在index.html里加一个loading css效果,当页面加载完成消失

✨前端路由(vue-router)

单页面应用程序(SPA)是指一个web网站只有唯一一个页面,所有组件的展示与切换都在这唯一一个页面内完成。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

前端路由:hash地址与组件之间的对应关系。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

// router/index.js
import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import Movie from '@/components/Movie.vue'
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components/tabs/Tab2.vue'

const router = new VueRouter({
    routes: [
        { path: '/', redirect: '/home' },
        { path: '/home', component: Home },
        { path: '/movie/:id', component: Movie, props: true }
        { path: 'about', component: About, children: [
            { path: 'tab1', component: Tab1 },
            { path: 'tab2', component: Tab2 }
        ]}
    ]
})
// App.vue
<template>
    <div class="app-container">
        <!-- 声明路由链接 -->
        <router-link to="/home">首页</router-link>
        <router-link to="/movie">电影</router-link>
        <router-link to="/about">关于</router-link>
        <!-- 路由占位符 -->
        <router-view></router-view>
    </div>
</template>
  • 路由重定向:用户在访问地址A的时候,强制用户跳转到地址C,从而展示特定组件页面。(redirect属性)
  • 嵌套路由:通过路由实现组件的嵌套显示。(children属性)
  • 动态路由匹配:Hash地址中可变部分定义为参数项,以提高路由规则的复用性(this.$route.params.id)。为了简化路由参数的获取方式,可以在路由规则中开启props传参(id),需要在props中声明参数。

路由懒加载文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. 使用箭头函数 + import动态加载
const List = () => import('@/components/list.vue');
const router = new VueRouter({
    routes: [
        { path: '/list', component: List }
    ]
});

2. 使用箭头函数 + require动态加载文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

const router = new VueRouter({
    routes: [
        {
            path: '/list',
            components: resolve => require(['@/components/list'], resolve)
        }
    ]
});

3. webpack的require.ensure技术文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

// r就是resolve
const List = r => require.ensure([], () => r(require('@/components/list')), 'list');
// 路由也是正常的写法  这种是官方推荐的写的 按模块划分懒加载 
const router = new Router({
  routes: [
  {
    path: '/list',
    component: List,
    name: 'list'
  }
 ]
}))

路由的hash和history模式的区别文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

Vue-Router有两种模式:hash模式history模式。默认的路由模式是hash模式。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. hash模式
  • hash模式是一种把前端路由的路径用井号 # 拼接在真实url后面的模式。
  • hash值会出现在URL里面,但是不会出现在HTTP请求中,对后端完全没有影响。所以改变hash值,不会重新加载页面
  • hash 通过监听浏览器 onhashchange 事件变化,查找对应路由应用。通过改变 location.hash 改变页面路由。
  • hash值变化对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的前进和后退。

2. history模式文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • history模式的URL中没有#
  • history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。
  • 如果想要切换到history模式,就要进行以下配置
const router = new VueRouter({
  mode: 'history',
  routes: [...]
})
  • history 模式是通过调用 window.history 对象上的一系列方法来实现页面的无刷新跳转。
  • API: history api可以分为两大部分,切换历史状态和修改历史状态:
    • 修改历史状态:包括了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了url,但浏览器不会立即向后端发送请求。如果要做到改变url但又不刷新页面的效果,就需要前端用上这两个API。
    • 切换历史状态: 包括forward()back()go()三个方法,对应浏览器的前进,后退,跳转操作。

总结:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. hash模式URL中有#号,history模式没有。
  2. pushState()设置新URL可以与当前URL一模一样,这样也会把记录添加到栈中。而hash设置的新值必须与原来的不一样,才会触发动作将记录添加到栈中。
  3. hash模式下,仅hash符号之前的url会被包含在请求中,后端如果没有做到对路由的全覆盖,也不会返回404错误;history模式下,前端的url必须和实际向后端发起请求的url一致,如果没有对用的路由处理,将返回404错误。

✨$route和$router的区别

  • $route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数
  • $router 是“路由实例”对象包括了路由的跳转方法,钩子函数等。

✨声明式导航、编程式导航

  • 声明式导航:在浏览器中通过点击链接实现导航的方式。如<a><router-link>
  • 编程式导航:在浏览器中通过调用API方法实现导航的方式。如location.href
编程式导航API说明
this.$router.push(“hash地址”)跳转到指定hash地址,并增加一条历史记录
this.$router.replace(“hash地址”)跳转到指定hash地址,并替换掉当前历史记录
this.$router.go(n)实现导航历史前进、后退
this.$router.back()在历史记录中,后退到上一个页面
this.$router.forward()在历史记录中,前进到下一个页面

✨Vue-router导航守卫

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

全局路由守卫文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • 全局前置守卫:router.beforeEach
    通常用来判断用户是否登录,如果没有登录就跳转到登录页。控制路由的访问权限。 全局前置守卫的回调函数中接收三个参数:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html
    • to:是将要访问的路由的信息对象
    • from:是将要离开的路由的信息对象
    • next:是一个函数,调用next()表示放行
const router = new VueRouter({...});
router.beforeEach((to, from, next) => {
    if (to.path === '/main') {
        const token = localStorage.getItem('token');
        if (token) next();  // 放行
        else next('/login');  // 跳转到login页面
    }
    else next();
})
  • 全局解析守卫:router.beforeResolve
    也接收三个参数to、from、next,并且这个钩子函数与beforeEach类似,也是路由跳转前触发,官方解释其与beforeEach的区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
  • 全局后置守卫:router.afterEach
    在路由跳转完成后触发,参数包括to、from,没有next,它发生在beforeEach和beforeResolve之后。可以用来跳转之后滚动条回到顶部。
router.afterEach((to, from) => {  
    // 跳转之后滚动条回到顶部  
    window.scrollTo(0,0);
});

路由独享的守卫文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

如果不想全局配置守卫的话,可以为某些路由单独配置守卫。可以直接在路由配置上定义 beforeEnter 守卫。beforeEnter 守卫 只在进入路由时触发,不会在 paramsquery 或 hash 改变时触发。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

组件内的守卫文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • beforeRouteEnter(to, from):在渲染该组件的对应路由被验证前调用,不能获取组件实例 this,因为当守卫执行时,组件实例还没被创建。
  • beforeRouteUpdate(to, from):在当前路由改变,但是该组件被复用时调用,举例来说,对于一个带有动态参数的路径 /users/:id,在 /users/1 和 /users/2 之间跳转的时候,由于会渲染同样的 UserDetails 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 this
  • beforeRouteLeave(to, from):在导航离开渲染该组件的对应路由时调用,可以访问组件实例 this

完整的导航解析流程文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

触发钩子的完整顺序文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
  2. beforeEach:路由全局前置守卫,可用于登录验证、全局路由loading等。
  3. beforeEnter:路由独享守卫
  4. beforeRouteEnter:路由组件的组件进入路由前钩子。
  5. beforeResolve:路由全局解析守卫
  6. afterEach:路由全局后置钩子
  7. beforeCreate:组件生命周期,不能访问tAis。
  8. created:组件生命周期,可以访问tAis,不能访问dom。
  9. beforeMount:组件生命周期
  10. deactivated:离开缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩子。
  11. mounted:访问/操作dom。
  12. activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。
  13. 执行beforeRouteEnter回调函数next。

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

  • 使用 location.href= /url来跳转,简单方便,但是刷新了页面;
  • 使用 history.pushState( /url ) ,无刷新页面,静态跳转;
  • 引进 router ,然后使用 router.push( /url ) 来跳转,使用了 diff 算法,实现了按需加载,减少了 dom 的消耗。其实使用 router 跳转和使用 history.pushState() 没什么差别的,因为vue-router就是用了 history.pushState() ,尤其是在history模式下。

✨Vuex

Vuex是组件之间数据共享方案,可以让组件之间的共享变得高效、清晰、且易于维护。Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

引入配置Vuex文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

// 在src文件夹下创建一个store文件夹,并在store目录下新建index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// vue 2.0 创建仓库:new Vuex.Store({})
// vue 3.0 创建仓库:createStore({})
export default new Vuex.Store({
  state: {
    // 数据
  },
  getters: {
    // vuex的计算属性
  },
  mutations: {
    // 改数据函数
  },
  actions: {
    // 请求数据函数
  },
  modules: {
    // 分模块
  }
})

// 在main.js中引入store,然后全局注入一下,这样就可以在任何一个组件中使用它了
import Vue from vue
import App from ./App.vue
import router from ./router
import store from ./store
Vue.config.productionTip = false
new Vue ({
    el: '#app',
    router,
    store,
    components: { App }
    template: '<App/>'
})

核心流程中的主要功能文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. Vue Components 是 vue 组件,组件会触发(dispatch)一些事件或动作(Actions);
  2. 在组件中发出的动作,肯定是想获取或者改变数据的,但是在 vuex 中,数据是集中管理的,不能直接去更改数据,所以会把这个动作提交(Commit)到 Mutations 中;
  3. 然后 Mutations 就去改变(Mutate) State 中的数据;
  4. 当 State 中的数据被改变之后,就会重新渲染(Render)到 Vue Components 中去,组件展示更新后的数据,完成一个流程。
前端面试基础题:Vue特性、虚拟DOM和Diff算法…

使用Vuex文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • state:存数据的地方,所有的数据都要存到state中;
  • mutations:唯一能够修改数据的方法就是提交Mutations,Mutations中存的是一些操作数据的方法;
  • actions:包含一些方法,和Mutations类似,不同的是Actions不能直接更改数据,它的作用是提交Mutations,Mutations里面包含的才是具体操作数据的方法。
  • getters:将computed提取出来,getters是store的计算属性
  • module:将store分成多个模块
export default createStore({
  state: {
    username: 'zs',
  },
  getters: {
    newName (state) {
      return state.username + '!!!';
    }
  },
  mutations: {
    updateName (state) {
      state.username = 'ls';
    }
  },
  actions: {
    updateName (ctx) {
      setTimeout(() => {
        ctx.commit('updateName')
      }, 1000);
    }
  },
  modules: {}
})
<template>
  <div>
    App
    <!-- 使用根模块state数据 -->
    <p>{{ $store.state.username }}</p>
    <!-- 使用根模块getters数据 -->
    <p>{{ $store.getters['newName'] }}</p>
    <button @click="mutationsFn">mutationsFn</button>
  </div>
</template><script>
import { useStore } from 'vuex'
export default {
  name: 'App',
  setup () {
    // 使用vuex仓库
    const store = useStore();
    // 使用根模块state的数据
    console.log(store.state.username);
    // 使用根模块getters的数据
    console.log(store.getters.newName);
    const mutationsFn = () => {
      // 提交根模块mutations函数
      // store.commit('updateName');
      // 调用根模块actions函数
      store.dispatch('updateName');
    }
    return { mutationsFn };
  }
}
</script>
  • actions和mutations的区别
  1. Mutation专注于修改State,理论上是修改State的唯一途径;Action业务代码、异步请求。
  2. Mutation:必须同步执行;Action:可以异步,但不能直接操作State。
  3. 在视图更新时,先触发actions,actions再触发mutations。
  4. mutation的参数是state,它包含store中的数据;action的参数是context,它是 state 的父级,包含 state、getters等。
  • Vuex 和 localStorage 的区别
  1. vuex存储在内存中,localstorage 则以文件的方式存储在本地,只能存储字符串类型的数据,存储对象需要 JSON的stringify和parse方法进行处理。 读取内存比读取硬盘速度要快。
  2. Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。vuex用于组件之间的传值。localstorage是本地存储,是将数据存储到浏览器的方法,一般是在跨页面传递数据时使用 。Vuex能做到数据的响应式,localstorage不能。
  3. 刷新页面时vuex存储的值会丢失,localstorage不会。
  • 分模块

存在两种情况:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. 默认的模块,state区分模块,其他getters、mutations、actions都在全局;
  2. 带命名空间 namespaced: true 的模块,所有功能区分模块,更高封装度和复用。
import { createStore } from 'vuex'

const moduleA = {
  state: () => {
    return { username: '模块A' }
  },
  getters: {
    changeName (state) {
      return state.username + 'AAAA';
    }
  }
}

const moduleB = {
  namespaced: true,  // 带命名空间的模块
  state: () => {
    return { username: '模块B' }
  },
  getters: {
    changeName (state) {
      return state.username + 'BBBB';
    }
  },
  muations: {
    update (state) {
      state.username = 'BBBB' + state.username;
    }
  },
  actions: {
    update ({ commiit }) {
      setTimeout(() => {
        commit('update')
      }, 2000);
    }
  }
}

export default createStore({
  state: {
    person: [
      { id: 1, name: 'tom', gender: '男' },
      { id: 2, name: 'lucy', gender: '女' },
      { id: 3, name: 'jack', gender: '男' },
    ]
  },
  mutations: {
    // 改数据函数
  },
  actions: {
    // 请求数据函数
  },
  modules: {
    a: moduleA,
    b: moduleB
  },
  getters: {
    boys: (state) => {
      return state.person.filter(p => p.gender === '男');
    }
  }
})
<template>
  <div>App组件</div>
  <ul>
    <li v-for="item in $store.getters.boys" :key="item.id">{{item.name}}</li>
  </ul>
  
  <p>A的username --- {{ $store.state.a.username }}</p>
  <p>A的changeName --- {{ $store.getters.changeName }}</p>
  <hr>
  <p>B的username --- {{ $store.state.b.username }}</p>
  <p>B的changeName --- {{ $store.getters['b/changeName'] }}</p><button @click="$store.commit('b/update')">修改username</button>
  <button @click="$store.dispatch('b/update')">异步修改username</button>
</template>
  • mapState辅助函数
    使用this.$store.state虽然可以很方便的将state里面的值融入computed,但是如果要取多个值,就会很麻烦,可以使用mapState方法自动将需要的state值映射为实例的计算属性。
computed:{
    userInfo(){
      return this.$store.state.userInfo
    },
    
   token(){
      return this.$store.state.token
    },
    
     friends(){
      return this.$store.state.friends
    },
    
    likes(){
     return this.$store.state.likes
    }
    ...
}

// 使用mapState
computed: mapState(['likes','friends','token','userInfo'])
  • mapGetters辅助函数
    mapGetters函数具有mapState的作用,而且其主要用法也是一样的,也能将getters的属性映射到实例的计算属性。
  • vuex持久化
    让在vuex中管理的状态数据同时存储在本地,可免去自己存储的环节。默认是存储在localStorage中的。使用到的插件是 vuex-persistedstate 。
import { createStore } from 'vuex'
import createPersistedstate from 'vuex-persistedstate'

import user from './modules/user'
import cart from './modules/cart'
import category from './modules/category'

export default createStore({
  modules: {
    user,
    cart,
    category
  },
  plugins: [
    createPersistedstate({
      key: 'erabbit-client-pc-store',  // 存储数据的键名
      paths: ['user', 'cart']  // 存储state中哪些数据,如果是模块下具体的数据需要加上模块名称'user.token'
    })
  ]
})

✨前端攻击手段(XSS、CSRF、DOS、页面劫持)

前端面试基础题:Vue特性、虚拟DOM和Diff算法…

1. XSS文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。 根据攻击的来源,XSS 攻击可分为存储型、反射型和 DOM 型三种:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • 存储型(持久型)
  1. 攻击者将恶意代码提交到目标网站的数据库中。
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
  • 反射型(非持久型)
  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  • DOM型攻击
  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL。
  3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

防御方法:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. 一种是使用纯前端的方式,不用服务器端拼接后返回(不使用服务端渲染)。另一种是对需要插入到 HTML 中的代码做好充分的转义。对于 DOM 型的攻击,主要是前端脚本的不可靠而造成的,对于数据获取渲染和字符串拼接的时候应该对可能出现的恶意代码情况进行判断。
  2. 内容安全策略 (CSP) CSP本质上就是建立白名单,开发者明确告诉浏览器哪些外部资源可以进行加载和执行,我们只需要配置规则,如何拦截是由浏览器自己实现的。

2. CSRF文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。CSRF 攻击的本质是利用 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

完成一次CSRF攻击,受害者必须依次完成两个步骤:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. 登录受信任的网站,并在本地生成Cookie
  2. 在不登出信任网站的情况下,访问危险网站

CSRF主要是冒用受害者登录凭证发起恶意的增删改并不会窃取受害者隐私信息文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

防御方法:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. 禁止三方网站获取cookie,比如设置Chrome的SameSite属性
  2. 服务端通过Referer Header 和 Origin Header来进行同源验证
  3. 利用token来鉴别,三方跨站请求并不能获取到头部的token,本站的接口在请求前都会在请求头增加token用于身份鉴权,三方请求并不会携带token
  4. 利用双重cookie来认证,在每个请求的参数都附加scrfCookie='随机数'防御参数,并在cookie中混入该防御参数值,服务端将请求头部的cookie中防御cookie参数和请求参数所带的该参数进行比对

3. DOS文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

DOS攻击通过在网站的各个环节进行攻击,使得整个流程跑不起来,以达到瘫痪服务为目的。最常见的就是发送大量请求导致服务器过载宕机。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

防御方法:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. 扩容服务器
  2. 进行实时监控,封禁某些恶意密集型请求IP段
  3. 增加接口验证,对于某些敏感接口,进行单个IP访问次数限制
  4. 进行静态资源缓存,隔离源文件的访问,比如CDN加速

4. 页面劫持文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

攻击者通过请求的数据传输过程进行数据修改,或者对网站域名进行泛域名解析以重定向网站,在网站中注入广告等。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

防御方法:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html

  1. 最有效且暴力的直接换成HTTPS,建立安全通道
  2. 进行漏洞监控,根据实际情况做出调整
  • XSS和CSRF的区别
  1. CSRF需要用户先登录网站,获取cookie;XSS不需要登录;
  2. CSRF利用网站本身的漏洞去请求网站的API;XSS是向网站注入JS代码,然后执行JS代码,篡改网站的内容。
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49423.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/gcs/49423.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定