前端常见面试题大全

前端本地存储的方式有哪些?

方式存储大小过期时间备注
localStorage5M永久存储,除非手动清除
sessionStorage5M会话级别(关闭浏览器就销毁; 可以设置过期时间)
Cookie4k默认是会话级别关闭浏览器就销毁; 可以设置过期时间请求自动携带; 原生操作极其麻烦(js-cookie)
Web SQL 已废弃
IndexedDB几百M(应用场景极少) 可以基于键值对可以存储大量的数据

JS 的参数是以什么方式进行传递的?

  • 原始数据类型: string number boolean null undefined symbol

const a = 1 存的就是值本身, 简单类型的数据在传递参数时 传递就是值, 将来修改时不会互相影响的!!

  • 引用数据类型: array object function

const a = { name: 'zs' } 存的是地址/引用, 复杂类型的数据在传递参数时 传递的是引用地址, 将来修改时会互相影响的!!

js中的垃圾回收?

前置理解: 将来浏览器对于不会再次使用的内存空间 是需要控制回收的,
核心问题: 这个内存到底是不是垃圾??? => 引用计数法 标记清除法

  1. 引用计数(ie): 是有问题的!!!

浏览器在分配内存时, 当发现这块内存有一个引用指向他, 就会记录下来这块内存的引用数, 只有当引用数为0时, 才会被确认为垃圾!

无法解决 循环引用 的问题!!! 见图, 引用计数的角度来说, 这块内存确实还有人用, 不认定为垃圾, 不能回收(本应该被回收)

  1. 标记清除(主流浏览器 google firefox):

从根部(全局)出发, 如果找不到这个内存, 就会被标记为"无法到达的对象", 就被认定为垃圾!!!

可以解决 循环引用 的问题!!!

作用域链?

共3种; 全局作用域 局部作用域 块级作用域

作用域存在嵌套的情况, 变量访问规则(就近访问): 从最近的作用域开始查找, 找到了直接用, 没找到往外层找,.. 一直找到全局,
变量访问的链式结构 称为 作用域链

什么是闭包?

函数和声明该函数的词法环境的组合

通俗一点: 内层函数访问外层函数的变量

闭包的优势: 缓存数据, 私有化数据
闭包的劣势: 如果不好好处理, 内存泄露(应该释放的内存没被释放)

总结: 因为内层函数访问了外层函数的变量, 如果内层函数被return出去, 将来这个内层函数会被缓存, 同时这个函数中用到的变量也会被缓存, 从而实现数据缓存 数据私有化; 伴随着内存泄露(重置null即可)

原型 与 原型链

每一个构造函数 都有自己的原型对象, 这个原型对象上所有的属性和方法 都可以供实例访问
原型的一家: 构造函数 原型 实例 的关系

原型链: 每个实例都有自己的原型, 原型本身也是个对象, 他也有自己的原型, ... 一层层的链式结构 => 原型链
属性的查找规则: 就近查找( obj.hh 从自己本身开始查找, 如果找不到, 去找他的原型, ... )

为什么需要原型???
如果没有原型, 所有实例的方法都需要额外分配内存, 这样浪费内存,把公共的方法统一放在一个地方, 供所有实例使用 => 原型的意义

js的继承

原型继承 组合式继承 寄生组合式继承 3种

  1. 原型继承 => 换原型

    Student.prototype = new Person()

  2. 组合式继承 => 换原型 + 借调父类构造函数

    Student.prototype = new Person() 继承了父类的方法
    Student构造函数内部 Person.call(this, ...) 借父构造函数初始化自己实例的属性 继承了父类属性

  3. 寄生组合式继承 => 换原型 + 借调父类构造函数

    Student.prototype = new Person()
    Student构造函数内部 Person.call(this, ...)

    之前替换原型 需要通过new 父类构造函数得到实例(new做了很多事)
    Object.create(obj) 基于传入的对象, 得到一个新对象, 新对象的__proto__直接指向传入对象

    Student.prototype = Object.create(Person.prototype)
    这一步省略了new的过程, 提升了性能

  4. class Student extends Person {}

判断一个数据是否为数组? => 数组的方法

数组的一家

Array.isArray(数据) true 是数组; false 不是数组

Object.prototype.toString.call(数据) '[object Number]' '[object Object]' '[object Array]' ....

数组去重?

  1. new Set方法
[...new Set(arr)]
  1. 准备一个新的空数组, 遍历老数组, 每遍历到一项, 先判断这个数据是否在新数组中存在了, 不存在 push进新的空数组
const arr = [1, 2, 3, 3, 2, 1]
   const temp = []
   arr.forEach(item => {
     if (!temp.includes(item)) {
       temp.push(item)
     }
   })
   console.log(temp)

this指向问题?

  1. 默认绑定 fn() => window
  2. 隐式绑定 obj.fn() => 调用者
  3. 显式绑定 call apply bind => 第一个参数
  4. new绑定 => 创建的实例本身
  5. 箭头函数 不存在this => 外层作用域的this

Promise是什么? 构造函数 异步代码的容器

三个状态 等待中pending 失败rejected 成功fulfilled

new Promise((resolve, reject) => {
... 封装异步代码
结束之后需要手动修改状态
resolve() => 修改为成功状态 => .then
reject() => 修改为失败状态 => .catch
})

状态凝固 => 一旦状态变化了, 将不能再次改变状态

手写promise

// Promise.all([p1, p2, p3, p4]).then((values) => { ... })
Promise.myAll = function(arr) {
 let total = 0
 let temp = []

 return new Promise((resolve, reject) => {
   // resolve 必须传入的arr中每个都成功
   arr.forEach((item, index) => {
     item.then((val) => {
       total++
       // temp.push(val)
       temp[index] = val
   if(total === arr.length) {
     // 都成功了
     resolve(temp)
   }
 })
})

 })
}

// Promise.race([p1, p2, p3, p4]).then((value) => { ... })
Promise.myRace = function(arr) {
 return new Promise((resolve, reject) => {
   arr.forEach(item => {
     item.then((val) => {
       resolve(val)
     })
   })
 })
}

Promise.resolve(1) // 快速创建一个成功的promise
new Promise((resolve, reject) => {
 resolve(1)
})

Promise.reject(0) // 快速创建一个失败的promise
new Promise((resolve, reject) => {
 reject(0)
})

深拷贝 浅拷贝

  • 浅拷贝: 只拷贝一层
  • 浅拷贝指的是对象中对象A需要用到对象B的属性,那么可以将对象B的属性利用for in语句来进行遍历,将需要的属性赋值给对象A,这个过程叫做浅拷贝
{...obj}
  • 深拷贝: 拷贝多层

  • 深拷贝指的是对象中对象A需要用到对象B的属性和方法,那么可以利用递归函数封装函数并且自调用的特点,将对象B的属性利用for in语句来进行多次遍历,将需要的属性、方法赋值给对象A,这个过程叫做深拷贝

    递归
    
    JSON.parse(JSON.stringify(obj))
    

区别:

  • 浅拷贝只可以拷贝简单数据类型(堆内存),对于复杂数据类型只拷贝内存中的堆内存,而栈内存不会拷贝
  • 深拷贝不仅可以拷贝简单数据类型(堆内存),还可以拷贝复杂数据类型(栈内存)

http的请求方式

get 获取
post 添加
delete 删除
put 更新(重置式)
patch 更新(补丁式)

请求报文: 请求行 请求头 请求体; 响应报文: 响应行 响应头 响应体;

http常见状态码

  • 2xx

    200 成功
    201 新建

  • 3xx

    301/302 重定向
    304 协商缓存

  • 4xx

    400 接口传参错误
    401 权限认证失败
    404 找不到

  • 5xx 服务器错误

https是如何做到更加安全?

https比http更加安全 在进行数据传输时, 对数据进行加密处理, 所以更加安全

https加密方案:

  1. 非对称加密 + 对称加密 两者结合
    将来数据传输必须以 对称加密 为主!!! 但是容易一开始泄露对称加密的 密钥
    用 非对称加密 传输 对称加密的密钥
  • 数据真正传输 还是 对称加密 数据传输效率得到保证
  • 非对称加密 传输 密钥 数据安全性得到保证
  1. 数字证书: 加密签发公钥
    将来你访问一个网站, 你希望得到服务端的响应数据, 数据是被对称加密 加密出来的!! 必须拥有密钥
    密钥必须让服务端给你, 通过非对称加密给你,

初步互通消息时, 客户端发送请求, 得到数字证书, 基于数字证书中的公钥 解密出 对称加密的密钥,
就可以解密传输的数据, 正常通信了...

数字证书(权威的CA机构颁发): 包含网站基本信息, 到期时间, 非对称加密的公钥 ,

  1. 数字签名: 防证书被篡改
    数字证书:
    网站: xxxx.xxx.xxxx
    公钥: sdafasdfasdfasdfasdfsadfsadfgdsg
    到期时间: 2040年10月20号
    签发机构: xxx机构
    签名: xxdaddsafsadhgdfhfldghkdfghdfg ==>> 将网站所有信息通过hash加密成签名

http常见的加密方案?

  1. 对称加密: 加密解密同一个密钥(对称) 可逆的过程
  • 优点: 加密效率很高, 加密速度快, 计算量小
  • 缺点: 如果一开始密钥就泄露了, 安全性完全无法得到保障!!!
  1. 非对称加密: 有两把钥匙 公钥 私钥 (公钥加密的数据私钥解密 私钥加密的数据公钥解密)
  • 优点: 安全性得到一定的保障
  • 缺点: 加密解密效率低 慢, 计算量大

例如:我们想去gitee提交信息, 本地生产了两把公钥 私钥, 将公钥给gitee
我(私钥加密) ==>> gitee(公钥解密)
我(私钥解密) <<== gitee(公钥加密)

  1. hash加密: 不可逆
    128645 ===>>> asdfasdfasdafas

一般后端数据库存储密码 一定是不可逆的hash加密 如md5 sha256

撞库: 暴力 模拟各种各样的密码 加密 得到一本字典
记录下 123456 ===>>> asdfaasdfasdfasd
123456 ===>>> asdfaasdfasdfasd
......

http2.x优势?

  • http2.x 二进制传输数据(更高效) http1.x文本形式传输数据 计算机只认识 0 1
  • http2.x 头部压缩技术, 减少请求头中重复携带的数据(我用上一次的请求头中的数据), 降低网络负担
  • http2.x 服务器推送技术 可以主动给客户端响应数据, 提高页面加载效率
  • http2.x 多路复用机制, 一个tcp连接 可以承载任意双向数据流, 少创建很多tcp连接(三次握手 四次挥手)

要想发请求, 得先建立tcp连接, 浏览器对于单个域名有6-8连接限制, 一个tcp连接只能发一次请求

一次完整的http服务的过程?

在地址栏中输入 www.jd.com 具体发生了什么???

  1. dns解析: 先拿着你输入的域名, 去找真正的ip地址
  • 每个人的电脑上都有一个文件 hosts, 记录了一些域名和ip映射关系
  • 会优先去本机hosts文件中去获取ip, 如果没有
  • 去找公网dns域名服务器, 获取ip

www.jd.com => 58.242.151.131

  1. 根据ip地址找到对应的服务器, 需要建立tcp连接, 三次握手!!!
  2. 成功建立tcp连接后, 进行http请求
  3. 服务器响应http请求, 浏览器得到网站的首页 index.html
  4. 浏览器解析html页面时, 解析到script link img, 会再发请求 获取 js文件/css文件/图片资源
  5. 浏览器渲染完整的网页给用户
  6. http服务完成后, 关闭tcp连接, 四次挥手!!!

三次握手?? 四次挥手??

三次握手 四次挥手 ==>> 体现出连接与断开的谨慎

三次握手 => 想要让双方确认收发消息的能力!!!

  • 第一次握手 客户端往服务端发消息, 你好, 在么? 能听到么??
    服务器能确认: 服务器可以收消息的能力 客户端有发消息的能力
  • 第二次握手 服务端回消息给客户端 在的! 你能听到我说话么?
    客户端确认: 客户端有发消息的能力, 客户端能收消息; 服务端能发消息 服务端能收消息
  • 第三次握手: 客户端再回消息给服务端 我能听到
    服务端确认: 客户端能发消息 能收消息 服务端能发消息 能收消息

四次挥手 => 为了保证数据传输的完整性

  • 第一次挥手 客户端发消息给服务端 服务端 你这个数据传完了么?
  • 第二次挥手 服务端先回一个消息 你等我一会 我检查一下
  • 第三次挥手 服务端回消息 确实没有了, 都说完了, 你可以走了
  • 第四次挥手 客户端回消息 那我走了 再见!!!

缓存

大类: 数据库缓存 服务器缓存 浏览器缓存
浏览器缓存: http缓存 + Cookie/localStorage/sessionStorage/websql/IndexedDB

http缓存的必要性: 每次重复的资源 希望直接走缓存, 不想每次发请求 => 优化网页加载的效率

  • 强缓存
  • 协商缓存

强缓存: 类似于食品的过期时间
将来第一次请求服务器资源时, 服务器会正常响应给你一张图片, 同时会告诉你这张图片的有效期,
expires: Sun, 25 Jan 2032 09:31:06 GMT (到期时间-绝对时间), 将来处于有效期内 直接走强缓存,
从缓存中获取该图片, 不会再发请求

expires将来会和本机时间对比, 本机时间是可以改的!! 有漏洞

cache-control: max-age=315360000 相对时间 315360000s = 10年 从你拿到该资源后, 10年后必过期

cache-control为主 expires为辅

一旦强缓存失效(未命中强缓存), 这个时候 该资源不能再次使用!!, 必须要发请求问服务端要了
图片这种资源很少会更新, 不存在过期了不能用了!!!!

  1. 这次发请求 会带上之前过期的图片资源, 问服务端 哥们 还能用么?? 服务端一检查, 发现没更新,
    此时不需要响应新图片, 直接告诉客户端走缓存, 更新过期时间 304 => 协商缓存成功
  2. 将来如果服务器发现图片更新了, 会直接响应一张新图片(自带新的过期时间), => 协商缓存失败, 200

服务端如何判断图片资源是否更新?
最后修改时间(最小单位秒) last-modified: Tue, 10 Sep 2019 05:51:30 GMT
不一致, 更新了
一致, 没更新

1s内如果发生了多次更新, last-modified就有问题

资源的唯一校验码: ETag: xdsddasfsd
ETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化。
如果1s内进行了多次更新, ETag是会实时变化的

强缓存与协商缓存 配合使用 缓存页面资源

浏览器如何解析css选择器?

div h3 span { ... }

以为的: 从左往右, 先找所有的div 再找所有div后代中的所有h3 , 再找左右h3后代中的左右span
比如: 页面中有10000个元素, 有4000个div 找4000次; 你需要在这4000个div中找后代中有没有h3, h3 300次

遍历树形结构的所有子节点 查找 比如层级有40层, 子节点会特别多

实际上: 从右往左, 先找所有的span, 再找所有span中有祖辈是h3的, 再找祖辈有div的
比如: 页面中有10000个元素, 有4000个span 找4000次,

你只需要找祖辈中有没有满足条件的, 比如层级有40层 也就40次

  1. 浏览器是如何进行界面渲染的?
  • 解析html, 得到dom树
  • 解析css, 得到样式规则
  • 基于dom树 和 样式规则 得到渲染树
  • 基于渲染树, 进行结构布局(layout) 重排/回流
  • 基于渲染树, 进行绘制(paint) 重绘

重排重绘?

  • 结构的变化 会引起重排
  • 非结构的变化 会引起重绘

重排必将重绘, 重绘不一定重排!!!!

尽可能避免重排:

  • 将来如果真要改变盒子大小位置, transform只是视觉效果
  • 集中修改样式 (这样可以尽可能利用浏览器的优化机制, 一次重排重绘就完成渲染)
  • 尽量避免在遍历循环中, 进行元素 offsetTop 等样式值的获取操作, 会强制浏览器刷新队列, 进行渲染
  • 使用文档碎片(DocumentFragment)可以用于批量处理, 创建元素

git基本操作

git服务器 gitee github; 公司中用的多是 gitlab, 支持私有服务器

git常见操作

  • git init
  • git add . git commit m ''
  • git push origin xxx-zs
  • git merge
  • git clone xxx
  • git add remote origin ...
  • git checkout -b xxx

什么是MVVM? 是一种设计模式

  • M Model(数据层) ajax请求回来的数据
  • V View(视图层) 页面
  • VM ViewModel(视图数据) 既能操作数据 也能操作视图
    • 数据变了, 操作视图自动更新
    • 视图变了, 操作数据自动更新
  1. 双向数据绑定的原理?
  • 如何知道数据变了?
    Vue2 Object.defineProperty
    Vue3 Proxy
  • 如何知道视图变了?
    @change @input

Vue2 和 Vue3 中监视数据的区别

  • Vue2 Object.defineProperty 劫持数据
    针对于每个属性去劫持, 如果数据复杂, 需要递归劫持, 成本高, 效率低
    对于数组数据的劫持/监视, 有问题 ==>> $set
  • Vue3 Proxy
    proxy对于整个对象数据的劫持 对象内部的任意属性发生变化 都会经过外层的proxy, 无需递归, 效率高
    proxy对于数组数据的更新也没问题

Vue的响应式系统? => 观察者模式(一对多的设计模式)

响应式: 数据变化了, 会通知到所有用到该数据的视图自动更新

1669036416351.png

观察者模式 目的在于 需要通知对应的watcher进行响应; 依赖收集(找到数据的依赖者)

一上来vue会解析渲染, 会进行依赖收集(找到数据的watcher), 收集到一个大的数据中,
当数据变化时, Object.defineProperty监视到数据变化了, 就会通知到你刚刚收集的watcher们进行响应(派发更新)

watcher也有分类: 侦听器watcher > 计算属性watcher > 渲染watcher

举个例子:在一个拍卖会上,拍买一个珍品(页面的数据),A和B(A和B便是watcher)同时想要这件珍品(同时需要修改这个数据),那么他们会去关注这个珍品的价格变化(关注数据的变化),如果变化了,而且还在A和B的接受范围内,那么,他就会重新去叫价,从而更改这件珍品的起拍价(更改数据,实现响应式原理)

Vue的生命周期?

Vue2                       Vue3
beforeCreate               Setup
created                    Setup
beforeMount                onBeforeMount
mounted                    onMounted
beforeUpdate               onBeforeUpdate
updated                    onUpdated
beforeDestroy              onBeforeUnmount
destroyed                  onUnmounted

activated
deactivated

vue组件通信?

  1. 父传子 子传父
    父传子: 父组件给子组件标签上以添加属性的方式绑数据; 子组件内部通过props接收
<son title="123" :num="456" />

子传父: 子组件内部通过$emit触发自定义事件; 父组件中需要给子组件标签上注册对应的自定义事件, 提供处理函数

this.$emit('xx', 12)     <son @xx="fn" />   fn(val) { ... }
  1. 事件总线(eventBus) vue3移除了
    理解: 组件A要和组件B通信(两个组件没有任何关系), 有个中介, 借助于eventBus通信
const eventBus = new Vue() 
export default eventBus

组件A => 组件B

组件A发消息:

eventBus.$emit('ss', 123123)

组件B收消息:

eventBus.$on('ss', function(val) { ... })
  1. provide inject 用于某个组件共享数据/方法 给子孙后代组件使用
    vue2中有这个语法 但是不好用,
    vue3中增强了

  2. 提供数据

    provide('val', 123)
    provide('fn', () => { ... 某个数据更新的代码 })
    
  3. 注入数据使用

    const num = inject('val')
    const fn = inject('fn')    fn('123')
    
  4. $refs $children $parent => 可以拿到组件

<Hello ref='hello' />   // 内部通过data提供了一个 money(组件自己的)   this.money
this.$refs.hello.money   

this.$children[0] // 获取组件中 使用到的第0个组件

$parent  // 获取父组件
  1. $attrs $listeners 使用场景: 有很多数据要隔代传
$attrs 批量获取数据 向下传递
$listeners  批量向上传递自定义事件
  1. Vuex
  • state 提供状态
  • mutations 提供修改状态的方法(同步的)
  • actions 提供消化异步操作的方法 - 不能直接在action中修改状态(异步操作结束后提交mutation)
  • getters 类似计算属性
  • modules 分模块
    namespaced: true 命名空间
this.$store.commit('mutation名', 123) // 页面中触发mutation 同步派发任务
this.$store.dispatch('action名', 123) // 页面中触发action  异步派发任务
  1. pinia 官方推荐的vue3使用的状态管理工具

  2. 提供状态

    state() {
      return {
        count: 0,
        money: 100
      }
    }
    
  3. actions用于修改状态(同步异步都可以) 修改状态 获取状态 直接通过this

    actions: {
    fn() {
      this.money = 1000
    },
    fnAsync() {
      const res = await Api()
      this.count = res.data.data
    }
    }
    
  4. getters

  5. store/user.js

export default defineStore('user', {
  state() {
    return {
      username: 'zs',
      age: 19
    }
  },
  actions: {
    addAge() {
      this.age++
    }
  }
})

页面组件如何使用仓库数据

import useUserStore from '@/store/user.js'
const user = useUserStore()
{{user.username}}  @click='user.addAge'

key的作用?

添加一个唯一标识, 优化对比复用策略(默认下标), 提高渲染性能

对比真实结构? 虚拟dom
真实dom结构太复杂了, 属性特别多,
一个js对象模拟真实dom结构, 身上只有关键的几个属性

真实dom结构 树形的!! 虚拟dom结构模拟也还是树形! 遍历一层还是巨大的!!

diff算法:

  • 首先比根元素, 如果根元素不同, 直接销毁重建!!!
  • 如果根元素相同, 对比出其他差异(属性), 考虑往下继续复用
  • 如果是兄弟元素, 默认按照下标去比, 建议添加key属性, 优化对比策略, 提升渲染性能

路由跳转传参的方式

query传参(多) params传参(极少) params传参配合动态路由使用(多)

  • 在地址栏传递的 刷新不丢失
this.$router.push('/login?name=zs&age=19')
this.$router.push({
  path: '/login',
  query: {
    name: 'zs',
    age: 19
  }
})
this.$route.query.name
  • 刷新会丢失, 在内存中传递
{ path: '/test', component: Test, name: 'test' } // 路由规则中设置name
this.$router.push({
  name: 'test'
  params: {
    money: 100
  }
})
  • 地址栏中传递, 刷新不丢失
{ path: '/user/:id', component: User } // 必须配合动态路由使用
this.$router.push('/user/123')
this.$route.params.id

前端如何处理权限问题?

RBAC role based access control 基于角色的权限控制
给员工分配角色 给角色分配权限 (给员工直接分配权限的危害)

1.页面访问权
在你点击登录之后, 在路由跳转之前, 获取你的个人信息(包含roles: ['home', 'salary', 'social']),
将来基于这个字段通过 addRoutes 动态添加对应的路由规则(动态路由), 因为你有这个权限, 所有你又这个路由规则, 所以你能访问这个页面

2.按钮操作权
在你点击登录之后, 在路由跳转之前, 获取你的个人信息(包含btns: ['del', 'edit']), 将来在页面中可以封装一个方法,
得到某个用户到底有没有这个按钮权, 有3种形式: 1. v-if 2. 禁用 3. 给你看给你点 给你提示 你权限不够

3.api访问权(后端)

首屏渲染加载过慢导致白屏?

  • 组件懒加载 路由懒加载 异步组件
  • 图片资源等压缩
  • cdn加速(花钱)

鱼皮知识星球前端面试题

编程导航前端经典面试题合集亮白.pdf

Tree-Shaking-树摇原理

Tree-Shaking性能优化实践 - 原理篇


标题:前端常见面试题大全
作者:mcwu
地址:http://mcongblog.com/articles/2023/04/03/1680504943415.html

    评论
    0 评论
avatar

取消