前端面试总结

211 阅读45分钟

html5和css

常见的水平垂直居中方案

  • flex布局
.father {
    display: flex;
    justify-content: center;
    align-items: center;
}
.son {
   ...
}
  • 绝对定位配合margin:auto
.father {
    position: relative;
}
.son {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    margin: auto;
}
  • 绝对定位配合transform
.father {
    position: relative;
}
.son {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}
  • 水平居中

    • 对于 行内元素 : text-align: center;

    • 对于确定宽度的块级元素:

      (1)width和margin实现。margin: 0 auto;

      (2)绝对定位和margin-left: (父width - 子width)/2, 前提是父元素position: relative

    • 对于宽度未知的块级元素

      (1)table标签配合margin左右auto实现水平居中。使用table标签(或直接将块级元素设值为 display:table),再通过给该标签添加左右margin为auto。

      (2)inline-block实现水平居中方法。display:inline-block和text-align:center实现水平居中。

      (3)绝对定位+transform,translateX可以移动本身元素的50%。

      (4)flex布局使用justify-content:center

  • 垂直居中

    1. 利用 line-height 实现居中,这种方法适合纯文字类
    2. 通过设置父容器 相对定位 ,子级设置 绝对定位,标签通过margin实现自适应居中
    3. 弹性布局 flex :父级设置display: flex; 子级设置margin为auto实现自适应居中
    4. 父级设置相对定位,子级设置绝对定位,并且通过位移 transform 实现
    5. table 布局,父级通过转换成表格形式,然后子级设置 vertical-align 实现。(需要注意的是:vertical-align: middle使用的前提条件是内联元素以及display值为table-cell的元素)。

隐藏元素的属性有哪些

1.opacity:0,该元素隐藏起来了,但不会改变页面布局,并且,如果该元素已经绑定 一些事件,如click 事件,那么点击该区域,也能触发点击事件的

2.visibility:hidden,该元素隐藏起来了,但不会改变页面布局,但是不会触发该元素已 经绑定的事件 ,隐藏对应元素,在文档布局中仍保留原来的空间(重绘)

3.display:none,把元素隐藏起来,并且会改变页面布局,可以理解成在页面中把该元素。 不显示对应的元素,在文档布局中不再分配空间(回流+重绘)

scoped样式穿透

scoped虽然避免了组件间样式污染,但是很多时候我们需要修改组件中的某个样式,但是又不想去除scoped属性

  1. 使用/deep/
<!-- Parent -->
<template>
<div class="wrap">
    <Child />
</div>
</template>

<style lang="scss" scoped>
.wrap /deep/ .box{
    background: red;
}
</style>

<!-- Child -->
<template>
    <div class="box"></div>
</template>

  1. 使用两个style标签
<!-- Parent -->
<template>
<div class="wrap">
    <Child />
</div>
</template>

<style lang="scss" scoped>
/* 其他样式 */
</style>
<style lang="scss">
.wrap .box{
  background: red;
}
</style>

<!-- Child -->
<template>
  <div class="box"></div>
</template>

清除浮动的方式

  • 添加额外标签
<div class="parent">
    //添加额外标签并且添加clear属性
    <div style="clear:both"></div>
    //也可以加一个br标签
</div>

  • 父级添加overflow属性,或者设置高度
  • 建立伪类选择器清除浮动
//在css中添加:after伪元素
.parent:after{
    /* 设置添加子元素的内容是空 */
    content: '';
    /* 设置添加子元素为块级元素 */
    display: block;
    /* 设置添加的子元素的高度0 */
    height: 0;
    /* 设置添加子元素看不见 */
    visibility: hidden;
    /* 设置clear:both */
    clear: both;
}

js

js的数据类型分类及类型判断

基本数据类型

boolean string number bigint null undefined symbol

对象数据类型

object function array

  • typeof 来判断数据类型,只能区分基本类型,即 “number”,”string”,”undefined”,”boolean”,”object”,“function”,“symbol”** (ES6新增)七种。对于数组、null、对象来说,其关系错综复杂,使用 typeof 都会统一返回 “object” 字符串。
  • instanceof 用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上,用于判断一个对象是否是某个类或某个子类的实例
  • Object.prototype.tostring.call
const a = "str";
console.log("typeof a :>> ", typeof a); // typeof a :>>  string

const b = 999;
console.log("typeof b :>> ", typeof b); // typeof b :>>  number

const c = BigInt(9007199254740991);
console.log("typeof c :>> ", typeof c); // typeof c :>>  bigint

const d = false;
console.log("typeof d :>> ", typeof d); // typeof d :>>  boolean

const e = undefined;
console.log("typeof e :>> ", typeof e); // typeof e :>>  undefined

const f = Symbol("f");
console.log("typeof f :>> ", typeof f); // typeof f :>>  symbol

const g = null;
console.log("typeof g :>> ", typeof g); // typeof g :>>  object

const h = () => {};
console.log("typeof h :>> ", typeof h); // typeof h :>>  function

const i = [];
console.log("typeof i :>> ", typeof i); // typeof i :>>  object

常用的数组方法

改变原数组的方法

  • push() 数组末尾添加一个或多个元素,并返回一个新数组

  • pop() 删除并返回数组的最后一个元素

  • shift() 删除并返回数组的第一个元素,若该数组为空,返回undefined

  • unshift() 数组开头添加一个或多个元素,返回新的数组长度

  • reverse() 数组倒序

  • soft() 数组排序a-b由小到大,b-a有大到小

  • splice(index,howmany,arr1,arr2) 用于添加或删除数组中的元素。从index位置开始,删除howmany个元素,并将arr1,arr2...从index位置插入,howmany为0时则不删除 不改变原数组的方法

  • concat(arr1,arr2...) 合并两个或多个数组,返回一个新数组

  • forEatch() 遍历数组,执行函数,直接打印Array.forEatch()结果为undefined

  • map() 遍历数组,执行函数,并返回一个新数组

  • slise() 按照条件查找出其中的部分内容

  • join() 将数组每一项用指定符号拼接起来,返回一个字符串

  • filter() 过滤数组

  • find() 查找符合条件的项,找到第一个符合条件的元素并返回这个元素

  • indexof() 检测当前值在数组中第一次出现的位置

  • includes() 判断数组内是否包含指定内容,返回值为布尔型

数组去重方法

  • 双重for循环+splice方法(NAN无效)
let arrObj = [
    { name: "小红", id: 1 },
    { name: "小橙", id: 1 },
    { name: "小黄", id: 4 },
    { name: "小绿", id: 3 },
    { name: "小青", id: 1 },
    { name: "小蓝", id: 4 }
];
function fn1(tempArr) {
    for (let i = 0; i < tempArr.length; i++) {
        for (let j = i + 1; j < tempArr.length; j++) {
            if (tempArr[i].id == tempArr[j].id) {
                tempArr.splice(j, 1);
                j--; // 这个别忘咯
            };
        };
    };
    return tempArr;
};
console.log(fn1(arrObj));

  • es6 Set方法,数据结构类似于数组,但是数据都是不重复的
const result = Array.from(new Set(arr)) 
console.log(result// [ 1, 2, 'abc', true, false, undefined, NaN ]
  • includes
function removeDuplicate(arr) { 
  const newArr = [] 
  arr.forEach(item => {
    if (!newArr.includes(item)){ 
      newArr.push(item)
    } 
  }) 
  return newArr
}
const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined, NaN ]
  • for循环+indexof+push(NaN无效)
function removeDuplicate(arr) { 
   const newArr = [] 
   arr.forEach(item => { 
     if (newArr.indexOf(item) === -1) {
        newArr.push(item) 
        }
     }) 
     return newArr // 返回一个新数组 
} 
const result = removeDuplicate(arr) 
console.log(result) // [ 1, 2, 'abc', true, false, undefined, NaN, NaN ]
  • filter方法会对满足条件的元素存放到一个新数组中,结合indexOf方法进行判断。
function removeDuplicate(arr) { 
   return arr.filter((item, index) => {
     return arr.indexOf(item) === index 
     })
 } 
const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined ]
  • Map对象是JavaScript提供的一种数据结构,结构为键值对形式,将数组元素作为map的键存入,然后结合has()set()方法判断键是否重复。
function removeDuplicate(arr) { 
  const map = new Map() 
  const newArr = [] 
  arr.forEach(item => { 
    if (!map.has(item)) { // has()用于判断map是否包为item的属性值
      map.set(item, true) // 使用set()将item设置到map中,并设置其属性值为true
      newArr.push(item) 
     } 
  }) 
  returnnewArr 
}
const result = removeDuplicate(arr) 
console.log(result) // [ 1, 2, 'abc', true, false, undefined, NaN ]

数组过滤

  • map 通过遍历数组,筛选出符合条件的元素
  • filter 根据指定的条件筛选出满足条件的元素
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // 输出 [2, 4]
  • find找到符合要求的第一个数值
const fruits = ['apple', 'banana', 'orange'];
const result = fruits.find(fruit => fruit === 'banana');
console.log(result); // 输出 "banana"
  • reduce方法可以利用累加器函数对数组中的元素进行累积计算,然后返回一个结果。通过结合条件判断,也可以实现数组过滤的功能。
const numbers = [1, 2, 3, 4, 5];
const evenSum = numbers.reduce((acc, num) => {
  if (num % 2 === 0) {
    return acc + num;
  }
  return acc;
}, 0);
console.log(evenSum); // 输出 6 (2 + 4)

深拷贝与浅拷贝

浅拷贝

拷贝基本数据类型时,不受任何影响,当拷贝引用类型时,源对象也会被修改。

let obj = {
	user:{
		name:"rosy"
	}
}
//assign 实现浅拷贝
let copy = Object.assign({},obj);
copy.user.name = "lucy";
console.log(obj.user.name);//lucy
console.log(obj == copy);//false

利用数组方法concat,splise,...扩展运算符

//数组相关方法实现浅拷贝

//利用concat 连接
let arr = [{say:"hello"}]
//不连接任何数组,相当于返回原数组
let copyArr = arr.concat();
console.log(copyArr[0] == arr[0]);//true
console.log(copyArr == arr);//false

//利用slice 截取
let sliceArr = arr.slice();
console.log(sliceArr[0] == arr[0]);//true
console.log(sliceArr == arr);//false

//开辟一个新变量
let newArr = [...arr];
console.log(newArr[0] == arr[0]);//true
console.log(newArr == arr);//false

//修改
newArr[0].say = "hi";
console.log(arr); //say:"hi"
delete newArr[0]["say"];
console.log(arr); //空
newArr[1] = "lucy";
console.log(arr); //没有lucy

深拷贝

  • 所有的拷贝的都是值
  • 利用序列化反序列化 JSON.parse JSON.stringify
let obj = {
	user:[{
		name:"lucy"
	}]
}
//深拷贝 字符串转换
let copy = JSON.parse(JSON.stringify(obj));
console.log(copy == obj); //false
console.log(copy.user == obj.user); //false
console.log(copy.user[0] == obj.user[0]); //false
console.log(copy.user[0]["name"] == obj.user[0]["name"]); //true

var && let && const

ES6之前创建变量用的是var,之后创建变量用的是let/const

三者区别

  1. var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
    let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
    const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。
  2. var可以先使用,后声明,因为存在变量提升;let必须先声明后使用。
  3. var是允许在相同作用域内重复声明同一个变量的,而let与const不允许这一现象。
  4. 在全局上下文中,基于let声明的全局变量和全局对象GO(window)没有任何关系 ;
    var声明的变量会和GO有映射关系;
  5. 会产生暂时性死区

暂时性死区是浏览器的bug:检测一个未被声明的变量类型时,不会报错,会返回undefined
如:console.log(typeof a) //undefined
而:console.log(typeof a)//未声明之前不能使用
let a

  1. let /const/function会把当前所在的大括号(除函数之外)作为一个全新的块级上下文,应用这个机制,在开发项目的时候,遇到循环事件绑定等类似的需求,无需再自己构建闭包来存储,只要基于let的块作用特征即可解决

作用域和作用域链

定义:简单来说作用域就是变量与函数的可访问范围,由当前环境与上层环境的一系列变量对象组成
1.全局作用域:代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域。
2.函数作用域:在固定的代码片段才能被访问

作用:作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

作用域链:一般情况下,变量到 创建该变量 的函数的作用域中取值。但是如果在当前作用域中没有查到,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

闭包

在js中变量的作用域属于函数作用域, 在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数, 由于其可访问上级作用域,即使上级函数执行完, 作用域也不会随之销毁, 这时的子函数(也就是闭包),便拥有了访问上级作用域中变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。

  • 闭包的特性

    • 1、内部函数可以访问定义他们外部函数的参数和变量。(作用域链的向上查找,把外围的作用域中的变量值存储在内存中而不是在函数调用完毕后销毁)设计私有的方法和变量,避免全局变量的污染。

      1.1.闭包是密闭的容器,,类似于set、map容器,存储数据的

      1.2.闭包是一个对象,存放数据的格式为 key-value 形式

    • 2、函数嵌套函数

    • 3、本质是将函数内部和外部连接起来。优点是可以读取函数内部的变量,让这些变量的值始终保存在内存中,不会在函数被调用之后自动清除

  • 闭包形成的条件

    1. 函数的嵌套
    2. 内部函数引用外部函数的局部变量,延长外部函数的变量生命周期
  • 闭包的用途

    1. 模仿块级作用域
    2. 保护外部函数的变量 能够访问函数定义时所在的词法作用域(阻止其被回收)
    3. 封装私有化变量
    4. 创建模块
  • 闭包应用场景

    闭包的两个场景,闭包的两大作用:保存/保护。 在开发中, 其实我们随处可见闭包的身影, 大部分前端JavaScript 代码都是“事件驱动”的,即一个事件绑定的回调方法; 发送ajax请求成功|失败的回调;setTimeout的延时回调;或者一个函数内部返回另一个匿名函数,这些都是闭包的应用。

  • 闭包的优点:延长局部变量的生命周期

  • 闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏

JS 中 this 的五种情况

  1. 作为普通函数执行时,this指向window
  2. 当函数作为对象的方法被调用时,this就会指向该对象
  3. 构造器调用,this指向返回的这个对象
  4. 箭头函数 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。
  5. 基于Function.prototype上的 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,`` bind方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this指向除了使用new `时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。

原型 && 原型链

原型关系:

  • 每个 class都有显示原型 prototype
  • 每个实例都有隐式原型 _ proto_
  • 实例的_ proto_指向对应 class 的 prototype

原型:  在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象

原型链:函数的原型链对象constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针是指向上一层的原型对象,而上一层的原型对象的结构依然类似。因此可以利用__proto__一直指向Object的原型对象上,而Object原型对象用Object.prototype.__ proto__ = null表示原型链顶端。如此形成了js的原型链继承。同时所有的js对象都有Object的基本防范

特点:  JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

for...in与for...of

for...in遍历对象, for...of 遍历数组 都可以用break跳出循环

防抖和节流

  • 防抖和节流都是优化性能的一种手段
  • 防抖: 在单位时间内频繁触发事件,只有最后一次生效
  • 节流: 在单位时间内频繁触发事件,只生效一次(也就是只有第一次生效)
  • 一个经典的比喻: 想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应假设电梯有两种运行策略,超时设定为15秒,不考虑容量限制电梯第一个人进来后,15秒后准时运送一次,这是节流电梯里第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖

防抖应用场景:

  • 处理搜索框的输入事件,避免每次输入都发送请求,而是等待用户输入完毕后再发送请求。
  • 处理窗口调整事件,避免频繁的重新渲染页面布局。
  • 处理按钮点击事件,避免用户误操作导致重复的请求或页面跳转。

节流应用场景:

  • 处理滚动事件,限制频繁触发的情况,例如在滚动加载场景中,只有在用户停止滚动一段时间后才加载内容。
  • 处理鼠标移动事件,避免频繁触发事件处理函数,尤其对于一些消耗性能较大的操作,可以控制处理的频率。
  • 高频点击
  • 表单重复提交

比较:

  • 防抖适合处理连续触发的事件,等待时间后执行最后一次触发的操作,常用于输入框、窗口调整等场景。
  • 节流适合处理频繁触发的事件,限制事件处理函数的执行频率,常用于滚动、拖拽等场景。

回流与重绘

  • 重排/回流(Reflow):当DOM的变化影响了元素的几何信息,浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。表现为重新生成布局,重新排列元素。

  • 重绘(Repaint): 当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。表现为某些元素的外观被改变 『重绘』不一定会出现『重排』,『重排』必然会出现『重绘』。

如何避免重绘或者重排?

  1. 集中改变样式,不要一条一条地修改 DOM 的样式。

  2. 不要把 DOM 结点的属性值放在循环里当成循环里的变量。

  3. 为动画的 HTML 元件使用 fixedabsoultposition,那么修改他们的 CSS 是不会 reflow 的。

  4. 不使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。

  5. 尽量只修改position:absolutefixed元素,对其他元素影响不大

  6. 动画开始GPU加速,translate使用3D变化

Cookie、sessionStorage、localStorage 的区别

  • cookie数据大小不能超过4k;sessionStorage和localStorage的存储比cookie大得多,可以达到5M+
  • cookie设置的过期时间之前一直有效;localStorage永久存储,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage数据在当前浏览器窗口关闭后自动删除
  • cookie的数据会自动的传递到服务器;sessionStorage和localStorage数据保存在本地

Async/Await 如何通过同步的方式实现异步

Async/Await就是一个自执行的generate函数。利用generate函数的特性把异步的代码写成“同步”的形式,第一个请求的返回值作为后面一个请求的参数,其中每一个参数都是一个promise对象.

promise 的async与await

async、await 是 ES8(ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作。

  • async 声明的函数,返回的结果是一个 Promise 对象。如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。
简单例子区分async关键字函数与普通函数的区别
async function fn1(){
    return 123
}

function fn2(){
    return 123
}
console.log(fn1())
console.log(fn2())

//输出结果为:
// Promise {<fulfilled>: 123}
// 123

  • await 等待的是一个表达式。这个表达式的计算结果是 Promise 对象或者其他值。
  • 单一的 Promise 链并不能发现 async/await 的优势,真正能体现出其优势的是其处理多个 Promise 组成的 then 链的时候(Promise 通过 then 链来解决多层回调的问题,现在又需要 async/await 来进一步优化它)。

假设一个逻辑,分多个步骤完成,每一个步骤都是异步的,并且依赖于上一个步骤的结果。(简单来说,每一个异步都要按指定的顺序来了执行)我们用 setTimeout 来进行一次模拟异步操作。

function getData(n) {
	return new Promise(resolve => {
		setTimeout(() => resolve(n + 200), n)
	})
}

function step1(n) {
	console.log(`step1 值为 ${n}`)
	return getData(n)
}

function step2(m, n) {
	console.log(`step2 值为 ${m} + ${n}`)
	return getData(m + n)
}

function step3(k, m, n) {
	console.log(`step3 值为 ${k} + ${m} + ${n}`)
	return getData(k + m + n)
}

async function getDataByAwait() {
	const time1 = 300
	console.time('用时')
	const time2 = await step1(time1)
	const time3 = await step2(time1, time2)
	const result = await step3(time1, time2, time3)
	console.log('最终结果是:', result)
	console.timeEnd('用时')
}

getDataByAwait()

使用promise链式调用
function getDataByPromise() {
	const time1 = 300
	console.time('用时')
	step1(time1).then(time2 => {
		return step2(time1, time2).then(time3 => [time1, time2, time3])
	}).then(times => {
		const [time1, time2, time3] = times
		return step3(time1, time2, time3)
	}).then(result => {
		console.log('最终结果是:', result)
		console.timeEnd('用时')
	})
}

getDataByPromise()
两个方法一对比,就知道asyncawait有多香了
  • 我们在使用 async/await 的时候,由于 Promise 运行结果可能是 rejected,所以我们最好把 await 命令放在 try catch 代码块中。
async getData = () => {
	try {
		await step1(200)
	} catch(err) {
		console.log(err)
	}
}

// 另一种写法
async getData = () => {
	await step1(200).catch(err = > console.log(err))
}

setTimeout、Promise、Async/Await 的区别

  1. setTimeout

    settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行。

  2. Promise

    Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行。

    console.log('script start')
    let promise1 = new Promise(function (resolve) {
        console.log('promise1')
        resolve()
        console.log('promise1 end')
    }).then(function () {
        console.log('promise2')
    })
    setTimeout(function(){
        console.log('settimeout')
    })
    console.log('script end')
    // 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout
    
  3. async/await

    async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。

    async function async1(){
       console.log('async1 start');
        await async2();
        console.log('async1 end')
    }
    async function async2(){
        console.log('async2')
    }
    
    console.log('script start');
    async1();
    console.log('script end')
    
    // 输出顺序:script start->async1 start->async2->script end->async1 end
    

ts与 js的区别

语法

  • 类型注解
  • 类和接口
  • 枚举

类型系统

  • 静态类型检查
  • 类型推断
  • 提供更丰富的类型

工具支持

生态系统(第三方库)

总结:TypeScript 是 JavaScript 的超集,通过引入静态类型检查和其他新功能,使开发更加稳定、可维护和高效。

vue原理

vue的响应式原理

数据响应式是指通过数据驱动DOM视图的变化,是单向的过程 通过 数据劫持结合发布者-订阅者模式 的方式来实现的(在数据变动时发布消息给订阅者,触发相应的监听回调)。 Vue内部通过 Object.defineProperty() 监听对象属性的改变,它有对应的两个描述属性get和set,当数据发生改变后,就会触发对应的set方法,set方法里会通知对应的订阅者watcher,watcher接收后将触发render函数,重新渲染数据。

vue双向数据绑定

我们都知道 Vue 是数据双向绑定的框架,双向绑定由三个重要部分构成
​
数据层(Model):应用的数据及业务逻辑
视图层(View):应用的展示效果,各类UI组件
业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来
而上面的这个分层的架构方案,可以用一个专业术语进行称呼:MVVM这里的控制层的核心功能便是 “数据双向绑定” 。自然,我们只需弄懂它是什么,便可以进一步了解数据绑定的原理
​
理解ViewModel
它的主要职责就是:
​
数据变化后更新视图
视图变化后更新数据
当然,它还有两个主要部分组成
​
监听器(Observer):对所有数据的属性进行监听
解析器(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数

vue事件绑定原理?

  1. DOM事件绑定

当在模板中绑定 DOM 事件时,Vue 在内部使用 addEventListener 将事件处理函数注册到真实的DOM元素上。例如: v-on:click="handleClick" 绑定click事件。

Vue 通过浏览器提供的DOM API向元素添加EventListener监听事件,当事件触发时,执行绑定的回调函数。

2.自定义事件

Vue 中可以使用 on方法监听自定义事件,emit 方法触发自定义事件。

Vue 内部通过Vue实例维护一个事件中心,通过事件中心来实现自定义事件的监听和触发。

一个组件在创建时会同时创建一个 vm 实例,vm 实例中会包含 on方法和emit 方法,在组件中你就可以通过 this 来调用这两个方法。在父组件中,通过 emit触发自定义事件,同时子组件通过on 来监听事件。

综上可知,Vue的事件绑定原理就是使用DOM事件和自定义事件两种方式,通过调用浏览器原生的DOM API和Vue内部提供的事件系统,来实现事件的触发和监听。

vue模板编译的原理?

Vue.js 是基于模板的渲染机制来实现页面的渲染的。Vue.js 通过将模板编译成渲染函数的方式来进行模板的渲染。模板编译的过程主要包括以下几个步骤:

  1. 解析模板字符串:Vue.js 将模板字符串解析成 AST(Abstract Syntax Tree)。
  2. 静态优化:在编译过程中,Vue.js 会静态地分析整个模板,检测不需要更改的部分,将其优化成静态内容,这些内容不需要在每次重新渲染时都重新生成。
  3. 代码生成:将 AST 转换成渲染函数。这个过程包括将每个 AST 节点转换成代码字符串并拼接成渲染函数。
  4. 渲染函数:将生成的渲染函数执行后,会得到一个 VNode(Virtual DOM 节点)。

这些步骤将模板编译成渲染函数,用于生成 Virtual DOM,并更新到页面中,从而实现页面的渲染。在每次有数据变化时,Vue.js 会重新调用渲染函数,生成新的 VNode,通过对比新旧 VNode 差异,从而仅仅更新需要更新的部分,提高页面的渲染效率。

描述组件渲染的过程?

Vue组件渲染的过程是将组件的模板渲染成真实的DOM节点并插入页面中的过程。它大致分为以下几个步骤:

  1. 创建Vue实例:在使用Vue框架时,需要创建Vue实例。Vue实例是Vue应用的入口,它将负责管理各个组件之间的数据传递和状态管理。
  2. 模板解析:在Vue组件中,使用HTML模板描述组件的结构和样式。当Vue实例被创建时,它会解析组件的模板,并将解析后的模板存储在内存中。Vue使用HTML解析器将模板解析为AST(抽象语法树)。
  3. 模板编译:在Vue实例渲染组件时,Vue将AST编译为渲染函数,渲染函数是一个返回HTML字符串的JavaScript函数。
  4. 组件渲染:当Vue实例需要渲染组件时,它会使用渲染函数将组件渲染成HTML字符串。
  5. 真实DOM生成:将HTML字符串转换成真实的DOM节点。Vue使用虚拟DOM算法,将HTML字符串转化为真实的DOM节点,Vue会尽量复用之前生成的DOM节点,以提高性能。
  6. 真实DOM插入:将真实的DOM节点插入到页面中。Vue将生成的真实DOM节点插入到指定的挂载点,完成组件渲染的过程。

需要注意的是,Vue会对组件进行一定的优化,包括条件渲染、属性绑定、事件处理等,以提高渲染效率和用户体验。

event-bus原理?

EventBus是消息传递的一种方式,基于一个消息中心,订阅和发布消息的模式,称为发布订阅者模式。

  1. on('name', fn)订阅消息,name:订阅的消息名称, fn: 订阅的消息
  2. emit('name', args)发布消息, name:发布的消息名称 , args:发布的消息
kotlin
复制代码
class Bus {
  constructor () {
    this.callbacks = {}
  }
  $on(name,fn) {
    this.callbacks[name] = this.callbacks[name] || []
    this.callbacks[name].push(fn)
  }
  $emit(name,args) {
    if(this.callbacks[name]){
       //存在遍历所有callback
       this.callbacks[name].forEach(cb => cb(args))
    }
  }
}

创建一个全局事件总线: 挂载在Vue.prototype之上

VUEX的原理?

Vuex是通过全局注入store对象,来实现组件间的状态共享

vue.$set方法的原理?

①传入的target如果是undefinednull或是原始类型,则直接跑出错误。

②如果传入的是一个数组的话,就会调用数组的splice方法进行实现响应式。

③如果不是一个数组,就当做对象来处理,先判断当前key在源对象是否存在,如果存在,说明当前key已经是响应式的,就直接进行操作对应的动作。如果key不在源对象中,就调用Object.defineReactive方法将该key添加到源对象上,并且实现了响应式

Vue中scoped原理?

给HTML的DOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性。

在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器的哈希特征值(如[data-v-2311c06a])来私有化样式。

nextTick的原理:

nextTick的参数(callback),在promise.resolve.than异步的行为里面去执行

image.png

vue.mixin的原理?

使用原理: Vue.mixin的实现原理是基于Vue对组件选项的合并策略,在创建和合并组件选项时,会递归地将对象的所有属性合并到目标对象上,如果有相同名字的属性,则会进行策略性合并。当合并的对象包含生命周期事件、methods、data等选项时,这些选项会以先来后到的顺序合并到目标组件中。

在使用Vue.mixin时,我们可以通过定义一个Object,将通用的逻辑封装到这个对象中,然后通过Vue.mixin()方法全局注册这个对象,从而让所有组件可通过“继承”这个Object的方式,达到共享的目的。

需要注意的是,使用Vue.mixin并不能完全解决代码复用的问题,过度使用Vue.mixin可能会导致代码难以维护,潜在影响可读性、可维护性等。因此,应慎重使用Vue.mixin,并确保对其的合理使用。

computed实现原理

实现原理的核心在于依赖收集。当计算属性被创建时,Vue.js会通过 Object.defineProperty() 方法将其定义为getter函数,并且在getter函数中,调用其依赖的属性的getter函数。当依赖的属性发生改变时,它们会通知计算属性的setter函数,从而触发计算属性的更新。

除了依赖收集,Vue.js还提供了缓存机制,以避免非必要的计算耗时。当一个计算属性首次被访问时,它的值会被计算,并缓存下来。只有当依赖的属性发生改变时,计算属性才会重新计算,否则直接返回缓存的值。

diff算法原理(重点)

是vdom中最核心、最关键的部分。Diff算法是虚拟DOM(Virtual DOM)的核心算法,用于比较新旧虚拟DOM树,找出变化的部分,最终只更新变化的部分,以达到优化渲染性能的目的。 宗旨:只比较同一层级, 时间复杂度O(N),深度优先 image.png 步骤

patch方法:对比当前同层的虚拟节点是否为同一种类型的标签

  • 是:继续执行patchVnode方法进行更深比对

  • 否:没必要比对了,直接整个节点替换成新虚拟节点

    kotlin
    复制代码
    function sameVnode(oldVnode, newVnode) {
      return (
        oldVnode.key === newVnode.key && // key值是否一样
        oldVnode.tagName === newVnode.tagName && // 标签名是否一样
        oldVnode.isComment === newVnode.isComment && // 是否都为注释节点
        isDef(oldVnode.data) === isDef(newVnode.data) && // 是否都定义了data
        sameInputType(oldVnode, newVnode) // 当标签为input时,type必须是否相同
      )
    }
    

patchVnode方法:

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

updateChildren方法: 新旧虚拟节点的子节点对比

首尾指针法,新的子节点集合和旧的子节点集合

然后会进行互相进行比较,总共有五种比较情况: image.png

  • 1、oldS 和 newS使用sameVnode方法进行比较,sameVnode(oldS, newS)
  • 2、oldS 和 newE使用sameVnode方法进行比较,sameVnode(oldS, newE)
  • 3、oldE 和 newS使用sameVnode方法进行比较,sameVnode(oldE, newS)
  • 4、oldE 和 newE使用sameVnode方法进行比较,sameVnode(oldE, newE)
  • 5、如果以上逻辑都匹配不到,再把所有旧子节点的 key 做一个映射到旧节点下标的 key -> index 表,然后用新 vnodekey 去找出在旧节点中可以复用的位置。

日常应用场景:循环中key的使用

vue基础

vue图片懒加载

$ npm i vue-lazyload -D

  1. main.js 在入口文件** import VueLazyload from 'vue-lazyload' //引入这个懒加载插件
  2. 在入口文件添加后,在组件任何地方都可以直接使用把 img 里的:src -> v-lazy**

为什么Vue中的v-if和v-for不建议一起用?

当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级(vue2),这意味着 v-if 将分别重复运行于每个 v-for 循环中,一起使用会造成性能浪费

  • 解决方案有两种,把v-if放在v-for的外层或者把需要v-for的属性先从计算属性中过滤一次

为何v-for中要使用key

  1. 必须使用key,不能是index和random,因为diff算法中通过tag和key来判断是否为相同节点。  匹配了key,则只移动元素,性能较好,未匹配key,则删除重建,性能较差。
  2. 这样可以减少渲染次数,提升渲染性能

循环问:vue什么时候操作DOM比较合适

mounted和updated都不能保证子组件全部挂载完成,所有使用$nextTick的时机进行操作DOM。

compute与watch的区别

  • 使用场景:compute适用于一个数据受多个数据影响的情况,watch适用于一个数据变化影响多个数据的情况
  • 区别:compute默认会走缓存,只有依赖数据发生变化时,才会重新计算,不支持异步,异步数据变化时无法做出相应改变,watch不依赖缓存,一旦数据发生变化就触发响应操作,支持异步

vue的data为什么是个函数

  • 根实例对象data可以是对象也可以是函数(根实例是单例),不会产生数据污染情况

  • 组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。采用函数的形式,initData时会将其作为工厂函数都会返回全新data对象

vue 组件通信的方式有哪些

  • props/$emit 父子组件通信

    父->子props,子->父 $on、$emit 获取父子组件实例 parent、children Ref 获取实例的方式调用组件的属性或者方法 父->子孙 Provide、inject 官方不推荐使用,但是写组件库时很常用

  • $emit/$on 自定义事件 兄弟组件通信

    Event Bus 实现跨组件通信 Vue.prototype.$bus = new Vue() 自定义事件

  • vuex 跨级组件通信

    Vuex、$attrs、$listeners Provide、inject

vue的常用指令

  • v-on : 绑定事件监听, 可简写为@
  • v-bind : 单向绑定解析表达式, 可简写为 :xxx
  • v-model : 双向数据绑定
  • v-for : 遍历数组/对象/字符串
  • v-show : 条件渲染 (动态控制节点是否展示)
  • v-if : 条件渲染(动态控制节点是否存存在)
  • v-else : 条件渲染(动态控制节点是否存存在)
  • v-text:将数据填充到标签中(不会解析填充内容中的HTML标签)
  • v-html:将数据填充到标签中(会解析填充内容中的HTML标签)
  • v-cloak:一个特殊属性,Vue接管标签后会删掉这个属性(没有值)
  • v-once:只渲染一次,之后Vue就不再渲染这个标签了(视为静态内容了)
  • v-pre:Vue不接管这个属性所在的标签(可用于加快编译速度,用于没有使用指令语法、没有使用插值语法的节点上)

总结:

v-html指令:

1.作用:向指定节点中渲染包含html结构的内容。

2.与插值语法的区别:

(1).v-html会替换掉节点中所有的内容,{{xx}}则不会。

(2).v-html可以识别html结构。

3.严重注意:v-html有安全性问题!!!!

(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。

(2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!

vue的生命周期

1、创建:beforecreate created beforemount mounted

2、更新:beforeupdate updated

3、销毁:beforedestroyed destroyed

父子组件:挂载:由父------->子;渲染:由子------->夫

vue父子组件生命周期器钩子函数顺序问题

  1. 父组件 beforeCreate 钩子函数
  2. 父组件 created 钩子函数
  3. 父组件 beforeMount 钩子函数
  4. 子组件 beforeCreate 钩子函数
  5. 子组件 created 钩子函数
  6. 子组件 beforeMount 钩子函数
  7. 子组件 mounted 钩子函数
  8. 父组件 mounted 钩子函数

做了什么:

beforecreate:创建一个空白vue实例,data和method尚未被初始化,不可使用

created:Vue实例初始化完成,完成响应式绑定data和method被初始化,未开始渲染模板

beforemount:编译模板,调用render生成vdom,未开始渲染DOM

mounted:完成渲染DOM,组件渲染完成,由“创建阶段”进入“运行阶段”

beforeupdate:data发生变化之后,但是尚未更新DOM

updated:data发生变化,并且完成DOM更新(不能在这一步修改data,否则会导致死循环)

beforedestroyed:组件进入销毁阶段,但是尚未进行销毁,任然可使用,可以用来接触一些自定义的事件,定时器自定义DOM节点等

destroyed:组件被销毁,所有子组件都被销毁

vue修饰符有哪些?

1、表单修饰符

(1).lazy ​ 在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 ,可以添加 lazy 修饰符,从而转为在 change 事件之后进行同步: ​

<input v-model.lazy="msg">
​

​ (2).number ​ 如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符: ​

<input v-model.number="age" type="number">
​

​ (3).trim ​ 如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符: ​

<input v-model.trim="msg">
​

​ 2、事件修饰符

(1).stop ​ 阻止单击事件继续传播。 ​

<!--这里只会触发a-->
<div @click="divClick"><a v-on:click.stop="aClick">点击</a></div>

​ (2).prevent ​ 阻止标签的默认行为。 ​

<a href="http://www.baidu.com" v-on:click.prevent="aClick">点击</a>
​

​ (3).capture ​ 事件先在有.capture修饰符的节点上触发,然后在其包裹的内部节点中触发。 ​

<!--这里先执行divClick事件,然后再执行aClick事件-->
<div @click="divClick"><a v-on:click="aClick">点击</a></div>

​ (4).self ​ 只当在 event.target 是当前元素自身时触发处理函数,即事件不是从内部元素触发的。 ​

<!--在a标签上点击时只会触发aClick事件,只有点击phrase的时候才会触发divClick事件-->
<div @click.self="divClick">phrase<a v-on:click="aClick">点击</a></div>

​ (5).once ​ 不像其它只能对原生的 DOM 事件起作用的修饰符,.once 修饰符还能被用到自定义的组件事件上,表示当前事件只触发一次。 ​

<a v-on:click.once="aClick">点击</a>
​

(6).passive.passive 修饰符尤其能够提升移动端的性能 ​

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->  
<!-- 而不会等待 `onScroll` 完成 -->  
<!-- 这其中包含 `event.preventDefault()` 的情况 -->  
<div v-on:scroll.passive="onScroll">...</div>

除了上述常见的修饰符,Vue 还提供了一些用于键盘事件的快捷键修饰符,例如:

  1. .keyCode: 只在特定的按键按下时才触发事件。
  2. .enter: 按下回车键时触发事件。
  3. .tab: 按下 Tab 键时触发事件。
  4. .delete: 按下删除键时触发事件。

还有一些其他的键盘快捷键修饰符,通过这些修饰符可以方便地绑定和处理各种键盘事件。

Vue中如何扩展一个组件?

1、内容扩展:slot

2、逻辑扩展:mixins,extends,composition api

slot理解及使用场景

Slot 艺名插槽,花名“占坑”,我们可以理解为solt在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中slot位置),作为承载分发内容的出口

使用场景

通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理

如果父组件在使用到一个复用组件的时候,获取这个组件在不同的地方有少量的更改,如果去重写组件是一件不明智的事情

通过slot插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用,比如布局组件、表格列、下拉选、弹框显示内容等

slot种类

具名插槽

具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。

  • 子组件的代码,设置了两个插槽(header和footer):
  • 父组件填充内容, 父组件通过 v-slot:[name] 的方式指定到对应的插槽中

默认插槽

默认插槽就是指没有名字的插槽,子组件未定义的名字的插槽,父级将会把 未指定插槽的填充的内容填充到默认插槽中。 注意:

  1. 父级的填充内容如果指定到子组件的没有对应名字插槽,那么该内容不会被填充到默认插槽中。即具名插槽用name属性来表示插槽的名字,不传为默认插槽

  2. 如果子组件没有默认插槽,而父级的填充内容指定到默认插槽中,那么该内容就不会填充到子组件的任何一个插槽中。

  3. 如果子组件有多个默认插槽,而父组件所有指定到默认插槽的填充内容,将会且全都填充到子组件的每个默认插槽中。

作用域插槽

作用域插槽其实就是带数据的插槽,即带参数的插槽,简单的来说就是子组件提供给父组件的参数,该参数仅限于插槽中使用,父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容。

1.子组件存放一个带数据的插槽

<template>
  <div class="child">

    <h3>这里是子组件</h3>
    <slot  :data="data"></slot>
  </div>
</template>

 export default {
    data: function(){
      return {
        data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
      }
    }
}

2.父组件通过 “slot-scope” 来接收子组件传过来的插槽数据,再根据插槽数据来填充插槽的内容

  <div class="father">
    <h3>这里是父组件</h3>
    <!--第一次使用:用flex展示数据:  class="tmpl"-->
    <child>
      <template slot-scope="user">
        <div class="tmpl">
          <span v-for="item in user.data">{{item}}</span>
        </div>
      </template>

    </child>

    <!--第二次使用:用列表展示数据-->
    <child>
      <template slot-scope="user">
        <ul>
          <li v-for="item in user.data">{{item}}</li>
        </ul>
      </template>

    </child>

    <!--第三次使用:直接显示数据-->
    <child>
      <template slot-scope="user">
       {{user.data}}
      </template>

    </child>

    <!--第四次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽-->
    <child>
      我就是模板
    </child>
  </div>
</template>

如果子组件中的某一部分的数据,每个父组件都会有自己的一套对该数据的不同的呈现方式,这时就需要用到作用域插槽。

总结:

//Parent
<template>
  <child>
   <!--默认插槽-->
   <template v-slot>
     <div>默认插槽</div>
   </template>
   <!--具名插槽-->
   <template #header>
     <div>具名插槽</div>
   </template>
   <!--作用域插槽-->
   <template #footer="slotProps">
     <div>
      {{slotProps.testProps}}
     </div>
   </template>
  <child>
</template>

vue.mixin

使用场景: Vue.mixin是一种混入(mix-in)技术,可以在多个组件之间共享组件选项,包括生命周期方法、data选项等等,使用Vue.mixin可以将一些通用的功能或逻辑封装起来,让多个组件可共同调用这些相同的逻辑,避免了冗余代码的出现。

在使用Vue.mixin时需要注意以下几点:

  1. 全局注册的Mixin会影响到所有Vue组件,包括第三方组件,因此需要确保Mixin的逻辑可适用于所有组件,不会产生意想不到的副作用。
  2. Mixin可能会覆盖组件本身的选项,因此需要仔细考虑Mixin的选项名称和组件本身选项名称的冲突。如果两者有重复,则Mixin的选项会覆盖组件本来的选项。一般来说,建议Mixin的选项命名要以特殊前缀开头以避免冲突。
  3. Mixin中的函数和数据,都是共享的,因此需要注意变量名和数据结构的一致性,避免出现数据混乱的情况。同时也需要注意数据结构的引用和拷贝,如果Mixin中的数据是对象或数组,需要避免直接修改这些数据的引用,推荐使用Object.freeze()或类似方法锁定数据对象。
  4. 注意在组件中使用parent或root来访问父组件或根组件的数据,因为Mixin在多个组件中都有使用,这些数据实际上是共享的,可能会导致数据不一致性。
  5. 不要在Mixin中使用箭头函数,因为在Mixin中this指向的是Vue实例,而箭头函数中的this指向的是全局对象。

总之,在使用Vue.mixin时需要确保Mixin本身的合理性,合理考虑Mixin与组件本身选项的交互和数据传递,以避免意外情况的出现。同时要保持代码的可维护性和可读性,减少不必要的代码迁移和重构。

mixin 项目变得复杂的时候,多个组件间有重复的逻辑就会用到mixin
多个组件有相同的逻辑,抽离出来
mixin并不是完美的解决方案,会有一些问题
vue3提出的Composition API旨在解决这些问题【追求完美是要消耗一定的成本的,如开发成本】
场景:PC端新闻列表和详情页一样的右侧栏目,可以使用mixin进行混合
劣势:1.变量来源不明确,不利于阅读
2.多mixin可能会造成命名冲突 3.mixin和组件可能出现多对多的关系,使得项目复杂度变高

nextTick使用

keep alive实现

作用:实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染

场景:tabs标签页 后台导航,vue性能优化

原理:Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。

Vue中权限管理怎么做?

一般分为:按钮权限和页面权限;也分为前端配置和后端配置:

页面权限

前端配置:在Vue中,可以使用路由拦截器实现权限控制。通过路由拦截器,可以在页面跳转之前判断用户是否具有访问权限。 例如:

javascript
复制代码
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store'

Vue.use(VueRouter)

const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import('@/views/Home.vue'),
      meta: {
        requiresAuth: true, // 需要登录才能访问
        requiresAdmin: true // 需要管理员权限才能访问
      }
    },
    {
      path: '/login',
      name: 'login',
      component: () => import('@/views/Login.vue')
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('@/views/About.vue'),
      meta: {
        requiresAuth: true // 需要登录才能访问
      }
    }
  ]
})

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // 判断是否需要认证
    if (!store.getters.isAuthenticated) {
      // 如果用户未登录,跳转至登录页面
      next('/login')
    } else {
      // 判断是否需要管理员权限
      if (to.matched.some(record => record.meta.requiresAdmin)) {
        if (store.getters.isAdmin) {
          next()
        } else {
          // 如果用户不是管理员,跳转至无权限页面
          next('/no-permission')
        }
      } else {
        next()
      }
    }
  } else {
    next()
  }
})

export default router

后端配置:会把所有页面路由信息存在数据库中,用户登录时,根据其角色查询可以得到他能访问的所有页面路由信息,然后返回给前端,前端通过addRoutes动态添加路由信息。 image.png

按钮权限

通常会实现一个组件指令。如v-permission,将按钮要求角色通过传给v-permission指令,在指令的moutend钩子中可以判断当前用户角色和按钮是否存在交集,有则保留按钮,无则移除。

单页应用和多页应用的优缺点?

单页应用和多页应用是两种常见的web应用程序的架构设计方案。它们各自有着自己的优缺点,适用于不同的场景。

单页应用(Single Page Application,SPA):

优点:

  • 用户体验好,局部刷新,不需要页面刷新,加载速度快,提升用户满意度。
  • 可以提供类似原生应用的用户体验,例如前后端分离实现的页面无刷新化跳转,多级下拉菜单等。
  • 前后端分离,API和UI开发分离,可以提高开发效率,工作分工更清晰。

缺点:

  • 首次加载时间长,因为要加载JavaScript/CSS文件等资源,可能会影响SEO优化。
  • 复杂性高,需要前端框架进行实现,不利于初学者上手,调试以及维护也相应复杂。
  • 安全性要求高,容易受到跨站点脚本攻击,需要更高的安全措施。

多页应用(Multiple Page Application,MPA):

优点:

  • 早期开发方式,对SEO优化友好,每个页面都有自己的URL地址。
  • 容易上手,不需要掌握很多特殊技能,语言不限。
  • 页面之间互相独立,不容易出现问题,容错率高。

缺点:

  • 用户体验比较差,每次页面切换都需要重新加载页面,用户体验不佳。
  • 开发效率低,每个页面都需要独立地开发和维护,工作量大。
  • 需要后端参与渲染页面,前后端耦合程度高。

综上所述,单页应用适用于对用户体验有较高要求的场景,例如实时应用、互动游戏等。而多页应用适合于需要SEO的场景,如电商网站、新闻资讯等。

vue3

vue3与vue2的区别

1.双向数据绑定原理发生变化

vue2 的双向数据绑定是利用ES5 的一个 API Object.definePropert()对数据进行劫持 结合 发布订阅模式的方式来实现的。

vue3 中使用了 es6 的 ProxyAPI 对数据代理。

相比于vue2.x,使用proxy的优势如下

  1. defineProperty只能监听某个属性,不能对全对象监听
  2. 可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
  3. 可以监听数组,不用再去单独的对数组做特异性操作 vue3.x可以检测到数组内部数据的变化

2. vue3支持碎片化(fragments)

就是说在组件可以拥有多个根节点。

vue2,编写页面的时候,我们需要去将组件包裹在<div>中,否则报错警告。

<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

vue3,我们可以组件包含多个根节点,可以少写一层,niceeee !

<template>
  <header>...</header>
  <main>...</main>
  <footer>...</footer>
</template>

3. Composition API

Vue2 是 选项式API(Option API),一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期函数等),导致代码的可读性变差,需要上下来回跳转文件位置。Vue3 组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起。

除了增强了代码的可读性、内聚性,组合式API 还提供了较为完美的逻辑复用性方案,举个🌰,如下所示公用鼠标坐标案例。

export default {
  props: {
    title: String
  },
  data () {
    return {
      username: '',
      password: ''
    }
  },
  methods: {
    login () {
      // 登陆方法
    }
  },
  components:{
            "buttonComponent":btnComponent
        },
  computed:{
	  fullName(){
	    return this.firstName+" "+this.lastName;     
	  }
}
 
}

vue3

export default {
  props: {
    title: String
  },
  
  setup () {
    const state = reactive({ //数据
      username: '',
      password: '',
      lowerCaseUsername: computed(() => state.username.toLowerCase()) //计算属性
    })
     //方法
    const login = () => {
      // 登陆方法
    }
    return { 
      login,
      state
    }
  }
}

4. 建立数据 data

Vue2 - 这里把数据放入data属性中

export default {
  props: {
    title: String
  },
  data () {
    return {
      username: '',
      password: ''
    }
  }
}

在Vue3.0,我们就需要使用一个新的setup()方法,此方法在组件初始化构造的时候触发。

使用以下三步来建立反应性数据:

从vue引入reactive使用reactive()方法来声名我们的数据为响应性数据使用setup()方法来返回我们的响应性数据,从而我们的template可以获取这些响应性数据

import { reactive } from 'vue'

export default {
  props: {
    title: String
  },
  setup () {
    const state = reactive({
      username: '',
      password: ''
    })

    return { state }
  }
}

template使用,可以通过state.username和state.password获得数据的值。

5. 生命周期钩子 — Lifecyle Hooks

Vue2--------------vue3

beforeCreate -> setup()

created -> setup()

beforeMount -> onBeforeMount

mounted -> onMounted

beforeUpdate -> onBeforeUpdate

updated -> onUpdated

beforeDestroy -> onBeforeUnmount

destroyed -> onUnmounted

activated -> onActivated

deactivated -> onDeactivated

- setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和methodon
- BeforeMount() : 组件挂载到节点上之前执行的函数。
- onMounted() : 组件挂载完成后执行的函数。
- onBeforeUpdate(): 组件更新之前执行的函数。
- onUpdated(): 组件更新完成之后执行的函数。
- onBeforeUnmount(): 组件卸载之前执行的函数。
- onUnmounted(): 组件卸载完成后执行的函数

若组件被<keep-alive>包含,则多出下面两个钩子函数。
- onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行 。
- onDeactivated(): 比如从 A组件,切换到 B 组件,A 组件消失时执行。

6.父子传参不同,setup() 函数特性

总结: 1、setup 函数时,它将接受两个参数:(props、context(包含attrs、slots、emit))

2、setup函数是处于 生命周期函数 beforeCreate 和 Created 两个钩子函数之前的函数

3、执行 setup 时,组件实例尚未被创建(在 setup() 内部,this 不会是该活跃实例的引用,即不指向vue实例,Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined)

4、与模板一起使用:需要返回一个对象 (在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用)

5、使用渲染函数:可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态

注意事项:

1、setup函数中不能使用this。Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined)

2、setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。但是,因为 props 是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性。

如果需要解构 prop,可以通过使用 setup 函数中的toRefs 来完成此操作: 父传子,props


import { toRefs } from 'vue'
 
setup(props) {
	const { title } = toRefs(props)
 
	console.log(title.value)
	 onMounted(() => {
      console.log('title: ' + props.title)
    })

}

子传父,事件 - Emitting Events

举例,现在我们想在点击提交按钮时触发一个login的事件。

在 Vue2 中我们会调用到this.$emit然后传入事件名和参数对象。

    login () {
      this.$emit('login', {
        username: this.username,
        password: this.password
      })
 }

在setup()中的第二个参数content对象中就有emit,这个是和this.$emit是一样的。那么我们只要在setup()接收第二个参数中使用分解对象法取出emit就可以在setup方法中随意使用了。

然后我们在login方法中编写登陆事件 另外:context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构

    setup (props, { attrs, slots, emit }) {
    // ...
    const login = () => {
      emit('login', {
        username: state.username,
        password: state.password
      })
    }

    // ...
}

3、 setup()内使用响应式数据时,需要通过.value获取

import { ref } from 'vue'

const count = ref(0) console.log(count.value) // 0

4、从 setup() 中返回的对象上的 property 返回并可以在模板中被访问时,它将自动展开为内部值。不需要在模板中追加 .value

5、setup函数只能是同步的不能是异步的

vuex

vuex的理解及使用场景

Vuex主要包括以下几个核心模块:

  1. State:定义了应用的状态数据
  2. Getter:在 store 中定义“getter”(可以认为是 store 的计算属性), 就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来, 且只有当它的依赖值发生了改变才会被重新计算
  3. Mutation:是唯一更改 store 中状态的方法,且必须是同步函数
  4. Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操
  5. Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中

vue-router

hash与history区别

为了构建 SPA(单页面应用),需要引入前端路由系统,这也就是 Vue-Router 存在的意义。
前端路由的核心,就在于 —— 改变视图的同时不会向后端发出请求。

  1. hash 就是指 url 尾巴后的 # 号以及后面的字符,history没有底带#,外观上比hash 模式好看些
  2. 原理的区别:
  • hash通过监听浏览器的onhashchange()事件变化,查找对应的路由规则
  • history原理:利用H5的 history中新增的两个API pushState() 和 replaceState() 和一个事件onpopstate监听URL变化
  1. hash 能兼容到IE8, history 只能兼容到 IE10。
  2. 由于 hash 值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange 事件(hashchange只能改变 # 后面的url片段);虽然hash路径出现在URL中,但是不会出现在HTTP请求中,对后端完全没有影响
  3. history的这种模式需要后台配置支持。比如:当我们进行项目的主页的时候,一切正常,可以访问,但是当我们刷新页面或者直接访问路径的时候就会返回404,那是因为在history模式下,只是动态的通过js操作window.history来改变浏览器地址栏里的路径,并没有发起http请求,但是当我直接在浏览器里输入这个地址的时候,就一定要对服务器发起http请求,但是这个目标在服务器上又不存在,所以会返回404

hash 模式和 history 模式都属于浏览器自身的特性,
Vue-Router 只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由。

使用场景: 一般情况下,hash和history都可以,除非更在意颜值,#夹杂在URL看起来确实不太美丽 调用history.pushState()相比于直接修改hash,存在以下优势:

  • pushState()设置的新 URL 可以是与当前 URL 同源的任意 URL;而hash只可修改#
    后面的部分,因此只能设置与当前 URL 同文档的 URL;
  • pushState()设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;
    hash设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
  • pushState()通过stateObject参数可以添加任意类型的数据到记录中;
    hash只可添加短字符串;
  • pushState()可额外设置title属性供后续使用。
    当然啦,history也不是样样都好。
    SPA 虽然在浏览器里游刃有余,但真要通过 URL 向后端发起 HTTP 请求时,
    两者的差异就来了。尤其在用户手动输入 URL 后回车,或者刷新(重启)浏览器的时候。
  1. hash模式下,仅hash符号之前的内容会被包含在请求中,
    http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,
    也不会返回 404 错误。
  2. history模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,
    http://www.abc.com/book/id
    。如果后端缺少对/book/id的路由处理,将返回 404 错误。
    Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持所以呢,
    你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,
    则应该返回同一个 index.html
    页面,这个页面就是你 app 依赖的页面。”

history缺点

  • 通过history api,我们丢掉了丑陋的#,但是它也有个毛病:
    不怕前进,不怕后退,就怕刷新f5,(如果后端没有准备的话),因为刷新是实实在在地去请求服务器的,不玩虚的。
  • 在hash模式下,前端路由修改的是#中的信息,而浏览器请求时是不带它玩的,所以没有问题。但是在history下,你可以自由的修改path,当刷新时,如果服务器中没有相应的响应或者资源,会分分钟刷出一个404来。需要后台进行Apache与Nginx进行简单的配置

路由导航守卫

Vue 路由导航守卫分为全局守卫、路由独享守卫、组件内置守卫 1、全局守卫

  • ① beforeEach 全局前置导航钩子,路由进入之前触发,用来身份验证、路由拦截等操作。
  • ② beforeResolve 路由确认之前,可以在此修改或筛选后续要执行的路由。
  • ③ afterEach 全局后置导航钩子​​​​​​​​​​​​​​,路由进入之后触发,用来进行路由跳转后的操作,比如页面滚动、统计PV等操作

2、路由独享守卫

  • beforeEnter 路由进入之前。对某个特定路由进行独立的前置验证或权限控制。

3、组件内置守卫

  • beforeRouteEnter 路由进入之前,一般用来初始化数据,此时组件还没被创建出来,因此 this 是 undefined
  • beforeRouteUpdate 路由更新之前,因为路由更新可能会引起一些组件内部状态的变化,因此这个钩子函数可以用来处理这些状态变化。
  • beforeRouteLeave 路由离开之前, 一般用来进行页面数据的保存或弹出提示等操作。

$route$router的区别

  • $route是“路由信息对象”,包括pathparamshashqueryfullPathmatchedname等路由信息参数。
  • $router是“路由实例”对象包括了路由的跳转方法,钩子函数等

axios

axios取消请求

步骤: 1.创建axios实例 2.创建请求拦截器 3.创建响应拦截器 4.发送请求 5.取消请求

//创建取消令牌
import { CancelToken } from 'axios'; 
const source = CancelToken.source();
//将请求令牌作为请求配置的一部分
instance.get('/api/data', { 
    cancelToken: source.token, 
  }) 
    .then((response) => {
        // 请求成功的处理     
        console.log(response);
    })
    .catch((error) => { 
        // 请求失败的处理 
        if (axios.isCancel(error)) { 
            console.log('请求被取消', error.message);
         } else { 
            console.error(error); 
            } 
      });
      
 //调用取消令牌的cancel方法,取消之前的请求
source.cancel('取消请求');

如何解决跨域

在vue.config.js文件中配置,有的则在config文件夹下的index.js文件中配置。此项配置只能处理非生产环境代理,生产环境需要配置ngixs代理

proxy: {
  '/api': {
    target: 'http://192.168.1.36:8080',// 需要代理的后端接口
    changeOrigin: true, //开启代理:在本地会创建一个虚拟服务端,然后发送请求的数据,并同时接收请求
    pathRewrite: {//重写匹配的字段,如果不需要在请求路径上,重写为""
      '/api': ''
    }
  }
}

网络

从输入URL到页面展示,中间经历了什么

URL解析

  • 首先判断你输入的是一个合法的URL 还是一个待搜索的关键词,并且根据你输入的内容进行对应操作 DNS 查询
  • DNS查询对应ip TCP 连接
  • 在确定目标服务器服务器的IP地址后,则经历三次握手建立TCP连接 HTTP 请求
  • 当建立tcp连接之后,就可以在这基础上进行通信,浏览器发送 http 请求到目标服务器 响应请求
  • 当服务器接收到浏览器的请求之后,就会进行逻辑操作,处理完成之后返回一个HTTP响应消息 页面渲染
  • 当浏览器接收到服务器响应的资源后,首先会对资源进行解析:
    • 查看响应头的信息,根据不同的指示做对应处理,比如重定向,存储cookie,解压gzip,缓存资源等等
    • 查看响应头的 Content-Type的值,根据不同的资源类型采用不同的解析方式

关于页面的渲染过程如下:

  • 解析HTML,构建 DOM 树
  • 解析 CSS ,生成 CSS 规则树
  • 合并 DOM 树和 CSS 规则,生成 render 树
  • 布局 render 树( Layout / reflow ),负责各元素尺寸、位置的计算
  • 绘制 render 树( paint ),绘制页面像素信息  浏览器会将各层的信息发送给 GPU,GPU 会将各层合成( composite ),显示在屏幕上

优化题

什么情况下会重绘和回流,常见的改善方案

浏览器请求到对应页面资源的时候,会将HTML解析成DOM,把CSS解析成CSSDOM,然后将DOM和CSSDOM合并就产生了Render Tree。在有了渲染树之后,浏览器会根据流式布局模型来计算它们在页面上的大小和位置,最后将节点绘制在页面上。

那么当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变,浏览器就会重新渲染页面,这个就是浏览器的回流。常见的回流操作有:页面的首次渲染、浏览器窗口尺寸改变、部分元素尺寸或位置变化、添加或删除可见的DOM、激活伪类、查询某些属性或调用方法(各种宽高的获取,滚动方法的执行等)。

当页面中的元素样式的改变不影响它在文档流的位置时(如color、background-color等),浏览器对应元素的样式,这个就是重绘。

可见:回流必将导致重绘,重绘不一定会引起回流。回流比重绘的代价更高

常见改善方案:

  • 在进行频繁操作的时候,使用防抖和节流来控制调用频率。
  • 避免频繁操作DOM,可以利用DocumentFragment,来进行对应的DOM操作,将最后的结果添加到文档中。
  • 灵活使用display: none属性,操作结束后将其显示出来,因为display的属性为none的元素上进行的DOM操作不会引发回流和重绘。
  • 获取各种会引起重绘/回流的属性,尽量将其缓存起来,不要频繁的去获取。
  • 对复杂动画采用绝对定位,使其脱离文档流,否则它会频繁的引起父元素及其后续元素的回流。

一次请求大量数据怎么优化,数据多导致渲染慢怎么优化

  1. 在大型企业级项目中经常需要渲染大量数据,此时很容易出现卡顿的情况。比如大数据量的表格、树
  2. 处理时要根据情况做不同处理:
  • 可以采取分页的方式获取,避免渲染大量数据
  • vue-virtual-scroller (opens new window) 等虚拟滚动方案,只渲染视口范围内的数据
  • 如果不需要更新,可以使用v-once方式只渲染一次
  • 通过v-memo (opens new window) 可以缓存结果,结合v-for使用,避免数据变化时不必要的VNode创建
  • 可以采用懒加载方式,在用户需要的时候再加载数据,比如tree组件子树的懒加载
  1. 还是要看具体需求,首先从设计上避免大数据获取和渲染;实在需要这样做可以采用虚表的方式优化渲染;最后优化更新,如果不需要更新可以v-once处理,需要更新可以v-memo进一步优化大数据更新性能。其他可以采用的是交互方式优化,无线滚动、懒加载等方案

SPA(单页面应用)首屏速度加载慢怎么解决

优化SPA首屏加载速度慢的问题可以从以下几个方面入手:

  • 代码压缩与打包:使用Webpack等构建工具进行代码压缩,合并和打包,减少代码体积,提高加载速度。

  • 图片优化:使用合适的图片格式,采用lazy load和懒加载等方式,减少页面图片加载带来的时间成本。

  • 异步加载组件和数据:采用异步组件和Data Fetching,可以延迟一些用户不紧急需要的组件和数据的加载时间,从而加快首屏加载速度。

  • 缓存页面和数据:在浏览器端实现缓存机制,减少重复请求和加载,提升页面渲染速度。

  • 启用Gzip压缩:将服务器上的静态资源采用Gzip压缩后再进行传输,减少网络带宽占用,提升加载速度。

  • 代码懒加载:使用codesplitting的技术,根据需要进行懒加载,减少页面加载时的资源开销。

  • 采用服务器渲染:对于复杂的SPA应用,为了提高首屏渲染速度,可以采用服务器渲染的方式,通过提前将页面渲染出来返回给浏览器,减少浏览器端的渲染时间和开销。

常见的几种SPA首屏优化方式

  • 减小入口文件积,路由懒加载
  • 静态资源本地缓存
  • UI框架按需加载
  • 图片资源的压缩
  • 组件重复打包
  • 开启GZip压缩
  • 使用SSR

减小入口文件体积

SPA项目,一个路由对应一个页面,如果不做处理,项目打包后,会把所有页面打包成一个文件,当用户打开首页时,会一次性加载所有的资源,造成首页加载很慢,降低用户体验,因此,我们需要将;路由全部改成懒加载,提高加载速度。

// 通过webpackChunkName设置分割后代码块的名字
const Home = () => import(/* webpackChunkName: "home" */ "@/views/home/index.vue")
const About = () => import(/* webpackChunkName: "about" */ "@/views/about/index.vue")

const routes = [
  {
    path: "/",
    name: "home",
    component: Home
  },
  {
    path: "/about",
    name: "about",
    component: About
  }
]

重新打包后,首页资源拆分为app.js和home.js,以及对应的css文件 image.png UI框架按需加载

在日常使用UI框架,例如element-UI、或者antd,我们经常性直接饮用整个UI

import ElementUI from 'element-ui'
Vue.use(ElementUI)

但实际上我用到的组件只有按钮,分页,表格,输入与警告 所以我们要按需引用

import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui';
Vue.use(Button)
Vue.use(Input)
Vue.use(Pagination)

组件重复打包

假设A.js文件是一个常用的库,现在有多个路由使用了A.js文件,这就造成了重复下载

解决方案:在webpackconfig文件中,修改CommonsChunkPlugin的配置

minChunks: 3

minChunks为3表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件

图片资源的压缩

图片资源虽然不在编码过程中,但它却是对页面性能影响最大的因素

对于所有的图片资源,我们可以进行适当的压缩

对页面上使用到的icon,可以使用在线字体图标,或者雪碧图,将众多小图标合并到同一张图上,用以减轻http请求压力。

开启GZip压缩

拆完包之后,我们再用gzip做一下压缩 安装compression-webpack-plugin

cnmp i compression-webpack-plugin -D

vue.congig.js中引入并修改webpack配置

const CompressionPlugin = require('compression-webpack-plugin')

configureWebpack: (config) => {
        if (process.env.NODE_ENV === 'production') {
            // 为生产环境修改配置...
            config.mode = 'production'
            return {
                plugins: [new CompressionPlugin({
                    test: /.js$|.html$|.css/, //匹配文件名
                    threshold: 10240, //对超过10k的数据进行压缩
                    deleteOriginalAssets: false //是否删除原文件
                })]
            }
        }

在服务器我们也要做相应的配置 如果发送请求的浏览器支持gzip,就发送给它gzip格式的文件 我的服务器是用express框架搭建的 只要安装一下compression就能使用

const compression = require('compression')
app.use(compression())  // 在其他中间件使用之前调用

使用SSR

SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器

从头搭建一个服务端渲染是很复杂的,vue应用建议使用Nuxt.js实现服务端渲染

小结:

减少首屏渲染时间的方法有很多,总的来讲可以分成两大部分 :资源加载优化 和 页面渲染优化 感知性能优化

  • loading
  • 骨架屏

Vue如何实现动态组件和异步组件?

动态组件:

动态组件:是Vue中一个特殊的Html元素:<component>,它拥有一个特殊的 is 属性,属性值可以是 已注册组件的名称一个组件的选项对象,它是用于不同组件之间进行动态切换的。

使用标签和is属性可以实现动态组件,可以在同一位置动态切换不同的组件。具体使用方法如下:

js
复制代码
<component :is="componentName"></component>

其中,componentName是一个变量,它的值可以是一个组件的名称,例如:

data() { return { componentName: 'ComponentA' } }

这样,就会渲染ComponentA组件。

异步组件:

异步组件:简单来说是一个概念,一个可以让组件异步加载的方式;它一般会用于性能优化,比如减小首屏加载时间、加载资源大小。

javascript
复制代码
    new Vue({
    // ...
        components: {
            'my-component': () => import('./my-async-component')
        }
    })
​
  • 何时使用异步组件?

①加载大组件 ②路由异步加载

vue中 动态组件和keep-alive的区别?

相同:Vue中动态组件和<keep-alive>都是用来优化组件性能的。

动态组件可以根据不同的条件以不同的组件形式来渲染同一个组件位置,它使得多个组件之间实现了灵活的切换。动态组件的优点是灵活,缺点是在不同组件之间频繁切换时,会导致组件的销毁和重建,使得页面状态丢失。动态组件不会默认缓存已经被销毁的组件。

<keep-alive>的作用是缓存已经渲染的组件,来避免重复渲染已经存在的组件,优化性能。通过使用<keep-alive>可以使组件在切换时保持状态,不会因为被销毁而导致页面状态丢失。

<keep-alive>有两个重要的属性:

  • include:被缓存的组件名称列表,只有该列表中的组件会被缓存。
  • exclude:不被缓存的组件名称列表,该列表中的组件不会被缓存。

使用<keep-alive>缓存组件时,需要注意,被缓存的组件在缓存期间是不会被销毁的,因此如果组件占用的资源比较多,缓存过多的组件会导致内存占用过高,从而影响网页的性能。因此在使用<keep-alive>时,需要合理地配置includeexclude属性,以减小缓存的开销。

vue如何兼容IE?

Vue.js 在默认情况下不支持 IE8 及其以下版本的浏览器,但是它可以通过一些方法来进行兼容:

  1. 在 HTML 文件的头部添加 meta 标签:
ini
复制代码
<meta http-equiv="X-UA-Compatible" content="IE=edge">

这样可以让 IE 浏览器使用最新的渲染模式来渲染页面。

  1. 启用 babel-polyfill:

在 Vue 项目中安装 babel-polyfill,它可以在代码中自动加入一些特性的 shim 和 polyfills,从而支持更广泛的浏览器。例如:

bash
复制代码
npm install babel-polyfill --save-dev

src/main.js 文件中引入:

javascript
复制代码
import 'babel-polyfill';
  1. 使用 es5 版本的 Vue:

Vue 2.0 以上版本默认使用了 ES2015+ 的语法,如果要在 IE8 中运行,则需要使用 es5 版本的 Vue,可以通过在业务入口处,比如 main.js 中手动安装并指定 es5 版本的 Vue:

javascript
复制代码
import Vue from 'vue/dist/vue.esm.js';
  1. 使用 Vue-cli 中的配置:

如果是使用 Vue-cli 工具创建的项目,可以通过修改 .babelrc 文件来配置,例如:

json
复制代码
{
  "presets": [
    ["env", {
      "targets": {
        "browsers": ["last 2 versions", "ie >= 8"]
      }
    }]
  ]
}

这个配置指定编译目标浏览器为 "last 2 versions" 和 "IE 8 及以上",可以实现对 IE8 的兼容。

总的来说,针对 IE 的兼容问题,可以结合不同的方法进行处理,具体方法可以根据项目需求来选择。

项目相关

token可以放在cookie里面吗

当被问这个问题的时候,第一时间要想到安全问题。通常回答不可以,因为存在CSRF(跨站请求伪造)风险,攻击者可以冒用Cookie中的信息来发送恶意请求。解决CSRF问题,可以设置同源检测(Origin和Referer认证),也可以设置Samesite为Strict。最好嘛,就是不把token放在cookie里咯。

封装全局dialog思路

自定义属性涉及:title(弹窗名称),content(弹窗内容),isdisable(是否显示),iscancel(是否显示取消按钮),isconfirm(是否显示确定按钮)

01构建一个Vue项目你需要做哪些内容

*   架子:选用合适的初始化脚手架(`vue-cli2.0`或者`vue-cli3.0`)
*   请求:数据`axios`请求的配置
*   登录:登录注册系统
*   路由:路由管理页面
*   数据:`vuex`全局数据管理
*   权限:权限管理系统
*   埋点:埋点系统
*   插件:第三方插件的选取以及引入方式
*   错误:错误页面
*   入口:前端资源直接当静态资源,或者服务端模板拉取
*  `SEO`:如果考虑`SEO`建议采用`SSR`方案
*   组件:基础组件/业务组件
*   样式:样式预处理起,公共样式抽取
*   方法:公共方法抽离

vue二次封装组件的技巧及要点

1.继承第三方组件的Attributes属性 使用$attrs(属性透传)

2.继承第三方组件的Event事件 listenersvue3中取消了listeners 在vue3中取消了listeners这个组件实例的属性,将其事件的监听都整合到了$attrs中

3.使用第三方组件的Slots $Slots

4.使用第三方组件的Methods