# 前端面试宝典
**Repository Path**: gumapc/vue_extension
## Basic Information
- **Project Name**: 前端面试宝典
- **Description**: 前端常见面试题
涉及:css、javascript、http、vue、源码系列
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 17
- **Created**: 2023-12-23
- **Last Updated**: 2023-12-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 面试题
## 前言
**本文档,主要针对面试题八股文进行整理,大家有需要整理的面试题可以在留言板进行留言,我会尽量对留言的面试题进行整理**
**面试题中如有文字错误或解读错误欢迎指正!**
## css
### 1. 盒子水平垂直居中?
- 父盒子开启flex,并设置主轴居中,侧轴居中
- 父盒子开启flex,子盒子设置margin:auto
- 子绝父相,子盒子设置top和left值为50%,利用transform基于自身回去-50%
- 子绝父项,子盒子设置上下左右均为0,在设置margin:auto也可以
### 2. 盒模型
- 四部分: 内容+内边距+外边距+边框
- 盒模型可以进行切换: box-sizing: border-box; ie盒模型/c3盒模型/怪异盒模型, 宽高定死,不受边框内间距撑开
- 默认是content-box: 标准盒模型, width+内间距+边框
### 3. flex:1
flex:1原理实际上是几个三个属性
- flex-grow: 1用户增大盒子
- flex-shrink: 1用来缩小盒子
- flex-basis: 0%覆盖width
### 4. c3新属性
- c3盒模型box-sizing
- flex布局
- transition过渡
- transform2D转换
- background-size背景缩放
- border-radius圆角
- 等等,不用都说说一些常用的就ok
### 5. bfc
概念: 块级格式化上下文,说白了,bfc是一种属性,拥有bfc会拥有独立的渲染区域,从而不会影响外界
触发bfc: position为absolute或fixed display为inline-block, table-cell, table-caption, flex, inline-flex overflow不为visible
bfc的应用:
外边距重叠: 触发bfc
浮动导致高度丢失: 触发bfc
清除浮动: 触发bfc
盒子重叠: 触发bfc
### 6. less/scss的特性
css预处理语言
- less/scss的异同或区别
相同点功能:
1. 变量声明
2. 混入
3. 变量插值
4. 嵌套
5. 运算
6. 导入
7. 作用域
8. 颜色函数
语法区别:
1. 变量声明
- less: @
- scss: $
2. 混入
- less: 定义混入通过点(.),使用混入也是通过点,如果没有参数可以省略小括号
- scss:定义通过@mixin,使用通过@include
3. 变量插值
- less: 通过@{}
- scss:通过${}
4. 颜色函数
- LESS 使用名为 spin() 的函数;
- SCSS 使用名为 adjust_hue() 的函数。
5. 条件语句循环语句
- less: 不支持
- scss: 支持
后缀名为.sass的sass3.0之前,比较恶心, 不用花括号,固定的缩进格式3.0之后后缀scss,和less差不多
### 7. vw/vh/px/em/rem/vmin/vmax区别
- vw和vh: 相对于视口的大小,100vh等于视口的高度,100vw等于视口的宽度
- px: 固定大小
- em: 大小相对于父元素的font-size: 20px 1em = 20px
- rem: 大小相对于html的font-size
- vmin: 大小等于最小的视口: 100vmin等于最小的视口
- vmax: 大小等于最大的视口
### 8. 让Chrome支持小于12px 的文字方式有哪些?
pc端的浏览器,字体默认最小只能显示12px
- transform: scale(0.5)
- -webkit-text-size-adjust: none; 字体就可以按照font-size去显示
谷歌27版本之后就不能使用了
- 使用图片
### 9. 移动端适配方案
- rem
- 百分比
- vw/vh, vmin/vamx
- flex
- 栅格布局
### 10. 重绘/回流(重排)
1. 重绘(repaint)
- 概念:即重新绘制
- 导致重绘:外观、风格发生变化即发生重绘,例如color、background、box-shadow、background-size
- 影响:重绘不一定回流
2. 回流(重排):
- 概念:当渲染树中元素的尺寸大小、布局等属性改变时,浏览器的布局需要调整,则需要重新渲染DOM。这个过程就叫回流。回流也叫重排(对整个页面进行重新排版)。
- 影响:回流必然会重绘
- 导致重排:width、height、margin、padding、font-size、position等等
3. 避免重绘和回流
- 多个样式修改不要一个一个更改,可以通过操作类名,一次统一修改
```javascript
// js width
// js height
// js color
.active {
width: 100px;
height: 100px;
color: red;
}
```
- 先将元素display:none,修改后在显示
- top修改为translate(不会引起重排)
- 避免使用table用div替换
## vue
### 1. v-if/v-show的区别?
- 效果: 控制元素显示和隐藏的
区别:
- v-if
- 原理: 移除或添加dom元素
- 优点: 懒渲染, 默认首次如果是false,元素不会创建, 如果是组件,可以重新触发生命周期
- 缺点: 频繁的删除重建
- 使用场景: dom首次判断是否显示还是隐藏,或者组件需要重新触发生命周期
- v-show:
- 原理:通过css样式 display:none/block
- 优点: 不会频繁创建dom
- 缺点: 首次渲染 false 但是也会创建
- 使用场景: 频繁的显示和隐藏使用
### 2. $route和 router作用?
$route: 获取当前路由信息 params query path
$router: 和new VueRouter完全是同一个对象,全局路由实例 可以使用路由方法进行前进后退跳转等
路由编程式导航都是通过this.$router实现的
### 3. 聊聊vuex?
vuex的作用? vuex的5个属性? vuex的优缺点? vuex的流程? vuex中的命名空间 ?如何触发mutation、action?
- vuex是什么?
vuex是基于vue状态管理的库
- vuex的作用和优点
实现组件数据共享,同时vuex的数据也是响应式,vuex数据操作远比组件内数据传递方便,方便维护和管理
- vuex的5个属性
state: 存储或定义共享数据,如果像访问数据,$store\mapState
mutations: 修改vuex数据的唯一来源,为了方便后期的调试和维护,当然也可以直接修改state数据,但是很不建议, commit提交mutaiotn
actions: mutations是用来处理同步的,devtools不准确,actions处理异步,dispatch,但是actions只用来处理异步,如果想修改数据,context中的commit提交mutations,
getters: 基于state进行派生数据
moudles: 将vuex数据进行模块化,方便维护,划分模块,每个模块中都拥有state/actions/mutaions/getters,每个模块可以设置命名空间,如果不设置,实际mutations或actions中的方法和全局的并无区别,开启命名空间后,必须通过模块名去访问
说一下两个流程
同步流程: commit > mutations > state
异步流程: dispatch > actions > mutations > state
开发: 统一异步流程
- vuex中的缺点\解决方案
实际vuex也是有缺陷的,不能持久化,想解决该问题/利用本地存储,或者利用插件实现自动存储vuex-persistedstate
- vuex-persistedstate:
- 默认还是localstorage
- cookie
- sesstion
- 应用场景
在开发项目的时候,一般用户信息或者token/或者网页的配置数据存入vuex
### 4. vue.use作用?
Vue.use({install(){}}, 1,2)
- 参数1: 可以传一个函数或者对象,如果是对象,对象中需要一个install函数,如果是一个函数,相当于是install函数
- 其他参数: 依次传给了install函数
install函数默认第一个参数是Vue,后面的参数来自于Vue.use后续参数
Vue.use本质是无法注册全局组件或者给Vue原型添加方法,但是我们在使用路由或者vuex或者element ui,实际还是在install函数内部通过Vue.component注册了全局组件或者给Vue.prototype手动添加方法
### 5. $nextTick有什么用?
**作用**
问题: vue中数据发生变化同步视图是异步的,所以数据发生变化不能立即获取最新的视图, 想获取最新的视图通过this.$nextTick
**原理**
this.$nextTick函数和vue源码中的nextTick函数,是同一个函数,源码中调用nextTick函数为了异步更新视图,我们在使用this.$nextTick函数的时候回调会在源码的nextTick函数之后执行,所以获取到最新的视图,
源码的nextTick函数异步原理利用的就是向下兼容可宏可微,源码中依次进行判断Promise/mutationobserver/setImmdiate/setTimeout
### keep-alive
作用: 缓存不活动的组件
使用: keep-alive组件包裹路由占位
使用场景: 列表进详情需要把列表页缓存,将列表的滚动条的位置记录下来,当重新进入列表页的时候设置滚动条位置
按需缓存:
- kepp-alive上的include或exclude匹配的是组件的name
- 结合路由的meta,添加一个缓存标识,通过在keep-alive缓存的位置获取当前路由信息上的meta中的缓存标识进行控制是否显示keep-alive包裹router-view还是直接显示router-view
生命周期:
被缓存的组件激活: actived
被缓存的组件失活: deactived
### 6. 生命周期
#### 组件
本质:就是函数,会在特定的阶段自动调用,生命周期函数
作用:可以让我们在某个阶段做一些事情
4个阶段
阶段1: 创建阶段
- beforeCreate: 开始创建实例,此时实例的数据和方法还没有
- created:
- 作用:实例已经创建完成,数据和方法都已存在
- 应用场景: 发送请求获取数据, 页面进入的方法需要立即执行
- 扩展: 如果非要在created中操作dom也可以,利用$nextTick
阶段2: 挂载阶段(dom)
- beforeMount: 开始挂载dom,真正的dom元素还没有挂载完成, 操作dom不可以
- mounted:
- dom已经挂载完成,可以操作真实dom
- 应用场景: 页面已进入操作dom
阶段3: 更新阶段
- beforeUpdate: 数据变了,但是视图还没变
- updated: 数据和视图都变了
阶段4: 销毁阶段
- beforeDestory: 即将销毁
- destoryed: 组件销毁
- 应用场景: 清除挂载在window相关的行为,例如定义器\事件
#### 父子
创建挂载阶段
父beforeCreated > 父created > 父 beforeMounted > 子beforeCreate > 子created > 子beforeMount > 子>mounted > 父mounted
更新阶段
如果更新的数据不涉及到子组件,只会父组件更新 父beforeUpdate > 父updated
如果更新的数据涉及到子组件, 父beforeUpdate > 子beforeUpdate > 子updated > 父updated
销毁阶段
父beforeDestory > 子beforeDestory > 子destoryed> 父destoryed
### 7. 插槽
**默认插槽:**
直接在组件标签中间进行传递
组件内部通过vue内置的slot组件进行接收
如果组件想给组件内部不同的区域进行自定义,需要使用具名插槽
**多插槽: 具名插槽**
直接在组件标签中间进行传递,但是需要通过slot或#指定是哪一个具名插槽
```vue
🎨
```
组件内部: 通过多slot进行接收,slot通过name进行区分
```vue
```
不管是普通插槽还是具名插槽都无法在父组件的插槽结构中获取子组件的数据,所以还需要使用作用域插槽
**作用域插槽**
直接在组件内部进行传递,但是同时可以通过v-slot="scope" 获取组件内部通过slot组件传递的数据
```vue
注册
{{ scope.age }}
{{ scope.data }}
🎨
```
组件内部通过slot组件属性向外传递数据,冒号传递的是变量,否则传递的就是字符串
```vue
```
应用场景: element ui很多组件,例如树形\表格
### 8. v-model
作用: 数据双向绑定(mode >> view),进行组件通信
原理: v-model就一个语法糖, 动态绑定了value和注册了input事件
使用场景:
在表单中使用
在组件上使用, 既要将数据传给子组件,子组件还要修改数据的时候,此时可以使用v-model进行简化
解析规则:
```vue
@input="color = $event" :value="color"
```
修改默认解析,在子组件内部
```vue
model: {
prop: 'color',
event: 'setColor',
},
```
v-model有一个缺点: 一个组件上只能使用一次
### 9 .sync修饰符
作用: 语法糖,也可以实现组件通信, 类似双向绑定(父向子传,子向父改)
原理: .sync解析出一个动态绑定的数据,解析一个自定义事件,@update:属性名,组件内部可以通过this.$emit('update:属性名的')进行触发
.sync: 可以使用多次, 而且.sync可以和v-bind结合直接传递一个对象,将对象的每个属性单独传递进去,单独的绑定v-on事件
```vue
这样会把 doc 对象中的每一个 property (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。
```
### 10. vue组件通信(传值)
- 父传子: 父组件属性方式传递,子组件(props)
- 子传父: 子组件通过$emit,父组件通过自定义事件
- eventbus: // 手写发布订阅模式
```javascript
class EventBus {
// 记录事件和回调
clientList = {
send: [() => {}, () => {}],
}
// 订阅事件,参数event事件名,callback 回调
$on = function (event, callback) {
// 将事件和函数记录
// 如果事件记录过,那就将回调push
if (this.clientList[event]) {
this.clientList[event].push(callback)
} else {
this.clientList[event] = [callback]
}
}
$emit = function (event, val) {
if (!this.clientList[event]) {
throw new Error(event + ' is not a event')
}
this.clientList[event].forEach((cb) => {
cb(val)
})
}
}
const eventBus = new EventBus()
// 订阅事件
eventBus.$on('send', (val) => {
console.log('send订阅' + val)
})
eventBus.$on('send', (val) => {
console.log('send订阅' + val)
})
eventBus.$emit('send', 1)
```
- 本质: vue实例对象
- 实现原理: 利用发布订阅模式
- 传递数据的组件,通过eventBus的$emit发布自定义事件,并传递数据
- 获取数据的组件,通过eventBus的$on订阅自定义事件,并通过回调函数接收数据
- 设计模式:发布订阅设计模式: 一对多的依赖关系, 发布方是一,依赖方是多,发布方发布消息,所有的依赖(进行订阅了)方收到通知
- vuex
- ref获取子组件
- v-model
- .sync
- $children: 可以获取当前组件的所有子组件,并以数组的格式返回数据
- $parent: 可以获取到当前组件的父组件, 返回当前组件的父组件
- provide/inject: 跨组件传值provide,进行给后代组件提供值,inject在后代组件上进行注入值
- $attrs: 获取到当前组件节点上的所有属性集合
- 数据提升将数据定义到父组件,一个组件通过子向父传值,父组件在传递给另一个组件
### 11-虚拟dom
什么是虚拟dom?
虚拟dom本质就是一个js对象,用来描述真正dom是什么样的,这个对象就称为虚拟dom
为什么出现?
虚拟dom可以进行高效更新,同时也可以使用虚拟dom进行跨平台: 开发的代码只是模板代码 => 虚拟dom => web(编译为web的dom) => (小程序的节点)
如何实现高效更新?
初始化渲染的时候,会根据数据和模板生成一份虚拟dom树,当数据发生变化,会根据新的数据和模板生成新的虚拟dom树,将两份虚拟dom树进行对比,对比的算法采用的是diff算法
diff算法?
同级比较.深度优先,而且采用了双指针算法,四个指针,遍历旧的虚拟dom有两个指针,指向开始的位置和结束的位置,同理新的虚拟dom也有这两个指针,循环的时候开始的指针对比完后,指针向后推,后面的指针对比后向前推,从而达到效率提升
diff对比之后的情况?
元素不同: 删除重建
元素相同,属性不同: 元素复用,属性更新
v-for:
无key, 看数据的变化是否影响到顺序,如果影响到顺序,影响到性能
无key, 看数据的变化是否影响到顺序,如果没有影响到顺序,性能没有影响
有key:不建议使用索引,索引会变化,建议使用唯一值,对比的使用key进行对比
### 12-mixins
mixins: 将组件中的逻辑功能进行复用,复用部分可以提取到一个js文件中,然后通过mixins这个选项将该文件中暴漏的对象进行混入即可
可以混入哪些: 正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中
优先级:
- 生命周期,组件和混入的都会调用(混入的先调用
- data/computed数据: 进行合并,冲突以组件为主,mixins被覆盖
- methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对
### 13-路由模式的区别
1. abstract支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。
- 是否有#/
hash有
history: 没有
- 是否经过服务器
hash: 不会
history: 会
- 是否需要后端配合
hash: 不需要
history: 需要
- 底层原理
hash: 跳转 window.location.href, 监听 onhashchange
history: 跳转history API, history.pushState和history.repleaceState 监听 onpopState(前进/后退)
封装的方法: pushState(history.pushState/history.repleaceState)
### 14. 用过哪些修饰符
修饰符: 在指令后面通过.语法使用的
- trim
- once
- number
- lazy
- native
- sync
- .enter
### 15. vue优缺点
优点:
- 简单易用
- 渐进式
- 响应式
- 双向数据绑定
- 虚拟dom
- 耦合低
- 用户体验好
- 结合vuerouter实现spa
缺点:
- vue2: 逻辑分散
- 无法兼容ie8一下,无法shim, 底层采用的Object.defineproperty,无法兼容ie8一下
- seo很不友好
### 16. vue-i18n
**概念: **
实现国际化(多语言),项目可以进行语言的切换
**使用:**
1. 装包
2. 在单独的模块中进行封装/引入/注册/配置语言包
3. 封装一个组件用来进行语言切换(this.$i18n.locale)
4. element-ui: 直接导入element-ui的语言包,同时在use element ui时候并配置i18n
### 17. computed和watch的区别
computed:
- 计算属性: 数据发生变化,重新进行计算从而得到新的数据
- 逻辑计算的时候一般使用计算属性
- 定义的时候是方法,使用的是属性
- computed内部return依赖的数据发生变化,,逻辑会进行重新计算
- computed实际只会计算1次,只要数据不变,不管使用多少次,后续的使用的是缓存
- computed不能处理异步
watch
- 进行监听,监听某一个数据如果发生变化,我们想进行逻辑的处理
- watch可以监视:props\data\computed\$route
- 当监听的数据发生变化,对应的函数会重新执行
- 监视的是一个对象: 开启深度监听 deep:true
- watch内部是可以处理异步的
项目应用:
计算属性:
vuex的getter/state数据: 都映射到计算属性中使用
统计审批次数:入职/离职
收货地址: 后端返回的是多个数据
小程序: 订单金额,订单数量
watch:
父组件异步向子组件传递数据

路由变化但是组件复用了
封装的对话框组件:数据变化,需要将数据通过自定义事件传给父组件

### 18. watch原理
1. vue内部通过initWatch进行初始化,循环watch对象,但是监视的值可以写成数组,所以进行两层循环
2. 每一个监视的回调都会创建一个用户watcher,内部通过createWatcher创建
3. watcher内部兼容了三种watcher, 渲染watcher/用户watcher/计算属性watcher
4. 如果是渲染watcher,this.getter等于expOrFn,如果是用户watcher通过parsePath生成了用户的getter,判断expOrFn是否为函数,还是一个字符串
5. 数据发生变化进入更新阶段,执行进入run函数,调用创建watcher传入的回调,这个回调实际就是watch监视的数据的回调
6. 旧值在watcher初始化的时候调用的get函数中获取到
7. 新值在run里执行的get函数中获取到,将新旧交替,将新旧值传给回调
8. watch的使用还可以通过实例.$watch访问,所以源码内部再createWatcher内部并没有直接new Watcher,而是将创建watcher的逻辑封装Vue.prototype上,然后在createWatcher中调用vm.$watch创建用于watcher
### 19. computed原理
1. 将计算属性的get和set转换为Object.defineProperty的get和set,并且计算属性如果写为简写的方式,函数直接交给了Object.defineProperty的get
2. 循环计算属性给每一个属性创建计算属性 创建计算属性watcher
3. 缓存原理:重写计算属性Object.defineProperty的get, 通过createComputedGetter进行生成,返回一个函数,在函数内部主要通过获取到计算属性对应的watcher的dirty,默认为true,计算过一次变为false,根据dirty判断是否重新计算(evaluate)重新计算,
4. 依赖的数据发生变化,重新渲染页面,依赖的数据收集渲染watcher:
- 通过stack记录多个watcher
- 在计算属性get中判断是否还有watcher
- 通过渲染watcher的depend方法内部循环deps,获取到每一个dep,计算属性依赖的每一个数据,每一个dep收集watcher,dep内部的depend方法
### 20. computed和watch执行顺序
初始化阶段computed会比watch先执行,原因就是在initState中先判断的是否有computed,初始化computed,再去判断是否有watch,如果有watch再去初始化watch
### 21. 单项数据流
数据是单项流动,数据可以从父组件流向子组件,父组件的数据发生变化,会自动同步子组件,反之不允许
### 22. v-if和v-for为什么避免同时使用
v2中:
v-for的优先级高于v-if,所以还是会先循环创建虚拟dom,利用v-if进行移除
- v-if写到外层
- 先通过计算属性将数据计算好
v3中: v-if优先级高
### 23. vue 组件中的 data 为什么是一个函数,返回一个对象?
如果不是一个函数返回一个新的对象,组件如果多次使用,实际公用的是同一个数据
但是如果是通过函数 返回一个新的对象,这样的话,每个组件的使用数据是独立的
### 24. 简易数据响应式原理
1. 概念: 数据发生变化,数据使用的地方会进行同步更新
2. vue中数据响应式的核心Object.defineProperty可以对对象的进行定义属性,内部有两个方法get,set,get数据读取会触发,set,数据变化会触发
3. vue劫持数据的时候必须要通过一个数据进行周转,出发get或者set直接用原数据,就会堆栈溢出
4. Object.definePropert一次只能劫持一个数据,所以源码中需要对数据进行循环遍历劫持,类似于递归的方式对所有的数据进行劫持
5. 对象新增的数据是无法劫持到的,因为新增的数据,需要劫持的数据已经被劫持了,对象新增的数据也就不是响应式的,vue中通过this.$set解决
6. 数组的下标修改数据Object.defineProperty.可以劫持的到,vue因为性能和用户体验的关系,取消了vue中检测数组下标的变化
7. 数组的7个方法是响应式的原因是因为vue对7个方法进行重写了,并不是完全重写,利用了切面编程,对方法进行封装和逻辑的处理,最终还是会调用原始的方法
8. 观察者设计模式: 一对多的数据依赖关系,和发布订阅的区别,没有调度中心,被观察者和观察者直接通信, 观察者内部会有update方法进行消息更新,被观察者内部会有add收集观察者的方法,通知观察者的方法notify,通过notify通过所有的观察者,观察者通过update更新消息
9. vue源码中: get中进行依赖收集,会给每个数据添加一个watcher观察者(每一个watcher对应的就是一个组件)
10. set中: a数据发生变化通知观察者(组件)进行更新,vue中更新是以组件为单位
### 25. vue源码
数据响应式数据劫持
1. data数据通过选项对象传递给Vue构造函数,Vue类接收数据,将数据传递给initMixin方法,方法内部通过_init接收数据,最终将数据initData,初始化data数据,
2. 首先判断data数据类型,如果是函数通过data.call(vm),如果是对象,直接返回对象,接下将data进行数据劫持
3. 将data数据传递到observe函数中,对data类型进行判断,将data传递到Observer类中的walk,walk起始函数
4. 在walk函数中循环数据,将数据传递给defineReactive,对每一个数据进行劫持,继续调用observe
5. 处理通过this实例访问data选项数据,此时在initData内部通过proxy将vm进行劫持,当访问vm上的数据时,代理到_data数据等同于data,进入walk内部的数据劫持
6. Observer中会给需要劫持的数据添加一个__ob__是否被劫持的标识
简易版本: 核心
data选项数据传入到initMixin(初始化所有), 内部将传入到initdata(初始化data数据), 获取data数据, 判断data数据类型,如果是函数通过data.call(vm),如果是对象,直接返回对象,接下将data数据传入到observe函数中,将数据传入,判断数据是否是对象, 如果是对象将数据传递给Observer类中的walk内部循环数据,将数据传递给defineReactive,对每一个数据进行劫持,继续调用observe
数组劫持:
Observer中进行判断数据类型是否为数组,如果是数组,走observeArray方法,对数组中的复杂数据类型进行劫持
7个数组方法重写: ['push', 'pop', 'shift', 'unshift', 'slice', 'reverse', 'sort']
AOP 切面编程实现, vue内部重写了这7个方法,但不是完全重写,自身的push unshift功能还是保留所以还会调用原来的方法,又对unshift push splice方法的增加的数据进行了劫持,其他的只做了通知notify更新通知操作
**模板编译:**
compileTofunction这个方法生成render函数,通过render函数生成虚拟dom,虚拟dom疏对比.利用patch方法进行dom的更新
通过parseHtml方法解析模板,解析模板的过程就是使用大量正则匹配,生成AST语法树,将AST语法树生成了render函数
**数据响应式数据发生变化:模板更新:**
创建了一个watcher,内部通过get调用了updateComponent这个方法: 编译更新整个组件内部会进行虚拟dom树的对比
收集watcher:
编译模板模板中用到哪些数据,给哪些数据收集watcher,通过dep进行watcher收集,每一个数据通过闭包的方式都有自己dep,通过deo收集watcher,所以每个数据都收集了自己的watcher, 数据劫持的get中收集watcher,拿不到watcher,通过Dep.target进行中转, watcher中的getter调用前将this存给Dep.target,然后在get中将Dep.target进行
通知:
数据发生变化触发set,获取到数据对应的dep,dep中通过subs存着watcher,dep中有一个notify方法循环收集所有的watcher,调用watcher的update方法进行组件更新 => get >getter > updatecomponent
vue数据响应式:
观察者模式:多对多
dep > watcher
watcher > dep
dep => depend > 调用watcher的addDep方法进行收集dep, 通过dep收集watcher通过addSub
异步更新:
quereWatcher watcher队列收集, 根据watcher的id进行将watcher存入quere对列中,调用watcher的get方法,不能同步调用,只会更新第一个数据的视图,保证所有数据更新完后在统一的更新视图,将watcher的get方法放到异步任务里
next原理:
this.$nextTick就是vue异步更新视图中封装的nextTick方法,利用异步更新视图,异步优先使用微任务,因为同步代码更新完成后进入微任务更快,优先使用Promise.resolve,如果Promise没有,使用MutationObserver也是微任务,如果MutationObserver也没有使用setImmediate是宏任务,他比setTimeout快,如果setImmediate也没有使用setTImeout
总结:
数据劫持 + 模板编译 + 观察者模式 + 异步更新视图nextTick
**数据劫持:**
数据传入到initMixin,将数据传入到initData,获取data数据,可能是函数/对象, 将数据传给observe函数中,数据判断是否为对象,以及是否被观测过__ob__,继续将数据传入到Observer中的walk函数,循环对象,对每一个对象通过defineReactive函数进行数据劫持
Observer中判断数据是否为数组,如果是数组observeArray对数组的复杂数据进行劫持,数组的7个方法通过AOP切面变成进行重写
**模板编译:**
通过compileTofunction生成render函数,内部通过大量的正则匹配生成AST语法树,将AST语法树生成render函数,通过render函数创建虚拟dom,将虚拟dom渲染成真实dom
**观察者模式:**
依赖收集,依赖通知
在数据劫持中get函数中进行watcher收集,因为watcher对应的一个组件的更新,通过dep进行收集,观察者模式多对多,dep收集watcher,watcher也会收集dep,数据发生变化在set中通过dep.notify进行依赖的watcher通知
**异步更新视图:**
通过quereWatcher保存所有的watcher队列,通过quere数组进行保存,同一个watcher不会进行重复保存,保证所有的数据都发生变化后再去更新视图调用队列中所有的watcher的update方法,所以应该通过异步去调用,此时封装了nextTick,和用户使用的$nextTick是同一个,本身是可宏可微, 向下兼容promise\mutationObserver\setImmediate\setTimout
### 26. vue 异步队列
**异步更新视图:**
通过quereWatcher保存所有的watcher队列,通过quere数组进行保存,同一个watcher不会进行重复保存,保证所有的数据都发生变化后再去更新视图调用队列中所有的watcher的update方法,所以应该通过异步去调用,此时封装了nextTick,和用户使用的$nextTick是同一个,本身是可宏可微, 向下兼容promise\mutationObserver\setImmediate\setTimout
### 27. 路由守卫/路由钩子
1. 全局守卫
对所有路由全都生效,进行权限的处理,比如是否登录的路由权限控制
- 路由前置守卫
beforeEach
- 路由后置守卫
arterEach
2. 路由独享守卫
针对某一条路由进行单向控制
beforeEnter
3. 组件内守卫
针对组件
beforeRouteEnter: 只要通过路由,不管是哪一条路由显示该组件就会触发,控制改页面是否展示
beforeRouteUpdate: 路由组件被服用时,例如/a/1 进入a页面,在当前组件重新进入/a/2 显示a页面,a页面就会被复用, 组件没有销毁,组件不会重新走created不会执行,此时可以使用beforeRouteUpdate进行解决
beforeRouteLeave: 应用场景: 表单页面的返回,提示用户当前表单未提交是否离开,离开当前路由组件:
### 28. 获取数据created和mounted
为什么有时候在created获取数据,为什么数据可视化大屏可能会在mounted中获取数据
如果涉及到dom的操作了,应该在mounted中
如果没有涉及到dom操作,在created中
### 29. 数据不响应的情况
- 对象新增属性
- 数组通过下标直接更改数据: arr[0] = 1 arr[0].name = '李四'
- 对象删除
**原理:**
**解决方案: **
更新对象: this.$set(对象, 'key', value)
更新数组: this.$set(数组. 下标, value)
对象删除: this.$fourceupdate()
上述情况在后面进行了可以支持数据响应式的,上面的也会同步更新
### 30. spa的优缺点


### 31. 服务端渲染ssr
- 为什么?
spa单页面应用的seo不友好,spa单页面,由js进行渲染页面,不同的路由规则渲染不同的组件,对应html就是一个空页面,百度爬虫在爬,什么都爬不到,导致seo不友好
- ssr
服务端渲染: 先由服务端将页面解析好返回,返回之后,浏览器进行渲染,直接将页面的内容进行渲染,html不在是一个空页面,爬虫可以爬到
- 配置
路由: 在pages下创建文件,自动生成路由
ui组件库: 在nuxt.config.js中通过plugins节点指定配置文件的路径,在对应的路径文件中进行配置
css全局样式: 可以在nuxt.config.js中的css节点中进行配置
seo优化: nuxt.config.js中通过head进行title和meta的配置
在pages中也可以通过head进行页面级别的配置
- 获取数据
生命周期分为
服务端
nuxtServerInit: 服务端初始化
RouteMiddleware: 中间件
validate: ok
asyncData: 获取服务端数据
asyncData中进行获取数据,该生命周期是在服务端执行的,所以不能使用this
客户端
created => fetch(fetch 方法用于在渲染页面前填充应用的状态树(store)数据, 与 asyncData 方法类似,不同的是它不会设置组件的数据。) = mounted

### 32. vue中使用了哪些设计模式
1. 发布订阅($on/$emit)
2. 观察者模式(vue源码的依赖收集dep和watcher)
3. 单例模式(router/vuex)只能vue.use一次,对应这些实例只能有一个
## vue3
### 1. v2和v3的区别
1. api
- v2: options API(选项式API):优点:对于新手容易,按照固定选项实现功能缺点: 对于代码的阅读\维护\复用(逻辑)不是很友好,v2中也可以通过Mixins实现逻辑的复用
- v3: composition API(组合式API)优点: 功能代码组合在一起
2. 使用
- v2: 配合vuex,对与ts的支持不是很好,不允许使用多个根节点
- v3: 推荐配合pina,底层就使用ts重构的,对ts支持很好,多个根节点
3. 性能
- v3的性能会比v2快1.2_2倍:
- v3采用了静态标记,给静态节点打上标记,diff的过程只对比动态节点,忽略静态节点
- 虚拟dom的创建:v2中,数据发生变化,会将模板进行重编译,生成所有的虚拟dom,v3中数据发生变化,看该虚拟dom是否参与更新,如果参与更新,创建新的虚拟dom,如果不参与更新,直接复用上一次的虚拟dom
- 事件函数的缓存: 事件函数每次在模板编译的时候会进行缓存
- v3的tree shaking:实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的,v3的打包比v2的小
4. 数据劫持
- v2:Object.defineProperty
- v3: Proxy
### 2. vite和webpack的区别
- vite: 工程化工具
- vite原理: 启动项目不会对项目进行完全打包,启动服务,将资源通过服务进行托管,项目加载而是通过浏览器利用esm(es module)的方式加载资源,使用到资源才会加载才会处理,这实际上是让浏览器接管了打包程序的部分工作。
- webpack 真正的打包: 先读取配置文件,根据配置文件的规则进行打包,读打包的入口文件,根据入口文件的依赖进行打包,js转低级语法:babel,三方文件:loader,html:html-webpack-plugin,css文件的抽离:mini-css-extract-plugin,启动浏览器,浏览器加载资源,直接加载不会再次处理
### 3. vue3 如何定义响应式数据
1. ref: 将简单数据类型或复杂数据类型处理为响应式
模板中使用自动解套value,在js中使用需要通过value访问到数据
2. reactive: 只能将复杂数据类型处理为响应式,直接用数据不需要value
推荐的使用方式:
- 语法层面: 简单数据类型推荐使用ref, 复杂数据类型推荐使用reactive
- 性能: 更推荐全部使用ref, ref的性能在vue3.2之后比reactive高260%
### 4. script setup语法
1. 语法: 在script标签上定义setup属性
2. 特性:
- vue3.2版本更新
- setup函数的语法糖,可以直接在script中定义数据和方法,默认可以直接在模板中使用
- 不需要export default 对象
### 5. computed计算属性
1. computed: 计算属性, 就是vue提供的一个函数.所以需要通过引入vue得到
2. 语法: 调用computed函数, 传递一个回调函数,在回调函数内部进行计算,将计算好的数据return出
3. 特性: 具有缓存, 多次调用多次定义, 除了可以传递函数,还可以传递对象, 包含get和set函数
### 6. watch监视
1. 概念: 监听数据变化, 也是vue提供的一个函数, 所以需要通过引入vue得到
2. 语法:
- 参数1: 监视的数据
- 监视单个数据: 直接写数据
- 监视多个数据: 写数组,数组内放数据
- 监视reactive对象中的某一个属性: 函数返回属性
- 参数2: 处理函数, 两个参数
- 参数1: 新值, 如果监视的是数组,参数1还是新值,以数组的方式体现
- 参数2: 旧值, 如果监视的是数组,参数1还是新值,以数组的方式体现
- 参数3: 配置对象
- 深度监听: deep: true, 监视的是ref的对象需要开启深度监听,监视的是reactive定义的对象不需要开启深度监听
- 首次监听: immediate: true, 开启首次监听
### 7. 生命周期函数
vue3中的生命周期函数来自vue,所以需要通过vue按需引入,生命周期函数可以多次调用
1. 创建阶段
- vue2: beforeCreate/created
- vue3: setup
2. 挂载阶段
- vue2: beforeMount/mounted
- vue3: onBeforeMount/onMounted
3. 更新阶段
- vue2: beforeUpdate/updated
- vue3: onBeforeUpdate/onUpdated
4. 销毁阶段
- vue2: beforeDestory/destoryde
- vue3: onBeforeUnMounte/onUnMounted
### 8. 组件通信
#### 8.1 父传子
1. 父组件通过属性绑定传递
```html
这是App组件: {{ money }}
------------------------
```
2. 子组件通过defineProps接收, 子组件接收的数据在模板中可以直接使用在js中,通过defineProps的返回值使用
```html
这是子组件: {{ money }}
```
#### 8.2 子传父
1. 子组件获取emit对象
```javascript
const emit = defineEmits(['buy-car'])
```
2. 通过emit触发自定义事件,并传递数据
```javascript
const buyCar = () => {
// console.log('买车', 1000000)
// 2. 通过emit对象触发一个自定义事件, 并且
emit('buy-car', 1000000)
}
```
3. 父组件定义自定义事件和事件函数
```html
```
#### 8.3 跨组件向下传值
1. 父级组件通过provide提供数据
```javascript
const money = ref(100000000)
provide('money', money)
```
2. 后代组件通过inject注入数据
```javascript
import { ref, inject } from 'vue'
const money = inject('money')
```
#### 8.4 跨组件向上传值
1. 父组件通过provide传递函数
2. 后代组件通过inject注入函数
3. 调用函数将数据通过参数的方式传给父级组件
#### 8.5 ref获取组件
1. 创建空的ref
2. 给组件绑定ref属性值是空的ref的名字
3. 组件和空ref的value绑定在一起
4. 利用ref.value获取子组件
```html
```
5. 子组件通过defineExpose向外暴漏数据
```javascript
这是comA组件
```
### 9. 过滤器
1. v3废除了过滤器
2. 想过滤数据: 定义函数
### 10. toRefs的使用
作用: 保证解构的数据还是响应式
原理: 将解构的数据变为ref处理
### 11. 路由
vue3的路由结合vue-router@4.x版本
1. 创建路由实例, 通过createRouter进行创建,createRouter就是一个函数,来自于vue-router的按需引入
2. 配置路由模式,通过createWebHashHistory(hash路由模式)、createWebHistory创建history路由模式
```javascript
import { createRouter, createWebHashHistory as createRouterMode } from 'vue-router'
// 可以创建路由实例
// 通过参数对象进行配置
const router = createRouter({
// createWebHashHistory 创建hash模式
// createWebHistory 创建history模式
history: createRouterMode(),
// 路由规则
routes: [
{
path: '/home',
component: () => import('../views/Home/index.vue')
},
{
path: '/about/:id',
component: () => import('../views/About/index.vue')
}
]
})
export default router
```
3. 获取路由实例, 通过useRouter, useRouter函数来自于vue-router, 调用useRouter获取到router实例
```javascript
import { ref } from 'vue'
// 引入useRouter
import { useRouter } from 'vue-router'
// 创建router实例
const router = useRouter()
const goAbout = () => {
router.push('/about/1')
}
```
4. 获取当前的路由信息, 通过useRoute, useRoute函数来自于vue-router, 调用useRoute获取到route
```javascript
import { ref } from 'vue'
// 引入useRoute
import { useRoute } from 'vue-router'
// 创建route
const route = useRoute()
```
### 12. pinia
#### 12.1 pinia相比较vuex的优势
- 比vuex对vue3的兼容会更好
- pinia对ts的支持更好
- pinia和vuex5的提案很像
- pinia的使用比vuex使用更简单方便
#### 12.2 pinia如何使用
- main.js中进行createPinia的引入,通过createPinia创建pinia实例,并进行挂载
```javascript
// 引入创建pinia实例
import { createPinia } from 'pinia'
const pinia = createPinia()
const app = createApp(App)
// 挂载pinia
app.use(pinia)
```
- 创建pinia模块, 在js文件中通过defineStore(来自于pinia),创建一个模块数据管理
- 参数1: 模块标识
- 参数2:配置对象(state,actions, getters)
- 返回值: 函数
```javascript
import { defineStore } from 'pinia'
// 参数1: 模块标识
// 参数2: 配置对象
// 返回值: 函数
const cartStore = defineStore('cart', {
state: () => {
return {
total: '111.00'
}
},
actions: {},
getters: {}
})
export default cartStore
```
- 使用模块的数据或方法, 导入模块, 直接调用模块的属性(state,getters)或方法(actions)
- 如果想对模块的数据进行解构,通过storeToRefs处理后解构的数据变为响应式了
- 模块肯定进行统一整合
#### 12.3 pinia有哪些属性
- state: 定义数据
- actions: 定义方法, 同步和异步都可以处理
- getters: 定义派生数据
## 小程序
### 1. wxss和css的区别?
- wxss是小程序配合wxml渲染结构样式
- css是网页结合html渲染结构样式
- wxss新增了rpx,适配单位, 750rpx等于整屏的宽度
- wxss区分全局样式和局部样式,全局样式app.wxss,局部样式,每个页面内部的wxss文件,权重,先看权重,谁的权重高就会把另一个覆盖,同等权重,局部的覆盖全局的
### 2. 原生小程序组件使用过哪些?
- view: 盒子
- text: 文本
- richtext: 富文本,可以通过nodes节点解析html标签
- scroll-view: 滚动区域
- swiper: 轮播图
- button: 按钮组件
### 3. 原生小程序中如何绑定事件?
- 通过bind: 或者bind
### 4. 原生小程序中如何修改数据并同步视图?
- this.setData(), 既可以更改数据,也可以同步视图
- 不可以直接修改数据, 直接this.data.数据名, 之更改数据,视图不变
### 5. 事件传参?
- 通过data-属性名, 核心通过自定义属性进行传参
- 通过事件对象的currentTarget里面的dataset获取到数据
### 6. 小程序中发起网络请求
- 通过WX.request,而且这个方法不支持promise,所使用原生小程序开发,需要对wx.request进行二次封装
- 小程序中不存在跨域的问题
### 7. 导航跳转方式?
- 声明式导航(navigator标签进行跳转),跳转到tabbar页面,需要配合open-type="switchTab",open-type="navigateBack" delta="层级"
- 编程式导航: wx.navigatorTo() 普通页面的跳转, wx.switchTab跳转到tabbar页面,wx.navigateBack
- 导航传参: 通过query(?根参数 key=value&key=value), 接收参数,通过onLoad的形参去接收
### 8. 监听上拉触底
- onReachBottom监听到触底
### 9. 小程序的生命周期函数?
1. 应用的生命周期
```javascript
// 触发一次
onLaunch() {
console.log('小程序开启启动,初始化完成')
},
// 小程序显示,多次触发
onShow() {
console.lg('小程序显示了')
},
onHide() {
console.log('小程序隐藏了')
},
onError() {
console.log('小程序出现异常')
},
```
2. 页面的生命周期
```javascript
// 1. onLoad 页面开始加载 发送请求获取数据,获取到导航参数
// 2. onShow 页面显示 (多次触发) 提示信息
// 3. onReady 页面初次渲染完成
// 4. onHide 页面隐藏 tabbar页面切换只是隐藏
// 5. onUnload 页面销毁 不是tabbar页面 b页面 返回 A页面对应的页面销毁 // 清理操作
```
3. 组件的生命周期函数
| **生命周期** | **参数** | **描述** | **最低版本** |
| --- | --- | --- | --- |
| **created** | 无 | 在组件实例刚刚被创建时执行(拿到数据) | [1.6.3](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) |
| **attached** | 无 | 在组件实例进入页面节点树时执行 | [1.6.3](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) |
| ready | 无 | 在组件在视图层布局完成后执行 | [1.6.3](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) |
| moved | 无 | 在组件实例被移动到节点树另一个位置时执行 | [1.6.3](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) |
| **detached** | 无 | 在组件实例被从页面节点树移除时执行 | [1.6.3](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) |
| error | Object Error | 每当组件方法抛出错误时执行 | [2.4.1](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) |
### 10. 定义全局组件和局部组件
**全局组件**
1. 创建一个组件
2. 通过app.json中的useingComponets进行组件注册,注册为全局组件
**局部组件**
1. 创建一个组件
2. 通过页面的json中的useingComponets进行组件注册,注册为局部组件
### 11. 组件通信
#### 父向子传值
- 父组件通过属性绑定传递
```javascript
```
- 子组件通过properties进行接收
```javascript
properties: {
list: {
type: Array,
required: true
}
},
```
#### 子向父传值
1. 子组件传值
```javascript
this.triggerEvent('自定义事件名', 传递的数据)
```
2. 父组件接收, 通过bind:绑定自定义的事件名, 通过事件函数的e.detail获取到子组件传递的数据
```html
```
3. 通过事件函数的e.detail接收数据
```javascript
getImage(e) {
console.log(e.detail)
},
```
#### 获取子组件的方式
1. 通过selectComponents获取子组件
```javascript
this.selectComponent('组件对应的选择器')
```
### 12. wxs是什么?
wxs weixin script: 在模板中无法调用js中定义的函数,可以调用wxs定义的函数,一般用wxs进行数据处理
缺点:
1. 不支持es6
2. 不支持小程序的api
3. 隔离性, wxs和js不能互相调用
优点:
1. 没有兼容性(小程序的版本库)
2. 在ios设备上比js快2-20倍
### 13. 原生小程序上线流程
- 代码进行上传
- 将版本设置为体验版本(产品和ui进行体验)(测试)
- 改bug,重复1\2步骤
- 提交审核
- 审核通过点击发布
### 14. uni-app小程序的上线流程
- 在hbuilderX中发行-小程序,就会打包
- 在微信开发者工具中点击上传,弹出填写上传信息,填写完毕进行上传
- 登录小程序管理系统(开发人员不会参与)
- 将版本设置为体验版本(产品和ui进行体验)(测试人员进行测试)
- 改bug,重复1\2步骤
- 提交审核
- 审核通过点击发布
### 15. 登录流程
1. 微信授权登录
1.1 获取用户信息(iv, rawData, encrytedData,signature)
```javascript
const userInfo = await uni.getUserProfile({
desc: '黑马优购获取您的用户信息'
})
const {
iv,
rawData,
encrytedData,
signature
} = userInfo[1]
```
1.2 调用uni-app的login方法获取code(用户唯一标识)
```javascript
const {
code
} = await uni.login()
```
2. 根据微信授权的信息进行接口登录
```javascript
const res = await login({
iv,
rawData,
encryptedData,
signature,
code
})
```
3. 登录成功后
- 将token存到vuex
- 判断是否有回跳地址,如果有,从哪来回哪去




### 16. 支付流程
1. 根据购买的商品信息生成订单,从而得到订单号
```javascript
async createOrder() {
// 要购买的商品
const payGoods = this.$store.state.cart.list.filter(item => item.goods_state)
// 通过map进行格式化
const goods = payGoods.map(item => {
return {
goods_id: item.goods_id,
goods_number: item.goods_count,
goods_price: item.goods_price
}
})
// 调起创建订单的接口
const {
message: {
order_number
}
} = await createOrder({
order_price: this.totalPrice,
consignee_addr: this.$store.getters['user/addressStr'],
goods: goods
})
// 获取到订单id
this.payGoods(order_number)
},
```
2. 预支付: 根据订单号得到支付的参数
```javascript
async payGoods(order_number) {
// 获取支付参数
const {
message: {
pay
}
} = await getPayParams({
order_number
})
//
},
```
3. uni.requestPayMent: 支付,需要很多参数
```javascript
// 发起微信支付
const [err, res] = await uni.requestPayment(pay)
if (err) {
uni.showToast('取消支付,跳转订单页面')
} else {
uni.showToast('支付成功,跳转订单页面')
}
```
### 17. tabbar页面切换如何传递参数
- 利用reLaunch
```javascript
uni.reLaunch({
url: '/pages/my/my?url=123'
})
```
- vuex
- 本地
### 18. uni-app从头开始写项目
#### 18. 1介绍
- 原生小程序和vue的结合
原生(写法相同): 组件\生命周期\api
vue: 数据绑定\数据渲染\事件绑定\逻辑的定义\计算属性\watch\过滤器\自定义指令\vuex 等等逻辑的处理
独立:
- App.vue (app.js和app.wxss)
- pages.json(app.json): 配置页面的路径和窗口的外观,全局组件也在这里进行注册,tabbar也是在该位置进行配置
- manifest.json打包配置: 不同的平台配置不同
#### 18. 2创建项目



#### 18.3 运行项目
点击运行

配置微信开发者工具的路径

微信开发者工具开启服务端口


#### 18.4 外观配置
在pages.json中通过globalstyle进行配置
```javascript
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
```
#### 18.5 tabbar的配置
在pages.json中通过tabbar进行配置
```javascript
"tabBar": {
"list": [
{
"pagePath": "页面路径",
"iconPath": "图标路径",
"selectedIconPath": "选中的图标路径",
"text": "文本"
}
],
},
```
#### 18.6 创建全局组件
1. 在项目根目录创建components目录
2. 右键创建组件


3. 直接使用
```html
```
#### 18.7 新建页面
在pages上右键进行新建页面

填写创建页面的信息

设为首页,可以在pages.json中进行配置
```javascript
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
}
}
,{
"path" : "pages/home/home",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
],
```
#### 18.8 组件库(vant-weapp)
[vant-weapp](https://vant-contrib.gitee.io/vant-weapp/#/quickstart)
1. npm init -y
2. 下包
```javascript
npm i @vant/weapp -S --production
```
3. 在项目的根目录创建一个wxcomponents目录

4. 修改名字

5. 定义全局组件
```html
"usingComponents": {
"van-button": "/wxcomponents/weapp/button/index"
}
```
6. 使用
#### 18.9 封装request
[flyio](https://wendux.github.io/dist/#/doc/flyio/interceptor)
1. 使用flyio
```javascript
npm i flyio
```
2. 在utils目录下新建request.js
```javascript
import Fly from 'flyio/dist/npm/wx'
import store from '@/store'
const fly = new Fly()
// 配置请求的基准地址, 将请求地址添加到服务器合法域名
fly.config.baseURL = ''
// 请求拦截器(拦截所有基于fly实例发出的请求, 统一携带token,配置请求头等)
//添加请求拦截器
fly.interceptors.request.use((request) => {
uni.showLoading({
title: '...加载中'
})
// 满足该条件的携带token
return request;
})
//添加响应拦截器,响应拦截器会在then/catch处理之前执行(对数据进行剥离,以及进行统一的错误处理)
fly.interceptors.response.use(
(response) => {
uni.hideLoading()
//只将请求结果的data字段返回
if (response.data.meta.status !== 200) {
uni.showToast({
title: '获取数据失败',
icon: 'error'
})
return Promise.reject(new Error('请求失败'))
}
return response.data
},
(err) => {
//发生网络错误后会走到这里
//return Promise.resolve("ssss")
}
)
export default fly
```
3. 接口还需要在小程序管理后台中配置服务合法域名,如果目前还没有合法域名,

4. 新建api目录,进行api的封装
#### 18.10 vuex状态管理
1. 无需下载,直接新建store目录,新建index.js, 借助createLogger进行vuex的数据调试
```javascript
import Vue from 'vue'
// 默认导入和按需导入和合并语法
import Vuex, {
createLogger
} from 'vuex'
import cart from './modules/cart.js'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {},
mutations: {},
actions: {},
getters: {},
modules: {
// 购物车模块
cart
},
plugins: process.env.NODE_ENV !== 'production' ? [createLogger()] : []
})
export default store
```
2. 在main.js中引入,挂载
```javascript
import store from './store'
const app = new Vue({
store,
...App
})
```
### 19. 原生小程序开发项目
#### 19.1 创建项目
1. 利用微信开发者工具进行创建

2. 填写项目基本信息

#### 19.2 项目清理
1. 创建新的首页,app.json中pages选项下新增路径,页面自动生成

2. pages下新建目录,在目录下在新建page也会自动生成,同时路径也会自动生成


3. 删除index和log
4. 配置小程序的外观,app.json中进行window和tabbar的配置
#### 19.3 封装request
1. 对wx.request进行二次封装
```javascript
const baseURL = ''
export function request({
url,
method = "GET",
data
}) {
return new Promise((resolve, reject) => {
wx.request({
url: baseURL + url,
method,
data,
success(res) {
resolve(res)
},
fail(err) {
reject(err)
}
})
})
}
```
#### 19.4 定义数据和渲染数据
1. 定义到data,
2. 数据渲染
- {{}},所有的数据均通过{{}},
- 列表渲染wx:for,
- 条件渲染wx:if wx:elif wx:else hidden
#### 19.5 数据修改和事件绑定
3. 数据修改
- this.setData({要修改的数据}), 既可以更新数据也可以更新视图
4. 事件绑定
- 通过bind: 或bindtap
- 事件函数定义在和data平级
- 事件传参: 通过data-自定义属性传,接收通过e.currentTarget.detail.dataSet
#### 19.6 组件注册
1. 全局组件
- 
- 
- 注册组件: 在app.json中的
```javascript
"usingComponents": {
"my-aaa": "/components/my-aaa/my-aaa"
},
```
2. 局部组件,在页面内部的json文件中进行配置
```javascript
{
"usingComponents": {}
}
```
#### 19.7 页面导航
1. 声明式导航
- 普通页面:
- tabbar页面:
- 后退:
2. 编程式导航
- 普通页面: wx.navigatorTo({url: ''})
- tabbar页面: wx.switchTab({url: ''})
- 后退: wx.navigatorBack({delta: 1})
3. tabbar页面跳转传参
- wx.reLaunch
#### 19.8 生命周期
1. 应用的生命周期
- onLaunch: 小程序初始化(加载一次)
- onShow: 每次看见小程序
- onHide: 小程序隐藏
- onError: 小程序出错
2. 页面的生命周期
- onLoad: 页面加载(加载一次) 获取到导航参数,数据请求
- onShow: 页面显示,
- onReady: 页面加载完成(加载一次)
- onHide: 页面隐藏
- onUnload: 页面销毁
3. 组件的生命周期
| **生命周期** | **参数** | **描述** | **最低版本** |
| --- | --- | --- | --- |
| created | 无 | 在组件实例刚刚被创建时执行 | [1.6.3](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) |
| attached | 无 | 在组件实例进入页面节点树时执行 | [1.6.3](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) |
| ready | 无 | 在组件在视图层布局完成后执行 | [1.6.3](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) |
| moved | 无 | 在组件实例被移动到节点树另一个位置时执行 | [1.6.3](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) |
| detached | 无 | 在组件实例被从页面节点树移除时执行 | [1.6.3](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) |
| error | Object Error | 每当组件方法抛出错误时执行 | [2.4.1](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) |
### 20. 小程序如何跳转另外一个小程序?
[小程序跳转另外一一个小程序](https://developers.weixin.qq.com/miniprogram/dev/api/navigate/wx.navigateToMiniProgram.html)
### 21. 小程序最近更新了什么?
[小程序更新日志](https://developers.weixin.qq.com/miniprogram/dev/framework/release/)
### 22. 小程序地图
[地图](https://www.yuque.com/liufusong/nvnnnk/beu10m)
### 23. 小程序嵌套过深,导致跳转失效?
1. 出现问题的原因:
小程序的页面栈最多10层,之前是5层,所以一旦超过10层,跳转则无反应
2. 解决思路
合理使用navigateTo、navigateBack、redirectTo、reLaunch
方式一:可以通过获取页面栈信息getCurrentPages,以及即将跳转的页面是否在页面栈中,如果有直接利用navigatorBack进行返回
方式二:可以通过获取页面栈信息getCurrentPages,是否超出限制,如果超出限制利用redirectTo进行跳转或者**wx.reLaunch进行跳转**
方式三:因为redirectTo/reLaunch跳转,会销毁当前页面栈进入另一个页面,a、b、c,b通过redirectTo到c,再返回会进入a,这个问题解决,可以维护自己的页面栈列表,进行进栈,弹栈维护,从自己维护的列表中取对应的页面,对应的往后退。
### 24. 小程序打包文件太大如何处理?
- 删除无用代码、组件
- 图片压缩[链接](https://tinypng.com/)
- 分包处理
### 25. 小程序如何下载文件?
小程序有内置的下载文件api, wx.downloadFile进行文件下载,单次下载允许的最大文件为 200MB
[参考链接](https://developers.weixin.qq.com/miniprogram/dev/api/network/download/wx.downloadFile.html)
### 26. 小程序项目选择uni-app的原因?
- 跨终端(但是你的项目没有多终端,只有小程序,说这个就不合适了)
- 开发风格贴近vue, 开发舒服
- 开发效率比原生效率快
- 微信小程序具备的uni-app均具备
- 微信开发者工具没有vscode和hbuilderx好用
### 27. 小程序内嵌h5页面?
- 可以通过web-view组件嵌入h5页面
## 项目
### echarts响应式
利用绑定window的resize事件监听窗口变化后,调用echarts实例的resize方法进行适配
```javascript
var myChart=echarts.init(this.$refs.echarts);
window.addEventListener("resize", () => {
myChart.resize(); // 自动实现图表的适配
})
```
### svg图标不显示
像小程序的登陆支付\权限\断点续传,想这些实际在第一次接触的时候感觉有一些难度,但是业务做完之后反而感觉没有什么复杂的,我就说一个最近做的项目,一个比较有趣的问题
我们做了一个移动h5项目
1. 项目中需要使用svg图标
2. 封装了一个svg图标使用组件
3. 上一个开发人员已经封装好的(在vue-element-admin复制过来的)
4. 组件在使用的时候,svg图标不显示, svg的宽高是有的,svg内部的use标签的高度和宽度是0
- 尝试修改svg组件的use样式,添加宽高/ 无效
- 调试组件是否正确注册成功/组件是没问题的
- 图标问题/也是没有问题
- 浏览器没有报错
- 项目可以正常运行
解决方案:
- 通过下载svg-sprite-loader这个解析器
- 修改vue.config.js中通过chainWebpack配置进行svg格式的文件通过svg-sprite-loader这个loader去处理

### 项目流程
1. 需求评审会
2. 产品出原型
3. ui根据原型出设计稿(后台管理系统有可能没有设计稿)
4. 项目分配(独立负责/负责某些模块)
5. 前端根据设计稿或者原型图进行项目框架的搭建
6. 后端如果没有出接口,mock模拟数据
7. 后端有了接口,根据接口文档和接口url进行接口联调
8. 完成项目功能
9. 进行自测和测试人员的测试,冒烟测试/bug修复
10. 项目打包上线
### 项目中配置babel
1. 安装所需要的包
2. 在babel.config.js中配置babel的插件
3. plugins节点下有env下面development/production,
### 项目优化
**打包层面**
1. 移除console.log: 通过babel的插件进行移除(babel-plugin-transform-remove-console)
2. soucemap: 映射生产阶段的代码和开发阶段的代码的对应关系,在生产阶段报错,根据map的映射文件提示出开发阶段的代码哪里出错, 项目真正上线,关闭(线上出现bug)
3. 路由懒加载: 当路由匹配的时候才会加载响应资源
4. splitChunks: 提取公共资源, 某一个文件被多次引入, 进行公共提取
5. gzip压缩: compression-webpack-plugin, 需要后端开启支持
6. 图片压缩: image-webpack-loader 实现图片压缩
7. runtimeChunks: 开启运行时代码, 运行时代码发生变化不影响app主模块的hash变化(可以继续使用强缓存)
8. 忽略打包+cdn资源: externals配置忽略打包, 资源文件可以通过cdn资源
9. ssr: 服务端渲染
解决首屏加载速度慢的问题,首屏服务端直接返回,还可以解决seo,实现ssrvue结合nuxt
**代码层面**
减少data
图片懒加载
建立映射表
路由懒加载
异步加载
长列表性能优化
vue2 v-if和v-for
组件销毁-清除事件定时器等
服务端渲染(ssr):
spa单页面首屏会等js加载完,将页面进行解析渲染,所以可能会首屏慢,而且seo百度爬的只有一个id的app
服务端渲染:后端将首屏解析出完整的dom结构,然后将首屏返回进行渲染,其他页面还是通过单页面的路由进行跳转
### token过期
token一般过期时间为2小时,refresh_token,过期时间较长(一周-两周)
token过期后, 我们在背后(用户不知情的情况下),偷偷的发送一个请求获取新的token, 通过refresh_token进行换取,登录状态就可以维持一周-两周,如果refresh_token也过期,跳到登录页
### 权限控制
项目中的亮点?难点?
在我的项目中关于权限是比较复杂的,
**登陆权限**
首先第一个我们项目考虑用户登录的权限,等用户登录成功,才有权访问登录的页面,这个相对来说比较简单,可以在全局守卫中通过判断是否有token即可
第二个: **token过期权限**
响应拦截器中监听后端响应回来的状态码是否401,token过期
业务逻辑(每个用户登入权限不同,所以能够访问的页面权限和菜单权限以及按钮权限都不同)
token过期: 两小时, 通过refresh_token, 无感(我们通过代码在背后偷偷的通过refresh_token 发请求换取新的token)
refresh_token: 真正过期一般为1个月,退出重新登录
应该通过refresh_token.换取新的token,如果refresh_token也过期了则进入登录页
第三个: **路由权限**
路由分为静态路由表和动态路由表,动态路由表会通过后端返回的用户的权限进行筛选,和我们自己的路由规则meta的标识进行对比,从而筛选出结果,但是这里会有bug,无法进入动态添加的路由规则,
通过addrouters动态添加路由表,还需要通过next('to.path'),
菜单无法渲染,菜单vue-element-admin使用的数据是$router.options.routes获取的所有路由,无法拿到动态添加的,所以此时我们需要自己通过vuex维护一份路由表,将自己维护的路由表进行菜单渲染
按钮权限:
通过封装自定义指令,自定义指令内部查询是否有该权限,如果没有将该按钮节点remove
### 你们的项目是如何打包部署的
1. 运行npm run build 进行打包,可以进行打包优化,打包之后将dist文件交给后端
2. 上线流程
自测
发起合并请求(development: 受保护的)
代码合并release进行测试
release代码合并到master
打包master/ 自动打包,我们是cicd(持续交付,持续部署)
gitlab+docker+Jenkins
### 技术栈
1. vue(全家桶 vue、vuex、vueRouter、axios、elementUi、)echarts-cos-js-sdk-v5、dayjs、js-cookie、vuex-persistedstate
2. xlsx、file-saver excel导入导出
### excel导入导出
excel导入导出
基于vue-element-admin进行实现,通过excel导入的数据字段key是中文,接口需要的是英文,为了后期容易维护,通过映射表进行数据的处理,时间excel的时间基于1900,js基于1970年份不准的,excel的时间戳类似于天数,将excel的时间处理为js的时间
权限功能: 见上面的权限控制
对:过滤器\自定义指令\组件的引入进行封装统一注册使用
组件封装:
页面工具栏\上传图片\excel导入导出\日历组件\表格组件\分页组件\对话框组件\表单组件
思想:
1. 准备结构:结构考虑复用灵活,一般使用插槽允许自定义
2. 组件的样式:考虑样式支持自定义,一般使用属性传值(有可能是样式,通过类名)
3. 组件的数据: 通过数据传递
4. 逻辑,事件,:例如弹框组件,点击遮罩弹框关闭,用户使用组件的时候,也需要监听到点击遮罩的行为,用户想进行自定义的逻辑,
### rem适配原理
核心原理就是1rem等于根(html)节点的字体大小,在不同的屏幕下修改根节点的字体大小
在项目中:
flexible,可动态更改html的字体大小, 开发过程中需要使用rem单位,开发过程最好使用px,解析后还是rem,postcss-px-to-rem
具体配置, 在项目根目录中.postcssrc.js专门配置postcss的配置, 该配置中会有一个plugins节点,配置postcss的所有插件, 将postcss-pxtorem的配置在plugins中进行配置
postcss-pxtorem:
- rootValue: 将多少px转为1rem
- propList: 将什么属性的单位转为rem *是所有
- exclude: 排除
```javascript
module.exports = {
plugins: {
'postcss-pxtorem': {
//1.
// rootValue: 37.5,
//2.
// rootValue: 75,
//3.
rootValue({ file }) {
return file.indexOf('vant') !== -1 ? 37.5 : 75
},
propList: ['*'],
exclude: 'github-markdown',
},
},
}
```
### 购物车模块
1. 方式: 登录和未登录
区分登录和未登录
未登录: 数据存放到vuex+本地
登录: 购物车数据 存到接口,获取购物车的数据将本地数据和接口数据进行合并,才是完整的购物车数据
添加购物车可以不登录
支付/结账必须登录
2. 不支持未登录
加入购物车/直接提示登录
### 小程序支付
1. 登录
- 获取微信用户信息:button 按钮组件添加open-type="getUserInfo" @getuserinfo="getUserInfo", 主要获取加密签名
- 调用微信小程序的login方法,得到code: 用户登录凭证
- 发起后端的登录请求接口
将code和加密签名传给后端,后端返回token
```javascript
code: res.code,
// 加密字符串和加密签名
encryptedData: info.encryptedData,
iv: info.iv,
rawData: info.rawData,
signature: info.signature
```
2. 支付
- 创建单: 根据购物的数据(收货地址\商品信息\商品金额)创建订单(发请求),服务端会响应order_id
- 预支付
根据订单id获取支付所需的参数
- 支付
调用uni的requestPayMent方法唤起微信支付需要传递timeStamp\noncestr\package等时间戳\随机字符串\加密签名,数据在预支付时获取的参数传递给requestPayMent即可
- 支付状态查询
### 网页支付
1. 登录
2. 前端准备支付按钮链接(像后端发送了请求,后端帮我们跳转到支付界面,用户登录支付宝进行支付),提供订单id和回跳地址(用户支付完成后跳到我们提供的回跳地址)
### 单点登录
概念: 一个大型公司有很多系统,用的是同一个账号,登录一个系统时,其它系统也可以正常访问
cookie:
某个系统登陆成功,再次去登录其它系统系带token,token如何在多个网站中共享
domain/path
domain: 设置网站域名 设置为主域名(父级域名)/二级域名是可以获取到cookie数据
path: 路径 /
脱离父级域名不可以共享了
认证中心
iframe
### 移动端项目类型
- 移动端app: 软件
1. 原生app: 安卓/ios 开发的,利用原生语言基于手机系统进行开发的软件,安卓的开发原生app利用的java和kotlin,ios利用的Oc语言(Object-c)/swift
2. 混合app: 跨平台,只写一套代码使用前端的语言可以调用原生的接口打包成原生应用: vue用weex,react用rn(react-native),angular(ionic), uni-app坑比较多, 利用hbuilder将h5网页打包成app, flutter(谷歌出品)/ dart
3. 原生app内嵌h5: 安卓/ios 开发原生应用搭建好建构包括一级页面,有可能有部分二级或三级页面,h5开发部门2级或3级页面,当做web网页去开发,然后将上线的链接嵌入到原生应用中
- 移动端web: 网页
在浏览器中运行的h5网页
- 移动端小程序
### 后端返回一万条数据渲染
长列表性能优化
1. pc端使用分页: 每页渲染10条,当渲染下一页的数据直接将上一页的数据覆盖掉
2. 滚动列表: 可视区域渲染: 长列表10000条数据,并不会创建100000个dom,而是只会创建固定的例如20dom,当滚动的时候,将可视区域dom进行销毁重新创建, vue借助vue-virtual-scroller
### 大文件上传/视频上传
1. 通过element-ui的el-upload进行文件上传
2. 选择文件后触发el-upload组件的change事件,然后通过参数file内部的raw获取到文件对象
3. 将文件对象进行切片
- 固定数量
- 固定大小
比如通过固定大小,切割的文件大小,根据file的size和每块的大小得到切割截取的次数,循环截取逻辑都是通过slice进行文件切片,循环截取.slice方法不是数组的slice方法,文件对象原型上的slice方法,继承自 Blob的slice
4. 断点续传
- 串行:点击暂停或者上传失败,return终止,这次请求完事后,下一次不在请求了
- 并行:点击暂停或者上传失败,return终止,取消请求
- chunList保存需要传送给后端的chunk,每上传成功一个,将chunkList中对应的chunk删除,下次重新请求从chunkList从头开始上传
5. 全部上传后,发送和并请求,后端会进行合并
6. 文件重新上传考虑文件内容相同文件名不同,上传过的秒传
spark-MD5根据文件内容生成hash,文件名变化不影响,通过SparckMd5.ArrayBuffer得到一个实例,通过该实例append一个buffer数据流
通过js api FIleReader 解析文件对象,实例有一个readAsArrayBuffer读取文件对象的流数据,读取是异步的,所以项目利用promise进行封装成功后利用resolve传出
根据end获取到hash,切两片列表中除了有一个chunk切片数据,还有一个filename: 由hash加索引
### 大数问题
#### 问题原因
这个问题并不难,但是在开发的时候没有注意到
1. 后端返回了一个列表数据,包含id,这个id是一个大数,列表进入详情,需要将id传入到详情页面
2. 详情页面内部通过id获取数据一直404,id不正确
3. 找问题,从路由传参到请求数据发现id没有问题,然后和后端进行联调,发现后端返回的id和我获取的id不一致
4. 实际问题产生的原因:后端返回了一个超过2的53次方的一个大数,而axios底层获取到后端原始json数据后通过JSON.parse处理,导致处理后的大数不精准了
#### 解决方案:
5. 让后端返回字符串格式
6. 前端处理:
- 后端返回原始数据, 不让axios处理, 我们处理然后在交给aioxs
- axios他有一个配置函数transformResponse, 获取到的后端返回的原始数据json数据,在transformResponse内部通过JSONBig(json-bigint)处理后端返回的json数据,他在处理json数据的时候,会将大数处理为对象,将大数对象转为字符串使用,得到了正确的id,内部重写了toString
```javascript
transformResponse: [
function (data) {
console.log(data)
// 1. 找到了处理的时机
try {
return JSONBig.parse(data) // a.num + ''
} catch (err) {
return data
}
},
],
```
### 断点/切片-流程
1. 通过element-ui的el-upload进行文件上传
2. 选择文件后触发el-upload组件的change事件,然后通过参数file内部的raw获取到文件对象
```javascript
onChange(file) {
// 1. 获取到文件对象
file = file.raw
// 2. 对文件对象进行切片
// 1. 固定数量 100
// 2. 固定大小 1MB
// 每块大小
const chunkSize = 5 * 1024
// 切割次数
const chunkCount = Math.ceil(file.size / chunkSize)
// 文件切片 不是数组的slice 文件对象原型上的slice方法,继承自 Blob的slice 二进制数据
const chunkList = []
for (let i = 0; i < chunkCount; i++) {
chunkList.push({
chunk: file.slice(i * chunkSize, i * chunkSize + chunkSize),
})
}
console.log(chunkList)
// 3. 将切片数据发送请求
},
```
3. 根据文件切片数量进行发送请求,一个文件切片发送一次
- 并行/一下发100个请求,很消耗性能,http最大请求并发_数_:6之后,再有_请求_进入会pending,并且会非常规律地每6个_请求_一批
- 串行: 一个一个请求
4. 断点续传
- 串行:点击暂停或者上传失败,return终止,这次请求完事后,下一次不在请求了
- 并行:点击暂停或者上传失败,return终止,取消请求
- chunList保存需要传送给后端的chunk,每上传成功一个,将chunkList中对应的chunk删除,下次重新请求从chunkList从头开始上传
5. 全部上传后,发送和并请求,后端会进行合并
6. 文件秒传,如果已经上传过,直接响应上传成功,所以还需要切名名字,切片名字不应该使用文件名,因为文件名可以修改,根据内容生成,使用spark-md5
- 安装spark-md5
- spark-md5将buffer数据处理hash(内容相同hash不变)
```javascript
function fileParse(file) {
return new Promise(resolve => {
const fileReade = new FileReader()
fileReade.readAsArrayBuffer(file)
fileReade.onload = res => {
resolve(res.target.result)
}
})
}
const buffer = await fileParse(file)
```
```javascript
const spark = new SparkMd5.ArrayBuffer()
spark.append(buffer)
const hash = spark.end()
```
```javascript
for (let i = 0; i < chunkCount; i++) {
const chunk = file.slice(i * chunkSize, i * chunkSize + chunkSize)
chunkArr.push({
chunk: chunk,
hash: hash + '-' + i
})
}
```
### 取消请求
axios上有一个CancelToken构造函数,通过该构造函数创建一个实例,将该实例绑定给需要取消请求接口的cancelToken属性上,然后当想取消请求的时候调用CancelToken内部返回的回调,调用改回调即可实现取消改接口的请求
### 图片懒加载的原理
监听图片是否在可视区域内,如果不在可视区域内图片的src可以显示一张雪花图或者显示默认占位,真正要显示的图片存在data-src中当图片在可视区域后,加载data-src的图片,保证data-src的图片加载完,在赋值给src,创建一个img元素,将创建的img元素的src设置为data-src,通过img的onload事件判断改img是否加载完成,如果加载完成将图片的src替换为data-src
如何监听图片在可视区域内:
利用滚动位置进行计算: 重复进行监听消耗性能
webapi: Intersection Observer 实现dom监听,并且是异步的,兼容性不好借助polyfill,或者使用intersection-observer npm包
[https://www.npmjs.com/package/intersection-observer](https://www.npmjs.com/package/intersection-observer)
### 实时更新数据库的数据
1. 轮询(性能不好)
2. websocket
我们开发实时通信采用的是websocket协议,没有直接使用h5提供的websocket,而是socketio包进行开发的,核心就是通过io方法传递链接的服务端的地址和参数,进行链接得到client实例
利用client实例提供的api进行实时通信
client.on('connect', () => {}) 链接成功
client.on('disconnect') 断开链接
client.on('message', () => {}) 接收服务端的消息
client.emit('message', () => {})
### 接口联调
1. 没有接口的时候使用mock模拟数据
2. 根据后端提供的接口文档和开发环境的接口进行测试, 使用APIpost或者postman主要测试后端接口能否跑通,是否有请求错误,或者响应的数据格式不正确,如果有问题及时和后端沟通
3. 根据后端提供的swagger接口文档进行接口的测试
### 接口文档
1. yapi
2. swagger
3. apipost
### 冒烟测试
真正的测试之前先测试基本的功能是否能够跑通,如果无法跑通打回
### 自定义指令
**语法:**
Vue.directive可以定义全局自定义指令
在组件选项对象中,通过directives进行局部自定义指令的定义
- 参数1: 自定义指令名字
- 参数2: 配置对象
- bind: 自定义指令和元素进行绑定(元素不代表渲染到页面上, 无法操作)
- inserted: 自定义指令所绑定的元素已经插入到dom中(操作dom)
- update: 自定义指令所在元素的虚拟dom发生更新后调用
- componentUpdated:指令所在组件的 VNode **及其子 VNode** 全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
**使用场景:**
- 默认图片
- 按钮权限
1. 关于自定义指令在项目中肯定要进行统一封装, 封装在direstives目录中,每一个自定义指令就是一个对象,key是自定义指令名字,value对象就是自定义指令的配置对象,然后在统一的引入注册
2. 项目中: 图片加载失败/按钮权限
图片加载失败:需要替换为默认图片,inserted,update,考虑图片最开始可能是null,请求完成后图片资源赋值,需要通过update监听图片所在的元素更新也要判断图片是否加载失败
按钮权限: 鉴权功能.api/路由菜单鉴权/按钮鉴权(自定义指令)
通过自定义指令将当前的按钮权限标识传入给自定义指令(v-isShowBtn="'add-user'"),自定义指令内部通过钩子函数通过第二个形参的value接收到权限标识,通过权限标识和后端返回的拥有的权限标识进行匹配,如果没有匹配,将el通过remove移除
原理: 如果不会说一下思路
我的理解是这样的.当然了因为这个确实没有看过,所以我说一下我的理解,vue源码在编译模板的时候,会通过compileTofunction这个方法生成render函数,通过render函数生成虚拟dom,虚拟dom疏对比.利用patch方法进行dom的更新
通过parseHtml方法解析模板,解析模板的过程就是使用大量正则匹配,生成AST语法树,将AST语法树生成了render函数,所以呐原理应该是在模板编译的时候匹配到v-开头的,然后获取到该指令后通过Vue.directives判断是否为自定义指令,如果是自定义指令在进入directives这个方法,该方法内部,通过传递的配置对象实现该功能
### 骨架屏
骨架屏(Skeleton Screen)是指在页面数据加载完成前,先给用户展示出页面的大致结构(灰色占位图),不会造成网页长时间白屏或者闪烁,在拿到接口数据后渲染出实际页面内容然后替换掉。Skeleton Screen 是近两年开始流行的加载控件,本质上是界面加载过程中的过渡效果。Skeleton Screen 给用户一种资源是逐渐加载呈现出来的感觉,使得加载过程变得流畅。
### 封装组件的思想
复用\抽离
复用: 复用性、灵活性
- 属性传值: 方便传递数据, 通过数据进行配置
- 结构: 通过slot插槽,进行自定义
- 逻辑: this.$emit,暴漏事件
### 你在开发的时候遇到问题如何解决?
- 分析: 根据错误定位出现错误的位置
- 分析代码的业务,检查一下代码,调试代码
- 直到代码调试成功, 可以通过打断点\删代码\请求的还需要配合network
- 调式到没有思路了
- 借助其他的参考/stackoverflow/掘金/csdn
- 三方库/借助官方文档或者github的isuse
- 搭梯子: 谷歌
- 技术交流群
## javascript
### bom和dom
**bom**
概念:浏览器对象模型,将浏览器看做了一个对象
顶级对象: window
包含关系: bom是包含dom的
常见的api: location、navigator、screen、setInterval/setTimeout、locstorage、sessionStorage
**dom**
概念: 文档对象模型, 将网页文档看做了一个对象
顶级对象 :document
包含关系: 被bom包含因为document是在window上,所以dom是属于bom的
常见api: getElementById getElementByTagName querySelector querySelectorAll
### slice和splice两者的区别
- slice 用于截取, 参数1,开始的位置,参数2结束的位置(不包含)
- splice: 用于删除/新增 参数1: 开始的位置,参数2: 删除的个数, 参数3和参数3之后的所有参数新增的数据
### substr和subString
- substr: 用于截取,参数1: 开始的位置,参数2截取的个数
- substring: 用于截取,参数1: 开始的位置,参数2: 结束的位置(不包含)
### 使用过哪些es6?
[函数](http://liufusong.top/interview/javascript/es6.html#%E5%87%BD%E6%95%B0-%E5%B0%96%E5%A4%B4%E5%87%BD%E6%95%B0)
### 2. 聊一聊promise
**概念:**
promise是一个对象/构造函数,es6新增的,更加灵活的处理异步,可以配合async和await将异步代码变为类似同步的同步,也可以解决我们的回调地狱
promise有三种状态:
pending
fullfield
rejected
不可逆
pending => (resolve)fullfield
pending =>(rejecte) rejected
**实例方法:**
Promise.then\catch\finnaly
**静态方法:**
all\allsettled\race\any
**解决回调地狱:**
利用then,会返回一个新的promise,继续调用then,从而构成链式
**promise穿透**
reesolve的结果会交给then,但是then的参数如果不是回到函数,继续向下传
**终级解决方案:**
async await
await 用来修饰promise, async用来修饰await就近的函数,async修饰的函数返回值是promise,所以可以继续使用await修饰
静态方法:
all: 可以获取到多个promise处理异步的结果,all发起的异步是并行的,并且Promise.all的返回值是promise,所以可以调用then,这个then,all的所有promise都resolve成功后才执行,有任意一个reject即进入all的catch, all的then返回的结果就是对应的promise返回的数据
allSettled: 可以获取多个promise处理异步的结果,then,不管resolve还是reject都会执行then,then的返回结果是一个数组对象,对象内部会通过status记录状态,通过value记录值,status记录的状态: fulfilled/rejected
Promise.race 使用和 all 一样,但是只返回第一个结果,不管成功或失败
Promise.any 返回第一个成功的结果
### Generator
概念: 也是es6的,可以将函数的控制权交出,也可以利用generator更方便的控制异步,实际async和await就是他的语法糖
区分: 星号
如何交出控制权: 通过yield进行控制权交出,通过next逐步调用
如何处理异步: 可以通过yield配合promise达到类似async和await的效果,通过yiled返回promise,在promise中处理异步,等异步成功调用resolve,这样在外部可以通过next.value获取到promise,通过then等待成功后,执行下一次的next
而且对应的自执行generator函数有co库,可以去自执行generator
区别:

### new的过程
创建一个新对象
// 将新对象的__proto__ 指向了 Person.prototype
// 将构造函数内部的this指向新对象
// 执行构造函数给this(实例对象)添加属性或方法
// 默认return this
// 如果写了return 看 数据类型
// 数据类型是简单数据类型: return简单数据类型忽略 最终还是return this
// 如果数据类型是复杂数据类型: 最终得到是该复杂数据类型 return this无效
### 字符串反转
```javascript
'hello'.split('').reverse()
```
### forin/forof区别
for in:一般用来遍历对象,并且可以遍历原型对象,不建议循环数组,可以循环
for of:不可以进行遍历普通对象的,可以遍历数字字符串数组和 newSet 对象等等,并且可以进行 break 终止,不可以 return
### 中断for
- continue:终止该次循环,进入下一次,使用所有循环结构
- break:跳出循环,执行循环后的语句,如果多层循环,只会退出当前层循环
- return:终止循环,结束当前方法
### 统计数组中出现次数最多的元素
```javascript
// 统计出出现次数最多的元素 返回次数和元素
const arr = [1, 2, 3, 2, 3, 2, 3, 7, 8, 7, 6, 7, 5, 7, 0, 7, 7, 7, 7, 2, 5, 5, 5, 5, 5, 5]
function repeatCount(arr) {
// key 是元素 value: 次数
const obj = {}
let max = 0
let maxItem = null
for (let i = 0; i < arr.length; i++) {
const item = arr[i]
if (obj[item]) {
obj[item]++
} else {
obj[item] = 1
}
if (obj[item] > max) {
max = obj[item]
maxItem = item
}
}
console.log(maxItem, max)
}
repeatCount(arr)
```
### 递归使用场景
```javascript
export function transListToTree(data, pid) {
const arr = []
data.forEach((item) => {
if (item.pid === pid) {
// 当前: item 就是1级数据 item.id
const children = transListToTree(data, item.id)
if (children.length) {
item.children = children
}
arr.push(item)
}
})
return arr
}
```
### 聊一聊异步
**为什么有异步?**
js是单线程,如果js语言是多线程,在操作dom容易混乱,所以js就是单线程,问就是,如果某一个任务比较耗时,阻塞运行,js把耗时交给浏览器执行,交给浏览器执行的这些耗时任务: 异步任务
js在es5之前无法发起异步,es6的Promise可以发起异步
**异步执行流程**
1. 主线程先判断任务类型
- 如果是同步任务,主线程自己执行
- 如果是异步任务,交给宿主环境(浏览器)执行
2. 浏览器进行异步任务的执行,每个异步执行完后,会将回调放进任务队列,先执行完成的先放进任务队列,依次放入
3. 等主线程任务全部执行完后,发现主线程没有任务可执行了,会取任务队列中的任务,由于任务队列里是依次放入进来的,所以取得时候也会先取先进来的,也就是先进先出原则
4. 在任务队列中取出来的任务执行完后,在取下一个,依次重复,这个过程也称为 eventLoop 事件轮训
### es6模块化和commonjs模块化的区别
1. es模块化的导入: import import {} import * as
2. es6模块化的导出: export export default
3. commonJs模块化导入: require
4. commonjs模块化导出: module.exports module
### 继承的实现方式
### 闭包
### 垃圾回收
### 尖头函数和普通函数的区别
1. 箭头函数this指向上下文,普通函数this看调用方式
2. 剪头函数没有arguments对象,普通函数具备arguments对象
3. 箭头函数没有prototype, 普通函数具备prototype
4. 箭头函数不能用做构造函数,普通函数可以用做构造函数
## typscript
### ts的优势/好处
- 错误前置: ts是属于静态类型语言,先将ts编译为js文件,在编译的过程中就可以发现错误,实际配合vscode插件,写代码的时候就已经可以发现错误
- 对高级语法的兼容
- 项目后期维护会更好
- 代码提示帮助我们减少考虑可以使用哪些数据或方法
- ts自身支持类型推断,所以并不是所有的数据都需要添加类型约束
### ts的数据类型
- 原始数据类型
number/string/null/undefined/boolean
Array/Object/function
- ts新增的
元组/联合类型/字面量/枚举/any/never/unknow
any: 可以赋值任意数据类型
unknow: 可以赋值任意数据类型,但是不能任意调用属性或方法
### 什么是元组?
即限制长度,又限制类型,精准的限制每一项的类型
### 联合数据类型?
通过 | 进行多个累心过的联合
### keyof和typeof区别
- typeof: 获取一个数据的类型格式,获取到的数据格式
- keyof: 获取类型对象key的集合,得到的是key的联合类型
### 泛型
作用: 用来约束某个数据配合多个类型使用,保证类型安全,从而达到复用
例如: {a: '', b: '', c: 12}, {a: '', b: '', c: boolean}
泛型使用: 通过尖括号<定义类型变量>,某个数据类型不确定可以使用类型变量,使用泛型的时候<传递具体的类型>
泛型: 泛型函数/泛型接口/泛型type
### type和interface的区别
type:
- 给任意数据类型进行命名复用
- 可以进行扩展通过&扩展type/interface
interface:
- 给复杂数据类型进行类型复用
- 可以实现继承, A(interface) extends B(type/interface)
## http和https
### 区别
1. 默认端口
- http:80
- https:443
2. 传输过程
- http:明文,被截获不安全
- Https:密文,截获的是加密后的
3. ssl证书:_SSL 证书_就是遵守 SSL协议(它是在传输通信协议(TCP/IP)上实现的一种安全协议,采用公开密钥技术),由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能
- 是什么: ssl 证书
- 颁发: CA
- 干啥: 数据传输加密
- http:不需要
- https:需要
1. http:基于7层协议中的应用层(提供应用程序间的交换和数据交换)
2. https:基于7层协议中的传输层(传输层协议提供计算机之间的通信会话,并确保数据在计算机之间可靠地传输。)
物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
### 对称加密
发送方和接收方使用同一个密钥(一串字符串)进行加密和解密
1. 服务端使用密钥进行加密
2. 客户端使用密钥进行解密
但是第一次要传输一次密钥,如果密钥被拦截,就被破解了
性能好,速度快,缺点,密钥被拦截被破解
### 非对称加密
一个公钥,一个私钥
公钥加密,私钥解密
1. 服务端(私钥)
2. 客户端(公钥)
3. 客户端公钥加密传输(被拦截无法解密,需要用私钥)
4. 服务端通过私钥解密
优点:安全
缺点:性能差,耗时间
### https

使用非对称加密进行密钥传输,使用对称加密进行数据传输
如何保证首次传输的公钥是安全的,需要网站机构,进行网站和公钥的登记(CA机构,颁发证书,安全可靠)
### 状态码
1. 2xx: 请求处理成功状态码
- 200: 客户端发的请求被服务端正常处理
- 201:请求成功创建新的资源
- 202: 服务接受请求但还没处理
- 204: 请求成功但没有返回内容
2. 3xx:请求重定向
- 301: 永久重定向
- 302: 临时重定向
- 304: 服务端资源没有变化,使用强缓存
3. 4xx: 请求失败
- 400: 客户端请求的语法有误,服务端无法处理
- 401: 当前请求需要验证权限,一般token问题
- 403: 服务端拒绝访问资源
- 404: 资源不存在
4. 5xx:服务器问题
- 500: 服务器内部错误
- 503: 服务器停机维护/超负载,无法处理请求
- 505: 服务器不支持,或者拒绝支持在请求中使用的 HTTP 版本
## 浏览器+++
### 跨域
**跨域的原因是同源策略**
同源策略是浏览器提供的一种安全机制,可以防止跨站脚本攻击
也就是A网站请求B网站的资源,是否能够使用的问题
同源策略:协议(http/https)、域名/IP地址、端口号,一致则同源,代表是同一个网站,资源共享
有一项不同既不同源,代表是两个网站,此时资源不共享
**跨域的本质: 浏览器**
为什么会出现跨域?
当下,最流行的就是`**前后分离**`项目,也就是`**前端项目**`和`**后端接口**`并不在一个域名之下,那么前端项目访问后端接口必然存在`**跨域**`的行为.
解决:
JSONP: 利用的不是xhr请求, 利用的script标签的src可以跨域, 请求接口资源,同时携带callback回调函数名字, 将数据传给回调函数, 解决get不能解决post
cors: 后端开启
正向代理服务器原理图解: 只能用于开发期间

具体配置:
```vue
vue.config.js
devServer: {
proxy: {
'/api': {
target: '接口url地址'
}
}
}
```
**上线: nginx\将项目放置统一服务下**
**nginx: 反向代理**
在nginx服务器nginx.conf配置文件
```javascript
server {
listen 8083;# 监听的端口
server_name localhost; #监听的主机名 也可以是域名或者ip地址
location ~ /api/ {
proxy_pass http://localhost:8084;
}
location ~ /prod/ {
proxy_pass http://localhost:8085;
}
}
```
⚠️
正向代理:代理客户端
反向代理:代理的服务器
### 地址栏输入url过程
1. url解析:是合法url还是搜索关键词
2. dns解析:本地/hosts/dns服务器
3. TCP链接:通过ip地址/3次握手,1次:确认客户端发送能力,服务端接收能力;2次:确认服务端的发送能力、接受能力;此时服务端不知道客户端的接受能力;3次:确认客户端的接受能力
4. 发起http请求
5. 响应请求
6. 页面渲染: html 生成dom树,style生成css规则树,dom树和css规则树生成render树,render树进行layout(谷歌重排)/reflow(火狐回流),render树上的paint和paint setup事件将render树绘制为像素信息
16.6ms浏览器干啥了?
dom和css 由 GUI引擎线程进行解析的
js由JS引擎线程(v8)解析的
浏览器的刷新频率60hz,16.6ms一次
刷新一次,会执行js,如果js执行时间超过16.6毫秒,css样式重绘重排丢帧,原因就是js引擎线程和GUI引擎线程是互斥的
window.requestIdleCallback():
_requestIdleCallback_利用的是帧的空闲时间
时间切片, 没有执行完放到下一帧,余出时间渲染css
7. 断开链接: 是否有keep-alive,有:关闭网页断开,网页运行期间,如果有新的请求,不会重新链接断开; 没有keep-alive;请求完毕断开链接,下次请求重新链接
8. 4次挥手:1. 客户端发出断开/等待,客户端进程发出连接释放报文,所以FIN=1;2. 服务端同意,响应服务端收到FIN之后,如果同意断开就发回一个ACK确认,ACK=1。根据规定,确认号ack=u+1,然后带上一个随机生成的序列号seq=v。此时处于半链接,有可能有数据没发送完,继续发送未发送完的数据;3. 服务端发送完成/等待状态,4. 客户端确认收到数据,服务器只要收到客户端发出的确认就进入关闭状态。服务器结束TCP连接的时间要比客户端早
### 如何应对xss攻击
概念: 跨站脚本攻击,原理: 通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序
场景: 评论功能,或者通过v-html渲染
解决: 采用三方库,dompurify,使用比较简单,装包,通过该包中的sanitize方法可以将要渲染的数据提前进行过滤,过滤掉可能会有攻击的代码
### csrf攻击
CSRF(Cross Site Request Forgery, 跨站域请求伪造)是一种网站攻击方式
攻击原理:利用请求自动携带cookie
攻击流程:
1. 张三登录某银行网站(http://mouyinhang.com)
2. 登录成功将用户信息存储到cookie中
3. 在银行网站引导张三进入(攻击网站)
4. 再攻击网站中向银行网站发送转账请求(http://mouyinhang.com/zhuangzhang)会自动携带cookie,
5. 攻击成功
A网站 => 登录 => 用户信息存到cookie => 张三跳到B网站 => B网站发请求 A网站/转账请求 自动携带cookie => 服务端 用户信息 处理成功
A网站 => 登录 => 用户信息存到cookie => 转账 发起请求携带referer/www.a.com => 服务端 获取referer, referer是合法的,相应成功,转账成功
A网站 => 登录 => 用户信息存到cookie => 张三跳到B网站 => B网站发请求 A网站/转账请求,没有携带referer 自动携带cookie => 服务端一看,没有referer, 不知道是请求是在哪里过来的,相应失败
解决方案:
1. 在请求头中添加referer字段,向服务端表明来源,服务端通过监测来源是否合法
2. 服务端在返回一个token,客户端将token存储后,每次都在额外携带一个token
3. 加入验证码
### SQL注入
SQL 注入就是在用户输入的字符串中加入 SQL 语句,如果在设计不良的程序中忽略了检查,那么这些注入进去的 SQL 语句就会被数据库服务器误认为是正常的 SQL 语句而运行,攻击者就可以执行计划外的命令 或访问未被授权的数据。
- 过滤SQL语句关键词/select/update。。。
### ddos
DDoS 攻击,全称是 Distributed Denial of Service,翻译成中文就是分布式拒绝服务
攻击者短时间内向目标服务器发起大量请求大规模消耗主机资源,从而导致其他人无法正常访问
解决方案:
后端/运维处理
高防服务器:花钱,有人保护
黑名单:拉黑ip
ddos清洗:会对用户请求数据进行实时监控,及时发现 DOS 攻击等异常流量,在不影响正常业务开展的情况下清洗掉这些异常流量。
cdn加速
## webpack
### loader/plugin/babel的区别
loader: 解析非js文件
plugin: webpack的插件,增强webpack的功能
babel: 语法降级,使浏览器进行兼容
### 项目中配置babel
1. 安装需要的包
2. 在项目中通过.babelrc或babel.config.js进行配置
3. preset: 预设,转换的语法 es6 => es5
4. env: 根据环境进行配置
- development
- production
- test
plugins: 插件,例如移除console的插件应该在production环境中使用
### webpack打包优化
1. soucemap,将打包压缩后的文件进行映射,从而可以让项目出现问题,精确的定位到开发代码的哪一行,但是会产生很多的map文件,所以生产阶段需要关闭: productionSourceMap: false
2. splitChunks:将公共代码进行提取,webpack在打包时,,如果某一个文件在很多文件中使用会被重复打包:
- 通过chunks进行提取方式的配置
- all:不管同步还是异步都提取一个文件
- initial:同步使用了,异步也使用了,提取两次
- async:只提取异步加载的模块
- cacheGroups:配置提取方案
- name:生成的名字
- test:匹配的路径或资源名称
- chunks:单独进行chunks
- priority:优先级,数越大,优先级越高
- reuseExistingChunk:如果要提取的模块已存在直接复用
- minChunks:最小引用次数
3. vue-cli3默认开启prefetch,在加载首页的时候,就会提前获取用户可能访问的内容,提前加载其他路由模,所以我们要关闭该功能
这里要注意:不会影响首屏的加载速度,实际是为了优化子页面,可以快速打开子页面,但是像移动端,用户可能只会访问首页,也预加载其他模块的资源,浪费用户流量
config.plugins.delete('prefetch')
4. 打包成gzip,可以进行资源请求的时候速度更快,通过compression-webpack-plugin将文件打包成压缩包 在chainwebpack中配置,可以通过配置项修改打包的阀值,文件名\其他配置的,需要服务器nginx配置
5. runtimeChunk:运行时chunk,异步加载的代码,如果不开启,运行时代码或者代码没有发生变化,项目重新打包,此时我们的主模块会进行重新打包,的hash会发生变化(app.sdfjkad123(hash).js),项目部署后,会导致强缓存失效;开启runtimeChunk,会将运行时代码信息单独的存放到runtime.h12h3.js文件中,此时我们的主模块代码没有发生变化,或者运行时代码发生变化,都不会影响到主模块,所以主模块不会重新更新,主模块可以继续使用本地缓存;但是还需要配合script-ext-html-webpacl-plugin,将runtimeChunk代码生成到行内,如果是一个单独的文件,多发起一次请求
6. 通过image-webpack-loader进行图片的打包压缩
7. 开启路由懒加载 将每个路由进行单独打包
### 自己搭建脚手架
1. 初始化项目的基本目录和package.json文件
2. 安装webpack webpack-cli包
3. 默认会查找到src/index.js文件进行打包,最终输出dist内部index.js
4. 查找入口之前,加载webpack.config.js
- 打包的入口: input
- 打包的出口: output
5. 配置babel: webapck默认只能处理低级的js文件,利用babel进行降级
- 下载预设和插件
- babel.config.js进行配置: plugins: 用于配置插件,preset: 用于配置预设
6. 配置loader: 解析三方文件(css/less/scss/png/jpg/vue..)通过loader进行解析
- 下载loader css-loader/less-loader/vue-loader
- 在webpack的config.js文件中的moudle/rules节点进行配置,通过test匹配后缀文件,{test: /\.css/, loader: ['css-loader']}
7. plugin插件: html-webpack-plugin, 可以打包html文件,并且会自动引入打包的js文件
- 在webpack.config.js文件中引入插件
- 将插件放入到: webpack.config.js中plugins: []数组中, 一般插件都是构造函数,在new的时候通过参数对象进行配置
- mini-css-extract-plugin: 用于抽离css文件
```javascript
import HtmlWebpackPlugin from 'html-webpack-plugin'
module.exports = {
plugins: [new HtmlWebpackPlugin({
template: '/public/index.html',
filename: 'index.html'
})]
}
```
### babel的原理
1. 将代码转换为AST语法树
2. 将AST语法树进行遍历(babel-traverse),遍历的过程进行更新\添加\删除等等
3. 将处理的AST语法树转为新的js代码