Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

复习笔记 #7

Open
XingGuoZM opened this issue Mar 15, 2023 · 8 comments
Open

复习笔记 #7

XingGuoZM opened this issue Mar 15, 2023 · 8 comments

Comments

@XingGuoZM
Copy link
Owner

XingGuoZM commented Mar 15, 2023

性能优化

页面加载过程
浏览器解析,查询缓存,dns查询,建立连接,服务器处理请求,服务器发送响应,客户端收到页面,解析html,构建渲染树,开始显示内容(白屏时间),首屏内容加载完成(首屏时间),用户可交互(DomContentLoaded),加载完成(load)

浏览器进程:主进程,GPU进程,渲染进程,网络进程
渲染进程包括js引擎线程、事件触发线程、定时器触发线程、异步http请求线程、GUI渲染线程

js引擎线程主要负责解析js并运行代码,一直等待任务队列中的任务的到来,然后加以处理。当js引擎执行时,GUI渲染线程就会被挂起,GUI更新会被保存在一个队列中等到js引擎空闲时立即被执行

GUI渲染线程主要负责渲染浏览器界面,解析html、css构建DOM树和RenderObject树,布局,绘制。需要重绘或者由于某种操作引起回流时该线程就会执行。

GUI渲染线程和js引擎线程互斥,为了防止渲染结果的不可预期

事件触发线程:用来控制事件循环,当事件满足触发条件时,将事件放入到js引擎所在的执行队列中

定时器触发线程:setTimeout和setInterval所在的线程,定时任务并不是由js引擎计时的,是由定时触发线程计时的,计时完毕后通知事件触发线程

异步http请求线程:浏览器有一个单独的线程用于处理ajax请求,当请求完成,若有回调函数,通知事件触发线程。

为什么js是单线程的?
多线程的复杂性,多线程操作需要加锁,编码复杂性升高
如果同时操作dom,在多线程不加锁的情况下,最终会导致DOM渲染结果不可预期

为什么GUI渲染线程和js引擎线程互斥?
由于js是可以操作dom的,如果同时修改元素属性并同时渲染界面,那么渲染线程前后获得的元素就可能不一致。因此为了防止不可预期的结果,浏览器设定GUI渲染线程和js引擎线程为互斥关系

脚本加载:动态加载、异步加载

  • 白屏时间、首屏时间、可交互时间计算问题
    白屏时间 = 页面开始展示时间点 - 开始请求的时间点
    开始请求的时间点,performance Timing.navigation Start
    页面开始展示的时间点,开始解析body的时间点就是页面开始展示的时间,可以通过在head标签尾部插入script标签来统计时间节点,或者通过performance Timing.domLoading Start(直接忽略head解析时间)

首屏时间 = 首屏内容渲染结束时间点 - 开始请求时间点

结束时间点可以通过以下几种方式获取

  1. 首屏模块的标签标记:在html文档中对应首屏内容的标签结束位置,使用内联的js代码记录当前的时间戳作为首屏内容渲染结束的时间点。
  2. 统计首屏内加载最慢的图片的时间:通常首屏内容加载最慢的就是图片资源,由于浏览器对每个页面的tcp连接数有限制,使得并不是所有的图片都能立刻开始下载和显示,因此在构建完成后会通过遍历首屏内的所有图片标签,并监听所有图片的onload事件,最终遍历图片标签的加载时间获取最大值,将这个最大值作为首屏时间
  3. 自定义首屏内容计算:统计首屏图片加载的时间比较复杂,所以忽略图片等资源的加载情况,只考虑页面主要dom,只考虑首屏的主要模块,而不是严格意义首屏线以上的所有内容

可交互时间 = 用户可以正常进行交互的时间点 - 开始请求的时间点
performance Timing.domInactive,代表dom结构结束解析的时间点,即docuemnt.readyState变为inactive

性能检测工具
chrome performance、lighthouse

火焰图:Main记录了渲染进程中主线程的执行记录
Summary:各指标时间统计表
bottom-up:事件时长排序列表

  • 性能问题表现行为
    首屏白屏,打包文件过大,首屏加载时间过长,可能原因js阻塞页面渲染
    交互卡顿,js执行时间过长,占用主线程,阻塞任务执行,不能在16.7ms内完成任务而掉帧

  • 相关知识点、
    webpack工作过程
    初始化,构建,生成
    读取配置(webpack.config.js或者shell)参数创建一个编译器对象(compiler),初始化编译环境(注入内置插件、初始化ruleset集合、加载配置插件),compiler执行run方法,找出entry中所有入口,调用compilation.addEntry将入口文件转换为dependence对象

编译模块:根据entry对应的dependence创建module对象,调用loader将模块转译为标准的js内容,调用js解释器将内容转换为AST对象,从中找出该模块依赖的模块,再递归本步骤直到左右入口依赖文件都经过了本步骤的处理,处理完成之后得到了每个模块被翻译后的内容以及他们之间的依赖关系图

根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再把每个chunk转换成一个单独的文件加入到输出列表,这是最后修改文件内容的机会。在确定好输出内容之后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

  1. webpack工程化

babel编译+webpack打包

常用plugin有
terser-webpack-plugin(压缩代码)
html-webpack-plugin(创建html文件到输出目录,将webpack打包后的chunk自动引入到这个html中)

const HtmlPlugin = require('html-webpack-plugin')
new HtmlPlugin({
  filename:'index.html',
  template:'pages/index.html',
})

split-chunk-plugin:用来提取第三方库(react等)和公共代码(js、css),用于多页面应用程序,生成公共chunk,避免重复引用

{
  entry:{
    vendor: './index.html',
  },
  plugins:[
    new webpack.optimize.commonChunkPlugin({
      name:['vendor','runtime'],
      filename:'[name].js'
    })
  ]
}

常用loader:css-loader、style-loader
tree-shaking:是什么?有什么用?应用场景?有什么坑?怎么解决的?webpacke的tree-shaking和rollup的tree-shaking有何不同?
通过静态分析之后,给没有被使用的代码打上标识,然后通过压缩工具删除

副作用:一个函数会或者可能会对函数外部产生影响的行为
esm/import()
babel
压缩/terser,unglyfy
公共包复用,split-chunk-plugin
2. 网络
http https
http缓存
状态码
3. js异步
4. 页面重绘重排
5. 懒加载

less
变量和函数
嵌套和作用域
css隔离
运行时方案:BEM,命名规范, .block__element--modifier
编译时方案:
属性选择器+唯一的ID属性,vue的scoped,vue-loader支持
css-modules,css-loader支持

阶段划分

  1. webview容器初始化和主文档(html)下载
  2. 解析DOM和加载css
  3. 主js加载和执行,获取接口并执行剩下的js
  4. 渲染返回数据

优化点

  • 首屏优化
    native loading与h5骨架屏
    串行接口请求移动到服务端处理
    接口拆分,页面懒渲染,非实时更新数据缓存
  • 数据请求优化
    主接口预请求(prefetch)、请求失败重试

webpack打包体积优化方法
tree-shaking去除死代码

  • 代码分割:
  1. 入口配置(提取第三方库vendor)
  {
    entry:{
      main: './src/index.js',
      vendor: ['react','react-dom'],
    }
  }
  1. optimization.splitChunks
    默认值

新的chunk可以被共享,或者模块来自于node_modules
新的chunk体积大于20k
按需加载chunks时,并行请求的最大数量<=30
加载初始化页面时,并发请求最大数量<=30

当满足最后两个条件时,最好使用较大的chunks

optimization: {
  splitChunks:{
    chunks:'async',
    minSize:3000,
    maxAsyncRequests:5,
    maxInitialRequests:3,
    ...
  }
}

按需加载和路由懒加载

// 第三方库按需加载
import {Button,Select} from 'element-ui';

// 路由的懒加载import()或者require.ensure()
const detail = ()=>import('@/components/Detail');
const routes = [
    {path: '/comment', component: r => require.ensure([], r(require('./Comment')), 'comment')}
];

tree-shaking,用split-chunk-plugin(optimization的splitChunks)进行切割chunks,副作用和压缩工具terser协同处理死代码

webpack之前版本
const UnglifyJsPlugin = require('unglify-webpack-plugin');
plugins:[new UnglifyJsPlugin()]

webpack4 配置mode:production

渲染速度优化
逻辑代码优化
图片等静态资源优化
动画优化:对手机进行机型的等级划分为高端机、中端机和低端机,通过window.navigation.user

交互优化
构建速度优化

@XingGuoZM
Copy link
Owner Author

XingGuoZM commented Mar 15, 2023

工程化

webpack打包过程

  1. 获取参数
  2. 初始化compiler(编译器)
  3. 根据entry中的配置找到入口文件
  4. 编译,调用所有配置loader对模块进行转化,递归,最终得到处理后的内容和他们的依赖关系
  5. 根据入口和依赖关系组成一个个包含多个模块的chunck,再把chunck组装成一个bundle
  6. 根据配置确定输出路径和文件名,把内容写入文件

babel编译+webpack打包
webpack基本功能
plugin/loader:
常用plugin:
terser-webpack-plugin(压缩代码)、
html-webpack-plugin、
split-chunk-plugin(提取和分离代码)
常用loader:css-loader、style-loader
tree-shaking:是什么?有什么用?应用场景?有什么坑?怎么解决的?webpacke的tree-shaking和rollup的tree-shaking有何不同?
tree-shaking既然依赖es6 import和export进行静态分析,那么tree-shaking支持es5之前的js代码吗?
通过静态分析之后,给没有被使用的代码打上标识,然后通过压缩工具删除

副作用:一个函数会或者可能会对函数外部产生影响的行为
esm/import()
babel
压缩/terser,unglyfy
公共包复用,split-chunk-plugin

loader和plugin区别:转译器和扩展器

@XingGuoZM
Copy link
Owner Author

XingGuoZM commented Mar 15, 2023

React
fiber:任务分割、异步执行,优先级
fiber架构的优点:随时暂停、恢复渲染、并发/优先级渲染
fiber与调用栈的另一个区别是,栈帧在函数返回以后就销毁了,而fiber会在渲染结束以后继续存在,保存组件实例的信息(比如state)
为什么不使用generator来实现协作式调度?引用资料知乎讨论
一方面,如果React全面使用generator,那么React内部的调度逻辑、用户编写的所有组件都是generator,这会给用户增加心智负担,并且大量使用generator会有不小的性能开销,过于依赖执行引擎的优化;
另一方面,Fiber架构能够更加灵活地让React从任意一个Fiber恢复执行(不只是从上次中断的地方恢复,而且能够从更早的Fiber恢复),而generator函数只能回到之前的yield状态,不能回到更早的执行状态。

hooks

  • react hooks如何理解它及实现原理?
    类组件(难以实现拆分和复用)和函数组件(轻量、灵活和易于组织)
    函数组件更加契合react的设计理念,UI=F(data)
    react hooks出现是为了帮助函数组件补全这些(相比于类组件)缺失的能力,改进react组件的开发模式

  • 为什么useState要使用数组而不是对象
    es6解构赋值,
    如果返回的是数组,那么使用者可以对元素命名,灵活方便,使用多次可以根据需要命名。可以降低使用的复杂度

  • react hooks解决了哪些问题

  1. 组件之间复用状态逻辑
  2. 利于拆分逻辑,不必像类组件一样强制按照生命周期划分
  • react hooks使用限制
  1. 不要在循环、条件或者函数嵌套中调用hook
    调用时按照顺序加入链表,如果在循环、条件和函数嵌套中调用很可能会导致取值错位执行错误的hooks
  2. 在react函数组件中调用hook
  • useEffect和useLayoutEffect区别
    同:处理副作用,底层调用同一个函数mountEffectImpl
    异:useEffect在React渲染过程中是异步调用的,用于绝大多数场景,而useLayoutEffect在所有dom变更之后同步调用

  • react hooks在平时开发的过程中需要注意的问题和原因

  1. 避免在循环、条件或嵌套函数中调用hook,必须在函数顶层使用hook
    因为react利用调用顺序来正确更新相应的状态,在这些情况下调用hook容易导致调用顺序不一致

  2. useState和push、pop、splice更改数组对象
    使用push直接更改数组无法获取到新值,在类组件中不会有问题

  3. useState设置状态的时候只有第一次有效,后续更新需要通过useEffect

  4. useCallback

  5. useContext

  • react hooks和生命周期的关系
    constructor等同于useState
    getDerivedStateFromProps等同于useState里的update函数
    useEffect cleanup等同于componentwillunmount

  • 虚拟dom(跨平台和渲染性能)

真实的dom的js对象抽象表示,配合不同的渲染工具使跨平台渲染成为可能。通过事物处理机制可以将多次DOM修改的结果一次性更新到页面上,减少页面的渲染次数,提高渲染性能。

每次数据发生变化时,虚拟dom都会缓存一份,变化之时,现在的虚拟dom都会与缓存的虚拟dom进行对比,在vue或react封装了diff算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。

现代前端框架的一个基本要求就是无需手动操作dom,手动操作dom无法保证程序性能,多人开发可能出现性能较低的代码,另一方面省略手动dom操作可以大大提高开发效率

虚拟dom可以保证性能下限,在不进行手动优化的情况下,提供过得去的性能。

真实dom:html字符串+重建所有dom
虚拟dom:生成vnode+diff+必要的dom更新

虚拟dom另一个好处就是跨平台

  • react diff算法原理
    通过对比两颗虚拟dom树的变更差异,将更新的补丁作用于真实dom,以最小代价完成视图更新

diff算法的三个策略,分别基于树,组件、节点
shouldComponentUpdate

  1. 树同层级比较,忽略节点跨层级操作,提升对比效率
  2. 组件对比,类型不同,不对比直接重渲染,同类型组件可以通过shouldComponentUpdate、PureComponent、React.memo()来判断是否需要diff
  3. 组件中同层级元素列表对比,插入、移动和删除操作,稳定、可预测并且唯一的key可以提高节点复用
  • react key的作用?为什么要加上key?主要解决什么问题?
    react key

可以通过key来标识元素是新创建的还是移动的,提高节点复用,从而减少不必要的diff和重渲染

react key主要用于追踪列表中的哪些元素被修改、被添加或被删除的辅助标识。

@XingGuoZM XingGuoZM changed the title 性能优化 复习笔记 Mar 16, 2023
@XingGuoZM
Copy link
Owner Author

XingGuoZM commented Mar 16, 2023

网络

tcp和udp最大区别

Http

  • http特性
    可扩展协议、无状态有会话、基于tcp/tls连接,http依赖于面向连接的tcp进行消息传递

  • get和post区别
    副作用和幂等:对服务器资源做修改即副作用,幂等即发送m和n次请求(两者不相同且都大于1),服务器上资源的状态一致。get是无副作用、幂等的,post主要是有副作用、不幂等的
    技术上区分

  1. get请求能缓存,post请求不能
  2. get请求没有post请求那么安全,因为请求都在url中,且会被浏览器保存历史记录,post请求放在请求体中更安全
  3. url有长度限制,会干预get请求,浏览器决定
  4. get请求只能进行url编码,只能接收ascii字符。而post没有限制,支持更多的编码类型,而且不对数据类型做限制
  5. get请求会把请求报文一次性发出去,而post会分为两个tcp数据包,首先发送header部分,如果服务器响应100(continue),然后发送body部分
  • http 状态码
    1XX: 信息状态码,服务器收到请求,需要请求者继续执行操作
    2XX: 成功状态码,操作被成功接收并处理
    3XX: 重定向状态码,需要进一步操作以完成请求

    1. 301: 永久重定向
    2. 302: 临时重定向
    3. 304: 未修改(协商缓存)
      4XX: 客户端错误状态码,
    4. 400: 客户端请求语法错误
    5. 401: 发送的请求需要有通过http认证的认证信息
    6. 403: 请求资源的访问被服务器拒绝
    7. 404: 服务器上没有找到请求的资源
      5XX: 服务器错误状态码,
    8. 500: 服务器内部错误
    9. 501: 服务器不支持当前请求所需要的某个功能
    10. 502: 无效网关
    11. 503: 服务器暂时处于超负载或正在停机维护,无法处理请求
    12. 504: 网关超时
  • http 头部
    request headers、response headers

  • http1.0、http1.1、http2、http3区别
    http1.0:

  1. 引入http头概念
  2. content-type,具备了传输除纯文本HTML文件以外其他类型文档的能力
  3. 每个tcp连接只能发送一个请求,发送数据完毕,连接就关闭,tcp建立成本很高。
  4. 短连接,每一个http请求都由它自己独立的连接完成,意味着发起的每一个http请求之前都会有一次tcp握手

http1.1:

  1. 连接可以复用,长连接,默认开启connection:key-alive。在一个tcp连接上可以传送多个http请求和响应,减少了建立和关闭连接的消耗和延迟。
  2. 增加管道:允许在第一个应答被完全发送完成之前就发送第二个请求,降低通信延迟。复用同一个tcp连接期间,即便是通过管道同时发送了多个请求,服务端也是按照请求的顺序依次给出响应的。客户端在未收到之前所发出所有请求的响应之前,将会阻塞后面的请求(排队等待),即“队头阻塞”。
  3. 支持响应分块,分块编码传输,即Transfer-Encoding:chunked Content-length声明本次响应的数据长度。使用Content-length字段的前提是服务器发送响应之前必须知道响应数据的长度。
  4. 引入额外的缓存控制机制,在Http1.0中主要使用header里的If-Modified-Since,Expires来作为缓存的判断标准,Http1.1则引入更多的缓存判断的标准,Entity tag,If-None-Match,Cache-Control等

http2.0

  1. 二进制协议
  2. 多路复用,允许同时通过单一的HTTP2连接发起多重的请求-响应消息
    采用二进制格式传输取代http1的文本格式,单个连接上可以并行交错的请求和响应,之间互不干扰。
  3. 解决队头阻塞问题,二进制分帧层,交错发送,重新组装
  4. 压缩headers
  5. 服务端推送

https
对称加密
非对称加密
公钥加密数据,私钥解密数据,私钥掌握在颁发公钥的一方

https中间人攻击发生在哪个阶段?

tls握手过程

  • 客户端发起一个http请求,告诉服务器自己支持哪些hash算法

  • 服务端把自己的信息以数字证书的形式返回给客户端,证书中包含公钥和密钥,私钥由服务器持有

  • 客户端收到服务器的响应之后会先验证证书的合法性(证书中包含的地址和正在访问的地址是否一致,证书是否过期),验证通过后客户端就会生成一个密钥,并且用公钥加密,让服务端用私钥解密,解密后就用这个密钥进行数据传输

  • 服务端收到加密过的随机数并使用私钥解密获得R3,这时候两端都拥有了3个随机数,可以通过这3个随机值按照之前约定的加密方式生成密钥。接下来的通信就可以通过该密钥来加密解密

http缓存

  • 强缓存
    Cache-Control和Expires
    Expires的值和头里面的Date属性的值来判断缓存是否还有效。Expires是web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从缓存取数据,无需再次请求。Expires的一个缺点是返回的到期时间是服务器的时间,是一个绝对的时间,如果客户端时间和服务器时间不一致,就会存在误差

Cache-Control指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发送请求到服务器取数据,其设置的是一个相对时间。
Cache-Control:max-age=31536000表示距离发起请求31536000s内都可以命中缓存

Cache-Control:no-store表示没有缓存
Cache-Control:no-cache表示有缓存但要重新验证

Cache-control:private表示私有缓存,专用于某单个用户的,中间人(中间代理、cdn等缓存)不能缓存此响应
Cache-control:public表示公有缓存,中间人可以缓存响应

  • 协商缓存
    If-Modifed-Since、Last-Modified
    Last-Modified表示本地文件最后修改日期,浏览器会在request header加上If-Modifed-Since(上次Last-Modified的值),询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,但是如果在本地打开缓存文件,就会造成Last-Modifed被修改,所以在HTP/1.1出现了ETag

If-none-match、ETags
资源的变化都会导致ETag变化,和最后修改时间没有关系,Etag可以保证这个资源的唯一性,If-None-match的header会将上次返回的Etag发送给服务器,询问该资源的etag是否有更新,有变动就会发送新的资源回来。

If-none-match、ETags的优先级高于If-Modified-Since 、Last-Modified

  • cookie
  • 跨域

@XingGuoZM
Copy link
Owner Author

XingGuoZM commented Mar 16, 2023

// 多叉树遍历
function layerSum(root) {
  function traverse(root) {
    if (!root.children) return 0;
    let sum = 0;
    for (const item of root.children) {
      sum += traverse(item) + 1;
    }
    return sum;
  }
  return traverse(root) + 1;
}

const res = layerSum({
  value: 2,
  children: [
    { value: 6, children: [{ value: 1 }] },
    { value: 3, children: [{ value: 2 }, { value: 3 }, { value: 4 }] },
    { value: 5, children: [{ value: 7 }, { value: 8 }] }
  ]
});

console.log(res);
// 虚拟dom转真实dom
const vnode = {
  tag: 'DIV',
  attrs: { id: 'app' },
  children: [{
    tag: 'SPAN',
    children: [{ tag: 'A', children: [] }]
  },
  {
    tag: 'SPAN',
    children: [
      { tag: 'A', children: [] },
      { tag: 'A', children: [] }
    ]
  }
  ]
}

function render(vnode) {

  const setAttr = (node, attrs) => {
    if (!attrs) return;
    const kv = Object.entries(attrs);
    if (kv.length > 0) {
      for (let item of kv) {
        node.setAttribute([item[0]], item[1]);
      }
    }
  }
  const root = document.createElement(vnode.tag);
  setAttr(root, vnode.attrs);
  function buildTree(root, node) {
    if (!node.children.length === 0) return;
    for (let i = 0; i < node.children.length; i++) {
      const element = document.createElement(node.children[i].tag);
      setAttr(element, node.children[i].attrs);
      root.appendChild(element);
      buildTree(root.children[i], node.children[i]);
    }
  }
  buildTree(root, vnode);
  return root;
}

render(vnode)

实现new

/**
 * 创建一个空对象
 * this指向
 * 原型链完整
 * 返回
*/

function myNew(fn,...args){
  var obj=new Object();
  fn.prototype = obj.__proto__;
  const ret = fn.apply(obj,args)
  return typeof ret==='object'?ret:obj;
}

实现call
实现bind

实现compose

const compose = (arr)=>{
  if(arr.length===0) return arr[0];
  return arr.reduce((pre,curr)=>{
    return (...args)=>pre(curr(args))
  })
  
}

实现防抖和节流

//防抖
const debounce = (fn)=>{
  const timer= null;
  return function(){
      if(timer!==null){
        clearTimeout(timer);
        timer = null;
      }
      timer = setTimeout(()=>fn.apply(this,arguments),300);
  }
}
// 节流
const throttle=(fn)=>{
  const timer = null
  return function(){
    if(timer===null){
      setTimeout(()=>{
        fn.apply(this,arguments);
        clearTimeout(timer);
        timer=null;
      },300)
    }
  }
}

实现promise.finally方法

/**
 * 不管成功或者失败都调用
 * finally不接收参数,但需要链式传递
*/
Promise.prototype._finally = function(fn){
  return this.then(
    data=>Promise.resolve(fn()).then(()=>data),
    err=>Promise.reject(fn()).then(()=>throw err)
  );
}

实现promise.all方法

/**
 * 记录resolve次数
 * 返回结果存入顺序
 */
const _all = (pList) => {
  const ans = [];
  let count = 0;
  return new Promise((resolve, reject) => {
    pList.forEach((item, i) => {
      Promise.resolve(item).then((res) => {
        ans[i] = res;
        count++;
        if (count === pList.length) resolve(ans);
      }).catch(reject)
    })
  })
}

实现promise.race方法

const _race = (pList)=>{
  return new Promise((resolve,reject)=>{
    pList.forEach(fn=>{
      Promise.resolve(fn).then(resolve,reject);
    })
  })
}

实现promise.allSettled方法

const _allSettled = (pList) => {
  const ans = [];
  let count = 0;
  const valid = (callback, i, result) => {
    count++;
    ans[i] = result;
    if (count === pList.length) callback(ans);
  }
  return new Promise((resolve, reject) => {
    pList.forEach((item, i) => {
      Promise.resolve(item)
        .then((res) => valid(resolve, i, res))
        .catch((err) => valid(reject, i, err));
    })
  })
}

请求(promise)并发

const scheduledPromise = async (maxLimit, pList, callback) => {
  const ret = [];
  const runPool = [];

  for (const item of pList) {
    const p = Promise.resolve().then(() => callback(item));
    ret.push(p);

    if (pList.length > maxLimit) {
      const e = p.then(() => runPool.splice(runPool.indexOf(e), 1));
      runPool.push(e);
      if (runPool.length >= maxLimit) {
        await Promise.race(runPool);
      }
    }
  }
  return Promise.all(ret);
}

请求(promise)重试

const promiseRetry = (fetchData,times)=>{
  return new Promise((resolve,reject)=>{
    const run = ()=>{
      Promise.resolve(fetchData())
      .then(resolve).catch(()=>{
        --times==0 ? reject('重试次数达到上限') : run();
      })
    }
    run();
  })
}

请求(promise)超时

const promiseTimeout = (fetchData, time) => {
  const timeout = new Promise((_, reject) => setTimeout(() => reject('超时了'), time));
  const pList = [timeout, Promise.resolve(fetchData())];
  return Promise.race(pList);
}

数组扁平化

/**
 * 数组扁平化
 *
 * flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]);
 */

const flatter = (arr) => {
  if (!arr.length) return;
  return arr.reduce((pre, cur) => {
    return Array.isArray(cur) ? [...pre, ...flatter(cur)] : [...pre, cur];
  }, []);
}


// 迭代方式
const flatter2 = (arr) => {
  if (!arr.length) return;

  while (arr.some(item => Array.isArray(item))) {
    arr = [].concat(...arr);
  }
  return arr;
}
// 深拷贝,忽略function、循环引用、undefined等
const deepClone = (obj) => {
  if (typeof obj !== 'object' || obj === null) return obj;
  const ans = Array.isArray(obj) ? [] : {};

  for (let item in obj) {
    if (typeof item !== 'object' || item === null) {
      ans[item] = obj[item];
    } else {
      ans[item] = deepClone(obj[item]);
    }
  }
  return ans;
}

const obj = { a: { b: { c: { d: 1 } } } }
const deepObj = deepClone(obj);
console.log(obj === deepObj, deepObj)

@XingGuoZM
Copy link
Owner Author

XingGuoZM commented Mar 17, 2023

js基础

var b = 10;
(function b(){
   b = 20;
   console.log(b); 
})();
for (var i = 0; i< 10; i++){
   setTimeout((i) => {
   console.log(i);
   }, 1000,i)
}

异步
promise
promise 链是分阶段构造的,因此在处理异步函数时必须注意对错误函数的处理
Promise then 第二个参数和catch的区别是什么?
模拟实现finally

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

async-await:特性、串行并行、错误处理
async 函数

async函数返回一个promise,如果返回值不是promise,则会用promise包装一下返回

async function fn(){return 1} 
// 等价于
async function fn(){return Promise.resolve(1)}

await表达式会暂停整个async函数的执行进程并让出其控制权,只有当异步返回成功或者失败才会恢复,如果在forEach里则不会被阻塞
await关键字只有在async函数中有效,在async主体外使用会抛语法错误
async函数体等价于由多个或0个await表达式分割开的,第一行代码到第一个await是同步执行的,不含await的async函数会同步执行,函数体内只要有await就会异步执行

async function fn(){await 1} 
// 等价于
async function fn(){return Promise.resolve(1).then(()=>undefined)}
async function foo() {
  const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000));
  const p2 = new Promise((_, reject) => setTimeout(() => reject("2"), 500));
  const results = [await p1, await p2]; // 不推荐使用这种方式,请使用 Promise.all 或者 Promise.allSettled
}
foo().catch(() => { }); // 捕捉所有的错误...

// 
const next = (msg) => Promise.resolve().then(() => console.log(msg))
async function bar() {
  // 串行
  // await next(1)
  // await next(2)
  // 并行
  const a = next(1);
  const b = next(2);
  await a;
  await b;
}


// async 重写promise链
// 前
// async function getData(url) {
//   return fetchData(url)
//     .catch((e) => fallback(e))
//     .then((v) => handleData(v))
// }
// 后
// async function getData(url) {
//   let v;
//   try {
//     v = await fetchData(url);
//   } catch (e) {
//     v = fallback(e)
//   }
//   return handleData(v);
// }

输出代码顺序

async function async1() {
  console.log('1');
  await async2();
  console.log('2');
}
 
async function async2() {
  console.log('3');
}
 
console.log('4');
 
setTimeout(function() {
    console.log('5');
}, 0);  
 
async1();
 
new Promise(function(resolve) {
    console.log('6');
    resolve();
  }).then(function() {
    console.log('7');
});
 
console.log('8');

setTimeout和setInterval

为什么要用 settimeout 模拟实现 setinterval?setinterval 的缺陷是什么?
每个setTimeout产生的任务会直接push到任务队列中,而setInterval每次把任务push到任务队列前,都要进行以下判断,看上次的任务是否还在队列中,如果有则不添加,没有则添加。

@XingGuoZM
Copy link
Owner Author

用户时长统计上报
何时开始:document监听事件DOMContentLoaded,window监听事件load(兜底)
何时结束:页面销毁
如何防作弊
怎么判断是否空闲:window监听,toustart和scroll事件
如何解决上报出错问题
白名单和黑名单
与宿主页面的隔离

虚拟滚动

  • 虚拟滚动列表:滚动锚定、计算顶部偏移量
  • 核心原理:
  • 监听滚动,在合适的时机在上方或者下方增加偏移量,
  • 不可见区域用空的div把高度撑起来
  • 把容器内的撑开,形成滚动条,滚动条的偏移scrollTop
  • 原理:核心就是通过监听滚动来判断当前dom距离顶部的位置,计算可视区域第一条item距离顶部的高度 scrollTop
  • 适用
  • 1.每一项都等高
  • ahooks:useInfiniteScroll、useVirtualList
  • useVirtualList存在问题:
  • 闪烁、
  • 抖动、
  • 拖动滚动条时自动无限加载、
  • 回调函数、
  • 无法覆盖复杂情况(不定高的瀑布流)

@XingGuoZM
Copy link
Owner Author

css

BFC

flex:1
flex: 0 1 auto;
flex-grow: 空间剩余时放大比例,0代表空间剩余,项目不放大
flex-shrink: 空间不足时缩小比例,1代表空间不足,项目将缩小
flex-basis: 分配多余空间之前,项目占据的主轴空间,设置宽度值跟着宽度走,没有设置宽度(auto)则按实际宽度走。

@XingGuoZM
Copy link
Owner Author

vue

响应式原理
渲染机制

computed原理
手写classnames

vue指令系统

v-if原理,v-if和v-show区别
v-for作用域、v-for遍历对象、v-if和v-for同时存在、v-for和key、v-for原理
v-on和v-bind
双向绑定及v-model原理
生命周期及其作用域和执行上下文(this)问题
生命周期钩子可以用箭头函数吗?
watch原理
组件通信:props+$emit、provide+inject
组件插槽:固定名,动态名、数据传递
模版解析
组件注册
异步组件:defineAsyncComponent
props传递、单向数据流

组合式函数(composition api)原理
组合式函数与Mixin、无渲染组件、react hooks对比

自定义指令和插件

vue构建部署、安全、性能、稳定性等问题

vue 虚拟dom diff:静态提升、更新标记

this.$attrs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant