复习

74 阅读27分钟

Vue、React

相同点

  1. 虚拟dom
  2. 组件化思想
  3. 组合功能(HOC,Mixins)

不同点

  1. 开发方式不同
  2. 监听数据不同
  3. 数据流不同
  4. 组件通信不同

Vue diff算法

我们先根据真实DOM生成一颗virtual DOM,当virtual DOM某个节点的数据改变后会生成一个新的Vnode,然后VnodeoldVnode作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使oldVnode的值为Vnode。 diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。patch函数接收两个参数oldVnodeVnode分别代表新的节点和之前的旧节点,判断两节点是否值得比较,不值得比较则用Vnode替换oldVnode,值得比较则执行patchVnode

patchVnode这个函数做了以下事情

  1. 找到对应的真实dom,称为el
  2. 判断VnodeoldVnode是否指向同一个对象,如果是,那么直接return
  3. 如果他们都有文本节点并且不相等,那么将el的文本节点设置为Vnode的文本节点。
  4. 如果oldVnode有子节点而Vnode没有,则删除el的子节点
  5. 如果oldVnode没有子节点而Vnode有,则将Vnode的子节点真实化之后添加到el
  6. 如果两者都有子节点,则执行updateChildren函数比较子节点,这一步很重要

updateChildren这个函数做了以下事情

  1. Vnode的子节点VcholdVnode的子节点oldCh提取出来
  2. oldChvCh各有两个头尾的变量StartIdxEndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldChvCh至少有一个已经遍历完了,就会结束比较。

React diff

传统的diff算法通过循环递归对节点进行依此对比,其算法复杂度达到了O(n^ 3) , React中的diff算法,算法复杂度为O(n) , React通过三大策略完成了优化:

  1. Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计:同级比较,既然DOM 节点跨层级的移动操作少到可以忽略不计,那么React通过updateDepthVirtual DOM 树进行层级控制,也就是同一层,在对比的过程中,如果发现节点不在了,会完全删除不会对其他地方进行比较,这样只需要对树遍历一次就OK了
  2. 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构::React对于组件的策略有两种方式,一种是相同类型的组件和不同类型的组件
  • 对同种类型组件对比,按照层级比较继续比较虚拟DOM树即可,但有种特殊的情况,当组件A如果变化为组件B的时候,有可能虚拟DOM并没有任何变化,所以用户可以通过shouldComponentUpdate() 来判断是否需要更新,判断是否计算
  • 对于不同组件来说,React会直接判定该组件为dirty component(脏组件) ,无论结构是否相似,只要判断为脏组件就会直接替换整个组件的所有节点
  1. 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分:对于同一层级的一子自节点,通过唯一的key进行比较

fiber

1.Fiber 是对 React 核心算法的重构 2.在浏览器中js引擎和页面渲染引擎是在同一个渲染线程之内,两者是互斥关系。如果在某个阶段执行任务特别长,例如在定时器阶段或Begin Frame阶段执行时间非常长,时间已经明显超过了16ms,那么就会阻塞页面的渲染,从而出现卡顿现象 3.在 react16 引入 Fiber 架构之前,react 会采用递归对比虚拟DOM树,找出需要变动的节点,然后同步更新它们,在reconcilation(协调)期间,react 会一直占用浏览器资源,会导致用户触发的事件得不到响应。 4.Fiber 可以理解为一个执行单元,每次执行完一个执行单元,react 就会检查现在还剩多少时间,如果没有时间则将控制权让出去。React Fiber 与浏览器的核心交互流程如下:首先 React 向浏览器请求调度,浏览器在一帧中如果还有空闲时间,会去判断是否存在待执行任务,不存在就直接将控制权交给浏览器,如果存在就会执行对应的任务,执行完成后会判断是否还有时间,有时间且有待执行任务则会继续执行下一个任务,否则就会将控制权交给浏览器。requestIdleCallback是 react Fiber 实现的基础 api 。callback中会接收到默认参数 deadline ,其中包含了以下两个属性:

  • timeRamining 返回当前帧还剩多少时间供用户使用
  • didTimeout 返回 callback 任务是否超时

React生命周期

1.  初始化阶段  :   由ReactDOM.render()触发---初次渲染 1. constructor()

2. getDerivedStateFromProps

3. render()

4. componentDidMount()

2. 更新阶段 : 由组件内部this.setSate()或父组件重新render触发

1. getDerivedStateFromProps

2. shouldComponentUpdate()

3. render()

4. getSnapshotBeforeUpdate

5. componentDidUpdate()

3. 卸载组件 : 由ReactDOM.unmountComponentAtNode()触发

1. componentWillUnmount()

Hooks不能写在条件语句或循环语句中

在 React 中,每个组件都是一个独立的函数。这也意味着每次组件被重新渲染时,它的函数体都会重新运行一遍。因此,React 需要确保每次运行函数时,Hooks 的执行顺序都是一致的。如果将 Hooks 写在函数的任何其他位置,其执行顺序就有可能发生变化。

常用的hook

useState useEffect useContext useMemo useCallback useRef

useMemo和useCallback的共同点:

接收的参数都是一样的,第一个是回调函数,第二个是依赖的数据 它们都是当依赖的数据发生变化时才会重新计算结果,起到了缓存作用

useMemo和useCallback的区别:

useMemo计算结果是return回来的值,通常用于缓存计算结果的值 useCallback计算结果是一个函数,通常用于缓存函数

useRef 的作用
  • 用useRef获取React JSX中的DOM元素,获取后你就可以控制DOM的任何东西了。
  • 用useRef来保存变量,注意:useRef 每次都会返回相同的引用
  • 可以用来生成对 DOM 对象的引用。

HTTP

主要就是为了将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器

影响一个 HTTP 网络请求的因素主要有两个:带宽和延迟。 现在网络基础建设已经使得带宽得到极大的提升,我们不再会担心由带宽而影响网速,那么就只剩下延迟了

  • 浏览器阻塞(HOL blocking):浏览器会因为一些原因阻塞请求。浏览器对于同一个域名,同时只能有 4 个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制,后续请求就会被阻塞。

  • DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP 才能建立连接。将域名解析为 IP 的这个系统就是 DNS。这个通常可以利用DNS缓存结果来达到减少这个时间的目的。

  • 建立连接(Initial connection):HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。

HTTP1.0和HTTP1.1的一些区别

  1. 缓存处理,在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
  2. 带宽优化及网络连接的使用,HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
  3. 错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
  4. Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。
  5. 长连接,HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。

SPDY:HTTP1.x的优化

  • 降低延迟,针对HTTP高延迟的问题,SPDY优雅的采取了多路复用(multiplexing)。多路复用通过多个请求stream共享一个tcp连接的方式,解决了HOL blocking的问题,降低了延迟同时提高了带宽的利用率。

  • 请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接共享的基础之上有可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,这样重要的请求就会优先得到响应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文件等加载,这样可以保证用户能第一时间看到网页内容。

  • header压缩。 前面提到HTTP1.x的header很多时候都是重复多余的。选择合适的压缩算法可以减小包的大小和数量。

  • 基于HTTPS的加密协议传输,大大提高了传输数据的可靠性。

  • 服务端推送(server push),采用了SPDY的网页,例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。SPDY构成图:

数据类型

  • Boolean
  • Null
  • Undefined
  • Number
  • BigInt:BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值
  • String
  • Symbol

判断数据类型

typeofinstanceof 和 Object.prototype.toString()

通过 typeof 操作符

  1. null 的判定有误差,得到的结果 如果使用 typeofnull得到的结果是object
  2. 操作符对对象类型及其子类型,例如函数(可调用对象)、数组(有序索引对象)等进行判定,则除了函数都会得到 object 的结果。

通过 instanceof 操作符也可以对对象类型进行判定,其原理就是测试构造函数的 prototype 是否出现在被检测对象的原型链上。

  • 该方法本质就是依托Object.prototype.toString() 方法得到对象内部属性 [[Class]]
  • 传入原始类型却能够判定出结果是因为对值进行了包装
  • null 和 undefined 能够输出结果是内部实现有做处理
javascript
复制代码

es6

  1. 关键字let, const
  2. 解构赋值
  3. set数据结构:类似于数组的数据结构,成员值都是唯一且没有重复的值
  4. map数据结构:类似于对象的数据结构,成员键是任何类型的值
  5. 箭头函数
  6. Promise

原型、原型链

原型:JS声明构造函数(用来实例化对象的函数)时,会在内存中创建一个对应的对象,这个对象就是原函数的原型。构造函数默认有一个prototype属性,prototype的值指向函数的原型。同时原型中也有一个constructor属性,constructor的值指向函数对象。 通过构造函数实例化出来的对象,其默认有一个__proto__属性,__proto__的值指向构造函数的原型。在原型对象上添加或修改的属性,在所有实例化出的对象上都可共享。

原型链:当在实例化的对象中访问一个属性时,首先会在该对象内部(自身属性)寻找,如找不到,则会向其__proto__指向的原型中寻找,如仍找不到,则继续向原型中__proto__指向的上级原型中寻找,直至找到或Object.prototype.__proto__为止(值为null),这种链状过程即为原型链。

继承

1. 原型链继承:

原型链继承中,通过将子构造函数的原型对象指向父构造函数的实例,实现了继承。这意味着子对象可以访问父对象原型链上的属性和方法。

2. 构造函数继承(经典继承):

构造函数继承是通过在子构造函数中调用父构造函数来实现继承。在构造函数继承中,通过在子构造函数中使用**call()apply()**方法,将父构造函数的上下文设置为子对象的上下文,从而实现继承。

3. 组合继承:

组合继承结合了原型链继承和构造函数继承,既继承了父构造函数的属性,又继承了父构造函数原型对象上的方法。在组合继承中,通过调用父构造函数的方式实现属性的继承,通过将子构造函数的原型对象指向父构造函数的实例实现方法的继承。

4. ES6类继承:

在ES6中引入了类的概念,通过class关键字和extends关键字可以实现类的继承。

作用域

作用域决定了代码区块中变量和其他资源的可见性

全局作用域、和函数作用域、块级作用域

1.全局作用域代码中任何地方都能访问到的对象拥有全局作用域

  • 最外层函数 和在最外层函数外面定义的变量拥有全局作用域
  • 所有末定义直接赋值的变量自动声明为拥有全局作用域

2.函数作用域,是指声明在函数内部的变量

3.块级作用域可通过新增命令let和const声明,所声明的变量在指定块的作用域外无法被访问。

  • 声明变量不会提升到代码块顶部
  • 禁止重复声明
  • 循环中的绑定块作用域的妙用

闭包

能够访问其他函数内部变量的函数,被称为闭包。简单来说,闭包就是函数内部定义的函数,被返回了出去并在外部调用

  1. 模拟私有属性
  2. 柯里化函数:柯里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

浏览器缓存策略

强缓存(不需要向服务器索要缓存)

设置 expires

就是过期时间,例如,expires: Tue, 18 Apr 2023 06:29:41 GMT 表示缓存将在这个时间后过期。这个到期日期是一个绝对日期。如果修改了本地日期,或者本地日期与服务器日期不一致,那么缓存过期时间就会出错。

设置 Cache-Control

HTTP/1.1增加了一个新的字段,cache-control 可以通过 max-age 字段设置过期时间,cache-control: max-age=7776000 另外 cache-control 还可以设置private/no-cache 等字段。

协商缓存(需要询问服务器缓存是否过期)

last-modified

即最后修改时间,当浏览器第一次请求该资源时,服务器会在响应头中添加 last-modified。当浏览器再次请求该资源时,浏览器会在请求头中带上 if-modified-since 字段,该字段的值为上一个服务器返回的最后修改时间,服务器比较两次,如果相同则返回 304,否则返回新的资源并更新 last-modified

ETag

HTTP/1.1 中的一个新字段表示文件的唯一标识符,只要文件内容发生变化,就会重新计算ETag。缓存过程与 last-modified 相同:服务器发送 ETag 字段 -> 浏览器再次请求时发送 If-None-Match -> 如果ETag值不匹配,则文件已更改,返回新的资源并更新ETag,匹配则返回304。

last-modified 和 ETag 的比较

ETag 比 last-modified 更准确:如果打开没有修改的文件,last-modified 也会改变,last-modified 的单位时间是秒。如果文件在一秒钟内被修改,它仍然会命中缓存。

CDN、DNS

1.CDN,即内容分发网络,它能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,提高用户访问网站的响应速度。

2.DNS的全称是domain name system,即域名系统。作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的去访问互联网而不用去记住能够被机器直接读取的IP地址。通过域名,最终得到该域名对应的IP地址的过程则是域名解析的过程。 递归查询、迭代查询 递归查询时如果客户端所询问的本地域名服务器不知道被查询的域名的IP地址,那么本地域名服务器就以DNS客户的身份,向其他顶级域名服务器继续发出查询,直到查询到结果后,再层层传递回来。

性能优化

web性能优化

  1. css放头部,js放底部加载顺序、js异步
  2. 减少http请求:将多个小文件合并为一个大文件
  3. 缓存:dns缓存、浏览器缓存
  4. 压缩文件
  5. 图片优化:懒加载、响应式图片
  6. 服务端渲染:服务端渲染:服务端返回 HTML 文件,客户端只需解析 HTML。
  • 优点:首屏渲染快,SEO 好。
  • 缺点:配置麻烦,增加了服务器的计算压力。
  1. 骨架屏:屏能够在内容加载完成之前快速展示页面结构,让用户感受到页面加载的进度,从而提高了用户体验。

webpack性能优化

打包速度

  1. 多进程打包:thread-loader
  2. 合理配置 loader 的 include、exclude
  3. 缓存:cache-loader

打包大小

  1. 压缩html:html-webpack-plugin
  2. 压缩js:terser-webpack-plugin
  3. 压缩css:css-minimizer-webpack-plugin
  4. 删除冗余代码:tree-shaking

热更新原理

配置webpack-dev-server热更新,当修改本地代码时,会触发重新编译,此时webpackDevMiddleWare会将编译的产物保存到内存中。同时HotModuleReplacementPlugin 会生成两个补丁包hot-update.json、hot-update.js,这两个补丁包一个是用来告诉浏览器哪个chunk变更了,一个是用来告诉浏览器变更模块及内容。当重新编译完成,浏览器会保存当前hash,然后通上一次的hash 值拼接出要请求的描述文件路径,再根据描述文件返回的内容,拼接出要另一个要请求的补丁包文件。请求成功就开始执行webpckHotUdate了,会继续调用 hotApply,实质就是执行了我们当初在配置模块热更新第二步中的回调事件,从而实现了页面内容的局部刷新。

单点登录

用于多个应用系统间,用户只需要登录一次就可以访问所有相互信任的应用系统。

单点登录需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。

跨域

跨域是指浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。

同源策略 是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。   同源策略限制以下几种行为:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM和JS对象无法获得
  • AJAX 请求不能发送

1.jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据 2.跨域资源共享(CORS),CORS需要浏览器和服务器同时支持,浏览器将CORS跨域请求分为简单请求和非简单请求,非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求, Access-Control-Request-Method Access-Control-Request-Headers

3.nginx代理跨域 4.postMessage

cookie、localStorage和sessionStorage 三者之间的区别以及存储、获取、删除等使用方式

生命周期:

cookie:可设置失效时间,没有设置的话,默认是关闭浏览器后失效

localStorage:除非被手动清除,否则将会永久保存。

sessionStorage: 仅在当前网页会话下有效,关闭页面或浏览器后就会被清除。

存放数据大小:

cookie:4KB左右

localStorage和sessionStorage:可以保存5MB的信息。

http请求:

cookie:每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题

localStorage和sessionStorage:仅在客户端(即浏览器)中保存,不参与和服务器的通信

垂直水平居中

宽高固定

  • absolute + 负margin
  • absolute + margin auto

宽高不固定

  1. absolute + transform
  2. css-table
  3. grid
  4. flex

flex弹性布局

容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)

容器属性

  • flex-direction
  • flex-wrap
  • flex-flow
  • justify-content
  • align-items
  • align-content

项目属性

  • order
  • flex-grow
  • flex-shrink
  • flex-basis
  • flex:flex属性是flex-growflex-shrink 和 flex-basis的简写,默认值为0 1 auto。该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
  • align-self

大屏可视化适配(autofit.js)

  • 响应式布局(媒体查询)

  • vw/vh方案

    1. 概述:按照设计稿的尺寸,将px按比例计算转为vw和vh
    2. 优点:可以动态计算图表的宽高,字体等,灵活性较高,当屏幕比例跟 ui 稿不一致时,不会出现两边留白情况
    3. 缺点:每个图表都需要单独做字体、间距、位移的适配,比较麻烦
  • scale方案

    1. 概述:也是目前效果最好的一个方案
    2. 优点:代码量少,适配简单 、一次处理后不需要在各个图表中再去单独适配.
    3. 缺点:留白,有事件热区偏移,下面介绍的autofit.js已经完全解决了此问题
  • rem + vw vh方案

    1. 概述:这名字一听就麻烦,具体方法为获得 rem 的基准值 ,动态的计算html根元素的font-size ,图表中通过 vw vh 动态计算字体、间距、位移等
    2. 优点:布局的自适应代码量少,适配简单
    3. 缺点:留白,有时图表需要单独适配字体

transition

我们先来简单了解下transition族属性:

属性含义
transition-property指定使用过渡效果的css属性
transition-duration设置过渡动画持续时间
transition-timing-function设置动画的时间函数。
transition-delay设置动画的延迟时间

前端工程化

本质上就是将前端开发流程,标准化、规范化、工具化、自动化、简单化。通过规范和工具来提高前端应用质量及开发效率

  1. 技术选型
  2. 统一规范:代码规范、git 规范、UI规范,
  • 规范的代码可以促进团队合作
  • 规范的代码可以降低维护成本
  • 规范的代码有助于 code review(代码审查)
  • 养成代码规范的习惯,有助于程序员自身的成长
  1. 测试:测试是前端工程化建设必不可少的一部分,它的作用就是找出 bug,越早发现 bug,所需要付出的成本就越低。并且,它更重要的作用是在将来,而不是当下。

  2. 部署:在没有学会自动部署前,我是这样部署项目的:构建项目 npm run build。将打包好的文件放到静态服务器。 一次两次还行,如果天天都这样,就会把很多时间浪费在重复的操作上。所以我们要学会自动部署,彻底解放双手。

  3. 监控:作用是预警和追踪定位问题

  4. 性能优化:手动检查和工具(LightHouse)

  5. 重构:

重构

重构的目标

重构大概的意思是在不影响项目的功能使用前提下,使用一系列的重构方式,改变项目的内部结构。提高项目内部的可读性,可维护性。

为什么重构

随着业务需求的不断增加,变更,舍弃,项目的代码也难免会出现瑕疵,这就会影响代码的可读性,可维护性,甚至影响项目的性能。而重构的目的,就是为了解决这些瑕疵,保证代码质量和性能。

  1. 函数逻辑结构混乱,或因为没注释原因,连原代码写作者都很难理清当中的逻辑
  2. 因为对象强耦合或者业务逻辑的原因,导致业务逻辑的代码巨大,维护的时候排查困难。
  3. 重复代码太多,没有复用性。
  4. 随着技术的发展,代码可能也需要使用新特性进行修改。
  5. 随着学习的深入,对于以前的代码,是否有着更好的一个解决方案。
  6. 因为代码的写法,虽然功能正常使用,但是性能消耗较多,需要换方案进行优化。

什么时候重构

  1. 首先,重构是需要花时间去做的一件事。花的时间可能比之前的开发时间还要多。
  2. 其次,重构是为了把代码优化,前提是不能影响项目的使用。
  3. 最后,重构的难度大小不一,可能只是稍微改动,可能难度比之前开发还要难。

怎么重构

  1. 代码复用
  2. 函数无拓展性
  3. 函数违反单一原则函数违反单一原则最大一个后果就是会导致逻辑混乱。就比如对象转tree结构,返回的数据存在重复数据,写的函数是先去重,再转换,后续如果不需要去重,需要整体改动函数,那么我们在开始阶段就进行拆分,去重一个,转换一个,后续只需要注释一行就可以了。

抽离公共组件

什么是组件化

组件化是指解耦复杂系统时将多个功能模块拆分、重组的过程。

组件的设计原则

  1. 标准性:任何一个组件都应该遵守一套标准,可以使得不同区域的开发人员据此标准开发出一套标准统一的组件。
  2. 独立性:描述了组件的细粒度,遵循单一职责原则,保持组件的纯粹性,属性配置等API对外开放,组件内部状态对外封闭,尽可能的少与业务耦合。
  3. 复用与易用。
  4. 良好的接口设计。

组件的职能划分

  1. 基础组件:一般antd、element-ui基本可以满足。
  2. 业务组件:通常是根据最小业务状态抽象而出,有些业务组件也具有一定的复用性,但大多数是一次性组件。
  3. 通用组件:可以在一个或多个APP内通用的组件。

new 运算符

function create(Con, ...args) {
  let obj = {}
  Object.setPrototypeOf(obj, Con.prototype)
  let result = Con.apply(obj, args)
  return result instanceof Object ? result : obj
}

Promise请求并发数限制

function sendRequest(requestList,limits,callback){
    const promises = requestList.slice() // 取得请求list(浅拷贝一份)
    // 得到开始时,能执行的并发数
    const concurrentNum = Math.min(limits,requestList.length)
    let concurrentCount = 0 // 当前并发数
    // 第一次先跑起可以并发的任务
    const runTaskNeeded = ()=>{
        let i = 0
        // 启动当前能执行的任务
        while(i<concurrentNum){
            i++
            runTask()
        }
    }
    // 取出任务并且执行任务
    const runTask = ()=>{
        const task = promises.shift()
        task && runner(task)
    }
    // 执行器
    // 执行任务,同时更新当前并发数
    const runner = async (task)=>{
        try {
            concurrentCount++
            await task()
        } catch (error) {
        } finally{
            // 并发数--
            concurrentCount--
            // 捞起下一个任务
            picker()
        }
    }
    // 捞起下一个任务
    const picker = ()=>{
        // 任务队列里还有任务并且此时还有剩余并发数的时候 执行
        if(concurrentCount < limits && promises.length > 0 ){
            // 继续执行任务
            runTask()
        // 队列为空的时候,并且请求池清空了,就可以执行最后的回调函数了
        }else if(promises.length ==0 && concurrentCount ==0 ){
            // 执行结束
            callback && callback()
        }
    }
    // 入口执行
    runTaskNeeded()
}

promiseAll

Promise.myAll = (promises) => {
  return new Promise((rs, rj) => {
    let count = 0
    let result = []
    const len = promises.length
    if (len === 0) {
      return rs([])
    }
    promises.forEach((p, i) => {
      Promise.resolve(p).then((res) => {
        count += 1
        result[ i ] = res
        if (count === len) {
          rs(result)
        }
      }).catch(rj)
    })
  })
}

冒泡、快速排序

function bubbleSort(arr) {
    const len = arr.length
    for (let i = 0; i < len; i++) {
      for (let j = 1; j < len - i; j++) {
        if (arr[j - 1] > arr[j]) {
          [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
        }
      }
    }
    return arr
}

function quickSort(arr) {
    if (arr.length <= 1) {
      return arr
    }
    const middleIndex = Math.floor(arr.length / 2)
    const middle = arr.splice(middleIndex, 1)[0]
    const leftArr = [], rightArr = []
    for (let i = 0; i < arr.length; i++) {
      const current = arr[i]
      current < middle ? leftArr.push(current) : rightArr.push(current)
    }
    return quickSort(leftArr).concat(middle, quickSort(rightArr))
}

树的深度

var maxDepth = function (root) {
  if (root == null) return 0;
  return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
};

防抖、节流

function debounce(func, wait, immediate) {
  let timeout;
  return function () {
    const context = this;
    const args = [...arguments];
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      const callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, wait)
      if (callNow) func.apply(context, args)
    }
    else {
      timeout = setTimeout(() => {
        func.apply(context, args)
      }, wait);
    }
  }
}
// 定时器版本
function throttle(func, wait) {
    let timeout;
    return function() {
        let context = this;
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}
// 时间戳版本
function throttle(func, wait) {
    var previous = 0;
    return function() {
        let now = Date.now();
        let context = this;
        let args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}

数组扁平化

1flat(Infinity)
2toString()转为字符串
3、巧用reduce
const flatten = (arr, deep = 1) => {
    if (deep <= 0) return arr;
    return arr.reduce((res, curr) => res.concat(Array.isArray(curr) ? flatten(curr, deep - 1) : curr), [])
}

数组转tree

function tranListToTreeData(list) {
  const treeList = []
  const map = {}

  list.forEach(item => {
    if (!item.children) {
      item.children = []
    }
    map[item.id] = item
  })

  list.forEach(item => {
    const parent = map[item.pid]
    if (parent) {
      parent.children.push(item)
    } else {
      treeList.push(item)
    }
  })
  return treeList
}

倒数第K大的数

var findKthLargest = function(nums, k) {
    const arr = sortArr(nums)
    return arr[arr.length - k]
};
var sortArr = function(nums) {
    if(nums.length <= 1) return nums
    let l = [] ,r = []
    let mid = parseFloat((nums.length) / 2)
    let midVal = nums.splice(mid,1)[0]
    for (let i = 0; i < nums.length; i++){
        if(nums[i] < midVal){
            l.push(nums[i])
        } else {
            r.push(nums[i])
        }
    }
    return sortArr(l).concat(midVal).concat(sortArr(r))
}