Created
July 30, 2020 23:20
-
-
Save zzjtnb/a1a358fcbf9ba0928b7723e2ef00e26c to your computer and use it in GitHub Desktop.
课程主题是 **项目**,会讲到公司项目研发的流程。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 课程介绍 | |
| 正处于春招的黄金时期,为了帮助大家找到更好的工作,更快的找到工作。组织本次的专项课程。 | |
| 今日的课程主题是 **项目**,会讲到公司项目研发的流程。 | |
| 因为项目一般**不是**面试时候的核心考察点,所以今天还会讲到一些项目中**经典问题及解法**。 | |
| 因为**时间有限**, 老师这里准备的都是面试中**最常见的问题**, | |
| 以**讲义**内容为主, 如果讲解完毕后仍有时间, 我们再继续扩展其他 | |
| # 学习目标 | |
| * [x] 理解 promise 主要解决的问题及常规用法 | |
| * [x] 能够写出一个可以被链式调用的函数 | |
| * [x] 能粗略的实现 promise的基本结构 | |
| * [x] 可以自己封装一般的 api 请求模块 | |
| * [x] 了解公司一般的角色(岗位)及职责 | |
| * [x] 知道一个公司常规项目的流程 | |
| * [x] 知道公司常规的缺陷管理的流程 | |
| * [x] 知道常规的代码管理方式 | |
| * [x] 知道常规系统的登录部分是如何设计的(简单登录/单点登录) | |
| * [x] 知道系统的权限部分如何设计的 | |
| * [x] 可以将数组结构的数据转成树结构的数据 | |
| * [x] 可以将树结构的数据转成数组的结构 | |
| * [x] 知道如何限制接口的访问频率, | |
| * [x] 可以实现简单的 debounce | |
| # 问题列表 | |
| | 知识点 | | |
| | ----------------------------------- | | |
| | promise的源码和实际项目使用 | | |
| | 前端后端具体的合作的流程 | | |
| | 从研发到上线的具体详解 | | |
| | 项目开发中遇到的问题 | | |
| | 对axios再次封装(网络请求模块封装) | | |
| | 混合APP原生APP打包上线 | | |
| | 限制接口调用频率 | | |
| | 权限管理结合项目前端实现 | | |
| | 打包到项目部署 | | |
| | 实现登录 | | |
| | 管理项目 | | |
| | 处理树形结构 | | |
| # 面试题详解 | |
| ## 理解promise | |
| **【问题分析】** | |
| 怎么理解 promise ? 写出指定代码的输出内容? | |
| **【答题思路】** | |
| 关于 promise 通常会有三种题型。第一种就是问概念;第二种就是 给一段代码让说出他的输出;第三种 就是让你自己实现一个 promise ,来考察你对 promise 的理解和编程能力。(第三种一般是针对中高级的程序员) | |
| 本节我们先回顾一下 promise的概念,然后一起来做两个题。 | |
| 1. promise 概念 | |
| **promise 定义**A promise is an object that may produce a single value some time in the future: either a resolved value, or a reason that it’s not resolved (e.g., a network error occurred) | |
| https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-promise-27fc71e77261 | |
| https://promisesaplus.com/implementations | |
| 2. promise 题目1 | |
| ```js | |
| 数字打印的顺序 | |
| const first = () => (new Promise((resolve, reject) => { | |
| console.log(3); | |
| let p = new Promise((resolve, reject) => { | |
| console.log(7); | |
| setTimeout(() => { | |
| console.log(5); | |
| resolve(6); | |
| }, 0) | |
| resolve(1); | |
| }); | |
| resolve(2); | |
| p.then((arg) => { | |
| console.log(arg); | |
| }); | |
| })); | |
| first().then((arg) => { | |
| console.log(arg); | |
| }); | |
| console.log(4); | |
| //1.同步的代码(最高) | |
| //3 | |
| //7 | |
| //4 | |
| //2. 微任务的异步代码(次高,then) | |
| //1 | |
| //2 | |
| //3. 宏任务的异步代码(最低,setTimeout) | |
| //5 | |
| //6 不执行 | |
| ``` | |
| 3. promise 题目2(理解 then 的第二回调函数和 catch) | |
| ```js | |
| // 画出以下异步函数执行的可能的路线。 | |
| // https://developers.google.com/web/fundamentals/primers/promises | |
| // 蓝线表示执行的 promise 路径,红路表示拒绝的 promise 路径。 | |
| // https://developers.google.com/web/fundamentals/primers/imgs/promise-flow.svg | |
| asyncThing1() | |
| .then(function() { | |
| return asyncThing2(); | |
| }) | |
| .then(function() { | |
| return asyncThing3(); | |
| }) | |
| .catch(function(err) { | |
| return asyncRecovery1(); | |
| }) | |
| .then( | |
| function() { | |
| return asyncThing4(); | |
| }, | |
| function(err) { | |
| return asyncRecovery2(); | |
| } | |
| ) | |
| .catch(function(err) { | |
| console.log("Don't worry about it"); | |
| }) | |
| .then(function() { | |
| console.log("All done!"); | |
| }); | |
| ``` | |
|  | |
| ## promise 实践 | |
| **【问题分析】** | |
| 如何把大象🐘放到冰箱里? | |
| **【答题思路】** | |
| 开门/装大象/关门 都是异步的操作。 | |
| 通过本题加强对 promise 的理解,理解 promise 解决的核心问题是什么。 | |
| ```js | |
| /////////////////方法1 callback////////////////// | |
| //1. 准备函数 openDoor putIn closeDoor done | |
| //2. 执行函数 | |
| console.time() | |
| const openDoor=(cb)=>{ | |
| setTimeout(cb,1000) | |
| } | |
| const putIn=(cb)=>{ | |
| setTimeout(cb,3*1000) | |
| } | |
| const closeDoor=(cb)=>{ | |
| setTimeout(cb,1000) | |
| } | |
| const done=()=>{ | |
| console.timeEnd(); | |
| console.log('done'); | |
| } | |
| openDoor(()=> putIn(()=> closeDoor(done) )); | |
| ////// | |
| //const openDoor = cb => { | |
| // setTimeout(cb, 1000); | |
| // }; | |
| // const promise = () => { | |
| // return new Promise((res, rej) => { | |
| // setTimeout(() => { | |
| // res(); | |
| // }, 1000); | |
| // }); | |
| // }; | |
| /////////////////vs///////////////////// | |
| /////////////////方法1 promise////////////////// | |
| //1. 准备promise函数 openDoor putIn closeDoor done | |
| //2. 执行函数 | |
| console.time() | |
| const openDoor=()=>new Promise(res=>{ | |
| setTimeout(res,1000) | |
| }) | |
| const putIn=()=>new Promise(res=>{ | |
| setTimeout(res,1000*3) | |
| }) | |
| const closeDoor=()=>new Promise(res=>{ | |
| setTimeout(res,1000) | |
| }) | |
| const done=()=>new Promise(res=>{ | |
| console.timeEnd(); | |
| console.log('done2'); | |
| res(); | |
| }) | |
| openDoor() | |
| .then(putIn) | |
| .then(closeDoor) | |
| .then(done) | |
| ``` | |
| ## 如何实现链式调用 | |
| **【问题分析】** | |
| 类似 `b.then().then().then()` 的链式调用是如何实现的? | |
| **【答题思路】** | |
| 因为要不断的调用,所以一定是返回自己,或者返回一个 和自己类似的结构。 | |
| 后续实现 Promise 的时候,会用得上! | |
| 面试就心里强大一点就行;出给我的题 面试官如果不知提前知道答案也 可能答不出来 | |
| ```js | |
| class Test1{ | |
| then(){ | |
| console.log(6666); | |
| return this; | |
| } | |
| } | |
| var a= new Test1(); | |
| a.then().then().then() | |
| class Test2{ | |
| then(){ | |
| console.log(77777); | |
| return new Test2(); | |
| } | |
| } | |
| var b= new Test2(); | |
| b.then().then().then() | |
| ``` | |
| ## promise 单元测试 | |
| 1.准备测试框架 | |
| 参考[jest](https://jestjs.io/docs/en/getting-started) 官网 | |
| ```shell | |
| # 1.初始化项目 | |
| npm init | |
| # 2.安装 jest | |
| yarn add --dev jest | |
| #npm install --save-dev jest | |
| # 3. 支持 es2015+ 和 ts | |
| yarn add --dev babel-jest @babel/core @babel/preset-env @babel/preset-typescript @types/jest | |
| # 4. 添加 文件 babel.config.js | |
| // babel.config.js | |
| module.exports = { | |
| presets: [ | |
| ['@babel/preset-env', {targets: {node: 'current'}}], | |
| '@babel/preset-typescript', | |
| ], | |
| }; | |
| # 5.配置命令 | |
| "test": "jest", | |
| "test:watch": "jest --watchAll" | |
| ``` | |
| 2.为了检查 Promise 实现的正确性,我们提前准备好单元测试 | |
| ```js | |
| import MyPromise from "./index"; | |
| // 1 | |
| test("1.promise 参数函数会立即执行", function() { | |
| var string; | |
| new MyPromise(function() { | |
| string = "foo"; | |
| }); | |
| expect(string).toBe("foo"); | |
| },500); | |
| it("2. promise 在 then 的回调函数中可以拿到 resolve 的数据。", function(done) { | |
| var testString = "foo"; | |
| var promise = new MyPromise(function(resolve) { | |
| setTimeout(function() { | |
| resolve(testString); | |
| }, 20); | |
| }); | |
| promise.then(function(string) { | |
| expect(string).toBe(testString); | |
| done(); | |
| }); | |
| },500); | |
| // 3 | |
| it("promise 可以有多个 then,并且会依次执行", function(done) { | |
| var testString = "foo"; | |
| var promise = new MyPromise(function(resolve) { | |
| setTimeout(function() { | |
| resolve(testString); | |
| }, 20); | |
| }); | |
| promise.then(function(string) { | |
| expect(string).toBe(testString); | |
| }); | |
| promise.then(function(string) { | |
| expect(string).toBe(testString); | |
| done(); | |
| }); | |
| },500); | |
| it("4.promise 可以嵌套多个 then,then的回调中可以返回 promise ", function(done) { | |
| var testString = "foo"; | |
| var promise = new MyPromise(function(resolve) { | |
| setTimeout(function() { | |
| resolve(); | |
| }, 20); | |
| }); | |
| promise | |
| .then(function() { | |
| return new MyPromise(function(resolve) { | |
| setTimeout(function() { | |
| resolve(testString); | |
| }, 20); | |
| }); | |
| }) | |
| .then(function(string) { | |
| expect(string).toBe(testString); | |
| done(); | |
| }); | |
| },500); | |
| // 5 | |
| it("5.promise 可以嵌套多个 then,then的回调中可以返回 一个普通值", function(done) { | |
| var testString = "foo"; | |
| var promise = new MyPromise(function(resolve) { | |
| setTimeout(function() { | |
| resolve(); | |
| }, 20); | |
| }); | |
| promise | |
| .then(function() { | |
| return testString; | |
| }) | |
| .then(function(string) { | |
| expect(string).toBe(testString); | |
| done(); | |
| }); | |
| },500); | |
| // 6 | |
| it("6.resolved 状态的promise ,如果调用 then 方法会立即执行", function(done) { | |
| var testString = "foo"; | |
| var promise = new MyPromise(function(resolve) { | |
| setTimeout(function() { | |
| resolve(testString); | |
| }, 20); | |
| }); | |
| setTimeout(function() { | |
| promise.then(function(value) { | |
| expect(value).toBe(testString); | |
| done(); | |
| }); | |
| }, 200); | |
| },500); | |
| it("7. 二次调用 resolve 不会产生影响。", function(done) { | |
| var testString = "foo"; | |
| var testString2 = "bar"; | |
| var promise = new MyPromise(function(resolve) { | |
| setTimeout(function() { | |
| resolve(testString); | |
| resolve(testString2); | |
| }, 20); | |
| }); | |
| promise.then(function(value) { | |
| expect(value).toBe(testString); | |
| }); | |
| setTimeout(function() { | |
| promise.then(function(value) { | |
| expect(value).toBe(testString); | |
| done(); | |
| }); | |
| }, 50); | |
| },500); | |
| ``` | |
| ## promise 源码 | |
| **【问题分析】** | |
| 如何实现一个 Promise ? | |
| **【答题思路】** | |
| promise 是一个对象,一般是通过 new Promise ()来实例化的;所以这里我要实现 Promise 类! | |
| promise 的 then 是可以链式调用的,所以可能会用到上面提到的,链式调用的实现。 | |
| 根据逐个单元测试的要求来实现 Promise | |
| 主要实现 Promise 的构造方法和 then 方法; 后面会以链接的方式给出完整的实现。 | |
| **【解题代码】** | |
| ```ts | |
| const State = { | |
| pending: "pending", | |
| resolved: "rejected", | |
| rejected: "rejected" | |
| }; | |
| const noop = () => {}; | |
| class MyPromise { | |
| constructor(exclutor) { | |
| exclutor(this._resolve.bind(this), this._reject); | |
| } | |
| _state = State.pending; | |
| _value; | |
| _resolve(val) { | |
| if (this._state === State.pending) { | |
| this._value = val; | |
| this._state = State.resolved; | |
| this._runResolveArray(); | |
| } | |
| } | |
| _reject() {} | |
| _runResolveArray() { | |
| //执行 then 传入进来的 onRes | |
| this._resArray.forEach(item => { | |
| // const item | |
| const result = item.handle(this._value); | |
| const nextPromise = item.promise; | |
| if (result instanceof MyPromise) { | |
| result.then(val => item.promise._resolve(val)); | |
| } else { | |
| item.promise._resolve(result); | |
| } | |
| }); | |
| } | |
| _resArray = []; | |
| then(onRes, onRej = noop) { | |
| // if (this._state === State.pending) { | |
| const newPromise = new MyPromise(() => {}); | |
| const item = { promise: newPromise, handle: onRes }; | |
| this._resArray.push(item); | |
| // } | |
| if (this._state === State.resolved) { | |
| this._runResolveArray(); | |
| } | |
| return newPromise; | |
| } | |
| } | |
| export default MyPromise; | |
| ``` | |
| 【问题延伸】 | |
| 几种Promise 的实现 | |
| https://github.com/ericyang89/my-promise | |
| https://github.com/vividbytes/implementing-promises | |
| https://github.com/iam91/zpromise/blob/master/src/zpromise.js | |
| ## 项目api 请求模块封装 | |
| **【题目分析】** | |
| 你项目中的网络/api 请求模块是如何封装的? | |
| **【答题思路】** | |
| 1. 接口请求一般是异步的,可以返回 promise 更加清晰。 | |
| 2. 网络请求url 的公共部分可以单独配置到 网络请求内部。 | |
| 3. 针对所有的接口可以进行统一的处理。这也是面向切面编程的一个实践。 | |
| 4. 可以借助第三方库 axios 快速的封装 网络请求模块。 | |
| **【解题步骤|代码】** | |
| ```ts | |
| import axios from "axios"; | |
| import constant from "../constant"; | |
| import reactNavigationHelper from "./reactNavigationHelper"; | |
| import commonToast from "./commonToast"; | |
| //配置请求url 的公共部分及超时时间 | |
| const commonHttp = axios.create({ | |
| baseURL: constant.baseUri, | |
| timeout: 10 * 1000 | |
| }); | |
| commonHttp.interceptors.response.use( | |
| function(response) { | |
| return response; | |
| }, | |
| function(error) { | |
| //针对所有接口统一处理登录过期的问题 | |
| if (error.response.status === 401) { | |
| commonToast.show("登录过期"); | |
| reactNavigationHelper.navigate("Login"); | |
| } | |
| return Promise.reject(error); | |
| } | |
| ); | |
| export default commonHttp; | |
| ``` | |
| ## 公司角色及职责 | |
| 一般公司与项目流程有关的角色 | |
|  | |
|  | |
| ##项目流程 | |
| **【题目分析】** | |
| 你们的开发流程/项目流程大概是什么样的? | |
| **【答题思路】** | |
|  | |
|  | |
|  | |
|  | |
| 简要说明项目流程: | |
| 1. 产品出需求文档;开需求会; | |
| 2. 估时,部分项目写技术方案;接口阅读 | |
| 3. 开始开发;测试给冒烟测试/测试用例评审 | |
| 4. 前后端联调 | |
| 5. 冒烟测试 | |
| 6. 提测 | |
| 7. 跟测/上环境 | |
| 8. 上线/线上跟踪/项目总结 | |
| ## 项目缺陷管理 | |
| **【题目分析】** | |
| 如何做bug/缺陷管理? | |
| **【答题思路】** | |
| 1. 缺陷管理角色 | |
|  | |
|  | |
| 2. bug 处理流程 | |
|  | |
|  | |
| ## 代码管理 | |
| **【题目分析】** | |
| 你们的代码大概怎么管理的? | |
| 怎么管理 git 分支的? | |
| **【答题思路】** | |
|  | |
| ## 项目打包 | |
| **【题目分析】** | |
| 你们项目为什么需要打包/编译/构建? | |
| **【答题思路】** | |
| 为什么要项目打包? | |
| 1. es2015+ 转 es5 | |
| 2. Js/css/图片 文件合并 | |
| 3. ts 转 js | |
| 4. 模板编译 jsx 转 js | |
| 5. less/sass 转 css | |
| 6. css | |
| 7. 代码压缩 | |
| 8. 针对不同环境生成不同的代码 | |
| ## 常规登录流程 | |
| **【题目分析】** | |
| 你们项目中登录大概是怎么实现? | |
| **【答题思路】** | |
| 1. 最简单的登录流程 | |
|  | |
| 2. 端点登录-sso | |
|  | |
| 3. 注意问题 | |
| 1. 密码一般不用明文保存 | |
| 2. 发送 用户名 密码 的网络请求一般不用 get | |
| ## 项目中权限管理 | |
| **【题目分析】** | |
| 你们项目中权限管理是怎么实现? | |
| **【答题思路】** | |
| 1. 常规权限模型 | |
| RBAC基于角色的权限访问控制(Role-Based Access Control) | |
|  | |
|  | |
| 2. 常规管理系统中的权限 | |
| + 根据登录用户返回菜单列表— — 可能是一颗树 | |
| + 根据登录用户控制接口权限 | |
| 3. 前台系统中的权限管理 | |
| + 更专注于入口的控制 | |
| + 越权的提示也不可少 | |
| + 多级权限可能会涉及到计算 | |
| **【问题延伸】** | |
| 菜单列表通常是一个树的结构,后端一般喜欢给一个列表(数组),该如何去构建?! | |
| ## 根据数组构建树 | |
| **【题目分析】** | |
| 根据数组构建树? | |
| **【答题思路】** | |
| 1. 给定原始数据(originData),期待生成一个树形结构的对象(treeData)。 | |
| ```ts | |
| export type Item = { | |
| id: number; | |
| parentId: number; | |
| name: String; | |
| }; | |
| export type Node = Item & { children: Node[] }; | |
| export const originData: Item[] = [ | |
| { | |
| id: 0, | |
| parentId: null, | |
| name: "生物" | |
| }, | |
| { | |
| id: 1, | |
| parentId: 0, | |
| name: "动物" | |
| }, | |
| { | |
| id: 2, | |
| parentId: 0, | |
| name: "植物" | |
| }, | |
| { | |
| id: 3, | |
| parentId: 0, | |
| name: "微生物" | |
| }, | |
| { | |
| id: 4, | |
| parentId: 1, | |
| name: "哺乳动物" | |
| }, | |
| { | |
| id: 5, | |
| parentId: 1, | |
| name: "卵生动物" | |
| }, | |
| { | |
| id: 6, | |
| parentId: 2, | |
| name: "种子植物" | |
| }, | |
| { | |
| id: 7, | |
| parentId: 2, | |
| name: "蕨类植物" | |
| }, | |
| { | |
| id: 8, | |
| parentId: 4, | |
| name: "大象" | |
| }, | |
| { | |
| id: 9, | |
| parentId: 4, | |
| name: "海豚" | |
| }, | |
| { | |
| id: 10, | |
| parentId: 4, | |
| name: "猩猩" | |
| }, | |
| { | |
| id: 11, | |
| parentId: 5, | |
| name: "蟒蛇" | |
| }, | |
| { | |
| id: 12, | |
| parentId: 5, | |
| name: "麻雀" | |
| } | |
| ]; | |
| export const treeData: Node = { | |
| children: [ | |
| { | |
| children: [ | |
| { | |
| children: [ | |
| { children: [], id: 8, name: "大象", parentId: 4 }, | |
| { children: [], id: 9, name: "海豚", parentId: 4 }, | |
| { children: [], id: 10, name: "猩猩", parentId: 4 } | |
| ], | |
| id: 4, | |
| name: "哺乳动物", | |
| parentId: 1 | |
| }, | |
| { | |
| children: [ | |
| { children: [], id: 11, name: "蟒蛇", parentId: 5 }, | |
| { children: [], id: 12, name: "麻雀", parentId: 5 } | |
| ], | |
| id: 5, | |
| name: "卵生动物", | |
| parentId: 1 | |
| } | |
| ], | |
| id: 1, | |
| name: "动物", | |
| parentId: 0 | |
| }, | |
| { | |
| children: [ | |
| { children: [], id: 6, name: "种子植物", parentId: 2 }, | |
| { children: [], id: 7, name: "蕨类植物", parentId: 2 } | |
| ], | |
| id: 2, | |
| name: "植物", | |
| parentId: 0 | |
| }, | |
| { children: [], id: 3, name: "微生物", parentId: 0 } | |
| ], | |
| id: 0, | |
| name: "生物", | |
| parentId: null | |
| }; | |
| ``` | |
| 2. 单元测试 | |
| ```ts | |
| import arrayToTree from "./arrayToTree"; | |
| import { originData, treeData } from "./data"; | |
| test("arrayToTee", () => { | |
| expect(arrayToTree(originData)).toEqual(treeData); | |
| }); | |
| ``` | |
| 3. 实现步骤 | |
| + 实现函数签名 | |
| + 实现函数体 | |
| + 处理 root | |
| + 实现 addChildren 方法 | |
| + 执行 addChildren | |
| **【解题步骤】** | |
| ```ts | |
| import { Item, Node } from "./data"; | |
| // + 实现函数签名 | |
| // + 实现函数体 | |
| // + 处理 root | |
| // + 实现 addChildren 方法 | |
| // + 执行 addChildren | |
| const arrayToTree = (arr) => { | |
| if (!Array.isArray(arr) || arr.length < 1) return null; | |
| const [root] = arr.filter(item => item.parentId === null); | |
| const addChildren = (node, dataList) => { | |
| const children = dataList | |
| .filter(item => item.parentId === node.id) | |
| .map(item => addChildren(item, dataList)); | |
| return { ...node, children }; | |
| }; | |
| return addChildren(root, arr); | |
| }; | |
| export default arrayToTree; | |
| ``` | |
| ## 树的遍历 | |
| **【题目分析】** | |
| 把上一节树的数据转成数组? | |
| **【答题思路】** | |
| 1. 单元测试 | |
| ```ts | |
| import treeToArray from "./treeToArray"; | |
| import { originData, treeData } from "./data"; | |
| test("arrayToTee", () => { | |
| expect( | |
| treeToArray(treeData).sort((a, b) => (a.name > b.name ? 1 : -1)) | |
| ).toEqual(originData.sort((a, b) => (a.name > b.name ? 1 : -1))); | |
| }); | |
| ``` | |
| 2. 大概步骤 | |
| + 编写函数框架 | |
| + 实现 `const nodeToArray = (node: Node, result: Item[]) => {}` | |
| + 调用 nodeToArray | |
| **【解题步骤】** | |
| ```ts | |
| import { Node, Item } from "./data"; | |
| const treeToArray = (node: Node) => { | |
| const nodeToArray = (node: Node, arr: Item[]) => { | |
| const { children, ...item } = node; | |
| arr.push(item); | |
| children.forEach(child => nodeToArray(child, arr)); | |
| return arr; | |
| }; | |
| return nodeToArray(node, []); | |
| }; | |
| export default treeToArray; | |
| ``` | |
| **【问题延伸】** | |
| + 二叉树遍历 https://leetcode.com/problems/binary-tree-preorder-traversal/ | |
| + 二叉树层序遍历 https://leetcode-cn.com/problems/binary-tree-level-order-traversal/ | |
| + bfs | |
| ## 如何限制接口访问频次 | |
| **【题目分析】** | |
| 如何限制接口访问频次? | |
| **【答题思路】** | |
| 接口请求部分的逻辑我们可以封装成一个函数 fun。假设存在一个处理函数的函数better,接受fun,返回一个 betterFun。当 betterFun 被多次调用,fun 只在不要的时候执行。 | |
| `const betterFun=better(fun);` | |
| 限制接口访问一般涉及到用户的输入。主要有两个经典场景:`debounce` 强制函数在某段时间内只执行一次,`throttle` 强制函数以固定的速率执行。 [对比demo](https://css-tricks.com/the-difference-between-throttling-and-debouncing/) | |
| 1. 使用 debounce(防抖) https://codepen.io/llh911001/pen/EVMLJw?editors=101 | |
| 2. 使用 throttle (截流) https://codepen.io/llh911001/pen/XmGYKV?editors=101 | |
| debounce 单元测试 | |
| ```ts | |
| import debounce from "./debounce"; | |
| jest.useFakeTimers(); | |
| test("debounce", () => { | |
| const func = jest.fn(); | |
| const debounced = debounce(func, 1000); | |
| debounced(); | |
| debounced(); | |
| jest.runAllTimers(); | |
| expect(func).toHaveBeenCalledTimes(1); | |
| }); | |
| test("debounce twice", () => { | |
| const func = jest.fn(); | |
| const debounced = debounce(func, 1000); | |
| debounced(); | |
| debounced(); | |
| setTimeout(debounced, 20); | |
| setTimeout(debounced, 2000); | |
| jest.runAllTimers(); | |
| expect(func).toHaveBeenCalledTimes(2); | |
| }); | |
| ``` | |
| **【解题步骤】** | |
| 实现 debounce | |
| ```ts | |
| const debounce = (fn, delay: number) => { | |
| let inDebounce; | |
| return function(...args) { | |
| const context = this; | |
| clearTimeout(inDebounce); | |
| inDebounce = setTimeout(() => { | |
| fn.apply(context, args); | |
| }, delay); | |
| }; | |
| }; | |
| export default debounce; | |
| ``` | |
| **【问题延伸】** | |
| 1. 实现 throttle | |
| ```ts | |
| /** | |
| * | |
| * @param fn {Function} 实际要执行的函数 | |
| * @param delay {Number} 执行间隔,单位是毫秒(ms) | |
| * | |
| * @return {Function} 返回一个“节流”函数 | |
| */ | |
| function throttle(fn, threshold) { | |
| // 记录上次执行的时间 | |
| var last | |
| // 定时器 | |
| var timer | |
| // 默认间隔为 250ms | |
| threshold || (threshold = 250) | |
| // 返回的函数,每过 threshold 毫秒就执行一次 fn 函数 | |
| return function () { | |
| // 保存函数调用时的上下文和参数,传递给 fn | |
| var context = this | |
| var args = arguments | |
| var now = +new Date() | |
| // 如果距离上次执行 fn 函数的时间小于 threshold,那么就放弃 | |
| // 执行 fn,并重新计时 | |
| if (last && now < last + threshold) { | |
| clearTimeout(timer) | |
| // 保证在当前时间区间结束后,再执行一次 fn | |
| timer = setTimeout(function () { | |
| last = now | |
| fn.apply(context, args) | |
| }, threshold) | |
| // 在时间区间的最开始和到达指定间隔的时候执行一次 fn | |
| } else { | |
| last = now | |
| fn.apply(context, args) | |
| } | |
| } | |
| } | |
| ``` | |
| 2. debounce.leading vs debounce.trailing | |
| debounce.leading demo: https://codepen.io/dcorb/pen/GZWqNV | |
| debounce.trailing demo: https://codepen.io/dcorb/pen/KVxGqN | |
| ```ts | |
| function debounce(func, wait, immediate) { | |
| var timeout; | |
| return function() { | |
| var context = this, args = arguments; | |
| var later = function() { | |
| timeout = null; | |
| if (!immediate) func.apply(context, args); | |
| }; | |
| var callNow = immediate && !timeout; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(later, wait); | |
| if (callNow) func.apply(context, args); | |
| }; | |
| }; | |
| ``` | |
| 参考: | |
| https://css-tricks.com/the-difference-between-throttling-and-debouncing/ | |
| http://hackll.com/2015/11/19/debounce-and-throttle/index.html | |
| https://zhuanlan.zhihu.com/p/50367441 | |
| http://demo.nimius.net/debounce_throttle/ | |
| ## 前后端联调注意事项 | |
| 【题目分析】 | |
| 你们公司(上一家公司),一般是怎么进行前后端联调的? | |
| 【答题思路】 | |
| 1. 项目是前后端分离的项目;后端负责提供数据接口,前端负责页面。 | |
| 2. 一般接口完成之后,后端会给一份接口文档,包含接口地址,请求类型,请求的参数,可能的数据返回。 | |
| 3. 部分后端同学可能不写文档,会进行口头沟通。口头沟通的时候需要记录必要的信息。 | |
| 4. 联调的时候最好是工作时间(免得晚上,别人下班了),因为可能涉及到沟通。 | |
| 5. 最好开发之前想好你需要哪些数据,然后和后端提前沟通一下。避免一下你需要的数据,后端没有开发,导致项目工期延误。 | |
| ## 复习js(可选) | |
| [讲义](./how javascript run.md) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment