前端常见的面试题总结

218 阅读1小时+

H5

h5的新特性:语义化标签、表单增强、canvas、本地存储、web worker、自定义事件

1. 语义化标签

就是新增Header Footer这些标签,语义化更强烈,方便开发人员和机器编译

2.表单控件

正则手写手机号

let iphoneReg = /^1[3-9][0-9]{9}$/

正则手写邮箱

let emailRegt = /^[a-zA-z0-9_-]+@[a-zA-Z0-9_-].[a-zA-Z]{2.6}$/

3. canvas

画布,就是类似于window的画图。工具,在HTML中,默认效果与div类似,背景色是默认色,支持2D图像效果、3D图像效果

4.本地存储>重

  • 本地存储有localStorage(永久存储),是存到硬盘中的,存储量5M左右,一般使用场景是储存用户的token,还有就是单一的数据、标识数据、体积小的这些数据。

  • 还有sessionStorage(会话存储),存到内存中的,存储量5M左右,使用场景:在vue的路由权限管理中,在访问动态路由下,刷新页面会导致白屏,其原因是因为刷新页面以后,Vuex中的数据重新初始化,用户动态路由信息会丢失,会导致动态路由失效(路由表routes中没有对应的动态路由), 解决方案就是将Vuex中用户数据在刷新页面之前(利用window绑定beforeUnload事件, beforeUnload事件用户点击浏览器的刷新按钮,浏览器正式刷新之前触发的事件)存储到 sessionStorage 中,在刷新页面之后将保存的数据更新到Vuex中

  • 还有cokkie,cokkie分为持久化的cokkie和会话cokkie,存储量在4kb左右,是由服务器生成,保存在浏览器端,但是这个东西不安全,容易被截获,现在流行的服务器都支持禁用cokkie

  • 还有session,由服务器生成,保存在服务器端,使用的时候是将sessionId传递给浏览器端,利用cookie作为载体传递,缺点是cookie有的他都有

  • 还有就是小程序,可以永久存储,存储量的话单个key大小上限是1M,左右key上限10M

  • 最重要的还有个==token==,三部分组成:header(表头)、payload(负载)、signature(签名)。

    • token可以实现7-15天免登录,用户首次登陆,获取对应的token,保存在本地,以后每次发起业务请求都会携带token,每次请求的时候都会先验证token有效性,通常设置token的有效期就是7 || 15天,当用户在7 || 15天之内再次访问网站,会先验证本地是否有token,如果有就直接携带本地的token进行业务请求,如果token没有过期就能正常使用,如果token过期需要重新登录获取新的token

    • ==无感刷新token==:

      ​ 理解:用户在进行核心业务逻辑的时候(支付,操作订单等), token即将要过期要在用户不知情的情况下,悄悄的刷新token换成有效token

      ​ 实现:用户登录以后会返回给用户两个token,一个是访问令牌:access token,一个是刷新令牌: refresh token,访问令牌带有有效token,正常发起请求携带的token必然是访问令牌而不是刷新令牌。一个是刷新令牌,刷新令牌没有过期时间,在token即将过期的时候,或者刚刚过期的时候用户正在发起业务请求,如当用户发起支付请求的时候,检测到访问令牌即将要过期或者刚刚过期不久,使用刷新令牌发起请求获取新的带有有效期的token, 访问令牌分为短期、中期、长期。

  • 自定义事件有web Worker、Vue2事件总线对象、pubsub

  • web worker

    1. js是单线程的,设计成单线程的原因是因为js可以操作DOM(增删改), 操作DOM势必会导致视图重新渲染
    2. 假设js是多线程的,那么就可能存在一种现象,在多个线程中同时操作一个DOM,就会导致视图更新渲染发生混乱甚至是报错
    3. 因为js是单线程的,所以所有的任务都在js的主线程上执行,如果主线程上有运算量特别大的代码段会阻塞后续代码的执行,甚至是影响视图的渲染
    4. 所有H5退出了web Worker,worker是一个独立的线程,它的特点是异步执行的不会阻塞js主线程后续代码的执行,我们可以将运算量大的任务交给worker线程去执行,在worker线程计算以后将计算的结果再交给js主线程,这样做的好处是不会阻塞js主线程代码的执行,不会影响页面的渲染
    5. 虽然worker是独立的线程,但是它还是受控于js的主线程,而且worker没有window对象,也不能操作DOM
  • iframe是一个内联框架,就是在一个页面中可以嵌套另外一个页面,实际中一般用于单独的功能页面,比如:客户聊天

    • 如果两个页面不同源,想要进行通信的话,需要绑定自定义事件 ‘message’,然后利用postMessage进行触发自定义事件,在这里要注意的是:1.绑定事件和触发事件的对象必须是同一个对象。2.发送数据给对方的时候,必须要声明对方页面所在的服务器地址

css3

1. animation

  • 通过anniation设置动画的属性 如:动画名 动画曲线~

  • 需要搭配@keyframes使用,在@keyframes中有两种方式,

    from to 或者 百分比

  • 动画是简单动画时 只有两帧的动画适合用from to

  • 复杂动画 超过两帧动画 适合用百分比

2. BFC

BFC 被称为 块级格式上下文 指的是一个独立的渲染区域 让处于BFC内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响
具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素,并且 BFC 具有普通容器所没有的一些特性。通俗一点来讲,可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部

触发条件、开启BFC:**

  • 设置浮动,不包括 none
  • 设置定位,absolute 或者 fixed
  • 行内块显示模式,inline-block
  • 设置 overflow,即 hidden,auto,scroll
  • 表格单元格,table-cell

BFC特点:

  • BFC 是一个块级元素,块级元素在垂直方向上依次排列。
  • BFC 是一个独立的容器,内部元素不会影响容器外部的元素。
  • 属于同一个 BFC 的两个盒子,外边距 margin 会发生重叠,并且取最大外边距。
  • 计算 BFC 高度时,浮动子元素也要参与计算。

应用:

  • 阻止 margin 重叠
  • 包含内部浮动: 清除浮动,防止高度塌陷
  • 排除外部浮动:阻止标准流元素被浮动元素覆盖

3. 选择器权重

  • !important>行内样式>#id>.class>tag(标签)>*>继承>默认
  • 从右到左依次解析

4. flex(弹性盒子)

属性:

  • flex-direction 定义主轴的方向 column是横 row是竖着
  • flex-wrap 定义是否换行
  • flex-flow 是 flex-direction 属性和 flex-wrap 属性的简写形式
  • justify-content 定义项目在主轴上的对齐方式
  • align-items 定义项目在侧轴上的对齐方式

伸缩个体的属性

  • order 定义项目的排列顺序。数值越小,排列越靠前,默认为 0。

  • flex-grow 定义项目的放大比例,默认为 0,即如果存在剩余空间,也不放大。

  • flex-shrink 定义了项目的缩小比例,默认为 1,即如果空间不足,该项目将缩小。

  • flex-basis 定义了在分配多余空间之前,项目占据的主轴空间。它的默认值为 auto,即项目的本来大小。

  • flex 是 flex-grow, flex-shrink 和 flex-basis 的简写,默认值为 0 1 auto。

  • align-self 允许单个项目有与其他项目不一样的对齐方式,可覆盖 align-items 属性。

  • 请回答: flex:1代表什么?

    • flex-grow: 1 如果存在剩余空间, 该项目会放大。
    • flex-shrink: 1 如果剩余空间不足,该项目会缩小。
    • flex-basis: 0% 设置为 0% 之后,即不占据主轴空间,但是因为有 flex-grow 和 flex-shrink 的设置,该项目会自动放大或缩小。

JS

1. 数据类型

基本:string number undefined boolean null bigint symbol

引用:function array object

  • Symbol的使用场景: 作为对象的key属性

    ​ - 数组原型对象上的迭代器属性Symbol.interator

  • typeof 返回值有几种?(array和null返回object)

    ​ string, number, boolean, bigint, symbol,

    ​ undefined, object, function

instanceof:

​ - 语法: A instanceof B

​ - 检测A是否是B的实例

​ - 检测B的显示原型是否出现在A的原型链上

2. 数组

  1. arr.unshift() 头增

  2. arr.shift() 头减

  3. arr.push() 尾部增

  4. arr.pop() 尾部减

  5. arr.splice( 起始索引index, 需要删除的个数, 新的元素1)

  6. arr.reverse() 反转数组

  7. arr.sort() 排序

      let arr = [5,2,4,1,3]
    
                let arr1 = arr.sort(function(a,b){
                    return a-b//升序
                    return b-a//降序
                })
                console.log(arr1);
    
  8. arr.fill() 填充

    arr.length=100;
    arr.fill(1)
    console.log(arr); //输出100个1
    
  9. arr.join() 字符串拼接数组

    let arr = [5,2,4,1,3]   
    let a = arr.join('~')
    console.log(a);// 5~2~4~1~3
    
  10. arr.concat([拼接的字符串]) 合并数组

  11. arr.slice(1,3) 截取数组元素 从下标1截取到下标3 下标3不要

    let arr = [5,2,4,1,3]
    let a = arr.slice(1,3)  
    console.log(a); // [2, 4]
    
  12. arr.valueOf() 方法返回 Array 对象的原始值,不会改变原数组

  13. arr.toString() 输出为拼接完的字符串

    let arr = [5,2,4,1,3]
    let a = arr.toString() 
    console.log(a); //5,2,4,1,3
    
  14. arr.forEach() 遍历数组

    let arr = [5,2,4,1,3]
    let a = arr.forEach(function(item,index){
    	console.log(item,index); //遍历数组、下标     
            }) 
    
  15. arr.map() 遍历数组

    出现需要修改现有数组的内容并将结果存储为新变量的时候就可以用

    let arr = [5,2,4,1,3]
    let a = arr.map(function(item){
         return item*5 //遍历数组*5      
            }) 
    console.log(a); //[25, 10, 20, 5, 15]
    
  16. arr.filter() 过滤数组

    let arr = [5, 2, 4, 1, 3]
    let a = arr.filter(function (item) {
        return item >= 10000  //数组内1000以下的不要
            })
    console.log(a);// []
    
  17. arr.some() 只要有一个返回 true,整体就返回 true,

    ​ 所有都是 false,整体才返回 false

  18. arr.every() 所有都是 true ,整体才返回 true

    ​ 只要有一个返回 false,整体就返回 false。

  19. arr.reduce() 常用于统计、累加和求和等功能

    let arr = [5, 2, 4, 1, 3]
            
    let a = arr.reduce((total,item)=>total +item)
    console.log(a);// 总和15
    
  20. indexOf 判断是否包含某个元素,包含返回其下标,不包含

    ​ 返回-1

    let arr = [5, 2, 4, 1, 3]
            
    console.log(arr.indexOf(4));// 2
    console.log(arr.indexOf(99));// -1
    
  21. find 查找某个元素,找到返回这个元素,找不到返回 undefined

  22. includes 判断是否包含某个元素 包含返回 true.不包含返回 false

不改变数组的方法:

filter 、 concat 、 slice、 join 、 map 、 some 、 every 、indexOf

3.作用域/作用域链

  • 作用域是用来约束变量查找及作用范围的,设计它的初衷就是用于隔离变量,防止污染,命名冲突,
  • 作用域链就是查找变量的时候,先在当前作用域下的变量对象中查找,如果有就使用,如果没有就向上一级作用域下的变量对象中查找,如果有就使用,如果还没有就继续向上查找,直到找到全局作用域下的变量对象global,如果还没有就报错,xxx is not defined
  • 分为4类:全局、局部、块级、eval作用域
  • 我认为作用域本质是一个抽象的概念,真正落地的是作用域下的变量对象,这个对象是变量提升的产物
  • 我认为的作用域链是一个数组,在该数组中存放的是一个个的变量对象,第一个变量对象一定是当前函数作用域下的变量对象,最后一个变量对象一定是全局的global, 在中间可能会掺杂闭包对象,有没有闭包对象有无函数嵌套,内部函数是否引用了外部函数局部变量,产生**作用域链的时机是在函数体定义的时候,**一个函数的作用域一旦产生了就是固定的,不可以修改

4.变量提升/预解析

  • 变量提升分为全局和函数变量提升

  • 当js代码定义以后,正式执行之前,js引擎会先做预解析工作

  • 会先创建一个空的 变量P对象

  • 会收集当前作用域下的变量,函数,以及函数的参数(针对函数的预解析)

  • 收集的依据是关键字: var,function

    • 遇到var以后,将var后边的变量提前声明到变量对象中 但 是不赋值,其值是undefined
    • 遇到function以后,会提前在变量对象中定义该函数体, 该函数必须是有效函数(被调用或者被return)
  • 确认this的指向

  • 创建完整的作用域链

5.闭包>重

  • 闭包就是一个闭合的对象,闭合的数据来源外部函数的局部变量。

  • 函数嵌套、内部函数引用外部函数的局部变量、外部函数的调用、内部函数是有效函数都可以==产生闭包==

  • 闭包可以延长外部函数局部变量的生命周期,可以从函数的外部访问内部的私有变量

  • 闭包对象和外部变量对象是两个不同的对象,闭包对象中的属性有哪些取决于内部函数用了哪几个外部函数的局部变量,值是问外部函数的局部变量要的

  • 使用完闭包以后,如果确认不再使用内部函数的时候,要及时清除闭包,让内部函数变垃圾对象就可以清理闭包

  • 使用场景

    • React中高阶函数, 扩展: 将一个函数作为参数传递给另外一个函数,或者将一个函数作为返回值被return
    • Vue中生命周期函数中, 比如: 在生命周期函数中开启定时器,在定时器中使用当前组件的实例this
    • Vue源码中,响应式原理依赖收集的时候,dep对象创建的时候
    1、优点
    ①可以把局部变量长期驻留在内存中(可以理解为优点②的原因)
    ②在某些方面提升代码的执行效率 【注意!一旦过头就会转换成缺点】
    ③可以避免全局变量对命名空间的污染
    ④可以从一个域中取出原本访问不到的变量去使用
    2、缺点
    ①由于闭包里作用域返回的局部变量资源不会被立即销毁回收,所以可能会占用更多的内存。
    ②过度使用闭包会导致性能下降,建议在非常有必要的时候才使用闭包
    

6.原型>重

1.理解

  • 本质是一个对象,该对象中存公共的方法,可以让多个实例复用
  • 公共的数据(方法)池
  • 实例对象身上都有__proto__属性,实例对象的__proto__属性和其构造函数的prototype指向的是同一个对象,该对象就是他们的原型对象
  • 我们一般称__proto__为隐式原型, prototype为显示原型

2.设计思想

  • 因为我们需要在公共的容器中保存多个数据,而且我们不确定后期开发者会添加什么公共的方法进来,最好的方式通过key:value的映射关系保存,方便后期查找使用
  • 公共的数据池数据是为了给所有的实例访问,好处是减少定义的次数,节省内存空间
  • 因为是公共数据,所以修改数据的入口必须是唯一的,而构造函数是唯一的,所以给构造函数添加prototype属性可以访问原型对象
  • 为了保证公共数据的安全,所有的实例对象只能拥有可读权限

3.原型链

  • 当查找 对象的属性 的时候 先在自身找, 如果有就使用,如果没有,会沿着__proto__原型链去查找,如果有就使用
  • 如果还没有就继续沿着原型链向上查找,直到找到Object.prototype对象,如果还没有会返回undefined
  • 注意
    1. Object.prototype.__proto__隐式原型上有和Object.prototype一样的方法,比如: toString,这一层我们称之为Object.prototype的保护层
    2. 原型链的终点一定是null,null相当于对象属性查找结束的条件

4.原型继承

  • 子类的原型 成为 父类的实例
  • Child.prototype = new Parent();
  • 问题: 原型继承会导致子类原型对象上的constructor构造器属性丢失,
  • 解决: 重新设置子类原型上的构造器属性

5.class类继承

  • 实现: extends关键字
  • 总结和原型继承有什么区别?
    1. 原型继承是修改子类原型对象的指针, 会导致子类原型对象上的constructor构造器属性丢失
    2. class类继承:
      1. 子类的原型 成为 父类的实例
      2. 自动又给子类的原型添加了constructor属性,同时将其他的属性移除

7. 事件轮询机制

宏任务和微任务

  1. js代码分为 同步任务异步任务, 而异步任务又分为 宏任务微任务
  2. js代码执行先执行js主线程上全局的同步任务, 在这个过程可能会产生异步任务,异步任务的回调不会立马执行,而是被根据异步任务的分类分别放入宏任务队列或者是微任务队列
  3. 当全局的同步任务执行完先看有无微任务队列,如果有就会把当前微任务队列中的所有微任务回调都依次执行再看宏任务队列中有无宏任务,如果有就执行一个(第一个被放入队列的回调)宏任务,执行完以后再看有无微任务队列,如果有就全部清空所有的微任务,如果没有执行下一个宏任务

同步:

  • 会阻塞后续代码的执行 -直到等到当前同步任务的结果以后才会放行

    异步

  • 非阻塞的,开启异步任务的同时,会继续执行后续的代码

  • 需要使用异步回调的形式来通知异步任务的结果

8.promise>重

  • promise可以解决 回调地狱 的问题,利用then方法的链式调用,使用同步的流程来表达异步的行为

  • 有三种状态: pending(初始化), fulfilled(成功状态),rejected(失败状态)状态切换只能有两种方式,pending ---> fulfilled || pending ---> rejected, 只能切换一次,而且是不可逆的

  • 使用流程:当初始化一个promise实例以后,该实例的状态为pending,在Promise的执行器函数中执行异步任务,根据异步任务的执行结果来决定如何修改promise的状态

  • Promise对象的方法:

    • resolve

    • reject

    • all:管理的所有promise的实例的状态都为成功状态的时候,返回的promise的状态才是成功的

      ​ 应用: 大文件切片上传

    • race: 会根据 第一个发生改变的 promise的状态来决定返回promise的状态

    • allsettled:管理的所有promise的实例的状态都发生改变以后,就会返回成功的promise

  • promise并没有很好的解决回调地狱的问题,因为其本身也需要使用then方法中的回调,所以后期推出了async, await,现在解决回调地狱的终极方案, promise + async + await

  • async await

    • async: 异步函数
    • await:必须搭配async函数使用,await 后边通常需要跟一个异步任务,异步任务的返回值通常是一个promise实例,在await后边紧跟的promise实例状态没有发生改变之前,await语句会阻塞后续代码的执行,执行promise实例的状态为成功状态才执行后续的代码,awiat不需要回调通知异步的结果

9.模块化

  • 是具备特定功能的js代码集合,每个模块具备特定的功能,每个模块内部的变量都是私有的, 除非当前模块向外暴露内容。当要使用指定的模块的时候,需要先引入对应的模块对象
  • CommonJs 主要应用于服务端Node.js
    • 暴露: exports || module.exports 引入: require ('路径')
  • ES6
    • 暴露: export || export default
    • 引入: import xxx from '模块的路径' || import {x} from '模块路径'

模块化向外的内容是什么?暴露的是引用地址还是值本身?

  • 向外暴露的是一个原始对象, js模块化内部会创建一个新的对象,该对象用于包裹向外暴露的内容
  • 引入的模块对象的内容是引用地址 ,在两个不同的文件中引入同一个模块的内容, 如果在其中的一个文件中操作模块向外暴露的内容,另一个文件也会受影响

10.this>重

  • 格式化上下文对象
  • 分类:全局this: window 和 函数this: 看函数如何被使用
  • 函数的this特点
    • 函数的this在函数调用时决定的,而不是定义的时候
    • 函数自调用this: window
    • 对象.方法调用this: 对象
    • new 构造函数this: 实例对象
    • 强制绑定this: bind, apply,call, this指向的是指定的对象
    • 箭头函数this: 没有自己的this,this指向的外部作用域的this

call, apply, bind的区别

  • call,apply都是指定this以后立即调用该函数
  • call,apply指定this的同时传递参数的方式不一样,call是从第二个参数开始依次往后传递, apply如果需要传递参数,第二个参数是数组,需要传递的所有参数都会放入该数组中
  • bind会返回一个新的函数,该返回的函数this指向的是bind中指定的this,bind指定this的同时如果需要传递参数同call传递的方式一样

11.垃圾回收机制

  1. js引擎针对内存空间进行优化的一个方案
  2. 在短时间内重复进行垃圾回收机制的计算,找到所谓的垃圾对象进行回收,从而释放内存空间
  3. 垃圾回收机制常用的分为两种:分别是1. 引用计数法,2.标记清除法
  4. 引用计数法指的就是查看内存地址有几个引用(指针), 当引用个数为0的时候就回收对应的内存数据,释放对应的内存空间,但是引用计数法有问题,当两个对象中互相保存对方的引用地址以后,这两个对象永远不会被回收掉,浪费内存空间
  5. 现在常用的回收机制是标识清除法,在标记阶段将有效的对象进行标记,在清除阶段清除没有做标记的数据,但是直接清除会导致内存碎片化, 从而导致之后的内存分配的速度变慢,内存空间的利用率低,因此在原有的基础上升级 标记整理清除, 就是将为标记的数据空间统一放至一侧,然后统一清除释放内存空间,这样做的好处没有内存碎片化

12.深度克隆

什么是深度克隆?

  • 克隆的数据有两种,一种是值本身(值传递),一种是引用地址(引用传递)

  • 要求克隆的数据必须都是值而不是引用地址的时候就是深度克隆

为什么要深度克隆?

  • 如果克隆的数据是引用地址的话,修改克隆以后的数据会影响原数据

深度克隆的核心思想?

  • 克隆的数据始终得是值本身也就是基本数据类型的时候(函数也可以直接克隆),才进行克隆,否则就继续迭代目标数据,直到克隆的目标数据是基本数据类型

13.TS

js的超集,在原有js的基础上升级扩展了增强型的语法,ts是一个强类型的语言,对语法及数据类型的要求更加的严格,可以保证我们能够精准的使用对应的数据类型,避免因为数据类型的不匹配导致的程序错误,ts相对于js而言新增了很多的语法,比如: interface,type,泛型,元祖,enum,函数重载等。

14.泛型

  • 当在实际开发中,需要声明数据的数据类型的时候,不明确到底此刻是什么类型,可以使用泛型进行占位, 在明确数据的类型再传入实例的数据类型
  • 分类:函数泛型、class类的泛型、接口泛型

15.interface和type的区别

  1. interface

    1. 用于流动的数据(动态的数据)
    2. 只有管道(函数的形参位置),就需要使用接口
    3. interface可以通过extends实现继承
    4. interface可以重复定义,而且重复定义的接口会自动合并接口中的内容
  2. type

    1. 用于描述静态的数据

    2. 比如: 定义一个人的信息,定义一个num等

    3. type不能使用extends继承

    4. type可以类型交叉,语法: aType & bType

      1. 引用类型交叉,取并集
      2. 基础类型交叉, 取交集
    5. 不能重复定义,会报错

16.函数重载

  1. 函数签名
    1. 由四部分内容组成
      1. 函数名 + 形参名 + 形参的类型 + 返回值的类型
    2. 分类:
      1. 重载签名
        1. 没有函数体,只有函数名 + 形参名 + 形参的类型 + 返回值的类型
      2. 实现签名
        1. 有函数体,实现签名就是针对重载签名的具体实现
  2. 函数重载的特点:
    1. 实现签名中形参的类型及返回值的类型必须要包含函数重载签名声明过的
    2. 重载签名存在的意义:
      1. 重载签名是对实现签名的约束
      2. 有了重载签名以后,ts可以精准的推断出传入的实参的数据类型及返回值的类型
  3. 使用时候使用函数重载
    1. 函数的参数类型及返回值的类型不止单一的类型, 有多种类型的可能性
  4. 扩展理解:
    1. ts: 根据函数参数的类型不同,从而决定函数体的行为不同
    2. java: 根据函数参数的个数不同,从而决定函数体的行为不同

17.ts和js区别

  • ts可以重载,js不可以
  • ts是js的超集,ts支持js里边的所有类型的语言,ts是一个强类型的语言

http

1.上班后如何和后端人员沟通

  • 定制接口
    • 协商添加字段
    • 协商接口如何编写,如:接口地址,请求方式
  • 调用接口
    • 平时调接口数据,参考的是后端人员提供的接口文档
    • 万一根据接口文档得不到数据时
      • 只要需要确认发送的请求是严格按照接口文档来的
      • 必须和还后端沟通,同后端人员进行开发联调,一起找出原因
  • 测试阶段
    • 前端开发的项目要交给后端人员,后端部署服务器
    • 前后端 测试联调,在局域网内模拟线上的环境,进行测试,在测试过程中发现问题,前后端快速确认问题原因及解决方案
    • 测试项目没有问题呢,将项目部署带到线上,上线!!!
  • 调试bug联调
    • 线上环境中项目出bug,需要在线下环境进行bug调试,前后端人员一起解决

3. TCP的三次握手及四次挥手

  • 三次握手

    • 1.客户端先发起第一次握手,发送syn包给服务器端

      ​ 发syn:为了证明客户端有发起请求的能力

    • 2.服务器接收到请求以后,发起第二次握手,将syn包返回给客户端,同时携带ack包

      ​ 返syn包:通知客户端

      ​ 发ack包:有响应数据的能力

    • 3.客户端接收到服务器端返回的数据,发起第三次握手,将ack包提交给服务器端

      ​ 返ack包:通知服务器端确实有响应数据的能力,因为客户端已收到你的响应数据

    • 三次握手成功后,即可发起正常的前后端通信请求

  • 四次挥手

    双方都需要明确这次通信要中断

    • 1.客户端发起第一次挥手,明确客户端要主动断开
    • 2.服务器端收到客户端第一次挥手请求后,做出挥手响应,基于客户端单方面通信就中断了,客户端不能再发请求
    • 3.服务器端发起第三次挥手,明确服务器端要主动断开
    • 4.客户端在接收到服务器端挥手请求后,做出挥手响应,基于服务器端单方面通信就中断了,服务器不能再响应数据

4.当在浏览器输入一个地址,按下回车发生了什么?(重)

  • 1.解析URL的合法性和有效性

  • 2.判断是否有缓存,有则使用,过期或没有则发请求

    强缓存和协商缓存...

  • 3.DNS解析,将域名地址解析为ip地址

  • 4.TCP三次握手

  • 5.发请求,

  • 6.返数据,

  • 7.页面渲染

  • 8.四次挥手

5.强制缓存和协商缓存

  • 浏览器端第一次发请求给服务器端
  • 服务器返回对应的响应数据,状态码200,如果需要做缓存,在响应头会携带expires和cache-control,同时还会携带etag和last-Modified
  • 如果浏览器发现有缓存的字段,就会对该数据进行缓存处理,同时保存对应字段
  • 下一次浏览器发起同样的数据请求,先看本地是否有缓存,先看有无expires和cache-control字段,如果没有,说明没有缓存,直接发起请求, 如果有,看是否过期,如果没有过期,直接使用,此时是不需要发起请求的,直接从内存或者是硬盘中读取缓存数据
    • 如果是从内存中读取的,状态是200(from memroy cache)
    • 如果是从硬盘中读取的, 状态是200(from disk cache)
  • 如果缓存的时间过期了,就看etag和last-Modified字段,当浏览器刷新或重新请求资源时,会在请求头携带 If-None-Match(它的值就是之前保存的 Etag) 和 If-Modified-Since(它的值就是之前保存的 Last-Modified)两个字段发送给服务器,服务器会判断 If-None-Match 和 If-Modified-Since 和服务器保存的 Etag 和 Last-Modified 字段是否一致;
  • 如果不一致,说明服务器端保存的数据修改过,证明不能使用浏览器端的过期的缓存数据了,此时会直接返回新的资源数据,同时将新的Etag 和 Last-Modified 返回去,如果一致,会返回304的状态码,浏览器发现状态为304会直接重定向到缓存区读取缓存数据
  • 有了last-modified为什么还要设计etag???
    1. last-modified只能单纯的根据修改的时间判断是否能够使用浏览器的过期缓存数据,但是容易被一些假象迷惑,如:进行两次修改,第二次修改之后将文件数据还原成修改第一次修改之前的样子,文件内容不变,但是last-modified的时间改变,服务器会自动判定浏览器不能使用缓存了吧,浪费资源
    2. etag是根据文件的所有的索引节点(文件中的每一个节点标识),大小没有变,及时修改时间变了,etag也不会变,这样能够精准的判定浏览器是否应该使用缓存数据

6.Web Socket

话术:
h5的新特性,是一种在单个 TCP 连接上进行全双工通信的协议,只需要一次握手就可以 一旦握手成功会保持长链接,常应用在 客服 微信聊天上
实际开发中中要考虑的场景:
1.链接断开,是因为close事件触发了,处理方案是自动重连机制,重连的话不能只有一次或者无限次,一般整10次左右就可以,这能既能够保证通信恢复正常也能减少服务器的压力
2.长时间没有收到服务器信息,它的特点就是通道没有中断 和 长时间没有收到服务器信息,处理方案呢是使用心跳检测机制,利用定时器发送心跳检测包,这个时间不能太长也不能太短,一般一分钟,太短服务器压力会变大,太长失去检测的意义
3.正常情况下检测心跳没有收到心跳反应,处理方案是自动断开连接和重新握手

7.http和https的区别

  • HTTP协议传输的数据都是未加密的 ,80端口,速度快
  • https加密,443端口, 需为网站购买和配置ssl证书,会产生一定的费用。 速度慢

ajax

  • 浏览器针对ajax请求的安全策略,叫同源策略,所谓的同源策略指的是协议,域名,端口号三者必须完全一样,否则就是不同源,不同源就是跨域

解决跨域方案

  • proxy代理

    1. 分类:

      1. 正向代理: webpack-dev-server
                1. 理解: 代理服务器在客户端,服务器端不知道由谁发出来的请求
                2. ​	开发环境
      2.  反向代理: nginx
                1. 理解: 代理服务器在服务器端nginx, 不知道请求由哪台服务器发出的
                2. 线上环境
      

      ii. 理解

      1.  正常由浏览器发请求,给指定的服务器,如果不同源有跨域问题,导致请求的数据浏览器端不能直接使用
      2. 代理就是将原本由浏览器发起的请求交给代理服务器发,而服务器和服务器之间没有跨域问题	
      
      - 配置:
      项目的服务器: http://www.xxx.com
      请求资源的服务器:  http://www.atguigu.com/g/getUserInfo
      请求资源的服务器2http://www.xxx.com/a/getOthenInfo
      - 请求地址设置: /api/getUserInfo
        - 实际发出去的请求: 目标服务器地址 + /getUserInfo
      /api: {
        target: 目标服务器地址(http://www.atguigu.com),
        pathRewrite: {
          ^/api: ''
        }
      }
      
  • cors

    1. 实现原理

      1. 服务器设置响应头:

         Access-Control-Allow-Origin: 执行的域 || * 
        
  • JSONP(JSON with padding) padding垫子 垫片

    1. 实现原理

      1. 利用script标签的src属性不受跨域的限制
      2. 本质:发送并不是ajax请求
    2. 问题:只能解决get请求的跨域

      function cb(data){
        console.log('data就是服务器端返回的数据', data);
      }
      
      // 客户端: <script src="http://www.atguigu.com/g/getUserInfo?callback=cb"></script>
      // 服务器端: res.send(cb + '(' + data + ')')
      

手写ajax

// 1. 创建实例
let xml = new XMLHttpRequest();


// 2. 设置请求的方法及url
xml.open('GET', 'http://www.atguigu.com')

// 3. 监听绑定回调
/* 
  readyState: 0 1 2 3 4(客户端接收数据完毕)
*/
xml.onreadystatechange = function(){
  if(xml.readyState === 4){ // 客户端接收数据完毕
    if(xml.status === 200){ // 响应成功的数据
      console.log(xml.response);
    }
  }
}

// 4. 发送请求
xml.send();

webpack>重

  1. 概念

    webpack是一个静态资源模块的打包工具,在webpack的世界里认为所有的文件都是模块, webpack本身只能识别js, json文件,如果要打包其他的资源模块需要对应的loader进行加载识别,如果要扩展打包的功能,比如合并,压缩文件内容需要对应的plugins

    打包流程:
    1. 解析配置文件:Webpack首先会读取并解析配置文件,根据配置文件中的入口文件、输出路径、插件等信息来进行后续的打包操作。
    
    2. 解析模块依赖:Webpack会根据入口文件,递归地解析模块之间的依赖关系,构建出整个应用程序的依赖关系图谱。在解析过程中,Webpack会根据配置文件中的loader对不同类型的文件进行转换,例如将ES6语法转换为ES5语法,将CSS文件转换为JS文件等。
    
    3. 打包输出文件:在解析完所有模块的依赖关系后,Webpack会根据配置文件中的输出路径和文件名规则,将所有模块打包成一个或多个文件,并输出到指定的目录中。在输出过程中,Webpack会根据配置文件中的plugin对打包结果进行优化、压缩等处理。
    
    4. 构建完成:当Webpack完成所有的打包操作后,会输出打包结果的统计信息,包括打包时间、输出文件大小、模块数量等信息。
    
  2. url-loader

  3. 首次会将图片转换成base64的编码,并且打包到js文件中, 这样做的好处是减少图片请求的个数,减轻服务器的压力!但是不是所有的图片都会转成base64,对于图片转换要求是最大体积8kb,大于8kb的图片就不转base64,如果大于8kb的图片也转成base64的话,会导致加载图片的时候过慢,甚至导致图片失真, 如果图片较大,图片就会放在数据库中,通过连接访问而不是扔在本地

  4. 项目优化

    1. 项目上线运行慢 怎么处理

      1. 并发请求过多

        1. 问题:渲染慢 甚至页面卡顿
        2. 优化:1. 合并请求 2.延迟发送
      2. 操作DOM(增删改)频繁

        1. 问题:操作DOM会导致页面重绘 重排
        2. 优化: 减少DOM操作
        3. 扩展:重绘 重排
          1. 重绘就是重新渲染 不一定发重排 修改颜色就会导致 重绘
          2. 重排 就是页面结构重新排列,重拍必会伴随重绘
            1. 开发中要减少重排
            2. 修改元素大小 对DOM的增删改
      3. 页面中运算最大的代码吗端会阻塞渲染

        1. 问题:阻塞页面渲染及后续代码执行 甚至白屏

        2. 优化: web Worker

          ​ 将js主线程的同步耗时代码放入worker线程中转换成异步任务

  5. webpack优化

    1. 开发优化:有利于开发人员
      1. source-map资源映射 能够精准提示错误在的源文件位置
      2. hotmodule replacement热模替换 热加载 能够让再次编译的速度加快
      3. oneof较少loader对同一个文件进行二次或者多次加载 减少加载时间
    2. 生产优化:有利于用户
      1. 压缩代码 如:html css js 图片
      2. 代码切割:需要配合import()函数 最终按需加载
        1. 如果不进行代码切割 所有的代码在一个js文件中 会导致首屏渲染变慢
      3. 预加载preload
        1. 提前加载即将要使用的代码
        2. 扩展:图片预加载 小程序分包的预下载

webpack VS vite

  1. webpack底层基于js编写的 运行速度毫秒级别
  2. vite底层基于go语言 运行速度纳秒级别
  3. webpack启动方式类似于编译性语言 先编译 后渲染
  4. vite先启动 然后按需编译 渲染 vite首屏渲染的性能不及webpack
  5. 现在来看vite的生态链不及webpack

Vue>重

1.生命周期

必须要说11个
话术:
1. vue2中常规的8个
   1. beforeCreate 创建前
   2. created       创建后
   3. beforeMount 挂载前
   4. mounted     挂载后
   5. beforeUpdate  更新前
   6. updated    更新后
   7. beforeDestroy   销毁前
   8. Destoryed    销毁后 
   v3有两种开发模式一种是选项式一种是组合式,选项式和v2的选项式绝大部分都一样就是销毁阶段是beforeUnmount和unmounted,其他都是一样的,组合式中在创建前是setup,setup在beforeCreate.之前执行的,并且只执行一次
2. 用keep-alive缓存组件2个
   1. activated 
   2. deactivated
3. 错误捕获1个
   1. errorCaptured
  1. 特殊的

    1. Vue2中可以使用vm.$destroy()主动销毁对应的组件 2. Vue3中取消该方法,Vue3中没有能够直接销毁组建的API,但是我们可以利用v-if控制组件的销毁
    2. setup函数被设计的初衷用来代替beforeCreate和created 更重要的是在setup函数内部可以进行composition API的形式开发
  2. 错误捕获errorCaptured

    1. errorCaptured本身只能够捕获后代组件的错误,在捕获的钩子函数中可以获取错误的信息 错误来源地 发生错误的组件实例
      1. 如果没有return false错误会自动向上传递直到全局
      2. 如果return false会阻止错误继续向上传递
    1. 全局捕获

      main.js
      Vue.config.errorHandler = function(){}
      
      1. 使用场景:
        1. 全局捕获用于捕获线上环境中在用户的终端发生的不可预期的错误
        2. 一旦捕获到错误就上传错误,进行错误日志统计,将错误信息发送给服务器,进行汇总统计然后分析错误,解决错误

1.1 V3自定义钩子hook怎么使用

  • 它们的本质是一些可以在组件内部使用的函数,这些函数能够让你在不影响组件逻辑的情况下,增强和扩展组件的功能。
  • Hook的主要作用是允许在组件之间重用状态逻辑。举个例子,如果你有一个处理异步请求和管理请求状态的功能,那么你可能会在多个组件中需要这个功能。在Vue2.x中,你可能需要使用mixins或者HOC(高阶组件)来抽象和重用这些逻辑,但这通常会导致命名冲突和逻辑混乱。

2.埋点

  1. 意义:
    1. 收集用户的行为
    2. 收集用户的轨迹
    3. 收集数据的访问量
  2. 实现:
    1. 利用绑定事件埋点
    2. 利用定时器埋点
      1. 用户访问页面的时间
      2. 用户停留某一个板块的时间
  3. 好处:
    1. 可以根据埋点获取的信息分析总结得出网站上哪些板块用户比较喜欢,哪些板块用户无人问津,进行及时的调整
    2. 可以得出当前用户的喜好进行针对性的内容推荐

3.组件通信方案

1.props

父→子

  • 可以实现父子双向通信,如果要实现子传父,需要配合自定义事件

2.自定义事件

子 => 父

3.全局事件总线

任意组件通信

  1. 核心思想:自定义事件
  2. 实现:
    1. 定义事件总线对象:Vue.prototype.$bus = new Vue();
    2. 定义自定义事件: this.bus.bus.on(‘eventName’, callback)
    3. 触发自定义事件:this.bus.bus.emit(‘eventName’, data)
  3. vm和vc的关系
    1. vm是Vue的实例
    2. vc是组件的实例
    3. vc ----> VueComponent.prototype ---> Vue.prototype ---> Object.prototype
    4. 可以人为vm是vc的原型
  4. 注意:
    1. 大型的项目中最好不要使用Vue的事件总线对象
      1. 会导致Vue核心函数原型对象上定义的内容越来越多,响应就会变慢
      2. 如果是协同开发的话,可能会导致自定义事件名重名

4.v-model

双向数据同步

  • v-model适用于双向绑定 数据同步,主要作用于表单身上:用冒号value 和 input事件实现,还可以作用于自定义组件身上,Vue2中v-model用冒号value 和 input事件实现,Vue3中用冒号modelValue和update:modelValue实现,

5. .sync修饰符

  • 在原本props实现 父→子 的基础上,增加子→父。Vue3中移除
  • .sync修饰符的本质:通过自定义事件,接收子组件过来的数据

6.attrsattrs`与`listeners

  • $attrs用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。
  • $listeners用于实现当前组件的子组件,向当前组件的父组件通信(孙→祖)。

7.refref`、`children$parent

  • $refs$children用于 :父→子。
  • $parent用于:子→父。

8.provideinject

实现祖孙组件直接通信

provide,inject和props的区别

1. props可以实现双向通信
2. provide,inject只能实现由祖先组件向后代组件传递数据
3. props要实现给后代组件传递数据,需要逐层传递
4. provide祖先组件和后代组件直接对接
5. 优缺点:
   1. provide,inject在开发中使用更方便,因为不需要逐层写代码
   2. props在数据传递及使用的过程中性能相对更高一些,因为inject在需要数据的时候,需要逐层向外去查找对应的provide组件

9.插槽

  • 能实现父组件向子组件插入html结构,也是一种组件通信的方式,适用于 父组件 => 子组件
  • 插槽分为默认插槽、具名插槽、作用域插槽
  • 作用域插槽:每个组件实例身上的属性及数据都是当前组件实例私有的数据,如果想要访问别的组件身上的数据等同于在跨作用域访问数据,利用作用域插槽实现了子作用域的数据导给了父组件实例作用域中供父组件使用

10.Vuex

可以实现任意组件通信

  • vuex是专为 Vue.js 应用程序开发的状态管理模式 ,是在Vue中实现集中式状态(数据)管理的一个Vue插件,专门管理(读/写)多个组件的共享状态(数据),一种重要的组件通信方式,且适用于任意组件间通信。

  • vuex有五大核心属性:state、getter、mutation、action、module。

    首先state,是一个全局的状态存储,是唯一的数据载体,对应vue里面的data,是响应式的,vue组件可以直接访问其中的值,但是只可以读取,不可以进行改写操作。

    而getter可以认为是vuex的计算属性,它的作用简单来说就是过滤,组合。可以通过getter定义函数,它的返回值会根据它的依赖被缓存起来,且只有当它的依赖发生了变化才会重新计算。

    然后mutation是vuex中唯一一个可以修改数据的地方,mutation可以定义事件函数,在vue组件中可以通过commit提交事件,调用函数,但是mutation中的操作必须是同步的,不可以存在异步操作的情况。

    还有action和mutation比较相似,不同的是actions中不直接修改state,而是通过commit调用mutation修改数据,并且action中可以存在异步处理逻辑。

    最后module会在项目特别大,数据特别多的时候启用,它可以将vuex分割成模块,每个模块都会具有state、getter、mutation、action、也可以继续用module嵌套子模块

设计思想: 为什么mutation需要同步修改state的数据

​ 因为异步无法把控最终的执行时机, 容易导致操作state的数据发生混乱,会导致Vue开发工具调试Vuex的数据的时候,会失效,无法查看mutation的payload数据

pinia为什么舍弃了mutation

  1. Vuex当初在设计的时候,没有完整的异步解决方案,异步不可控
  2. 现在ES6+推出了完善的异步解决方案,promise + async + await

1.pinia

Pinia是vue生态里Vuex的替代者,一个全新的vue状态管理库

优点:
1.抛弃了Vuex中的Mutation,减少了我们工作量
2.pinia中action支持同步和异步,Vuex不支持
3.良好的Typescript支持,毕竟我们Vue3都推荐使用TS来编写,这个时候使
  pinia就非常合适了
4.体积非常小,只有1KB左右
5.支持服务端渲染

4.路由

  • 路由分为后端路由和前端路由,1.后端路由注册在服务器上,需要发请求获取数据,返回的是json数据,2.前端路由是注册在前端项目中的,不需要发请求,返回的是路由组件

1.history模式下刷新404问题

  • history模式下,刷新页面其实在发请求给对应的服务器,请求对应的路由路径, 但是该路由路径是在前端注册的路由路径,服务器并没有,所有结果404 not found

hash模式和history模式的区别

  • 体现上不一样,hash模式路由路径前边有#
  • 底层实现的方法不一样
    1. hash: window.location.hash
    2. history: window.history对象
  • 刷新页面history会有404问题,因为history模式下会发请求给服务器,hash模式不会有404问题,不会发请求给服务器

2.路由传参4种

  1. query

    	query传参注册路由时无需操作,请求的时候携带参数就行,2.携带的方式是查询字符串的形式,多个参数用&链接,3.通过$route.query.key = value获取 4.可以搭配name和path使用 使用场景经常在参数复杂,尤其是需要体现key和value关系时,如分页page和limit
    
  2. params

    	params传参注册路由时要声明占位符,如url/:key 2.获取是$route.params.key = value  3.只能搭配name使用 使用的场景一般是简单的、单一的参数、不需要过分体现key是啥,如商品id,
    	需要注意的是1.如果注册的时候没有占位符,但是携带了params参数,是能够请求到对应的路由组件并且可以获取到对应的params参数,但是url中不会有params参数的体现,刷新页面会导致params参数丢失
    
  3. meta

    1. 路由元信息:元信息是描述信息本身的信息
  4. props

    布尔值

    1. 必须搭配params参数使用
    2. 当使用props以后,能够完全解耦(降低耦合度,关联性)$route和组件实例的关系

    对象

    1. 可以自定义任意字段信息传入到当前路由组件实例的props对象中,还能提高和$route的解耦性
    2. 但是不能获取到当前路由地址下的路由信息

    函数

    1. 通过声明形参获取路由器自动注入的路由信息 对象route,并且可以自定义任意字段打包传入到当前路由组件实例的props对象中

3.路由守卫

  • 所谓的守卫就是用来保护路由跳转的安全,可以在路由正式跳转 之前做一些预处理,判断的操作
  • 理解守卫就是:1.from:从哪来 2. to:到哪去 3. next: 放行

使用场景

  1. ==登录成功以后,拒绝二次访问登录界面==

    1. 前置路由守卫中,判断当前用户是否已登录,如果没有登录,正常放行到登录界面,如果已经登录,重定向到home页
  2. ==权限管理==

    1. 设计角度:

      1. 静态路由
      2. 动态路由
      3. 任意路由
    2. 实现:

      1. 初始化需要注册的是静态路由

      2. 在用户登录以后,根据登录用户的权限信息,生成动态路由信息,并通过router.addRoute || router.addRoutes追加到路由表中,并在其后添加任意路由

==当用户访问动态路由界面的时候,如果刷新界面,会出现白屏现象!!==

  • 原因:Vuex中的数据是保存在内存中的,当刷新界面,意味着重新初始化项目,内存空间重新分配,导致原有的用户信息丢失,用户信息一旦丢失,用户所有的动态权限信息也没有了,所以没有办法注册用户的动态路由,导致访问动态路由白屏,没有内容
  • 解决:
    • 利用sessionStorage解决:在用户点击刷新按钮,页面即将刷新之前,window.beforeUnload事件中将用户信息保存在sessionStorage中,在页面刷新之后,将sessionStorage中的数据同步到Vuex中,
    • 利用路由守卫解决:在刷新页面判断Vuex中是否已有用户的信息,如果没有重新获取,

4.路由懒加载

5.动态注册的路由,路由放在哪

  • 放到router

5.routeroute和router区别

  • route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,标识当前的路由信息,包含当前URL解析得到的信息,包含当前的路径、参数、query对象等。
  • router对象是全局路由的实例,是router构造方法的实例。router是VueRouter的一个对象,通过Vue.use(VueRouter)和Vue构造函数得到一个router的实例对象,包含了所有的路由还有许多关键的对象和属性。

6.MVVM和MVC

  • mvc是一种代码架构设计模式,前端中的mvc最主要的作用就是将视图和数据模型进行分离
  • MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版,整体和mvc差不多,最大的区别就是mvc是单向的,而mvvm是双向的,并且是自动的,也就是数据发生变化自动同步视图,视图发生变化自动同步数据,同时解决了 mvc 中几个不好的地方:大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。和当 Model 频繁发生变化,开发者需要主动更新到 View

7.监听属性watch和计算属性computed的区别

  • computed计算属性调用才会执行,有返回值 , watch监听属性不需要调用,监听的数据发生改变就会执行
  • computed计算属性能够完成的操作watch侦听属性都可以完成,但是watch侦听属性能够完成的操作computed不能全部完成,比如异步操作,computed内部不能执行异步操作,watch内部可以执行异步操作
  • computed计算属性实际上是调用本身的get函数,其值就是get函数的返回值,watch侦听属性实际上调用的是本身的handler函数,执行handler函数内部的代码,且没有返回值

8.请求拦截器和响应拦截器

1、请求拦截器

请求拦截器的作用是在请求发送前进行一些操作,例如在每个请求体里加上token,统一做了处理如果以后要改也非常容易。

关于拦截,这里只说原理,前端的请求,最终还是离不开 ajax,像vue 的 vue-resource 、axios,都只是对ajax进行了统一的封装,它暴露出来的拦截器,其实就是写了一个方法,把ajax写在这个方法里面,(我们先说请求拦截器)在执行这个方法的时候,先将请求时要添加给请求头的那些数据(token、后端要的加密码…具体要看实际情况)先执行一遍,都赋值给一个变量,然后再统一传给ajax,接下来就是执行ajax,这就是所谓的请求拦截,其实就是先执行要添加的数据,然后再执行ajax,如果把这个添加数据的过程抽出来,就成了所谓的请求拦截器;

2、响应拦截器:

响应拦截器的作用是在接收到响应后进行一些操作,例如在服务器返回登录状态失效,需要重新登录的时候,跳转到登录页。

响应拦截器也是一样如此,就是在请求结果返回后,先不直接导出,而是先对响应码等等进行处理,处理好后再导出给页面,如果将这个对响应码的处理过程抽出来,就成了所谓的响应拦截器;

9.v-show和v-if

  • v-show不展示DOM元素依然在,仅仅是使用样式隐藏掉了,不破坏 DOM结构。

  • v-if不展示的 DOM元素直接被移除。

  • v-show适用于切换频率高 v-if适用频率低

10.vue2和vue3的区别

  • API类型不同,v2是选型类api,vue3是组合式

  • 双向数据绑定原理不同

  • 生命周期钩子不同

  • 响应式原理不同:

    • Vue2底层是通过es5Object.defineProperty,使用Object.defineProperty()进行数据劫持 ,结合订阅发布的方式实现,有一定的局限性。
    • Vue3底层是通过es6Porxy, 使用Proxy代理,使用ref或者reactive将数据转化为响应式数据,能够更好地支持动态添加属性和删除属性。
  • 创建vue实例的方式不一样

    • vue2中使用new Vue()的方式,来创建实例

    • vue3中使用createApp()方法来创建实例

  • v-if和v-for优先级

    • 在v2中如果同时使用v-for 和 v-if 那么v-for的优先级是高于 v-if 的

    • v3中v-if 始终高于 v-for但是还是不建议一起使用

Vue源码

1.nextTick

nextTick就是vue通过异步队列控制DOM更新和nextTick回调函数先后执行的方式

1.作用: 可以保证在下一次DOM更新结束之前,执行nextTick传入的callback函数

2.原理: 分两种情况一种传cb,一种不传,

  • 传cb:底层有个timerFunc方法,在这个方法中有四种兼容性考虑,分别 是Promise、MutationObserver、setImmediate、setTimeout,其中首选是Promise,最无奈的选择是setTimeout。当调用nextTick的时候 会在timerFunc中利用这四种方案中的其中一种方式将传入的cb扔到异步队列。

用了nextTick以后一定能```够保证一定在下次DOM更新渲染之后才执行nextTick的cb吗?

  1. 首选当修改响应式数据之后,数据的修改是同步的,但是视图的更新是异步的,因为更新渲染视图的方式最终也会被通过nextTick扔到异步队列执行

  2. 我们能做的是nextTick使用必须要在操作影响DOM元素更新的响应式数据之后执行即可,这样能够保证我们写的nextTick的cb在更新视图的方法之后进入队列,最终更新视图的方法先出队列执行

  • 不传

    1. nextTick提前定义了flushcallbacks函数,该函数会将callbacks(用于保存多个函数的数组)的每一个函数循环遍历出进行调用
    2. flushcallbacks方法会被通过四种方式的一种扔到异步队列,当callbacks数组中的函数调用,会判断如果传递了cb函数,会调用该函数
    3. 如果当初nextTick没有传cb,首先会返回一个状态为pending的promise,当callbacks数组的函数调用的时候,会修改该promise的状态为resolved成功状态进而执行后续对DOM元素的操作行为

2.数据代理

  • 本质:1. 将data中所有key添加到组件实例身上通过

    ​ 2. 会为每一个属性身上添加get和set方法

    ​ 3. 当访问或者修改某一个data中的属性时,会自动触发对应的get和set方法,去返回原数据data中的属性值或者修改原数据data中的属性值

  • 好处:简化开发:可以直接通过组件实例this访问data中的数据以及修改

    ​ data中的数据

3.数据劫持

  • 本质:1.将原本的data劫持下来,通过Object.defineProperty()进行属性的 扩展

    ​ 2.为data中每一个属性添加get和set方法

  • 好处:1.当使用data中的属性的时候,会触发对应的get方法,建立响应式

    ​ 联系,为后期响应式做准备工作

    ​ 2.当修改data中的属性的时候,会触发对应的set方法,并触发对应

    ​ 的更新视图的方法,完成响应式

4.Vue2响应式原理

  1. vue2首先会利用Object.defineProperty进行数据代理,为实例身上添加data中的所有属性,这样的目的是为了访问或者使用data中的数据更加便利
  2. 同时利用Object.defineProperty进行对data中每一个属性实现数据劫持,所谓的数据劫持指的就是为data中每一个属性添加get和set方法;
  3. 当在模板结构中使用某一个响应式数据的时候,会触发其对应的get方法,在get方法中通过闭包闭合的dep对象调用depend方法创建响应式联系,收集依赖;
  4. 所谓的创建响应式联系 就是将dep对象添加到watcher对象的newDeps数组中,将dep的id属性添加到watcher对象的newDepIds的set容器中,紧接着将watcher实例添加到dep对象的subs数组中从而建立响应式联系
    1. 首先一个dep对象代表一个响应式属性,将dep添加到watcher是为了当watcher对象中更新视图的方法调用以后,知道要去更新哪些个响应式属性 2. 将watcher添加到dep对象的原因是,一个组件至少有一个渲染组件的watcher,但是也可能会有其他的watcher,比如computed计算属性watcher,当一个dep对应的响应式属性发生改变的时候,需要通知具体的watcher去为其服务更新
  5. 当修改一个响应式数据的时候,会触发其对应的set方法,在set方法中通过dep对象调用其notify方法,找到dep对象中subs数组,循环遍历找到所有的watcher调用其update方法进而更新视图,更新视图的方法最终是被通过nextTick扔到异步队列更新,所以我们也会说修改响应式数据,数据的修改是同步的,视图的更新是异步的

5.Object.defineProperty VS proxy

  1. Object.defineProperty侧重点是对象的属性,是给每一个data中的属性进行get、set的设置
    1. 后添加的属性并没有get,set方法,所以后添加的属性属于非响应式属性
    2. 如果后添加的属性也想成为响应式,需要使用Vue.set() || vc.$set()进行响应式属性的设置
  2. proxy侧重点是对象自身,代理的整个对象
    1. 后给对象添加的属性也是响应式属性
  3. 注意点:
    1. Vue2中有set方法
    2. Vue3中取消了该方法

6.Vue3响应式原理

  1. 通过proxy对的data对象进行代理,并设置对应的get和set方法

  2. 在get方法中,通过track方法进行依赖收集,在track内部创建weakMap对象,其中key是data代理对象,value是一个Map对象,在Map对象内部key是data中每一个属性,value是Set容器对象,在Set容器中保存和更新当前属性相关的所有Effect实例!!!Effect实例身上有run方法,可以进行视图的更新渲染,其中run方法等同于Vue2中watcher对象update方法

  3. 当修改data中的属性的时候,会触发代理对象身上的set方法,在set方法触发trigger方法,在trigger方法的内部,通过data中的key在之前创建的Map对象中能够对应的Set容器,通过对Set容器的遍历找到保存的所有的Effect实例,进而调用run方法用来更新渲染视

    let data = {
    	name:'curry',
    	age: 35
    }
    
    // weakMap对象
    {
    	key: Data代理对象
    	value: new Map()
    }
    
    // Map对象
    {
      key: data对象的所有属性(name || age)
      value: new Set()
    }
    
    // Set对象
    Set([Effect实例1, Et实例2.run() // 更新渲染视图
    

7.Vue2虚拟DOM Diff算法

  1. 对比方式

    1. 旧前和新前对比,如果一样指针后移一位

    2. 旧后和新后对比,如果一样指针前移一位

    3. 旧前和新后对比,如果一样

      1. 旧前指针后移
      2. 新后指针前移
      3. 旧前元素移动到旧后元素的后边
    4. 旧后和新前对比,如果一样

      1. 旧后指针前移
      2. 新前指针后移
      3. 旧后元素移动到旧前元素前边
    5. 如果上边的对比都不匹配,根据旧树创建key和index的映射关系

      1. 判断新树剩余元素中第一个node节点是否有key属性
      2. 如果有key属性,通过key属性找到新前元素在旧树中的下标及对应的元素
      3. 如果没有key属性,通过新前元素在旧树的列表中一一对比,找到是否有该元素(循环遍历对比的过程代价很大!!!)
      4. 如果在旧树中没有该元素,说明新有旧没有,就创建
      5. 如果在旧树中有该元素,将该元素所处的位置标记成undefined,同时将对应的旧的该元素移动到旧前的前边,同时新前的指针要后移一位
    6. 以上的5个步骤是在while循环中进行的,直到不满足while循环的条件才结束,符合的条件: oldStartIndex <= oldendIndex && newstartIndex <= newendindex

    7. 当while循环结束后,判断哪个条件先不满足,如果oldStartIndex > oldendIndex 说明旧的对比完了,新的还有剩余,创建剩余的新的元素,如果newstartIndex > newendindex说明新的对比完了,旧的还剩余,删除剩余的旧的元素

  2. 注意点:

    1. 对比的是新的虚拟DOM对象树和旧的虚拟DOM对象树
    2. 移动的真实的DOM树
    3. 为了保证渲染的效率,在文档碎片中
  3. 面试题: key属性的重要性

    1. key属性的特点:唯一的
    2. key属性的取值:index || 唯一标识id
    3. index可能存在的风险
      1. 存列表展示,后期不会进行增删改的操作,可以用
      2. 一旦可能对当前的列表数据进行增删改,就不能使用,因为对列表数据进行增删改的操作会导致index发生错位,

8.Vue3虚拟DOM Diff算法

  1. 对比方式:

    1. 前前对比,如果一样,指针向后移继续前前对比,如果不一样,停止前前对比

    2. 后后对比, 如果一样,指针向前移,如果不一样,停止后后对比

    3. 剩余的情况:

      1. 新有,旧没有,就创建新的

      2. 旧有,新没有,删除剩余未对比的旧的元素

      3. 新旧都有

        1. 创建Map对象用于映射新树的key和index

        2. 创建arr数组用于映射新树的index和旧树的index的关系

          1. 以剩余新树的元素个数为length创建的
          2. 初始化arr中所有的index指定的元素值都是0
          3. 新树元素的元素的标识从该数组的index为0的位置开始,index对应的value值是旧树的index + 1的值,这样做的好处能够通过数组找到元素在旧树的位置以及对应的元素内容
          4. 能够通过map映射找到元素在新树的位置用于决定对比的元素移动摆放的位置
        3. 重点!!!

          1. 移动的方式: 最长递增子序列

            // 2 4 1 5 8 3 7 6 9 22 58 17 89
            // 2 4 5 8 9 22 58 89 // 最长递增子序列
            // 1 2 4 5 8 9 22 58 89 
            // 1 2 3 4 5 8 9 22 58 89 
            // 1 2 3 4 5 7 8 9 22 58 89 
            // 1 2 3 4 5 6 7 8 9 22 58 89 
            // 1 2 3 4 5 6 7 8 9 17 22 58 89 
            
            // 4 5 8 9 22 58 89
            // 1 5 8 9 22 58 89
            // 5 8 9 22 58 89
            // 8 9 22 58 89
            // 3 7 9 22 58 89
            // 7 9 22 58 89
            // 6 9 22 58 89
            // 9 22 58 89
            // 22 58 89
            // 58 89
            // 17 89
            
            

9.keep-alive原理

  • 利用Map对象进行缓存组件实例及其相关内容
  • 在mounted和updated中会进行缓存,其中mounted第一次缓存,在updata缓存是因为当前组件的数据状态发生改变了,需要更新缓存内容
  • 当使用缓存组件时,从Cache缓存Map对象读取即可,将存放缓存映射的key的Set容器中删除原来当前组件对应的key,同时将当前的key追加到Set容器的队尾
  • 随着不断缓存新的组件,会导致内存空间越来越小,如果即将要缓存新的组件的时候发现内存不够,删除已缓存不活跃使用的组件从缓存中
  • ==如何判断哪个缓存组件不活跃==?? 从保存所有缓存组件key的Set中队首开始找,开始删除

小程序

1.生命周期

  1. 重点说页面的生命周期
  2. 一共5个: onLoad, onShow,onReady,onHide,onUnload
    1. onLoad和onShow
      1. 在这两个钩子函数中通常去发送请求
      2. 如果离开当前页面的时候,当前页面被销毁,通常在onLoad发送,因为下一次加载当前页面的时候,onLoad会再次执行
      3. 如果离开当前页面的时候,当前页面被隐藏,通常在onShow发送,如果在onLoad发当前页面的数据一直旧的数据
    2. 官网小程序生命周期的图示有问题,onLoad和onShow标注的过于靠前,实际上应该是在发送initdata到视图层之后才会执行onLoad和onShow,`可以去小程序性能与体验中查看小程序的启动流程图示即可

数据存储storage

  1. 等同于H5的localStorage
  2. 总容量上限是10M, 单个key的上限1M
  3. 提供了两种操作存储的方案
    1. 异步: wx.setStorage()
    2. 同步: wx.setStorageSync()
  4. 扩展:H5的本地存储。。。

0.小程序登录流程

  1. 登录是在小程序的客户端调用wx.login()获取用户的临时登录凭证code,该code有效期只有五分钟(code字段中包含生成code的时间戳)
  2. 客户端将code发送给商家服务器端,商家服务器会利用提前在本地保存的appid和appSecret(密钥)以及客户端发送的code三者作为参数对接微信的服务器端,微信服务器端会返回当前用户的openid(当前用户的唯一标识)
  3. 商家服务器端在接收到用户的openid以后,会对用户的openid及session_Key进行加密处理生成token与用户的openid和session_key进行关联存到数据库中,并将生成token返回给小程序客户端
  4. 小程序客户端需要将用户的标识token存储到storage,以后发起请求需要验证用户身份的时候,携带上token即可

2.小程序支付流程(重)

  1. 用户在商家客户端下单,商家客户端携带着用户标识,商品内容发送请求给服务器

  2. 商家服务器端会生成平台订单号,这个是为了商家服务器管理所有订单的标识

  3. 商家的服务器会将订单号返回给商家客户端(用于以后客户端通过订单号查询当前订单的状态)

  4. 商家客户端通过订单号请求商家服务器,商家服务器发起请求微信服务器,携带着用户标识、订单标识、商家标识、商品信息

  5. 微信的服务器会生成预支付订单,并返回给商家服务器端,商家服务器对预支付订单进行签名处理(二次加密),将加密后的支付参数返回给商家的客户端

  6. 商家客户端调用wx.requestPayment(支付参数)发起微信支付

    1. 直接发送请求给微信服务器端进行鉴权处理,鉴权通过以后,客户端会自动调起输入密码的小键盘
    2. 用户输入密码,发送请求提交授权(授权给微信服务器进行扣款处理)
  7. 微信服务器进行扣款以后

    1. 异步通知商家的服务器端支付结果
      1. 商家服务器保存支付结果,并修改订单状态
      2. 告知商家服务器端订单结果状态接收成功
    2. 通知商家服务器
      1. 当前用户的微信消息列表会有支付成功的消息提示
      2. wx.requestPayment的成功回调会自动调用
  8. 注意点:

    ​ ==当wx.requestPayment成功回调调用以后,一定在商家客户端发起请求给商家的服务器进行订单状态的查询,一定是根据商家服务器返回订单状态决定下一步应该干什么或者显示什么!!==

3.小程序分包

  1. 为什么要分包?

    1. 小程序要求发布的时候压缩包的体积不能大于2M,但是开发过程中随着项目内容的增加,总体积可能会大于2M
    2. 因为一些活动或者需要临时添加一些单独的业务模块,可以使用独立分包进行分包处理,这样做的好处是对原有的小程序主体项目没有影响而且可以和主体项目解耦,后期可以随时去掉这些临时添加的业务,比如: 6.18, 11.11活动相关页面
  2. 分包的特点:

    1. 分包可以是多个
    2. 分包以后,单个分包的体积上线还是不能大于2M
    3. 分包以后,所有包的总体积不能大于20M
  3. 常规分包

    1. 分包还是要依赖于主包,得先有主包,再有分包
    2. 分包是可以依赖主包的内容的
    3. 项目的主页面,tabBar页面这些页面不适合放入分包中
  4. 独立分包

    1. 不用依赖主包,能够独立加载
    2. 新版本是可以加载主包或者其他分包的资源(需要异步加载)
  5. 分包异步化

    1. 跨分包加载其他分包的js资源

      1. 利用异步加载,requires.async(其他分包的资源路径)
    2. 跨分包加载其他分包的自定义组件

      {
        "usingComponents": {
          "button": "../../commonPackage/components/button",
          "list": "../../subPackageB/components/full-list",
          "simple-list": "../components/simple-list",
          "plugin-comp": "plugin://pluginInSubPackageB/comp"
        },
        
        // 声明使用内置的组件进行占位,保证加载其他分包的自定义组件不会报错
        "componentPlaceholder": {
          "button": "view", // 
          "list": "simple-list",
          "plugin-comp": "view"
        }
      }
      
  6. 分包预下载

    在加载某一个包的时候,可以预先下载其他即将要使用的分包,用于提高用户体验

4.小程序发布流程

  1. 在微信开发工具中,点击 上传 并选择对应的发布的版本号及填写本次的项目备注(改动了哪些内容)
    1. 版本号选择三种方案
      1. 如果改动很大: 修改的是版本更新,改变的是1级的版本号
      2. 如果局部改动较大: 选择特性更新,改变的是2级版本号
      3. 如果改动较小:选择修改补丁,改变的是3及版本号
    2. 注意:上传压缩包的体积不能大于2M,会上传失败
    3. 上传成功之后,在小程序开发的后台管理界面,选择版本管理, 在版本管理中切换当前上传的项目为体验版,进行真机测试,如果测试没有问题,取消体验版切换为待发布的版本,点击提交审核,填写对应的主体内容即可,等待发布上线了。。。。

5.原生小程序和uni-app的区别

  1. 原生小程序

    1. 开发必须使用小程序提供的组件和API

    2. 也可以实现简单的双向数据绑定

      <input model:value='{{value}}' />
      

      **注意点:在原生小程序的双向数据绑定实现中,定义的变量名不能是简单的a,b,c会导致双向数据绑定失效**

  2. uni-app

    1. 基于Vue.js进行项目开发
    2. 一套代码可以适用于多个平台, 如: H5, 各种小程序,原生的应用App(Android, IOS)
    3. 有更丰富的插件及UI组件库,同时有自己的一套uniUI
    4. uni-app中有自己的API,但是和原生小程序的API功能特别相似

6.uni-app项目如何打包

  1. 使用工具HBuilderX的工具进行打包
  2. 打包的模式可以云打包
  3. 打包分类:
    1. android(apk)
    2. IOS(ipa)
  4. 打包注意事项:
    1. 需要提前生成uni-app项目的应用id
    2. 需要提前生成打包证书
      1. ios的需要注册苹果的开发者账号,获取当前开发者私有的证书
      2. android可以使用云端证书(公共的证书)也可以获取开发者私有的证书

7.在 UniApp 中控制页面的访问权限

可以通过以下几种方法实现:

  1. 路由拦截器(路由守卫):UniApp 支持使用路由拦截器来控制页面的访问权限。你可以在全局配置或者单个页面配置中设置路由拦截器,在页面跳转前进行拦截和权限验证。通过拦截器,你可以根据用户的登录状态、用户角色等条件来决定是否允许页面的访问。
  2. 条件渲染:UniApp 的模板语法支持条件渲染,你可以根据不同的条件来动态显示或隐藏某个页面组件。在页面加载时,根据用户的登录状态或其他权限标识,动态决定是否渲染某个组件,从而达到控制页面访问权限的目的。
  3. 后端接口验证:对于需要登录或特定权限才能访问的页面,你还可以在后端接口中进行权限验证。在请求后端接口时,后端服务器可以根据用户的身份信息和权限标识来判断是否有权访问页面内容。如果没有权限,则可以返回相应的错误提示或重定向到其他页面。

项目

1.权限管理

  1. 会拆分路由分为三部分: 静态路由,动态路由,任意路由(404)

  2. 先注册好静态路由,

  3. 根据当前登录用户的权限信息生成对应的动态路由,利用router.addRoutes || router.addRoute添加到路由表中,

  4. 最后再把任意路由也追加到路由表中(路由的匹配是从前到后的,一旦匹配成功就会停止匹配,如果先注册任意路由,会导致任意路由之后注册的路由全部失效)

  5. 遇到过什么问题?

    1. 不同权限用户切换登录
      1. 高权限 到 低权限
        1. 低权限用户仍然能使用高权限用户的权限,上一次高权限用户的路由信息保存在Vuex中,高权限用户退出登录的时候,没有及时清除其对应的动态权限信息,
        2. 解决方案, 任意用户退出登录的时候,要清除其对应的路由权限信息,下一个用户登录以后根据自己的标识重新获取对应的权限信息
      2. 低权限 到 高权限
        1. 问题:高权限用户可能会无法完整自己的权限,其权限依然对应的上一个登录的低权限用户的权限信息
        2. 解决方案: 使用深度克隆
    2. 在访问动态路由的时候,刷新页面会出现白屏问题
      1. 原因:用户的动态权限信息保存在Vuex或者是pinia中,刷新页面会导致整个项目重新初始化加载,Vuex或者pinia对应的会重新分配内存空间,重新初始化其对应的state数据,导致用户的动态权限信息丢失!!!
      2. 解决方案:
        1. 可以在页面即将要刷新之前将Vuex或者pinia中的数据保存到sessionStorage中,在页面刷新之后,从sessionStorage中读取出来重新设置vuex或者pinia中,这里利用window.beforeUnload事件监听页面是否即将要刷新!

2. 虚拟长列表

  • 在项目中当数据量很大的时候,如果直接全部渲染,会导致渲染的压力包括创建的DOM元素过多,容易造成页面卡顿,效果极差
  • 我们就会使用虚拟长列表进行优化
    • 在可视区显示创建的DOM元素,其他不可见的区域都是空白,长列表依然存在
    • 当用户滚动·我们的虚拟长列表的时候,我们需要动态计算出可视区域元素的内容是什么
    • 为了用户的体验好,可以添加一个缓冲区域,防止用户拖动过快,计算还没有实时的计算完出现空白

3.如何封装一个功能良好的组件或者是功能函数?

  1. 封装的意义
    1. 能够复用,减少重复的代码
    2. 能够保证在多个场景下使用,适用范围广
    3. 能够保证组件的安全性,即使没有使用过我们封装的组件也不会报错导致整个项目崩溃
  2. 封装良好组件注意点:
    1. 组件内部保存的是静态的数据及结构
    2. 动态的数据提取出来由使用者通过标签属性的形式传入,组件内部通过props接收
    3. 如何保证组件的安全使用
      1. 设置props属性的必要性required
      2. 设置props属性的数据类型 type
      3. 设置props属性的默认值default
  3. 封装功能函数
    1. 静态的数据保存在函数体的内部
    2. 动态的数据提取由使用者通过实参的形式传入
    3. 需要对形式进行的数据类型判断

4.大文件切片上传

  • 在项目中, 针对大文件上传,一次性传输文件会出现网络中断等情况。不会传输成功 ,所以我们会使用切片上传保证上传的效率。
  • 大文件切片上传会根据多个切片的数组分别上传切片的时候,客户端一旦将切片全部上传完毕,服务器端需要整理已上传好的切片,保存到数据库中
服务器端如何知道客户端切片上传完毕?

1.客户端先要知道切片上传完毕,然后主动通知服务器端进行切片
2.客户端会通过Promise.all([所有上传切片请求返回的promise实例])的方法知道全部文件上传完毕
服务器端如何判断哪些切片是一个文件?

1.每个切片上的hash值由三部分内容组成:name(大文件的名字)  + hash(切片的数组列表) +index(每个切片在数组中的下标)
如何实现断点续传 || 秒传

1.正式上传切片之前先问服务器端当前的切片之前是否已经上传过
2.服务器返回的needUpload有两种情况:

3.needUpload: false就是已经全部上传成功,直接返回可访问当前资源的链接地址实现了秒传

4.needUpload: true就是之前没上传过任何的切片,全部上传。还有就是之前上传过部分切片,正式上传之前,过滤掉已经上传好的切片,完成`断点续传!