前端面试题整理

257 阅读20分钟
1. 三个if判断的函数,怎么降低圈复杂度?手写代码实现
test(val) {
  if (val === 'zhifubao') {
    console.log('支付宝')
  } else if (val === 'weixin') {
    console.log('微信')
  } else if (val === 'yinlian') {
    console.log('英联')
  } else {
    console.log('其他')
  }
},
// 降低圈度复杂度的方法
promoteTest(val) {
  const obj = {
    'zhifubao': () => {
      console.log('支付宝')
    },
    'weixin': () => {
      console.log('微信')
    },
    'yinlian': () => {
      console.log('英联')
    }
  }
  return (obj[val] || (() => { console.log('其他') }))()
},
2.微任务和宏任务的区别

宏任务:

当前事件循环中正在执行的任务,例如:setTimeOut,setInterval, I/O,页面渲染及点击等;

微任务:

当前执行栈清空后立即执行的任务,例如:promise回调函数,mutationObserver回调函数,nextick回调函数;

区别:

每次事件循环的开始,都是先执行一个宏任务,然后再执行所有的微任务
微任务的执行优先级高于宏任务,微任务是当前任务执行完毕后立即执行,优先级高,宏任务是在每一轮时间循环中执行的,优先级低
在同一轮事件循环中,宏任务执行后会执行所有的微任务,直到微任务队列为空,然后再开始下一个宏任务。
过多微任务可能导致页面渲染延迟
3. css样式选择器优先级

!important > 内联样式 > id选择器 > class类选择器(包括伪类、属性) > 标签选择器 > 通用选择器

4. css实现三列布局,左右固定宽度,中间自适应
  1. 利用flex布局方法,左右固定宽度,中间元素设置flex: 1
<div class="container">
  <div class="left-column">左侧内容</div>
  <div class="center-column">中间自适应内容</div>
  <div class="right-column">右侧内容</div>
</div>
.container {
  display: flex;
}

.left-column {
  width: 200px; /* 左侧固定宽度 */
  background-color: #f0f0f0; /* 背景色 */
}

.center-column {
  flex: 1; /* 中间列自适应 */
  background-color: #e0e0e0; /* 背景色 */
}

.right-column {
  width: 200px; /* 右侧固定宽度 */
  background-color: #f0f0f0; /* 背景色 */
}

2. 利用绝对定位,左右两边的盒子分别使用left:0和right:0固定在两边,中间的盒子设置左右margin实现自适应

3. 利用浮动,第一个float:left,第二个float:right,第三个设置margin-left和margin-right

5. vue3与vue2的区别
  1. 速度更快:重写了虚拟DOM实现;编译模版的优化;更高效的组件初始化;SSR速度提高;update性能的提高
  2. 体积更小:通过webpack的tree-shaking功能可以去除多余代码,减少项目打包后的体积
  3. 更容易维护:使用compositon Api写法,增强了逻辑的清晰度,方便逻辑的灵活使用和组合;vue3底层用typeScript重构,对ts语法有更好的支持,自定义封装了很多类型
  4. 更接近原生: 可以自定义渲染 API
6. vue3新特性
  1. 支持多个根节点
  2. teleport:Teleport 是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术,就有点像哆啦A梦的“任意门”,可以很好的解决toast,modals等组件的z-index问题
  3. createRenderer:通过createRenderer可以自定义构建渲染器:代码如下
import { createRenderer } from '@vue/runtime-core'

const { render, createApp } = createRenderer({
  patchProp,
  insert,
  remove,
  createElement,
  // ...
})

export { render, createApp }

4. composition API(组合式的API):使代码更容易维护,逻辑更加的清晰,相同功能的变量可以进行集中式管理 5. v-for元素上的ref不再生成ref数组

  1. scopedSlots属性已删除,现在插槽数据都是通过scopedSlots属性已删除,现在插槽数据都是通过slot传递
  2. 生命周期函数的改变
  3. composition API 中 setup 函数的执行并没有使用bind绑定实例对象,所以没有this上下文,通过setup返回的对象可以直接暴露给模板,不需要 this
7. 对vue的理解
  1. 采用数据驱动模型(MVVM):
  • Model: 模型层,负责业务逻辑的处理以及和服务器进行交互
  • View:视图层,负责将数据模型转化为UI页面
  • ViewModel: 视图模型层,是视图层和模型层的通信桥梁
  1. 组件化:降低系统耦合度,方便调试,提高系统的可维护性
  2. 指令系统(v-if,v-for.....),数据双向绑定,不需要频繁操作DOM节点,更改页面只需要操作数据
8. css属性继承

以下代码中的属性都可以被子元素继承

font:组合字体
font-family:规定元素的字体系列
font-weight:设置字体的粗细
font-size:设置字体的尺寸
font-style:定义字体的风格
font-variant:偏大或偏小的字体

text-indent:文本缩进
text-align:文本水平对刘
line-height:行高
word-spacing:增加或减少单词间的空白
letter-spacing:增加或减少字符间的空白
text-transform:控制文本大小写
direction:规定文本的书写方向
color:文本颜色
visibility
caption-side:定位表格标题位置
border-collapse:合并表格边框
border-spacing:设置相邻单元格的边框间的距离
empty-cells:单元格的边框的出现与消失
table-layout:表格的宽度由什么决定
list-style-type:文字前面的小点点样式
list-style-position:小点点位置
list-style:以上的属性可通过这属性集合
9.虚拟DOM(源码还有待研究)
  1. 虚拟DOM:是对真实DOM树的抽象,在JS中虚拟DOM可以看成是一个对象,其中通常包含三个属性:标签(tag)、属性(attrs)、子元素对象(children)
  2. 虚拟DOM的优点:
  • 频繁操作DOM会出现页面卡顿,影响用户的体验,真实的DOM包含很多属性,数据庞大
  • 虚拟DOM采用diff算法,减少直接操作真实DOM带来的性能消耗
  • 抽象了渲染过程,实现了DOM的跨平台能力
10.SSR的优缺点(简单了解)
  1. SSR(Server-Side Rendering):服务端渲染
  • 优点:有利于SEO,加快了首屏的渲染速度
  • 缺点:增大了服务器的压力,三方库支持性低,代码兼容性不好
11.SPA优缺点及原理

原理:通过监听地址栏中的hash值变化来加载对应的资料用于驱动页面的变化,通过H5中提供的router API中pushState()方法来记录浏览器的历史记录。

1740125170030.png

12.vue中slot的理解及使用

插槽:通过使用插槽使组件更灵活及提高其可复用性,插槽分为以下几种:默认插槽、具名插槽、作用域插槽、动态插槽

// 默认插槽
<button type="submit"> 
    <slot> Submit <!-- 子组件默认内容 --> </slot> 
</button>
// 父组件
<SubmitButton>Save</SubmitButton>

// 具名插槽
<div class="container"> 
    <header> 
        <slot name="header"></slot> 
    </header> 
    <main> 
        <slot></slot>
    </main> 
    <footer> 
        <slot name="footer"></slot> 
    </footer> 
 </div>
//父组件
<BaseLayout>
    <template v-slot:header>
        <!-- header 插槽的内容放这里 --> 
    </template>
</BaseLayout>

// 作用域插槽
<div> <slot :text="greetingMessage" :count="1"></slot> </div>
//父组件
<MyComponent v-slot="slotProps"> {{ slotProps.text }} {{ slotProps.count }} </MyComponent>

// 动态插槽
//父组件中dynamicSlotName变量为slot name
<base-layout> <template v-slot:[dynamicSlotName]> ...</template>
13.v-if与v-show的区别及使用场景
  1. v-show隐藏为该元素添加css--display:nonedom元素依旧还在;v-if显示隐藏是将dom元素整个添加或删除
  2. v-if有更高的切换消耗,组件的v-if切换,会触发组件生命周期
  3. v-if和v-show为false时,都不会占据页面内容

使用场景:如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好

14.vue权限管理实现

权限控制可分为以下四种:

  1. 接口权限:通过axios请求拦截器进行拦截,如果接口错误码返回401,则自动跳转至无权限页面或者登录页
  2. 菜单权限:有两种实现方案:
  • 菜单与路由分离,菜单由后端返回,初始化的时候先挂载不需要权限控制的路由,根据后端返回的菜单name与前端路由定义的name相匹配,筛选出有权限的路由通过addRoutes动态挂载
  • 菜单与路由分离,菜单和路由都由后端返回,将后端返回的路由通过addRoutes动态挂载
  • 以上两种方案缺点:前后端配合要求高
  1. 按钮权限:通过自定义指令的方式判断是否有按钮权限
  2. 路由权限,有两种实现方案:
  • 初始化即挂载全部路由,并且在路由上标记相应的权限信息,每次路由跳转前做校验
  • 初始化的时候先挂载不需要权限控制的路由,比如登录页,404等错误页。在用户进行登录之后,在全局路由守卫里筛选当前用户有权限的路由通过addRoutes方法加入到路由表中
  • 以上两种方案都存在缺点:菜单跟路由耦合在一起
15.Vue.observable

Vue.observable:让一个对象变成响应式对象,Vue 内部会用它来处理 data 函数返回的对象,可用于非父子组件的通信,示例代码如下:

// 引入vue的js文件
import Vue from 'vue
// 创建state对象,使用observable让state对象可响应
export let state = Vue.observable({
  name: '张三',
  'age': 38
})
// 创建对应的方法
export let mutations = {
  changeName(name) {
    state.name = name
  },
  setAge(age) {
    state.age = age
  }
}

// 在vue文件中可按如下示例应用
<template>
  <div>
    姓名:{{ name }}
    年龄:{{ age }}
    <button @click="changeName('李四')">改变姓名</button>
    <button @click="setAge(18)">改变年龄</button>
  </div>
</template>
import { state, mutations } from '@/store
export default {
  // 在计算属性中拿到值
  computed: {
    name() {
      return state.name
    },
    age() {
      return state.age
    }
  },
  // 调用mutations里面的方法,更新数据
  methods: {
    changeName: mutations.changeName,
    setAge: mutations.setAge
  }
}
16.Vue中的$nextTick
17. 前端隐藏元素的方法有哪些

display: none;visibility:hidden;opacity:0;z-index:-1:position:absolate

18. 什么情况会导致前端内存泄漏;内存泄漏怎么排查以及如何解决
  1. 导致内存泄漏的场景如下:

    a. 意外创建的全局变量

    b. 未及时清理的定时器及事件监听器

    c. 闭包内应用的外部作用域变量未及时释放

    d. 打印的console.log变量,未及时回收

    c. 引用的dom未及时被清除

  2. 内存泄漏排查的方式:

    a. 浏览器开发工具:memory,performance面板(监控内存使用曲线)

    b. 代码审查

  3. 解决方法:

    a. 在页面卸载的生命周期钩子函数中及时释放资源

    b. 通过打包工具自动注释console.log代码

19. 闭包
闭包的形成条件
1. 函数嵌套:一个函数内部定义了另一个函数
2. 内部函数引用了外部函数的变量
3. 内部函数在外部函数之外被调用
  let count = 0; // 外部函数变量
  
  function inner() {
    count++; // 内部函数引用外部变量
    console.log(count);
  }
  
  return inner; // 返回内部函数
}

const closureFn = outer();
closureFn(); // 输出1
closureFn(); // 输出2
闭包得作用和弊端:
    作用: 创建私有变量,可长时间保存在内存中不被回收,可实现模块化开发,实际可运用到防抖、节流等函数得封装
    弊端:创建得私有变量没有及时回收容易造成内存的泄露;过度使用闭包可能影响性能
20. 解决IE6双边距

给元素设置:display: block

21. 前端长列表渲染解决方案
1. ### 虚拟滚动技术原理: 通过只渲染可视区域内的元素来解决长列表性能问题
2. 虚拟分页技术:通过前端假分页,分批渲染数据
3. 利用vue插件**vue-virtual-scroller**
22. 微信小程序分包与主包大小限制详解
1. 微信小程序的主包必须包含启动页面(如pages/index)和TabBar页面,其大小不得超过2MB,主包资源都是全局的,可以被所有分包公用
2. 每个分包(包括普通分包和独立分包)的代码体积均不得超过2MB
3. 分包数量没有明确限制,但有总包限制,总代码体积不得超过20MB
22. 微信小程序生命周期
1. onload();onShow();onHide();onUnload();onReady()
23. 微信小程序支付和登录流程
1. 支付流程: 小程序支付需要注册小程序并绑定商户号,调用`wx.login()`获取临时登录凭证(code),传送到后端与微信服务器交换access_token、openid和session_key,然后调用统一下单API获取支付参数,使用`wx.requestPayment()`发起支付请求,用户确认后完成支付。
2. 小程序端wx.login()得到登录码code,携带code请求接口换取session_key与openid;再通过接口获取token
24. 前端XSS以及CSRF攻击
1. ‌XSS(跨站脚本攻击): ‌攻击者将恶意脚本注入到受信任的网站中,当其他用户访问该网站时,脚本会在其浏览器中执行
2. CSRF(跨站请求伪造):攻击者诱使用户在已登录的Web应用中执行非本意的操作,利用用户对网站的认证状态,通过伪造请求来执行非授权操作
3. SQL注入:攻击者通过在Web应用的输入参数中注入恶意的SQL代码,从而执行未经授权的数据库操作
4. XSS和CSRF虽然都是Web安全威胁,但本质上是两种完全不同的攻击方式。XSS关注的是‌**客户端脚本执行**‌,而CSRF关注的是‌**服务器端请求伪造**‌。
解决方法如下:
    a. 实行CSRF令牌机制, 服务器为每个会话生成唯一随机令牌,嵌入表单隐藏字段,添加到请求头,服务器验证令牌的重要性
    b. 金额等字段只能输入数字或者特定格式,限制输入的长度
24. 浏览器输入URL到看到页面的过程
1. 浏览器解析URL,首先查找本地hosts文件,看是否有对应的域名。如果有,浏览器就会直接向该IP地址发送请求。如果没有,浏览器会将域名发送给DNS服务器进行解析,将域名转换成对应的服务器IP地址。
2. 建立TCP连接
3. 发送HTTP请求
4. 服务器响应,返回请求的资源
5. 根据返回的HTML,解析及DOM树构建
6. 处理css,并构建CSSOM数
7. 布局以及绘制树
25. 跨域与解决跨域的方法
区分是否跨域的三要素:协议,域名,端口号
1. JSONP: 利用`标签不受同源策略限制的特性,通过动态插入`标签来请求不同源的数据。JSONP只支持GET请求,并且需要在服务器端进行相应的配合。
2. CORS(跨域资源共享):CORS 是一种 W3C 规范,它定义了一种浏览器和服务器交互的方式来确定是否允许跨源请求。通过服务器端设置相应的HTTP头部信息,如`Access-Control-Allow-Origin`,来允许跨域请求
3. 代理服务器:通过搭建一个代理服务器来转发请求,使得前端可以通过代理服务器来间接访问不同源的资源。
4. 使用window.postMessagewindow.postMessageHTML5引入的一个新的API,允许来自不同源的脚本进行通信。
26. 浏览器HTTP请求缓存机制
1. 强制缓存**:在有效时间内,不会向服务器发送请求,直接从缓存中读取资源。控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Control的优先级高于Expires。
2. 协商缓存**:当强制缓存失效后,浏览器会携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存。控制协商缓存的字段有Last-Modified/If-Modified-Since和Etag/If-None-Match
27. # Pinia与Vuex对比
1. pinia对ts的支持性更好
2. pinia支持按需加载、轻量级、支持tree-shaking
3. vue3推荐的官方方案,可设置强缓存
28. 进程与线程
1. 进程是系统分配资源的基本单位,线程是进程的一个实体
2. 一个进程里面包含多个线程
3. 进程是独立运行的,拥有独立的系统资源;同一个进程下的线程资源共享,线程之间没有独立的地址空间,一个线程死掉就等于整个进程死掉

29. Map结构

Map结构是ES6引入的一种键值对集合数据结构

  1. Map以键值对形式存储数据,类似对象但键类型不受限,支持任意数据类型(包括对象、函数等)作为键
  2. 严格按插入顺序迭代键值对,而传统对象在ES6前无法保证顺序
  3. 在频繁增删操作的场景下,性能优于普通对象
  4. 通过size属性快速获取元素数量,无需手动计算

以下是基于Map结构实现的URL缓存系统,支持set/get操作并自动维护访问顺序: 当缓存达到maxSize限制时,自动移除最久未访问的URL条目;每次get操作会将对应URL移动到Map尾部,保持最近访问的数据在尾部;可缓存任意类型数据,包括HTML字符串、JSON对象等 set/get操作均为O(1)时间复杂度

class URLCache {
  constructor(maxSize = 100) {
    this.cache = new Map();
    this.maxSize = maxSize;
  }

  set(url, data) {
    // 存在则先删除再重新插入
    if (this.cache.has(url)) {
      this.cache.delete(url);
    } 
    // 超过容量时移除最久未使用的
    else if (this.cache.size >= this.maxSize) {
      const oldestKey = this.cache.keys().next().value;
      this.cache.delete(oldestKey);
    }
    this.cache.set(url, {
      data: data,
      timestamp: Date.now()
    });
  }

  get(url) {
    if (!this.cache.has(url)) return null;
    
    const entry = this.cache.get(url);
    // 更新访问时间
    this.cache.delete(url);
    this.cache.set(url, {
      data: entry.data,
      timestamp: Date.now()
    });
    return entry.data;
  }

  clear() {
    this.cache.clear();
  }

  get size() {
    return this.cache.size;
  }
}

30. 防抖和节流
1.防抖:单位时间内多次触发事件时,仅执行最后一次触发的事件。若在延迟时间内再次触发,则重新计时。典型应用场景包括搜索框输入联想、窗口大小调整等高频操作36。
2.节流:单位时间内仅执行一次事件,无论触发频率多高。适用于滚动加载、表单重复提交等需限制执行频率的场景
3. 两种函数的封装都都应用了闭包
  let timer = null;
  return function(...args) {
    const context = this;
    if (timer) clearTimeout(timer);
    if (immediate && !timer) {
      fn.apply(context, args);
    }
    timer = setTimeout(() => {
      if (!immediate) fn.apply(context, args);
      timer = null;
    }, delay);
  };
}

function throttle(fn, delay) {
  let lastTime = 0;
  let timer = null;
  return function(...args) {
    const now = Date.now();
    const remaining = delay - (now - lastTime);
    if (remaining <= 0) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      fn.apply(this, args);
      lastTime = now;
    } else if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        lastTime = Date.now();
        timer = null;
      }, remaining);
    }
  };
}

31. js的数据类型
1. 基本数据类型:stringnumberbooleannull,undefine,Symbol‌,bigint
2. 引用数据类型:数组,对象,函数,Map以及Set结构
3. 基本数据类型是存储于栈内存中的值,引用数据类型是存储于堆内存中的引用地址
32. vue3响应式数据原理
1. 创建代理对象
    a. 当使用 `reactive()` 函数包装一个普通对象时,Vue 会创建一个 Proxy 代理对象
    b. 这个代理对象拦截所有对原始对象的访问和修改操作
2. 拦截读取操作(get)
    a. 当访问对象的属性时,Proxy 的 get 拦截器会被触发
    b. Vue 在这个阶段执行依赖收集,记录当前正在运行的代码(称为"副作用函数")与该属性的关系
    c. 如果访问的值是对象,Vue 会递归地将其转换为响应式对象
3. 拦截写入操作(set)
    a. 当修改对象的属性时,Proxy 的 set 拦截器会被触发
    b. Vue 检查新值是否与旧值不同
    c. 如果值发生变化,Vue 会触发更新,通知所有依赖该属性的代码重新执行
 4. 响应式的核心作用:
    精确追踪:只有实际被模板/计算属性使用的属性才会建立依赖
    按需转换:避免无谓的深度遍历,只有访问到的属性才会递归转换
    性能优化:不需要像 Vue 2 那样递归遍历整个对象初始化
 5. 依赖收集系统三级数据结构:
     `TargetMap`WeakMap):原始对象 → DepsMap
     `DepsMap`Map):属性名 → Dep Set
     `Dep`Set):存储依赖该属性的副作用函数
 6. 响应式的核心就是effect副作用系统:
     Vue 的响应式核心是副作用管理系统。在Vue中,每个组件的渲染过程就是一个主要的副作用,它会修改DOM,改变用户看到的界面。想象一下,当响应式数据变化时,我们需要知道哪些代码需要重新运行,然后自动触发这些代码运行,还要避免不必要的重复运行。
33. vue3生命周期

一、创建阶段(Creation)

  1. setup()
    Composition API的入口点,替代Vue2的beforeCreatecreated,在组件实例初始化前执行,可访问props但无法使用this412。
  2. onBeforeMount
    组件挂载到DOM前触发,此时模板已编译但未渲染37。
  3. onMounted 组件挂载完成后调用,可安全操作DOM或初始化第三方库316。

二、更新阶段(Updating)

  1. onBeforeUpdate 响应式数据变化后、虚拟DOM重新渲染前执行,适合获取更新前的DOM状态312。
  2. onUpdated 数据变更导致的DOM更新完成后触发,需避免在此修改数据以防无限循环315。

三、卸载阶段(Unmounting)

  1. onBeforeUnmount 组件销毁前调用,用于清理定时器、取消事件监听等资源释放操作312。
  2. onUnmounted 组件完全销毁后触发,所有子实例和事件监听器已被移除315。

四、错误捕获阶段(Error Handling)

  • onErrorCaptured 捕获子孙组件的错误,可返回false阻止错误继续向上传播318。
34. vue3虚拟DOM
1. vue3虚拟DOM用于描述虚拟DOM的存在,使用createVNode()方法创建vNode节点,每个节点包含三个属性,分别为type, props, children
2. 触发虚拟DOM更新的场景有: props传值的改变,界面的强制更新方法的forceUpdate()调用,响应式数据发生改变
3. 虚拟DOM通过diff算法比对进行差异性渲染,能计算出更新的最小节点集合,避免整棵树重渲染,仅更新实际变化的节点。以下是diff算法的一些优点:
     a.‌双端比较:同时从新旧子节点数组的首尾两端进行比对,减少遍历次数。
     b.Key优化:通过`key`标识节点唯一性,复用相同节点避免重复渲染。
     c.静态提升:将静态节点提取到渲染函数外部,避免重复创建
 4. vue3采取批量异步更新策略,同一事件循环内的多次数据变更合并为一次更新,‌执行顺序遵守微任务调度规则。微任务的优先级大于宏任务
 5. 通过patch()将虚拟DOM映射为真实DOM,映射操作包括:节点替换,属性的更新以及子节点递归处理
 6. 虚拟DOM的优点:
     a. 减少重排/重绘:批量更新最小化浏览器渲染开销216b. 跨平台兼容:虚拟DOM可应用于服务端渲染或原生应用(如React Native10c. 开发效率:开发者无需手动优化DOM操作,专注于数据逻辑1014
35.