参考文档
基础
浮点数精度
- 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.onclickdom.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
组件通信
- prop
- v-model .sync
- ref
$parent $children $root$attr $listeners- provide inject
- 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 的监听
- 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 mouseouttouchstart 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
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave守卫。 - 调用全局的
beforeEach守卫。 - 在重用的组件里调用
beforeRouteUpdate。 - 在路由配置里调用
beforeEnter。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter。 - 调用全局的
beforeResolve。 - 导航被确认。
- 调用全局的
afterEach钩子。 - 触发 DOM 更新。
- 调用
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连接
- 浏览器渲染
- 解析html建立dom tree
- 解析css构建CSS Rule Tree
- 合并 dom css 构造 Rendering Tree
- 布局 Rendering Tree
- 绘制
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
内存泄露
未能释放已经不再使用的内存
生命周期
- 分配期(自动分配)
- 使用期(读写内存)
- 释放期(垃圾回收)
场景
- 全局变量、定时器、事件监听器
window.addEventListener - 闭包
Set value、map key(WeakSet、WeakMap可以解决)- 订阅发布事件监听器
on - 脱离 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)
- 成功后重新发起接口请求,失败就跳回登入页面