202208面试问题

191 阅读14分钟

参考文档

基础

浮点数精度

  • ECMAScript 采用的就是双精确度 64 位来储存一个浮点数
  • 0.1 转成二进制时是一个无限循环的数,在存储的时候就已经发生精度丢失了
  • 当我们用浮点数进行运算的时候,使用的其实是精度丢失后的数。
  • 向上进位,所以0.1+0.2>0.3

解决方案:

  • 转成整数后运算
    • 32.41 * 100 = 3240.9999999999995
  • 第三方库 bigNumber

Q:bigInt

面向对象编程、函数式编程

面向对象编程(OOP)通过封装变化使得代码更易理解。
函数式编程(FP)通过最小化变化使得代码更易理解。

设计模式

单例

限制一个类只能有一个实例化对象

优点:

  • 适用于单一对象,只生成一个对象实例,避免频繁创建和销毁实例,减少内存占用
  • 共享数据

缺点:

  • 不适用动态扩展对象

应用场景:

  • vuex
  • 登入弹窗
class Singleton {
  _instance = null;

  static getInstance() {
    return _instance || (this._instance = new Singleton());
  }
}

export default Singleton.getInstance()

发布订阅

一种对象间一对多的依赖关系
当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知

  • 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel)
  • 当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时
  • 由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

优点

  • 订阅者和发布者互不干扰,可以在调度中心实现类似权限控制之类的操作

案例:

  • websocket
  • eventBus
  • vue响应式原理
export default class Dep {
  constructor() {
    this.subs = [];
  }
  // 添加订阅者
  addSub(sub) {
    this.subs.push(sub);
  }
  // 通知订阅
  notify() {
    this.subs.forEach((watcher) => watcher.update());
  }
}

观察者

一种对象间一对多的依赖关系
当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知

观察者直接订阅主题,而当主题被激活的时候,会触发观察者里的事件

案例:

  • dom事件监听
    • dom.onclick
    • dom.addEventListener
  • promise

代理

为目标对象创造了一个代理对象,以控制对目标对象的访问

优点:

  • 保护目标对象
  • 扩展目标对象的功能

缺点:增加了系统的复杂度

案例:

  • v2 Object.defineProperty
  • v3 proxy

ES6

let const var 区别

  • 不存在变量提升
  • 不属于顶层 window
  • 不允许重复声明
    • const 是只读的常量
    • 一旦声明,常量的值就不能改变
  • 暂时性死区
  • 块级作用域
    • 为什么?
      • 内层变量可能会覆盖外层变量
      • 用来计数的循环变量泄露为全局变量
    • 避免在块级作用域内声明函数
      • 块级作用域中不要使用函数声明 function a(){}
      • 而是使用函数表达式 let a = () => {}

箭头函数

  • 没有自己的this对象
    • 不能用call()apply()bind()这些方法去改变this的指向
    • this 定义时所在的对象,而不是使用时所在的对象
  • 不可以使用arguments对象
  • 不可以当作构造函数
    • 不可以对箭头函数使用new命令
  • 不可以使用yield命令
    • 不能用作 Generator 函数

promise

Promise 是异步编程的一种解决方案,主要为了解决回调地狱

  • Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
  • 一旦状态改变,就不会再变
  • then 是 Promise 实例添加状态改变时的回调函数,异步执行

Promise.all()

  • 接受一个数组作为参数,返回一个新的Promise
  • 如果不是数组中的值不是Primise实例,会调用 Primse.resove() 转换
  • 状态都变成fulfilled,返回值组成一个数组,传递给的回调函数
  • 之中有一个被rejected,第一个被reject的实例的返回值,会传递给的回调函数

Promise.any()

  • 一个变成fulfilled状态 / 全部变成rejected状态

Promise.race()

  • 一组集合中最先改变状态 fulfilled / rejected 的Promise,返回一个新的Promise

Promise.allSettled()

  • 等待全部完成,不管每一个操作是成功还是失败
  • results的每个成员是一个对象
// 异步操作成功时
{status: 'fulfilled', value: value}

// 异步操作失败时
{status: 'rejected', reason: reason}

使用场景

  • axios fetch
  • new Image

async await

Generator 函数的语法糖

  • 更好的语义化,使得异步操作变得更加方便
  • 返回值是 Promise
  • 内部可以使⽤ await
    • await命令后面是一个 Promise 对象,返回该对象的结果;
    • 强制其他代码等待,直到Promise完成并返回结果
  • 可以使用 .catch 或者 try catch 捕获错误
  • try catch 中多个 async,其中一个报错后续 async 还会执行吗
    • 会执行

Map 和 Object 区别

map object 都是键值对的集合(Hash 结构)
map 是一种更完善的 Hash 结构实现

object

  • 键值 key 只能是 string number symbol
    • 数字 / 纯数字字符串时,会根据数字大小升序排序
    • 原型链
  • 无序
    • for in
  • 不能直接使用 for of
    • Object.keys()
    • Object.values()
    • Object.entries()

map

  • key 的范围不限于字符串,各种类型的值(包括对象)都可以当作键
    • 接受一个数组作为参数
  • 遍历顺序就是插入顺序
  • 具有 Iterator 接口
    • Map.prototype.keys()
    • Map.prototype.values()
    • Map.prototype.entries()
    • Map.prototype.forEach()

WeakMap

  • 只接受对象作为键名null除外),不接受其他类型的值作为键名
  • key 弱引用
    • 弱引用的只是键名,而不是键值
  • 不能遍历
    • 没有 size 属性

Array Set Map 区别

  • 都具有 iterable 接口

array

  • 一种类列表对象
  • 参数
    • new Array('a', 'b')
    • arrayLength
      • 返回一个 length 的值等于 arrayLength 的数组对象
      • 值为 undefined 的元素
      • Array.of() 将一组值,转换为数组

set

  • 类似于数组,但是成员的值都是唯一的,没有重复的值。
  • 参数数组,具有 iterable 接口
  • 精确相等(===)判断(NaN等于自身)

map

  • 键值对的集合(Hash 结构)

vue

组件通信

  1. prop
  2. v-model .sync
  3. ref
  4. $parent $children $root
  5. $attr $listeners
  6. provide inject
  7. eventBus vuex

data为什么是一个函数

  • data是一个函数时,重复创建实例的时候,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响,避免共享同一数据造成的数据污染

computer watcher 区别

computer

  • 根据所依赖的数据动态显示新的计算结果,返回一个响应式的值,具备缓存性,如果依赖项不变时不会重新计算
  • 简化行内模板中的复杂表达式
  • 修改 data 中的值可以使用 getter / setter

watcher

  • 对data的数据监听回调, 当依赖的data的数据变化时, 会执行回调

区别

相同点:都是观察页面数据变化。

不同点:

  • computed只有当依赖的数据变化时才会计算, 当数据没有变化时, 它会读取缓存数据
  • watch每次都需要执行函数。watch更适用于数据变化时的异步操作

MVVM

  • MVVM是一种软件架构模式 Model-View-ViewModel 的简写
  • ViewModel 将视图 UI 和业务逻辑分开
  • MVVM采用双向数据绑定
  • ViewModel能够观察到数据的变化,并对视图对应的内容进行更新
  • ViewModel能够监听到视图的变化,并能够通知数据发生变化

优点:

  • 分离视图View和模型Model , 降低代码耦合
  • ⾃动更新 dom,让开发者不用繁琐的操作 dom

vue2/3 响应式原理

  • 在 newVue() 后, Vue 会调用 _init 函数进行初始化,也就是init 过程,在 这个过程Data通过Observer转换成了getter/setter的形式,来对数据追踪变化,当被设置的对象被读取的时候会执行 getter 函数,而在当被赋值的时候会执行 setter函数。

  • 当render function 执行的时候,因为会读取所需对象的值,所以会触发getter函数从而将Watcher添加到依赖中进行依赖收集。

  • 在修改对象的值的时候,会触发对应的 setter, setter通知之前依赖收集得到的 Dep 中的每一个 Watcher,告诉它们自己的值改变了,需要重新渲染视图。这时候这些 Watcher就会开始调用 update 来更新视图。

  • 为什么要重写数组的方法?
    • 通过索引、Array.prototype 上的方法操作数组,不能触发 setter
    • 没办法监听数组长度的变化
  • vue3 为什么使用 proxy
    • object.definedPrototype 只能对属性监听,需要递归遍历;proxy是对整个对象进行监听
    • proxy 可以监听新增
    • 可以监听数组的长度变化
    • 支持对 set map 的监听
  • set/set / delete

v-model 语法糖

  • 将 data 绑定到 input value 上,监听 @input 方法,当事件触发时,改变 data
  • 一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件
    • this.$emit('input', newData);
  • 需要对一个 prop 进行 双向绑定
    • this.$emit('update:title', newTitle)

keep-alive

  • 不同组件间切换时,要求保持组件的状态,以避免重复渲染组件造成的性能损耗
  • 动态组件 / router-view 结合起来使用
  • include exclude 允许组件有条件地缓存
  • activated deactivated 生命周期钩子
<keep-alive>
    <component v-bind:is="currentTabComponent" />
</keep-alive>

vuex

为 Vue.js 设计的状态管理库,它采用集中式存储管理应用的所有组件的状态
一个全局单例模式管理,多个组件共享状态

  • State 单一状态树包含了全部的应用层级状态
  • Getter store 的计算属性
  • Mutation 改变 store 的唯一方法
  • Action
    • Action 提交的是 mutation,而不是直接变更状态
    • 异步操作
  • Module
    • 将 store 分割成模块

自定义指令

  • 复制
    • 文字 textarea.select() + document.execCommand('Copy')
    • 图片
      // img 资源转成 blod
      const data = await fetch(url || target.src);
      const blob = await data.blob();
      // 复制
      await navigator.clipboard.write([
        new window.ClipboardItem({
          [blob.type]: blob,
        }),
      ]);
    
  • 节流防抖
    • inserted 监听键盘事件执行函数,unbind 移除监听
  • v-permission 权限
    • inserted 遍历检查
  • v-longpress 长按
    • mousedown mouseout
    • touchstart touchend

生命周期

挂载阶段

  • beforeCreate 数据和方法都不能被访问
  • 初始化data和props,并且给数据绑定上对象劫持
  • create
    • 可以访问数据
    • 无法与Dom进行交互,可以使用 $nextTick
  • 编译阶段,创建出虚拟DOM
  • beforeMount
    • 可以更改数据,不会造成重渲染
    • 不能拿到 el
  • 挂载,生成html显示到页面
  • mounted
    • Dom挂载完毕,可以访问Dom

更新阶段

  • beforeUpdate
    • 可以获取到最新的数据,但是不会更新页面的数据
  • 更新中,将最新的数据重新渲染更新DOM,并且执行compile 和打补丁
  • updated
    • 当前阶段组件Dom已完成更新
    • 避免在此期间更改数据

销毁阶段

  • 在触发 $destroy( ) 函数后,就会触发销毁阶段
  • beforeDestroy
    • vm 实例可以被使用
  • destroyed

vue3 优点 / 区别

  • Tree shaking 体积更小,按需编译体积vue2要更小
  • 更好的支持ts
  • Composition API
    • 自定义 hook
    • 可以将功能对应的数据和业务逻辑抽离出来
    • 能够更好的组织逻辑,封装逻辑,复用逻辑

other

  • 作用域插槽使用场景
    • 访问子组件域内的数据
  • 对组件的理解
  • 对单页面的理解
  • uni-app 和 vue 的区别,哪些指令不能使用
  • 指令如何检查图片是否可看

vur-router

导航守卫

  • 全局
    • 前置 beforeEach
    • 解析 beforeResolve
    • 后置 afterEach
  • 路由独享
    • beforeEnter
  • 组件内
    • beforeRouteEnter
    • beforeRouteUpdate
    • beforeRouteLeave

完整的导航解析流程

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

历史模式

hash

  • 使用window.location.hash属性及窗口的onhashchange事件,可以实现监听浏览器地址hash值变化,执行相应的js切换网页
  • url #后面的字符(第一个#)也称作锚点
  • 不会随请求发送到服务器端,所以改变hash,不会重新加载页面
  • 监听 window 的 hashchange 事件
  • location.hash 值的变化会直接反应到浏览器地址栏

history

  • 利用history API实现url地址改变,网页内容改变
  • History 对象,它表示当前窗口的浏览历史
  • 方法:
    • forward() 前进
    • back() 后退
    • go() 跳转
    • pushState()
      • 在历史中添加一条记录
      • 不会触发页面刷新,只是导致 History 对象发生变化,地址栏会有变化
    • replaceState()
      • 修改 History 对象的当前记录
      • 不会触发页面刷新
  • popstate 事件
    • 每当 history 对象出现变化时,就会触发 popstate 事件
    • pushState() replaceState() 不会触发
  • 404 错误
    • 用户在浏览器中直接访问(刷新)时,返回404的错误
    • 开发环境 webpack devServer.historyApiFallback 设置为 true
    • 服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,回到 index.html页面

游览器

hash 是什么

  • Hash也称散列、哈希
  • 把任意长度的输入,通过散列hash算法变成固定长度的输出
  • md5 sha

特性

  • 不可逆
  • 运算速度快

应用场景

  • 信息加密
  • 数据校验
    • git commit id
  • 文件完整性校验

进程和线程

  • 进程是cpu资源分配的最小单位(是能拥有资源和独立运行最小单位
  • 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位一个进程中可以有多个线程

游览器包含哪些进程

  • Browser进程
    • 只有一个
  • 第三方插件进程
  • GPU进程
    • 最多一个,用于3D绘制等
  • 浏览器渲染进程
    • 默认每个Tab页面一个进程,互不影响

渲染进程

  • GUI渲染线程
  • JS引擎线程
  • 事件触发线程
  • 定时触发器线程
  • 异步http请求线程

GUI渲染线程与JS引擎线程互斥

  • 由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面,那么渲染线程前后获得的元素数据就可能不一致了
  • 因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行

游览器页面进入过程

  • 首先在浏览器中输入URL
  • 查找缓存
  • DNS域名解析
    • 域名对应的IP地址
  • 建立TCP连接
  • 发起HTTP请求
  • 服务器响应请求并返回结果
  • 关闭TCP连接
  • 浏览器渲染
    1. 解析html建立dom tree
    2. 解析css构建CSS Rule Tree
    3. 合并 dom css 构造 Rendering Tree
    4. 布局 Rendering Tree
    5. 绘制

http https 区别

http

  • 超文本传输协议,数据明文传输

https

  • 具有安全性的SSL加密传输协议
  • 是以安全为目标的 HTTP 通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性

get post 区别

get

  • 参数拼加到 URL 上进行参数传递的
    • URL 的长度是有限制的
    • 参数会保存在历史记录
  • 一般会被缓存
    • CSS、JS、HTML 请求等都会被缓存
  • 可以直接进行回退和刷新,不会对用户和程序产生任何影响

post

  • 参数写入到请求正文body中传递的
    • 没有大小限制
    • 不会保留到历史记录中
  • 默认是不会缓存
  • 直接回滚和刷新将会把数据再次提交

请求跨域问题

  • 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的。
  • 同源策略,是浏览器对 JavaScript 实施的安全限制,只要协议、域名、端口有任何一个不同,都被当作是不同的域。

解决方案

  • JSONP
  • CORS 跨域资源共享
    • 服务端设置Access-Control-Allow-Origin响应头
  • 服务端代理
  • window.postMessage

内存泄露

未能释放已经不再使用的内存

生命周期

  1. 分配期(自动分配
  2. 使用期(读写内存)
  3. 释放期(垃圾回收)

场景

  1. 全局变量、定时器、事件监听器 window.addEventListener
  2. 闭包
  3. Set valuemap key (WeakSet、WeakMap可以解决)
  4. 订阅发布事件监听器 on
  5. 脱离 DOM 的引用 已经被销毁的 dom 被其他地方引用

Cookie、sessionStorage、localStorage

区别:

  • 生成
    • cookie 服务器 / 客户端生成;会携带 HTTP 头中
    • sessionStorage / localStorage 客户端生产
  • 数据有效期
    • cookie 可以设置失效时间,会存储在硬盘中;没有设置时间,页面关闭消失
    • sessionStorage 页面关闭消失(刷新不会失效)
    • localStorage 一直存在,除非手动清除
  • 存储大小
    • cookie 一般为 4k
    • sessionStorage / localStorage 一般为 5M
  • 作用域区别
    • sessionStorage 不同窗口不能共享,即使是同一个页面
    • cookie localStorage 同源窗口中共享

跨页面通信

同源

  • localStorage / cookie
  • indexedDB
  • window.open + window.opener.postMessage
  • Service Worker
    • 长期运行在后台的 Worker,能够实现与页面的双向通信
    • 可以多页面共享
  • Shared Worker
    • 普通的 Worker 之间是独立运行、数据互不相通;
    • 而多个 Tab 注册的 Shared Worker 则可以实现数据共享

非同源

  • iframe + postmessage

webpack

是什么

js 应用程序的静态模块打包工具

loader plugin 区别

loader

  • 用于对模块的源代码进行转换成游览器能够识别的文件。
  • less / sass
  • babel / ts / vue

plugin

  • plugin是用来加强webpack功能的
  • 在整个编译周期都起作用
  • 打包优化和压缩,重新定义环境变量
  • html-webpack-plugin 拷贝压缩

做了什么

  • PostCSS 进行一些CSS的转换和适配
  • copy-webpack-plugin 复制静态目录
  • babel
    • babel.config.json 配置对 ts vue 的支持
  • eslint Prettier 统一代码风格
  • 配置别名、扩展名
  • 批量注册指令依赖 require.context()

优化

  • SplitChunksPlugin 分包
  • 动态导入 import()
  • 抽离 runtime 到一个单独的chunk中
  • 修改文件 hash
  • CDN
    • externals
    • html 中引入 cdn 地址
  • Terser
    • 压缩、丑化js,让bundle变得更小,生产默认开启
    • 开启多进程 parallel
  • 将 CSS ,压缩文件
    • 提取到单独的文件中
    • 压缩文件
    • Tree Shaking
      • PurgeCssPlugin
      • 删除未使用的 CSS
  • HTML 生产默认会压缩,开启 cache
  • HTTP 压缩 compression-webpack-plugin 改进传输速度和带宽利用率

项目

  • 微前端
  • Uniapp
  • oss 上传 / 断点续传
  • 字符长度(canvas)
    • 标准盒子模型 clientWidth
    • context.measureText(text).width
  • 埋点
    • 自己如何做

性能优化

  • 减少 http 请求
  • 图片可以使用 iconfont
  • 静态资源使用 CDN
  • 压缩、缓存文件
  • webpack 按需加载代码
  • 减少重绘重排
  • 事件委托
    • 捕获 目标 冒泡
  • requestAnimationFrame
  • 降低 CSS 选择器的复杂性

自动化部署

  • 配置.gitlab-ci.yml
  • 阶段
    • 可以设置缓存目录
  • install 安装依赖
  • build 打包项目
  • deploy 部署项目

搭建私库

  • 修改 package.json
    • name 包名
    • version 版本 description 描述 keyword 关键词用于搜索
    • main 入口文件,最终编译后的包文件
  • "lib": "vue-cli-service build --target lib --name packageName --dest lib packages/index.js"
    • --target 启用 lib 打包
    • --name 设置包名
    • --dest 输出目录
    • [entry] 打包的入口文件
  • npm publish 推送到 npm
module.exports = {
  mode: "production",
  entry: "./index.js",
  output: {
    path: path.resolve(__dirname, "./build"),
    filename: "coderwhy_utils.js",
    // AMD/CommonJS/浏览器
    libraryTarget: "umd",
    // window.coderwhyUtils
    library: "coderwhyUtils",
    // root 的值
    globalObject: "this"
  }
}

微信授权流程

  • 检查本地 token 是否过期
  • 过期,拼接微信授权跳转地址
    • appid 必须放在第一位
    • redirect_uri 跳转地址
      • local ip 不能作为参数,跳转到开发环境的页面再跳转回来
    • response_type=code
    • scope 静默 / 弹窗授权
  • 如果用户拒绝授权
    document.addEventListener('click', e => { 
      location.replace(authUrl) 
    })
    document.body.style.pointerEvents = 'none'
    
  • 微信授权返回页面,获取到url code,调用后端接口获取 token 存储在本地

微信JSSDK

  • 检查 windown.wx 是否存在,不存在创建<script>引入jssdk文件
  • 代理 wx api,使用时调用 await config()
  • 使用 href == config.lastHref
    • iphone 7p hash 变化也要重新 config
  • 如果没有 config ,则发送请求获取 config data
  • 调用 wx.config() jsApiList 成功后调用 api
  • 失败就再次调用 config()

crm 免密登入,静默登入

  • 登入后返回 token 和 refreshToken 存入 localstroage
  • 在接口请求拦截 header 添加 token
  • 过期后使用 refreshToken 去获取新的 token (新生产一个 axios)
  • 成功后重新发起接口请求,失败就跳回登入页面