# vue3_ts_77 **Repository Path**: ifercarly/vue3_ts_77 ## Basic Information - **Project Name**: vue3_ts_77 - **Description**: Vue3、TS、Pinia... 全家桶 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 10 - **Forks**: 3 - **Created**: 2022-07-14 - **Last Updated**: 2024-12-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: 77 ## README ## 00\. 总结 Day01: https://www.processon.com/view/link/62cff0cd07912906d7981fdc Day02: https://www.processon.com/view/link/62d142700e3e74607272ca97 Day03: https://www.processon.com/view/link/62d4a9631efad4037a10a059 Day04: https://www.processon.com/view/link/62d5351be401fd2596077274 Day05: https://www.processon.com/view/link/62d7d7160e3e741658b23a2a ## 01\. 后端接口还没开发完毕,你该怎么进行下去? 一般我会使用 Mock.js 这个工具包(JS 库),这个 JS 库有两个作用,第一它能拦截请求,第二它能模拟数据。 具体做的时候我会这样,新建一个 mock.js 文件,在项目的入口里面引入这个文件。 ```js import Mock from 'mock' Mock.mock(请求地址,请求方式,() => { // 模拟数据 return 模拟好的数据 }) ``` ## 02\. 后端怎么给你提供接口文档的? SwaggerUI:后端用来生成接口文档的一个框架,后端会通过这个框架生成接口文档地址,给前端使用。 ## 03\. 对外的这个商城项目你是怎么考虑 SEO 优化的? Vue 开发的页面是一个单页应用,说白了打包出来的代码只有空标签,后续的所有内容都是通过 JS 来加载过来(客户端渲染,CSR)。 说法 1:`Nuxt.js` 基于 Vue 的(服务端渲染,SSR)。 说法 2:是一个移动端套壳的项目。 ## 04\. 登录你开发了多久呀? 从写静态页面、接口联调、前端业务逻辑,前前后后差不多 2 天左右的样子。 ## 05\. 界面访问控制是怎么处理的? 在哪做的:是在路由全局前置导航守卫(beforeEach)里面处理的。 做了那么事情:如果有 token => 就 next 放行,如果没有 token,看一下访问的路径在不在白名单,在的话就 next 放行,不再就跳转到登录页。 ## 06\. 图片懒加载的原理? 如果图片进入了可视区,就把装图片地址的某个属性值给图片真正的 src 属性。 ```html ``` 如何判断图片是否进入了可视区呀? 第一种方式:位置判断(图片距离窗口顶部的距离 < 可视区的高度) ```js if (oImg.getBoundingClientRect().top - document.documentElement.clientHeight < 0) { 进入可视区了 } ``` 第二种方式:IntersectionObserver,可以检测某个 DOM 节点是否进入可视区 ## 07. 客户端如何拿到服务端实时的消息? 1\. 我们这个应用,需要用户手动刷新。 2\. 客户端轮询(开个定时器每隔一段时间发请求)。 3\. Websocket(一旦建立了连接,服务端能主动往前端推消息)。 ## 08. 图片上传怎么做的? 1\. 准备一个 input type file 框并隐藏。 ```html ``` 2\. 点击上传按钮,触发 type file 框的点击事件。 ```html ``` 3\. 监听 type file 框的 onchange 事件,通过 `e.targetf.files` 拿到文件信息。 ```js oFile.onchange = function() { e.target.files } ``` 4\. 创建 FormData 对象,把拿到的文件信息 append 到这个对象。 ```js oFile.onchange = function() { const file = e.target.files[0] const formData = new FormData() formData.append('avatar', file) formData.append('name', 'xxx') } ``` 5\. 使用 axios 把数据传到后端。 ```js oFile.onchange = function() { const file = e.target.files[0] const formData = new FormData() formData.append('avatar', file) formData.append('name', 'xxx') axios.post('/upload', formData) } ``` 6\. 前后上传相同的文件信息的时候的一个坑(不会再走 change 里面的业务代码了),解决方式就是每次清空。 ```js oFile.onchange = function() { const file = e.target.files[0] const formData = new FormData() formData.append('avatar', file) formData.append('name', 'xxx') axios.post('/upload', formData) this.value = '' } ``` ## 09. 图片预览是怎么做的? 1\. 把图片上传到后端,后端返回一个地址,前端把这个地址给图片的 src 属性。 2\. 前端可以把图片信息转成地址去显示。 ```html ``` ## 10. 项目介绍? 1\. 项目背景。 2\. 项目功能。 3\. 技术栈和团队情况。 4\. 我负责的模块(亮点和难点心里准备好)。 5\. ... ## 11. 你公司项目的开发流程是怎样的? 1\. 产品经理画出原型图(axure、墨刀、蓝湖),列出需求。 2\. 会召集相关人员(测试、前端、后端、设计)开会讨论需求(合理性、开发周期...)。 3\. 前端老大认领需求,根据实际情况进行拆分到每个人(会有每个人评估出开发时间)。 4\. 个人根据设计师提供的设计稿和后端提供的接口文档进行开发(Git 的工作流)。 5\. 测试、打包上线、运维、营销。 ## 12. 登录? 1\. 前端收集用户登录信息,进行校验。 2\. 校验通过把信息提交到服务端,服务端拿到信息之后进行加工处理然后返回用户表示(token)到前端。 3\. 前端拿到 token 之后把 token 持久化到本地(目的是持久化)并同步到 Vuex(方便获取和响应式)。 4\. 在全局前置导航守卫根据有无 token 做页面访问控制,有 token 就 next 放行,没 token 就看一下访问的这个页面在不在白名单,在的话同样调用 next 放行,不再就拦截到登录页。 5\. 在请求拦截器处统一携带 token。 ## 13. 跨域? 1\. 我们项目是基于 vue-cli 搭建的,前端可以通过配置代理服务器的形式来处理跨域。会在 `vue.config.js` 文件里面配置 webpack-dev-server 的 proxy 选项来代理某个地址到某个服务器。 2\. 后端通过 `Access-Control-Allow-Origin` 的响应头可以允许某些域名访问。 ## 14. 你公司用到的 Git 管理平台是什么? `Gitlab` ## 15. 这个 CDN 地址从哪儿来的? 是什么:内容分发网络。 国内常用的 CDN 服务商,七牛 CDN(花钱),需要把自己的资源传上去,拿到 CDN 地址直接使用。 ## 16. 权限? ==路由级别的权限== 1\. 用户登录成功之后,后端返回当前用户的标识。 ```js 标识 => ['user', 'news', 'channel'] ``` 2\. 前端拿到这个标识之后,筛选出有权限的路由(动态路由)。 ```js const filterRouter = [ { path: '/user', component: User }, { path: '/news', component: News }, { path: '/channel', component: Channel } ] ``` 3\. 接下来做了 2 件事情: 3.1 把筛选出来的路由通过 addRoutes 或 addRoute 添加到了路由实例,一旦添加到了路由实例,这个用户就拥有了访问某个路由的权限。 ```js router.addRoutes(filterRouter) ``` 3.2 把筛选出来的路由也添加到了 Vuex 一份,目的是为了给侧边栏去使用。 ==按钮级别的权限== 封装一个全局的指令或方法,这个方法只做 1 件事情,接收一个功能标识,看一下这个标识在不在后端返回的功能列表里面,在就返回 true,不再就返回 false。 ```js function btnQuanxian(tag) { return ['DEL', 'ADD', 'MODIFY'].includes(tag) } ``` 在做按钮权限控制的地方,调用这个方法并传递过去当前功能标识,根据这个方法返回是 true 还是 false,对当前按钮做禁用或启用,显示或隐藏的操作。 ```html ``` ## 17. 工作流? 1\. 我们的组长会在远端(Gitlab)建立 3 个分支:master、release(测试)、develop(开发)。 2\. 我会把远端的 develop 分支拉到本地,然后基于此分支开一自己功能分支(feature/问答)。 3\. 在自己功能分支上面写代码,完毕之后我会把自己的代码合并到 develop 并推送到远端。 我正在自己的功能分支上开发某个功能,开发了一半,突然在其他分支有一个紧急需求需要去处理。 可以通过 `git stash` 暂存一下当前写了一半的代码,切换到紧急分支去修复 Bug,修复完毕之后再切换回原来的分支,通过 `git stash apply` 把曾经暂存的代码拿过来,继续开发。 ## 18. Axios 封装? 怎么用:`utils/request.ts` 封装了具体的代码、`api/xxx` 具体模块请求函数的封装、`views/xxx` 视图去调用 api 中的请求方法。 怎么封:首先创建了 axios 实例,封装了 baseUrl、timeout 超时时间、transformResponse、请求拦截器、响应拦截器。 请求拦截器干了啥:统一携带 token、请求前开启进度条(nprogress) 响应拦截器干了啥:数据解包、关闭进度条(成功);统一错误处理、无感刷新(token 过期根据 refresh_token 再换取新 token,然后悄悄的把失败的请求再发出去)... ## 19. 项目要求? 1\. Vue3 + TS 如何开发项目:如何创建项目、怎么接收数据、如何触发自定义事件、interface 和 type 怎么用、学会 as 断言、知道把公共的类型提取到类型定义文件中。 2\. 封装组件:城市选择组件、数量选择组件、按钮组件、面包屑封装、骨架屏组件、图片懒加载原理和指令封装、数据懒加载 hooks 封装、动画的处理(Transition)、Message 组件封装(函数如何创建组件)、Dialog 组件封装、Checkbox 组件封装、Tab 选项卡(JSX 提高)、倒计时 Hook(函数)的封装、Axios 请求、本地存储...**传值和校验、插槽和作用域插槽、自定义事件、动画**。 3\. 商城网站的业务流程(核心业务:第三方 QQ 登录、购物车、支付...)。 ## 20. 编辑流程? 1\. 【根据订单 ID 获取订单详情】并进行填充数据。 2\. 编写自己的内容,收集到内容到调用【编辑接口】提交到后端。 3\. 【重新获取数据】。 ## 21. 移动端网页如何分享到微信? [官网](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html) 1\. 绑定域名到微信公众号的后台。 2\. 引入微信官网提供的 JS 文件。 3\. 通过 `wx.config` 接口注入权限验证配置。 ```js wx.config({ debug: true, // 开启调试模式,调用的所有 api 的返回值会在客户端 alert 出来,若要查看传入的参数,可以在 pc 端打开,参数信息会通过 log 打出,仅在 pc 端时才会打印。 appId: '', // 必填,公众号的唯一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名 jsApiList: [] // 必填,需要使用的 JS 接口列表 }); ``` 4\. 调用微信提供的相关分享接口。 ```js wx.ready(function () { //需在用户可能点击分享按钮前就先调用 wx.updateAppMessageShareData({ title: '', // 分享标题 desc: '', // 分享描述 link: '', // 分享链接,该链接域名或路径必须与当前页面对应的公众号 JS 安全域名一致 imgUrl: '', // 分享图标 success: function () { // 设置成功 }, }) }) ``` ## 22. 第三方登录 ### 业务流程 **【有账号已绑定】**(QQ 账号和小兔先账号):直接跳转到首页。 **【有账号未绑定】**:就直接输入已有账号,然后点击提交(提交之后就会和已有账号绑定并跳转)。 **【无账号未绑定】**:就直接输入新账号,然后点击提交(提交之后就会和新账号绑定并跳转)。 ### 前期准备 公司运维负责和第三方平台进行的对接,最终给我们前端提供了两个信息:**APPID** 和 回调地址,回调地址肯定是一个经过备案的域名,前端开发环境想直接在这个域名上去开发,需要配置系统 hosts 文件进行 DNS 解析,把这个域名解析到本地(localhost)。 ### 具体步骤 1\. 引入 QQ 提供的 JS 库。 2\. 处理**【有账号已绑定】**,调用 `QC.Login.check()` 方法检查登陆状态(说白了就是看一下是不是通过 QQ 扫码跳转过来的),如果是 QQ 扫码跳转过来的,通过 `QC.Login.getMe()` 拿到 **OPENID**(标识了当前 QQ 号),传递 **OPENID** ,调用后端接口进行登录,登录成功跳转到首页。 3\. 处理**【有账号未绑定】**,传递 **OPENID** 和已注册账号,调用后端接口进行登录,登录成功跳转到首页。 4\. 处理**【无账号未绑定】**, 传递 **OPENID** 和新注册账号,调用后端接口进行登录,登录成功跳转到首页。 ## 23. 加入购物车 通过 Vuex/Pinia 统一进行的管理,业务流程分为登录和未登录两种状态(根据有无 token 进行区分),登录的时候更新到线上,再获取最新的数据同步到本地(Pinia/localstorage);未登录直接同步到本地。如果说是本地的操作,最后在登录成功后要把本地的购物车数据和登录用户的购物车数据进行**合并**的操作。 ### 登录 1\. 加入购物车功能,传递 skuId 和 数量到后端。 2\. 处理购物车数据(例如计算有效商品、有效商品数量、有效商品价钱)并渲染。 3\. 删除购物车,需要传递 skuId 到后端。 4\. 【修改】,改变购物车中某个商品的选中状态,传递 skuId 和当前变化后的状态。 5\. 【修改】,改变购物车中某个商品的数量,传递 skuId 和当前变化后的数量。 6\. 【修改】,全选,传递选中后的状态(所有的 skuId)。 ### 未登录 根据 this.isLogin 如果是 false,就对本地的数据做如上的操作。 ## 24. token 和 cookie token 如果存储到 cookie 里面(存储到 cookie 里面这个事后端可以通过响应头植入,前端通过 document.cookie 也可以做) 其实就没必要在请求拦截器里面去携带了,即没必要 `config.headers!.Authorization = Bearer ${token}` 因为会自动携带!!! ## 25. Vue2 如何用函数的方式创建组件? ```js const 组件的构造函数 = Vue.extend(组件) function Message() { const dialog = new 组件的构造函数({ propsData: {}, }) document.body.appendChild(dialog.$mount().$el) } ``` https://gitee.com/ifercarly/daily-feedback/tree/01_Vue.extend/ ## 26. 支付? 如何拿到支付地址(后端提供的),基准地址(后端指定的,一般是接口的基准地址),包含了订单 ID(也是后端生成的)、**回调地址**(和支付宝平台对接的时候经过支付宝认证的一个地址),把这个支付地址给 a 标签的链接。 打开之后就会看到账号登录页面,登录后会进入付款页面,付款后会自动跳转到**回调地址**,回调地址里面包含了支付状态和订单 ID。 前端根据支付状态和订单 ID 获取支付详情并进行展示。 ## 27. 排序 ```html ``` ## 28. 判断数据类型 ```html ``` ## 29. 了解借用 ```html ``` ## 30. 了解迭代协议 ```html ``` ## 31. 数组去重 ```js const arr = ['a', 'a', 'b', 'c', 'b', 'a', 'd'] /* for (let i = 0; i < arr.length; i++) { for (let j = i + 1; j < arr.length; j++) { if (arr[i] === arr[j]) { arr.splice(j, 1) j-- } } } console.log(arr) */ // 统计数组中出现的次数 const o = {} for (let i = 0; i < arr.length; i++) { if (o[arr[i]]) { o[arr[i]]++ } else { o[arr[i]] = 1 } } // console.log(o) // { a: 3, b: 2, c: 1, d: 1 } // 假设法 let max = 0 let char = '' for (let attr in o) { if (o[attr] > max) { char = attr max = o[attr] } } console.log(`出现最多的字符是 ${char},出现了 ${max} 次`) ``` ## 32. 敏捷开发 说的是开发项目时候的一个方法论,或者说是能够提高项目开发效率的一个流程,一个方法。 done doing todo 昨天干了啥 现在正在干啥 将要做什么 ## 33. 防抖 ```html Document
``` ## 34. 节流 ```html Document
``` ## 35. Polyfill: forEach、map、filter、reduce、sort ```html ``` sort ```html const arr2 = [ { num: 5, }, { num: 1, }, { num: 4, }, ] Array.prototype.sort2 = function (callback) { for (let i = 0; i < this.length - 1; i++) { for (j = 0; j < this.length - 1 - i; j++) { if (callback(this[j], this[j + 1]) > 0) { ;[this[j + 1], this[j]] = [this[j], this[j + 1]] } } } return this } console.log(arr2.sort2((prev, next) => prev.num - next.num)) ``` ## 36. flat ```js // 扁平化数组后去重并排序 const arr = [1, [[2, 3, 8, 9, [[10, 1, [0, [99]]]], [4]], 1]] // console.log(arr.flat(100)) // console.log(arr.flat(Infinity)) // 使用 flat // const r = [...new Set(arr.flat(Infinity))].sort((prev, next) => prev - next) /* const r = Array.from(new Set(arr.flat(Infinity))).sort((prev, next) => prev - next) console.log(r) */ function flat(arr) { return arr.reduce((acc, cur) => acc.concat(Array.isArray(cur) ? flat(cur) : cur), []) } console.log(flat(arr)) ``` ## 37. 组合继承 ```html ``` ES6 extends ```html ``` ## 38. 传参 ````js // 第一种:路径传参 // 1 到底是不是路径参数,取决于后端 // /info/user/1 // 后端 // app.get(`/info/user/:id`, function() {}) // 第二种:query 传参,通过问号,会转换成 ? 号拼接的形式,/info/user?id=1 /* axios.get('/info/user', { params: { id: 1, }, }) */ // axios.get('/info/user?id=' + 1) // 第三种:请求体(POST、PUT、PATCH),body(请求体) axios.post('/info/user/id?age=18', { id: 1, }) ```` ## 39. 缓存 https://www.zhihu.com/people/weixiantx/posts ## 40. 说一下对 Node 的了解? 是什么:Node 是一个基于 V8 引擎的运行环境(软件)。 能干什么:能写后端服务、写接口、像 Electron 这种桌面端框架也能配合 Node 去使用,具有操作本地数据的能力、Node 在公司里面的主要应用是作为中间层来转发数据、开发各种脚手架工具,为前端生态注入生命力。 我对 Node 相关的 Web 框架也有一些了解,例如 Express 或 Koa 也能直接写 Web 应用。 ## 41. 说一下你对 Webpack 的了解 是什么:Webpack 是一个打包构建工具,能借助各种各样的 loader 把不同类型文件打包一起。 怎么用:一般用的时候我了解到的有这些配置项,例如可以通过 entry 配置入口,output 配置出口,通过 module 里面的 rules 选项配置匹配 loader 规则,通过 resolve 里面 alias 可以配置别名,通过 plugins 配置插件(HtmlWebpackPlugin 这个插件配置模板)来拓展 webpack 的能力,开发的时候也可以进行 devServer 服务的相关配置,上线的时候可以通过 external 选项来忽略某些包然后配置 CDN 的方式进行加载... 不过呢?现在开发中我更喜欢或者用的更多的是基于 Webpack Vue CLI 去创建项目,里面集成好了各种各样的配置,它提供的有一个 `vue.config.js` 可以直接进行二次按需配置。 ## 42. 移动端开发碰到问题 1\. IOS 下像 input、button 按钮它有自带的阴影效果,看上去很丑,可以通过下面办法结果。 ```css input[type="text"],input[type="button"] { -webkit-appearance: none; } ``` 2\. ios input 聚焦后,键盘收起页面留白的问题,解决方案:在失去焦点时重置滚动位置。 ```js function onInpuBlur() { setTimeout(() => { window.scrollTo(0, 0); }, 100); } ``` 3\. 移动端页面为了区分单机和双击,click 会有 300ms 延迟,可以通过 `fastclick.js` 进行处理。 4\. 上下拉动滚动条时卡顿、慢,解决如下。 ```css body {   -webkit-overflow-scrolling:touch;   overflow-scrolling: touch; } ``` 5\. 旋转屏幕时,字体大小调整的问题。 ```css html { -webkit-text-size-adjust:100%; } ``` 6\. **移动端 1px 问题**,其实我们写的 1px 在 iPhone dpr 为 2 的设备下表示 2 个物理像素点,导致看起来有点粗。 DPR: 物理像素比 => 物理像素 和 逻辑像素(px)的比值 假如在 DPR 为 2 的设备下,其实一个 1px(逻辑像素)占用 2 个物理像素点,所以我们写的 1px 看起来有点粗。 ```css div { /* border-bottom: 1px solid red; */ // 低版本的 IOS 不支持 0.5 border-bottom: 0.5px solid red; } ``` 通过缩放 ```html Document
``` 四个边框 ```html Document
``` ## 43. 深拷贝 版本 1 ```js const obj1 = { name: 'ifer', age: 18, s: Symbol(), fn: function () { return this }, reg: /\d/, un: undefined, d: new Date(), n: 2n, } const clone = (target) => { const type = Object.prototype.toString.call(target) // #1 日期或正则,'[Object date]' / '[Object regexp]' if (/(date|regexp)/i.test(type)) return new target.constructor(target) // #2 错误对象,'[Object error]' if (/error/i.test(type)) return new target.constructor(target.message) // #3 函数,'[Object function]' if (/function/i.test(type)) return new Function('return ' + target.toString())() // #4 简单数据类型 if (target === null || typeof target !== 'object') return target // #5 数组或对象 const result = new target.constructor() for (let attr in target) { result[attr] = clone(target[attr]) } return result } const obj2 = clone(obj1) console.log(obj2) ``` 版本 2 ```js const obj1 = { name: 'ifer', age: 18, s: Symbol(), fn: function () { return this }, reg: /\d/, un: undefined, d: new Date(), n: 2n, } const o = {} obj1.children = o o.parent = obj1 const clone = (target, map = new WeakMap()) => { const type = Object.prototype.toString.call(target) if (/(date|regexp)/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())() if (target === null || typeof target !== 'object') return target // !#2 取 target 对应的值 if (map.get(target)) return map.get(target) const result = new target.constructor() // !#1 target 当做 key,记一下 result map.set(target, result) for (let attr in target) { // #3 传递记忆 map 结构 result[attr] = clone(target[attr], map) } return result } const obj2 = clone(obj1) console.log(obj2) ``` ## 44. 兄弟通信 状态提升和 EventBus(发布订阅模式) ## 45. 双向数据绑定 Vue2 => defineProperty ```html Document

``` Vue3 => Proxy ```html Document

``` 观察者模式 ```html Document

``` Vue 和观察者模式的关系 // 1. Observer 对象负责对 data 中的数据进行递归劫持,都转换成 getter/setter 的形式。 // 2. 初始化数据的时候,就会触发 getter,调用 dep.addSub 添加 watcher。 // 3. 当数据变化后会触发 settter,会调用 dep.notify 方法发送通知,然后再调用 watcher 的 update 方法去更新视图;