# vue3_71
**Repository Path**: ifercarly/v3_71
## Basic Information
- **Project Name**: vue3_71
- **Description**: Vue3 71 期上课笔记
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 11
- **Forks**: 1
- **Created**: 2022-04-01
- **Last Updated**: 2023-01-05
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 0. 脑图链接
Vue3 day01: https://www.processon.com/view/link/6246d3ec0e3e74078d609f46
## 1. 如何介绍一个项目
项目背景、功能、技术栈、我负责的模块(心里面一定要准备碰到的问题以及解决方案)
## 2. 如何封装组件的
编辑/添加,公告头部的封装,每个页面都会用到的面包屑...考虑过哪些技术点:传值和校验、插槽和作用域插槽、自定义事件
## 3. 登录怎么做的
收集数据 => 前端进行校验 => 通过 Axios 提交到后端 => 后端校验成功后返回 Token 到前端 => 前端拿到 Token 后做了 2 件事件(存储到 Vuex 并持久化到本地)
有权限的接口会通过请求拦截器给请求头统一携带 Token。
界面访问控制:在路由全局前置导航守卫通过判断 Token 去实现的。
Token:过期...
## 4. APP
Web APP、可以安装的应用程序(Android、IOS、Android/IOS 提供 Webview + 套(网页))、uni-app 可以把网页打包成可以安装的应用程序
## 5. Git 工作流
**Gitlab** 公司内部私有部署的代码管理平台。
master => 打包上线
release => 测试分支
develop => 开发分支(普通开发者具有权限的分支)
功能分支 => 一般我在开发新功能的时候,会基于 develop 开一个自己的功能分支,在上面写代码,写完之后再合并到 develop
## 6. 这个项目接口请求是怎么做的/你对 axios 封装过哪些东西
首先我们会划分三个模块:`utils/request.js`、`api/*`、`组件.vue`
一般封装 axios 的时候会封装:首先创建一个 axios 实例、baseURL、timeout、请求拦截器(统一携带 Token、Token 过期时候的前端主动介入)、响应拦截器(成功的时候对数据进行脱壳、失败的时候统一错误提示、Token 过期处理)、transformResponse
Token 过期时候的前端主动介入:登录成功存一个时间戳、请求拦截器中,用当前时间戳 - 登录成功时候的那个时间戳,如果大于超时时间了,就直接拦截到登录页。
## 7. 每次发请求的时候,希望全局开启一个进度条,请求完毕关闭进度条
问题:在那做,做什么?
请求拦截器里面开启进度条,响应拦截器关闭进度条。
## 8. SEO
对外的,一般都要考虑 SEO,像商城网站(SSR、Nuxt.js)。
对内的,不需要,像后台管理系统。
## 9. CDN
内容分发网络:公司花钱 => 七牛 CDN => 买空间 => 把自己的资源上传到此空间 => 得到 CDN
## 10. 和 Vue 相关的性能优化的手段都考虑过哪些
- v-for 循环的时候加 key
- v-for 和 v-if 不要放一行(计算属性)
- v-if 和 v-show 区分使用场景
- 路由懒加载、组件动态加载
- KeepAlive
- computed 和 方法区分使用场景(优先 computed)
- ...
## 11. Websocket
轮询或 Websocket
## 12. 你们公司接口文档是怎么交付的
SwaggerUI:一套接口生成的集成方案,是后端生成接口文档的框架,一般后端会部署一个地址,提供给我。
## 13. 开发项目的时候,有没有碰到过数据明明变了,但是视图没有变,这种情况?
- 什么情况?
Vue2 有 2 种情况修改数据不是响应式的:给对象不存在的 key 进行赋值;通过索引修改数组的内容;
```
const obj = {}
obj.age = 18
```
```
const arr = ['a', 'b', 'c']
arr[0] = 'd'
```
- 为什么?
给对象不存在的 key 进行赋值不是响应式的为什么?
由于 Vue2 是通过 Object.defineProperty 递归劫持的 data 里面的每一个【属性】,一上来就进行了递归劫持的操作,所以后续添加的属性当然就没有被劫持到!
通过索引修改数组的内容为什么不是响应式的?
性能
- 怎么解决的?
```js
// const obj = {}
// obj.age = 18
// 解决
this.$set(this.obj, 'age', 19)
Vue.set(this.obj, 'age', 19)
```
```js
// const arr = ['a', 'b', 'c']
// arr[0] = 'd'
// 解决
this.$set(this.arr, 0, 888)
// 使用数组的变更方法
this.arr.splice(0, 1, 888)
```
- Vue3 呢?
Vue3 对数组通过索引修改就是响应式的,因为 Proxy 对数组的处理不存在性能问题!
Vue3 给对象不存在的 key 进行赋值也是响应式的啦,因为 Proxy 它劫持的直接就是整个对象(而不是一个个的属性),所以就无所谓你这个属性是不是后续添加的啦!
## 14. 项目开发的流程是怎样的
## 15. 小程序和网页开发的差异
- 宿主环境不一样(网页=>浏览器,小程序=>微信软件)
- 开发方式不一样(微信开发者工具)
- 上线流程不一样
## 16. 上传图片怎么做的
1. 准备一个 `` 并通过 hidden 属性隐藏
2. 准备一个按钮,给按钮绑定点击事件,在事件的回调里面主动触发 input:file 的 click 事件
3. 监听 input 的 onchange 事件,在回调里面通过 e.target.files 拿到文件信息
4. 创建 formData 对象,把文件信息放进去通过 axios 传到后端
```js
const form = new FormData()
form.append('avatar', e.target.files[0])
```
5. 图片预览:URL.createObjectURL(Blob) 或 FileReader(base64)
```html
```
## 17. 后端接口还没有开发好,作为前端你是怎么进行下去的
Mock: 模拟数据;拦截请求;
```js
// mock/index.js
Mock.mock('/api/users', 'get', (req, res) => {
// 通过 req 拿到前端的信息
// 根据此信息返回对应的数据(Mock 的数据)
res.send({ mock 的数据 })
})
```
```js
// main.js
import './mock'
```
## 18. 组件传值
### 父传子
1、父传子的第一种方法
父组件通过自定义属性传递,子组件通过 props 接收?
接收到的数据可以改吗?简单数据类型不能改,复杂数据类型引用不能改内容可以改,但是即便如此,也不建议直接改,为什么?
单项数据流思想:如果任何一个地方都可以修改数据,应用复杂出现错误的时候,意味着这个错误可能是任何一个地方导致的,不方便追溯!
怎么处理比较好?数据在哪来的就在哪改!
2、父传子的第二种方式
父亲通过 ref 获取子组件实例,调用此实例的方法的同时并传递参数,儿子方法中接收到参数做对应的修改!
3、父传子的第三种方式
```js
this.$children[0].changeAge(this.age)
```
4、父传子的第四种方式
```js
// 非 props 属性
this.$attrs
```
### 子传父
1、儿子通过 $emit 触发父亲自定义事件的同时并传递数据,父亲监听自定义事件的同时在回调里面进行数据修改的操作
2、this.$parent 拿到父组件实例的同时,调用方法并传参
3、例如父传子,只不过传递的是一个方法,子组件调用这个方法的同时并传递参数
4、通过 this.$listeners 拿到父亲的自定义事件,调用并传参
### 兄弟
1、状态(数据)提升
A 修改 B,把 B 中的数据提升到公共的父组件里面,A 通过**子传父**修改父亲的数据,父亲通过**父传子**传递把数据传递到 B
2、EventBus(事件中心、发布订阅)
Vue3 中 EventBus 被废弃了,你觉得为什么?
太过于灵活,大型项目不太方便追溯问题;由于 EventBus 本身实现起来**足够简单**,Vue3 处于自身体积更小的考虑,所以被废弃了!
如果我还想再用怎么办?官方推荐了两个包,例如 [mitt](https://github.com/developit/mitt) 或 [tiny-emitter](https://github.com/scottcorgan/tiny-emitter) 或者**自己实现一个**。
```js
export default class EventBus {
constructor() {
this.subs = {}
}
// 订阅 => 订阅的是一个回调函数
on(eventType, callback) {
this.subs[eventType]
? this.subs[eventType].push(callback)
: (this.subs[eventType] = [callback])
}
// 发布 => 发布事件让其(事件)对应的回到函数执行
emit(eventType, ...args) {
this.subs[eventType].forEach((callback) => callback(...args))
}
}
```
### 全局的数据通信和共享(Vuex)
回答 Vuex 分两方面:配置项;触发流程;
mutation 里面可以放异步吗?
非严格模式确实是可以放异步,代码也可以正常执行!严格模式下不能这样操作(写异步),会有警告。
不建议放异步代码,目的是为了形成数据快照(拿到当时的那个数据状态),为了配合 DevTools 调试。
## 19. 说一下权限管理怎么做的
### 路由级别的权限
用户登录成功之后,后端返回当前用户的标识;`['/user', '/news']`
前端拿到标识之后筛选出【有权限的路由】;`[{path: '/user', component: User}, {path: '/news', component: News}, ],#1`
然后做了 2 件事情!
第 1 件事情:通过 addRoutes/addRoute 把【有权限的路由】添加到路由实例;`router.addRoutes(#1)`,这样当前用户就具有了访问某个路由的权限啦;
第 2 件事情:把【有权限的路由】也添加到了 Vuex 一份,目的是为了给侧边栏或其他地方使用(通过 addRoutes 后续添加的路由,不能直接通过 router 获取到,官方推荐放到 Vuex 存储);
### 按钮级别的权限
封装一个全局的方法/指令,这个方法呢?只做一件事件,接收一个标识,内部进行判断,看一下这个标识这不在后端返回的功能列表里面,在就返回 true,不再就返回 false。
```js
function isFn(flag) {
return ['a', 'b', 'c', 'DELETE'].includes(flag)
}
```
在需要做按钮权限控制的时候,调用这个方法,根据返回的是 true 还是 false,对这个按钮做禁用/启用或显示/隐藏的操作。
```html
```
## 20. 继承
```js
// #1 Call 式继承 / 构造函数继承:继承的是属性
Person.call(this, name, age)
// #2 原型继承:继承的是方法
Star.prototype = new Person()
Star.prototype.constructor = Star
// 组合继承 = 构造函数继承 + 原型继承
```
不过,现在呢,我更喜欢使用 ES6 的 extends...
```js
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
say() {
console.log('!!!')
}
}
class Star extends Person {}
const s = new Star('尼古拉斯', 40)
console.log(s.name, s.age)
s.say()
```
extends
```js
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
say() {
console.log('!!!')
}
}
class Star extends Person {
// 儿子有 constructor 一定要有 super,有 constructor 的目的是为了添加 Star 自己的属性
constructor(name, age, money) {
super(name, age)
this.money = money
}
}
const s = new Star('尼古拉斯', 40, 1000)
console.log(s.name, s.age, s.money)
s.say()
```
## 深拷贝
1. JSON.stringify 的问题
```js
// ..........JSON.stringify 的确点............
// 1. 会忽略值为 undefined、函数、Symbol
// 2. 当值为 bigint 或循环引用的时候会报错
// 3. 日期会被转成字符串、正则会被转成空对象
// 4. ...
```
2. 三个点和 Object.assign 都是浅拷贝
```js
它们都是用来进行浅拷贝的,好的地方是能拷贝 undefined、函数、Symbol、正则...
```
3. 自己实现深拷贝:递归浅拷贝
```js
// const copy = (target) => {
// const type = Object.prototype.toString.call(target)
// // 正则、日期
// if (/(regexp|date)/i.test(type)) return new target.constructor(target)
// // 错误对象
// if (/error/i.test(type)) return new target.constructor(target.message)
// // 函数
// if (/function/i.test(type)) return new Function('return ' + target.toString())()
// // null 和 简单数据类型
// if (target === null || typeof target !== 'object') return target
// // 数组和对象
// /* const arr = []
// const obj = {} */
// const result = new target.constructor()
// for (const attr in target) {
// result[attr] = copy(target[attr])
// }
// return result
// }
```
4. 如何循环引用
```js
// const copy = (target, m = new Map()) => {
// const type = Object.prototype.toString.call(target)
// // 正则、日期
// if (/(regexp|date)/i.test(type)) return new target.constructor(target)
// // 错误对象
// if (/error/i.test(type)) return new target.constructor(target.message)
// // 函数
// if (/function/i.test(type)) return new Function('return ' + target.toString())()
// // null 和 简单数据类型
// if (target === null || typeof target !== 'object') return target
// // 数组和对象
// /* const arr = []
// const obj = {} */
// // #2 m 里面存储了 target 就直接返回
// // console.log(target, 233)
// if (m.get(target)) return m.get(target)
// const result = new target.constructor()
// // #3
// m.set(target, result)
// for (const attr in target) {
// // #4 传递 m
// result[attr] = copy(target[attr], m)
// }
// return result
// };
```
5. 实际我怎么做的
```js
const o = _.cloneDeep(obj1)
```
## 防抖和节流
```js
// 是什么?
// 防抖和节流都是性能优化的一种手段。
// 防抖:持续触发不执行,不触发的一段时间后才执行。
// 节流:持续触发也执行,只不过,执行的频率变低了。
// 实现一个?
// 匈牙利命名法的简版
// 类型 + 具体的含义
// const iNum = 8
// const bBar = false
// const aDiv = document.querySelctorAll('div')
// 把鼠标相对于盒子位置放到盒子里面
/* oDiv.onmousemove = function (e) {
let x = e.pageX - this.offsetLeft
let t = e.pageY - this.offsetTop
this.innerHTML = `x: ${x}, y: ${t}`
}; */
// 防抖一下
/* let timer = null
oDiv.onmousemove = function (e) {
clearTimeout(timer)
timer = setTimeout(() => {
let x = e.pageX - this.offsetLeft
let t = e.pageY - this.offsetTop
this.innerHTML = `x: ${x}, y: ${t}`
}, 200)
}; */
// 封装一个防抖/节流函数?
/* const debounce = (callback, time) => {
let timer = null
return function (e) {
clearTimeout(timer)
timer = setTimeout(() => {
callback.call(this, e) // window.callback(e)
}, time)
}
} */
/* oDiv.onmousemove = _.debounce(function (e) {
let x = e.pageX - this.offsetLeft
let t = e.pageY - this.offsetTop
this.innerHTML = `x: ${x}, y: ${t}`
}, 200) */
// 生活中的例子?
// 王者荣耀英雄回城是防抖还是节流
// 打散弹枪,节流
// 应用场景?
// 根据输入的内容请求接口?防抖
// 获取窗口缩放的大小,滚动位置?节流
// 实际开发怎么做?
oDiv.onmousemove = _.throttle(function (e) {
let x = e.pageX - this.offsetLeft
let t = e.pageY - this.offsetTop
this.innerHTML = `x: ${x}, y: ${t}`
}, 1000)
```
## 原型链
定义:多个对象之间通过 `__proto__` 链接起来的这种关系就是原型链。
## Promise
`.catch() 之后还能再触发 then 吗?`
```js
const p = new Promise((resolve, reject) => {
reject(new Error('233'))
})
p.catch((r) => {
console.log(r)
// return Promise.resolve(undefined)
}).then((r) => {
console.log(r)
})
```
如何实现并发请求,按顺序拿到结果?
```js
Promise.all([p1, p2]).then((r) => {
// r => [p1 的结果, p2 的结果]
})
// const p1 的结果 = await p1
// const p2 的结果 = await p2
```
## Vue2 双向数据绑定原理
```js
// 需求:双向数据绑定(数据变了,视图改变;视图改变,数据也变)
const data = {
name: 'ifer'
}
/* const tempData = { ...data }
Object.defineProperty(data, 'name', {
get() {
// 获取 name 的时候会走这儿
return tempData.name
},
set(newValue) {
// 设置 name 的时候会走这儿
tempData.name = newValue
}
})
// console.log(data.name) // 'ifer'
data.name = 'elser'
console.log(data.name) */
/* const tempData = { ...data }
Object.defineProperty(data, 'name', {
get() {
// 获取 name 的时候会走这儿
return tempData.name
},
set(newValue) {
// model => view => Data bindings
// #2 设置 name 的时候会走这儿
tempData.name = newValue
oP.innerHTML = newValue
oInput.value = newValue
}
})
// view => model => DOM Listeners
oInput.oninput = function (e) {
// #1
data.name = e.target.value
} */
/* oInput.oninput = function (e) {
oP.innerHTML = e.target.value
} */
```
Vue3
```html