# rz_86 **Repository Path**: ifercarly/rz_86 ## Basic Information - **Project Name**: rz_86 - **Description**: ~~~~~~~~~~~~~ - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 7 - **Forks**: 0 - **Created**: 2022-11-20 - **Last Updated**: 2023-01-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 1. 安装 vue-admin-template vue-element-admin 是一个完整的继承方法,不方便直接拿来二次开发,所以咱们选中的是精简后的 vue-admin-template。 a,从码云拉取代码 。 ```bash git clone https://gitee.com/panjiachen/vue-admin-template.git ``` b,切换到具体目录下。 ```bash cd vue-admin-template ``` c,安装所有依赖。 保证你的 NPM 源是正确的(在国内),如何查看源呢? ```bash npm get registry # 如果在这个 https://registry.npmjs.org/ 表示不对,因为是国外的,下的时候很慢 ``` 如何设置源到淘宝? ```bash npm config set registry https://registry.npmmirror.com/ ``` ```bash npm install ``` d,启动项目,查看 package.json 文件的 scripts 可知晓启动命令。 ```bash npm run dev ``` e,碰到的问题如下,自查自纠。 问题1. 'vue-cli-service' 不是内部或外部命令,也不是可运行的程序,解决如下: ```bash npm i @vue/cli-service -D ``` 问题2. 如果 Node 版本是 18.x 的,先卸载掉,再安装我发的 16.x 的。 问题3. 把网线拔了,换热点。 问题4. 注意 npm 镜像源。 ## 2. 如何把代码提交到远端? ```bash 1. 把 .git 文件删掉,因为拉过来的 .git 信息是【花裤衩】的 2. git init # 变成自己的仓库 3. git remote add origin 你的远程仓库地址 # 把本地仓库和远端的自己的仓库关联起来 4. git remove -v # 查看关联的地址,保证是自己的 5. git add . 6. git commit -m init # 第一次加 -u,后续再提交可以直接 git push 就行啦 7. git push -u origin master # 如果冲突了,想以当前提交的为准,强制提交 git push -u origin master --force ``` ```bash git branch 分支名 # 创建分支 git checkout 分支名 # 切换分支 git checkout -b 分支名 # 相当于上面两行,创建并切换分支 ``` 问题:我们之前切换分支之前,要先进行 `git add .`、`git commit -m msg`、然后才切换,你刚才为什么没有这样做呢? 如果你切换的是一个已经存在的分支,确实要保证当前分支是干净的,确实要 git add . git commit -m msg,然后切换,**建议**。 如果你切换的是一个新分支,无所谓,可以直接切换! ## 3. 你先做一下自我介绍吧? 你好面试官,我叫吕布,来自于**河南**,做前端开发 **3 年时间**了,上家公司在**杭州**那边工作; 我的主要技术栈是 **Vue**,对 Vue 比较熟一些,对 React 和小程序也有一些了解; 开发过的项目类型偏 PC **端**的会比较多一些,移动端的项目也有参与过开发; 我上家公司是做医疗方向的,所以对这方便的**业务**会比较熟一些; 大概是这样一个情况,谢谢你面试官。 ## 4. 掌握 Sass 的混入语法? ```scss // 定义:@mixin 名字 { 样式 } // 使用:@include 名字 @mixin clearfix { &:after { content: ""; display: table; clear: both; } } .box { // 相当于把上面的代码全部抄过来了一份 @include clearfix; } ``` ## 5. 说一下 axios 你是怎么封装的? 请求文件的封装:在 `utils/request.js` 里面创建一个 axios 实例,然后封装了 baseURL、timeout、请求拦截器、响应拦截器,并导出。 项目中是怎么用的:在 api 目录根据 `request.js` 中封装好的请求对象,再次创建请求函数并导出,一般在组件或 vuex 中的 actions 中使用。 ```js import request from '@/request.js' export const getUserList = () => { return request.get('/user') } ``` ## 6. 如何更方便的设置 NPM 源? ```bash npm i -g nrm # 全局安装一个命令行工具,叫 nrm,它可以很方便的设置源 nrm -V # 出来版本号,就表示安装好了 nrm ls # 查看有哪些源可以给我使用 nrm use taobao # 设置源到淘宝 npm get registry # 查看有没有设置成功 ``` ## 7. 关于跨域? 什么是跨域:当协议、域名、端口号任一不同就是跨域。 为什么有跨域:是**浏览器**处于安全性的考虑而做出的同源策略的限制。 解决方案如下。 后端处理:后端通过 CORS 去处理,原理就是通过设置一个 `Access-Control-Allow-Origin` 这个响应头来允许某些域名访问。 代理服务器:如果说是用 Vue CLI 创建的项目,可以在 `vue.config.js` 中配置 devServer 的 proxy 来代理到某个地址。 ## 8. 说一下对 Vuex 的理解? 是什么:Vuex 是一个全局的状态管理的 JS 库,它解决了非关系型组件之间的数据传递和共享的问题。 怎么用/配置项:state、mutations、actions、modules、getters、plugins。 触发流程:例如点击按钮发请求,希望把请求的结果存储到 vuex,说一下触发流程? 给按钮绑定点击事件 => dispatch action => action 中发请求 => 拿到结果之后通过 commit mutaion => 修改 state => state 中的数据是响应式的 => 所有视图用到 state 的地方自然就变了。 ## 9. 快捷键? Ctrl + J 命令行展开/收起 Ctrl + B 侧边栏展开收起 Ctrl + K / Ctrl + J 展开所有代码 Ctrl + K / Ctrl + 0 收起所有代码 Ctrl + K / Ctrl + { 收起当前的代码块 Ctrl + K / Ctrl + } 展开当前的代码块 Ctrl + D 选中所有的 Ctrl + P 跳文件 HOME / END / 上下左右 / Ctrl / Shift / Alt 任意组合,你就是高手了。 ## 9. 可选链操作符? ```js error.response?.data // 如果说 error.response 不为 null 或 undefined 才往右边取值,否则整体返回 undefined ``` ## 10. Hash 和 History 差异? 表现不一样:Hash 带 # 号,history 没有。 兼容性不同:Hash 不存在兼容性问题,History 是 H5 新增的,低版本 IE 不支持。 原理不一样:Hash 是监听 hashchange 事件拿到地址,用这个地址匹配对应的路由 path,展示对应的组件 component;History 是监听 popstate 事件,利用是 H5 history API 进行的跳转,也是拿到路由地址,匹配对应的路由 path,展示对应的组件 component。 History 需要服务端支持:History 对于服务端来说每次刷新都是一个新的请求,需要服务端支持(否则会 404),**服务端把所有浏览器地址发出的请求转发到 index.html 页面**,交给前端路由去托管。 ## 12. 构造函数中手动返回数据的问题? ```js // 什么是构造函数,取决于这个函数你怎么用 function Person(name, age) { this.name = name; this.age = age // 如果手动返回的是一个简单数据类型,会忽略 // return 8 // 如果手动返回的是一个复杂数据类型,其实 new 出来的结果就不再是实例了,而是这个复杂数据 return {} } // Person() // 普通函数 const p = new Person('ifer', 18) // 构造函数 ``` ## 13. 界面访问控制? 在哪做的? 在全局路由前置导航守卫 beforeEach 做了什么? 如果说有 token,看一下访问的页面是不是登录页,如果访问的是登录页,拦截到首页,否则执行 next() 放行; 没有 token,看一下访问的页面在不在白名单,如果在,执行 next 放行,否则拦截到登录页。 ## 14. Token 的失效处理? 前端主动处理:在登录成功后存一个时间戳(A),在请求拦截器处用当前的时间戳(B)减去 A,如果大于 Token 的有效时间了,就做退出相关的操作。 配合后端处理:在响应拦截器的 error 回调中,根据后端返回的错误状态码,如果是 401,表示 token 过期,直接做退出相关的操作。 ## 15. 兄弟通信? 需求:点击 A 中的按钮,让 B 组件中的数据 +1,A 和 B 是兄弟关系。 ![image-20221126121404565](README.assets/image-20221126121404565.png) **状态提升** 1\. 把操作的 B 组件的数据 count 提升到(剪切)公共的父组件 App。 2\. 利用自定义事件,在 A 组件点击按钮的时候修改父组件 App 的数据(子传父)。 3\. 再把父组件的变化后的 count 传递给 B 组件。 ![image-20221126121051384](README.assets/image-20221126121051384.png) ## 16. array-to-tree ```js function transArray2tree(list, id = '') { const arr = [] list.forEach(item => { if(item.pid === id) { const children = transArray2tree(list, item.id) if(children.length) { item.children = children } arr.push(item) } }) return arr } ``` ## 17. .sync 修饰符 干什么的:父传子,子再修改传递过来数据的一种便捷写法。 ```html this.$emit('update', 8) ``` 等价写法 ```html this.$emit('update:count', 8) ``` ## 18. 函数传参? ```js /* let a = 8 function fn(a) { a = 9 } fn(a) console.log(a) // 8 */ /* let a = 8 function fn() { a = 9 } fn(a) console.log(a) // 9 */ /* let a = 8 function fn(a) { a = 9 } fn() console.log(a) // 8 */ /* const a = [8] function fn(a) { a.push(9) } fn() console.log(a) // 没机会走了 */ /* const a = [8] function fn(a) { a.push(9) } fn(a) console.log(a) // [8, 9] */ /* const a = [8] function fn() { a.push(9) } fn(a) console.log(a) // */ const a = [8] function fn(a) { // a.push(9) a = [9] } fn(a) console.log(a) // 函数传参,如果传递的是简单数据类型,直接拷贝的是值,内部的修改不会影响外部 // 函数传参,如果传递的是复杂数据类型,直接拷贝的是地址/引用,函数内部对内容的修改会影响外部,对引用的修改不会影响外部 ``` ## 19. 校验规则? 添加子部门 name:从所有列表中找谁的 pid 等于我当前点击的这一行的 id,这个谁就是子部门,接下来从找到的子部门里面找 name 是否有和和我输入的相同的。 code:从所有列表中找谁的 code 等于我输入 code,如果有,就报错。 编辑 name:从所有列表中找谁的 pid 等于当前我点击这一样的 pid,这个谁就是兄弟(注意里面还包含了自己,记得要把自己排除)。 code:从所有列表中找谁的 code 等于我输入 code(记得要把自己排除)。 ## 20. 关于增删改查? 只要有问到增删改查,马上要能说出来思路,例如编辑。 1\. 点击某一个行的时候,根据当前行 id 请求详情,把详情赋值给 formData(formData 和表单是进行双向绑定的,所以会自动填充)。 2\. 收集数据。 3\. 点击确定按钮的时候调用编辑接口。 4\. 调用获取列表的接口,获取更新后的数据。 ## 21. 组件封装你都用到哪些技术? 1\. 传值和校验。 2\. 插槽和作用域插槽(父组件和拿到子组件的数据,然后进行加工处理后再传递给子组件)。 3\. 自定义事件。 ```html
``` 4\. 动画。 ```html ``` ## 22. 关于过滤器? ```js // 是什么:本质上来说是对数据进行处理的一个函数。 // 第一步 // Vue.filter(名字,函数) // 函数里面的参数 data 是什么呢? // 就是使用过滤器的时候管道符左边的那个数据 Vue.filter('dateFormat', (date) => { // 返回啥,就会把使用过滤器的地方替换成啥 return dayjs(date).format('YYYY-MM-DD') }) // 第二步
{{ new Date() | dateFormate }}
``` 项目中使用,`filters/index.js` ```js export const dateFormat = () => { // 过滤器处理逻辑 } export const textFormat = () => { // 过滤器处理逻辑 } ``` `main.js` ```js import * as filters from '@/filters' for(const filterName in filters) { Vue.filter(filterName, filters[filterName]) } ``` ## 23. 关于 Vue.use? 作用:可以用来注册插件(添加全局方法或 property、添加全局指令、注册组件、添加实例方法)。 ```js Vue.use({ // 会自动执行 install install(Vue) { Vue.component('PageTools', 组件) } }) // 和上面等价 Vue.use(function(Vue) { Vue.component('PageTools', 组件) }) ``` ## 24. 数据转换的小作业? ```js // #1 直接通过 excel 导入的数据如下 const excelData = [{ 入职日期: 43535, 姓名: '张飞1', 工号: 88088, 手机号: 15751786628, 转正日期: 43719 }] // #2 后端需要的数据如下 const results = [{ timeOfEntry: 43535, // 入职日期 username: '张飞1', // 用户名 workNumber: 88088, // 工号 mobile: 15751786628, // 手机号 correctionTime: 43719, // 转正日期 }] // 问题:如何转换? ``` ```js // !思路:根据原来的(直接通过插件导过来的数据)中文 key 找到对应的英文(怎么找呢?准备一个中英文对照的对象) // 把这个找到的英文作为【新对象】的 key,值还是原来的值 // !#1 先准备好一个中英文的对照关系 const userRelations = { 入职日期: 'timeOfEntry', 手机号: 'mobile', 姓名: 'username', 转正日期: 'correctionTime', 工号: 'workNumber' } // #2 准备一个大数组 const arr = [] results.forEach(item => { // !#3 准备一个新对象 const userInfo = {} // !#4 再次遍历 item,因为 item 里面的每一个 key 都要转 for (const attr in item) { // attr => 中文 key // item[attr] => 我想要的值 // userRelations[attr] => 这个是中文 key 对应的英文 const englishKey = userRelations[attr] userInfo[englishKey] = item[attr] } // 把每次加工好的新对象添加到新数组 arr.push(userInfo) }) ``` ## 25. hasOwnProperty 干啥的? 判断一个属性是不是在自身上面挂载的。 ## 26. 有没有自己封装过组件? 业务组件:任何一个 `.vue` 文件其实就是一个组件。 第三方 UI 组件:Element UI。 基于第三方 UI 组件封装过自己的组件:新增/编辑的弹框、通用的工具栏、基于 ElUpload 二次封装的上传(**能说**)。 **弄成自己的逐字稿。** ## 27. 说一下 Vue 有哪些内置组件? component(动态组件,可以更加 is 属性加载对应的组件)、KeepAlive、Slot、Transition ## 28. 导入导出的数据处理? 两种写法:for 循环和 map 的写法。 ## 29. 前端怎么做图片预览? ```html Document 上传 ``` ## 30. params 传参到底会丢失吗? 路由配置:`router/index.js` ```js { path: '/user/:id', name: 'user', component: () => import('@/views/user/index.vue'), hidden: true, meta: { title: '用户' } } ``` 直接输入页面路径:`http://localhost:8888/#/user/zs` 或下面方式跳转都不会丢失 ```js this.$router.push('/user/zs') ``` ```js // 同样不会丢失 this.$router.push({ name: 'user', params: { id: 'ls' } }) ``` 接收参数:`views/user/index.vue` ```html ``` **到底什么情况下才会丢失呢?** 使用 name 跳转,例如下面写法。 ```js this.$router.push({ name: 'user', params: { id: 'ls' } }) ``` **并且**路由配置的地方“写错了”。 ```js { path: '/user', // 这儿写错了 name: 'user', component: () => import('@/views/user/index.vue'), hidden: true, meta: { title: '用户' } } ``` ## 31. props 传值是异步的,如何解决异步带来的问题呢? ```js async editRole(userId) { this.userId = userId // !#1 调用子组件获取角色列表的方法 this.$refs.assignRole.getUserDetailById() // 上面代码报错了 // 解决方式一:nextTick /* this.$nextTick(() => { this.$refs.assignRole.getUserDetailById() }) */ // this.$nextTick(this.$refs.assignRole.getUserDetailById) /* this.$nextTick().then(() => { this.$refs.assignRole.getUserDetailById() }) */ // 解决方式二:直接调用子组件的方法并传参 await this.$refs.assignRole.getUserDetailById(userId) this.showRoleDialog = true } ``` ## 32. 生命周期钩子? ```js created: 时机相对较早,同时又能访问 data 里面的数据或 methods 里面的方法(因为创建好了)。 beforeDestroy: 解绑事件或清理定时器。 ``` 为什么异步放到 Action 里面? 其实异步代码放到 mutation 里面,默认情况下(非严格模式)能正常发,没什么问题。 之所以放到 action 里面,是为了形成数据快照,配合 Vue Devtools 观测到每一次对 Mutation 的触发的信息(包括类型和 payload)。 ## 33. 权限分配的流程? 通过【员工管理】页面给用户分角色。 ![image-20221205153328414](README.assets/image-20221205153328414.png) 通过【公司设置】页面,给角色分权限。 ![image-20221205153412330](README.assets/image-20221205153412330.png) 权限有哪些分类? 页面级别的按钮级别的权限。 权限从何而来呢?是通过【权限管理】页面而管理起来的。 ![image-20221205153518864](README.assets/image-20221205153518864.png) ## 34. ❗路由权限是如何产生实际效果的,怎么做的? 1\. 用户登录成功后,后端会返回当前用户的标识,假如返回的结果如下。 ```js ['employees', 'settings', 'departments'] ``` 2\. 前端拿到标识后,去从 asyncRoutes 中筛选出当前用户有权限的路由(有权限的路由就是和标识对应的路由对象),假如筛选的结果如下。 ```js const filterRoutes = [ { path: '/employees', name: 'employees', component: () => import('@/views/employees') }, { path: '/settings', name: 'settings', component: () => import('@/views/settings') }, { path: '/departments', name: 'departments', component: () => import('@/views/departments') } ] ``` 3\. 前端通过 `router.addRoutes` 添加到路由实例(可以理解为把上面的路由对象怼到了路由配置想 routes 里面了)。 ```js router.addRoutes(filterRoutes) ``` 4\. 此时当前用户就具有了访问某个页面的权限啦! **碰到的问题?** addRoutes 之后,刷新的时候碰到了 404 问题?解决如下。 ```js // 注释掉曾经手动配置的 404 路由,通过下面方式添加到路由实例的最后面 router.addRoutes([...otherRoutes, { path: '*', redirect: '/404', hidden: true }]) ``` **白屏问题?** ```js // 通过 addRoutes 后续添加的路由是“异步”的,不会马上生效,需要重新从 beforeEach 的再执行一遍就好了。 next({ ...to, replace: true }) ``` **侧边栏问题?** ```js // addRoutes 后续添加的路由配置项,通过 this.$router.options.routes 直接拿不到,而侧边栏的信息就是这样获取的,所以展示不出来 // 解决:在 Vuex 中也存放了一份完整的路由(其中就包含筛选过后的动态路由) ``` **后续的用户可能会收到上一个用户的路由信息的影响?** 所以退出的时候,要重置一下路由(包含路由实例 router 和 Vuex 中的筛选过来的 routes) 路由配置信息添加到 router 实例:能保证通过浏览器地址栏访问! Vuex 中的筛选过来的 routes:能保证侧边栏看到。 ## 35. 功能级别的/按钮级别的权限怎么做的? 封装一个全局的指令或方法,这个方法只做 1 件事情,接收标识,内部判断一下在不在后端返回的**功能列表**里面,在的话就返回 true,不在的话就返回 false。 ```js function checkPermission(tag) { // 后端返回的功能列表 const list = ['DEPARS_DELETE', 'DEPARS_ADD'] return list.includes(tag) } ``` 接下来我只需要在做按钮权限控制的地方调用一下这个方法,传递过去标识,根据方法返回的是 true 还是 false,对这个按钮做一个禁用或启用,显示或隐藏的操作。 ```html ``` ## 36. 一个小问题? ```js const o = {} const a = 'bbb' // 如果用点去设置属性,点后面的就是一个普普通通的字符串 o.a = 88 // { 'a': 88 } // 中括号里面这个东西没有加引号,会当做变量 o[a] = 77 // { 'a': 88, 'bbb': 77 } o['a'] = 99 // { 'a': 99, 'bbb': 77 } console.log(o.a) ``` ## 37. 工作开发流程? 1\. 产品经理提出需求(有的外包公司直接拿的是甲方的需求,有我们自己公司的项目经理和他们对接)。 2\. 会召集大伙(前端、后端、UI、测试)开会,分析需求的合理性。 3\. 前端拿到原型图,老大协调拆分项目的开发周期(一般是按小时为单位的),一般新手往往过于乐观,评估的时间太短了。 4\. 老大会把项目的基本架子搭建好,把初始代码提交到内部 Gitlab 平台,建好对应的分支(Git 工作流)。 5\. 我在自己的功能分支去开发,完毕之后合并到 develop 开发分支,最后测试的时候由老大合并到 release 分支交给测试去测试。 6\. 测试发现 Bug 会通过内部的禅道管理平台提给对应的负责人,对应的人修复完之后,测试再进行复测,没问题之后关闭这个 Bug。 7\. 一切完毕之后有老大负责打包上线,这一块个工作我参与的不多。 ## 38. ElementUI 弹框的时候后面的如何覆盖前面的? ### 方法 1 参考:https://www.jianshu.com/p/a07aa3192edf `utils/resetMessage.js` ```js import { Message } from 'element-ui' let messageInstance = null // 声明一个 resetMessage 方法,接收配置项 const resetMessage = (options) => { // 第二次及以后调用的时候 messageInstance 已经存在了 // 如果已经存在实例则关闭 if (messageInstance) { messageInstance.close() } // messageInstance 就是 Message 的实例,可以直接像 Message 一样调用进行弹框,例如 messageInstance() messageInstance = Message(options) } ['error', 'success', 'info', 'warning'].forEach(type => { // 往 resetMessage 这个函数上加了 4 个属性叫 'error', 'success', 'info', 'warning' // 这四个属性对应的值又是一个一个的函数,所以我可以这样用 resetMessage.success() resetMessage[type] = options => { if (typeof options === 'string') { options = { message: options } } options.type = type return resetMessage(options) } }) export const message = resetMessage ``` `main.js` ```js import { message } from '@/utils/resetMessage.js' Vue.prototype.$message = message ``` 以后可以直接下面两种方式使用。 ```js import { message } from '@/utils/resetMessage.js' // JS 文件中 message.success('tip') // Vue 文件中 this.$message.success('tip') ``` ### 方法 2 ```js // 调用 Message 之前,先关闭所有的 Message.closeAll() Message.error(error.message) ``` ## 39. this 指向? ```js 'use strict' // 对语法的解析更加严格,开启严格模式 // 普通函数中的 this 调用的时候才能确定 function fn() { // 严格模式下是 undefined console.log(this) } fn() const o = { fn } o.fn() // 箭头函数中的 this 在定义的时候就已定确定了 const test = () => { console.log(this) } ``` ## 40. mixins? 作用:可以实现不同组件间的相同的数据和业务逻辑的复用,提高开发效率。 **怎么用呢?** 1\. 先定义一个对象并导出, `mixins/check.js`。 ```js export default { data() { return { age:18 } }, methods: { checkPermission(tag) { // 后端返回的功能列表 const list = ['DEPARS_DELETE', 'DEPARS_ADD'] return list.includes(tag) } } } ``` 2\. 在组件中导入这个对象,`employees.vue`,这叫局部 mixin。 ```js import check from '@/mixins/check' export default { mixins: [check, a, b, c] } ``` 3\. 全局。 ```js Vue.mixin({ data() { return { age: 19 // 这个 age 任何一个组件都可以用 } } }) ``` **问题?** 命名冲突;数据来源不清晰; **优先级?** 混入过来的内容,执行顺序是怎么样的? 如果配置了相同的生命周期函数,先执行混入的,再执行组件自身的。 data 和 methods:相同的会覆盖,组件内的会覆盖 mixin 中的。 ## 41. Vue2 中 v-model 和 sync 在组件上使用的区别? v-model 和 .sync 修饰符都是组件传值时候的一种便捷写法,不同的是 v-model 在组件上只能写一次,.sync 可以写任意多次。 ```js // v-model 相当于 v-bind:value 和 v-on:input 的语法糖,在组件上只能写一次 // 组件中通过 props 接收 value,通过 this.$emit('input') 触发修改 // 接收时候的 value 和触发的事件 input 能不能改? // 可以的,通过 model 选项可以修改 ``` `App.vue` ```html ``` `Test.vue` ```html ``` 其实传递 value 和 input 可以通过 model 选项修改,具体如下。 ## 42. 和 Promise 相关的问题? ```js /* const fn = () => { setTimeout(() => { const age = 18 return age }, 1000) // return undefined } const r = fn() console.log(r) // undefined */ // 如何拿到 18,回调函数 /* const fn = (callback) => { setTimeout(() => { const age = 18 // 实参 callback(age) }, 1000) } fn(function(age) { console.log(age) }) */ // 改成 Promise /* const fn = () => { return new Promise((resolve) => { setTimeout(() => { const age = 18 resolve(age) }, 1000) }) } const r = fn() r.then(res => console.log(res)) */ const fn = () => { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('出错了')) }, 1000) }) } const r = fn() // catch 之后再写 .then 还会被触发吗? r.catch(e => { console.log(e.message) // return Promise.resolve(undefined) }).then(r => { console.log(r) // return Promise.resolve(undefined) }).then(r => { console.log(r) }) ``` 编写一个睡眠函数? ```js /* const sleep = (time) => { return new Promise((resolve) => { setTimeout(() => { resolve() }, time) }) } */ const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)) ;(async() => { console.log(1) // 等三秒之后打印 2,除了 setTimeout,也可以基于 Promise 编写一个睡眠函数 await sleep(3000) console.log(2) })() ``` ## 43. 怎么去减小打包体积? 通过 Webpack 提供的 externals 选项忽略某些不需要打包的大文件,然后再通过 CDN 外链的形式加载过来。 内容分发网络,国内用的多的是七牛 CND。 `一旦把资源上传了七牛了,后续请求这个文件的时候,它会去最近的服务器去要,很快!` ## 44. 生成 1 ~ 100? ```js [...Array(101).keys()].slice(1) ``` ```html ``` 演示 .sync。 `App.vue` ```html ``` `Test.vue` ```html ```