安全性
token 验证 处理令牌续期问题,在header中获取到新令牌时,替换老令牌,以达到用户无感刷新令牌
1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码
2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token
3、前端拿到token,将token存储到localStorage,并跳转路由页面
4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
5、每次调后端接口,都要在请求头中加token
6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401
7、如果前端拿到状态码为401,就清除token信息并跳转到登录页面
请求拦截器 request.interceptors.request.use 登录外的大部分请求体添加 token 以及sha256加密 (消息摘要)
增加图像验证码 3 1 纯前端随机数据 通过css旋转做到 2 后端返回图片 登录的时候带上 3 滑块
加密 sha256 base64
request
1 请求拦截request.interceptors.request.use
2 响应拦截
后台默认返回转json格式,blob等其他格式需要转响应头
刷新token
1 401 未授权
1 如果未授权 重定向首页 移除
umi+dva
dva 是基于redux,redux-saga react-router 的轻量级前端框架 redux用于处理仓库的同步数据,redux-sage用于处理仓库的异步数据,react-router用于页面跳转
特点约定式的组织model组织
父组件的数据通过props传递给子组件,而子组件里更新了props , 导致父组件更新 ,那么这样有很多子组件的话,就会导致数据错乱,没有办法管理了
单向数据流
所有状态的改变可记录、可跟踪,源头易追溯;所有数据只有一份,组件数据只有唯一的入口和出口,使得程序更直观更容易理解,有利于应用的可维护性;一旦数据变化,就去更新页面(即data -> 页面),但是没有(页面 -> data);如果用户在页面上做了变动,那么就手动收集起来(双向是自动),合并到原有的数据中。
双向数据绑定
无论数据改变,或是用户操作,都能带来互相的变动,自动更新。
最后举个例子,vue中父传子的props就是单向数据流,而v-modal就是双向数据流
umijs。现在前端开发的一大痛点就是,上手开发一个项目,上来就是一堆的配置,其中最重的可能就是webpack。umijs就是为了解决这个问题,希望能将开发者从无穷无尽的配置中解放出来,只关注业务代码
1.创建DOM树(分析HTML元素,构建一颗DOM树)
2.创建StyleRules(分析CSS文件和元素上的inline样式,生成页面的样式表)
3 执行JavaScript:加载并执行JavaScript代码(包括内联代码或外联JavaScript文件);
4.创建Render树(将DOM树和样式表,关联起来)
5.布局Layout(浏览器开始布局,为每个Render树上的节点确定一个在显示屏上出现的精确坐标)
6.绘制Painting(调用每个节点paint方法,把它们绘制出来)
js 是单线程
宏任务包括:setTimeout setInterval Ajax DOM事件
微任务:Promise async/await
宏任务 macro-task包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
微任务 micro-task包括:process.nextTick(node 环境非谷歌), Promises, Object.observe ( 第二个参数的回调方法可以监视第一个参数对象发生变化的过程 可以用notify发通知), MutationObserver(监听dom结点)。
this.setstate 同步事件 定时器中挑出react 控制 是同步 但是事件中类似于异步 在 React 的生命周期以及绑定的事件流中,所有的 setState 操作会先缓存到一个队列中,在整个事件结束后或者 mount 流程结束后,才会取出之前缓存的 setState 队列进行一次计算,触发 state 更新。
微任务比宏任务的执行时间要早
主线程 >> 主线程上创建的微任务 >> 主线程上创建的宏任务
1)所有的同步任务都在主线程上执行,行成一个执行栈。
2)除了主线程之外,还存在一个任务列队,只要异步任务有了运行结果,就在任务列队中植入一个时间标记。
3)主线程完成所有任务(执行栈清空),就会读取任务列队,先执行微任务队列在执行宏任务队列。
4)重复上面三步
只要主线程空了,就会读取任务列队,这就是js的运行机制,也被称为 event loop(事件循环)。
执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。
WebWorker就是给主线程创建多线程的环境,实现主线程运行时,WebWorker线程在后台运行,两者互不干扰,待WebWorker线程任务完成后,将结果返回给主线程。
1)所有的同步任务都在主线程上执行,行成一个执行栈。
2)除了主线程之外,还存在一个任务列队,只要异步任务有了运行结果,就在任务列队中植入一个时间标记。
3)主线程完成所有任务(执行栈清空),就会读取任务列队,先执行微任务队列在执行宏任务队列。
4)重复上面三步
只要主线程空了,就会读取任务列队,这就是js的运行机制,也被称为 event loop(事件循环)。
事件循环 题目https://blog.csdn.net/frontend_frank/article/details/115475305?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165381081316780357212870%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165381081316780357212870&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-115475305-null-null.142^v11^pc_search_result_control_group,157^v12^new_style&utm_term=%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF&spm=1018.2226.3001.4187
什么是fiber
那么如何理解react中的fiber呢,两个层面来解释:
从运行机制上来解释,fiber是一种流程让出机制,它能让react中的同步渲染进行中断,并将渲染的控制权让回浏览器,从而达到不阻塞浏览器渲染的目的。
从数据角度来解释,fiber能细化成一种数据结构,或者一个执行单元。,每一个被创建的虚拟dom都会被包装成一个fiber节点,它具备如下结构:
我们可以结合这两点来理解,react会在跑完一个执行单元后检测自己还剩多少时间(这个所剩时间下文会解释),如果还有时间就继续运行,反之就终止任务并记录任务,同时将控制权还给浏览器,直到下次浏览器自身工作做完,又有了空闲时间,便再将控制权交给react,以此反复。
const fiber = {
stateNode,// dom节点实例
child,// 当前节点所关联的子节点
sibling,// 当前节点所关联的兄弟节点
return// 当前节点所关联的父节点
}
1 内存中维护一颗虚拟DOM树,数据变化时(setState),自动更新虚拟 DOM,得到一颗新树,然后 Diff 新老虚拟 DOM 树,找到有变化的部分,得到一个 Change(Patch),将这个 Patch 加入队列,最终批量更新这些 Patch 到 DOM 中。
2 在组件更新和渲染是有2个过程 reconciler和render阶段: 调和和渲染
调和阶段(Reconciler):官方解释。React 会自顶向下通过递归,遍历新数据生成新的 Virtual DOM,然后通过 Diff 算法,找到需要变更的元素(Patch),放到更新队列里面去。
渲染阶段(Renderer):遍历更新队列,通过调用宿主环境的API,实际更新渲染对应元素。这一个过程是一旦任务开始进行,就无法中断,那么 js 将一直占用主线程, 一直要等到整棵 Virtual DOM 树计算完成之后,才能把执行权交给渲染引擎,
时间分片(time slicing)和暂停(supense)。
Fiber 把当前需要执行的任务分成一个个微任务,安排优先级,然后依次处理,每过一段时间(非常短,毫秒级)就会暂停当前的任务,查看有没有优先级较高的任务,然后暂停(也可能会完全放弃)掉之前的执行结果,跳出到下一个微任务
ReactDOM.render() 和 setState 的时候开始创建更新。
将创建的更新加入任务队列,等待调度。
在 requestIdleCallback 空闲时执行任务。
从根节点开始遍历 Fiber Node,并且构建 WokeInProgress Tree。
生成 effectList。
根据 EffectList 更新 DOM。
Time Slicing 时间分片和Suspense。
Fiber Reconciler 在阶段一进行 Diff 计算的时候,会生成一棵 Fiber 树。这棵树是在 Virtual DOM 树的基础上增加额外的信息来生成的,它本质来说是一个链表。
在react中,主要做了以下的操作:
为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务
增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行
更新过程中首先, 当前是哪个组件触发的更新, React 是知道的( this 指向), 于是 React 会针对当前组件计算其相应的到期时间(上面提到了计算方法), 并且基于这个到期时间, 创建一个更新 update , 将引起改变的 payload (比如说 state/props ), 作为此次更新的一个属性, 并插入当前组件对应的 Fiber Node 的更新队列(它是一个单向链表数据结构。只要有 setState 或者其他方式触发了更新,就会在 fiber 上的 updateQueue 里插入一个 update,这样在更新的时候就可以合并一起更新。
(假设更新一个组件需要 1ms,如果有200个组件要更新,那就需要 200ms,在这200ms的更新过程中,浏览器唯一的主线程都在专心运行更新操作,无暇去做任何其他的事情。想象一下,在这 200ms 内,用户往一个 input 元素中输入点什么,敲击键盘也不会获得响应,因为渲染输入按键结果也是浏览器主线程的工作,但是浏览器主线程被 React 占用,抽不出空,最后的结果就是用户敲了按键看不到反应,等 React 更新过程结束之后,那些按键会一下出现在 input 元素里,这就是所谓的界面卡顿。)
Fiber 的工作流程:这个执行的目的就是,让react中低优先级的异步任务更新,不会阻塞浏览器的高优先级的动画的更新,能够保证浏览器动画流畅性
(1)React 会自顶向下通过递归,遍历新数据生成新的 Virtual DOM,然后再转换为 Fiber 节点,并为其设置优先级,创建 Update,加入到更新队列(Update Queue),等待调度
(2)然后,进行requestWork,判断 UpdateQueue(更新队列)中的任务是同步还是异步,可能是用expirationTime来判断的
如果是同步任务,则立即执行,执行完了就行了
如果是异步任务,再判断当前任务是否过期,如果过期了也立即执行;如果没过期则进入 调度的流程。
<1>首先使用 requestIdleCallback API(如果浏览器不支持需要polyfill)让浏览器先执行它自己高优先级的任务,然后在浏览器空闲的时候再来获取时间片;最后 在获取到空闲时间片之后,再来执行react低优先级的异步任务,也就是进入 下面的reconcile阶段
<<1>>浏览器在执行react异步任务的时候,会传入一个deadline对象,这个参数也是表示一个时间,代表react异步任务可以执行多久(为了避免react任务执行过久造成卡顿),如果超过了这个时间,react就会进行中断当前Fiber节点的更新任务(因为Fiber节点是一个一个的执行的,所以可以中断),把控制权还给浏览器。然后又判断当前浏览器是否有空闲时间片,再进行这个循环
<<2>>而具体的异步任务 就是,从根节点开始遍历 Fiber Node,构建 WokeInProgress Tree,并且生成 effectList。这个阶段可以理解为就是 Diff 的过程
<2>最后,把UpdateQueue中的所有任务全部执行完了之后,进入commit阶段根据 EffectList 更新 真实DOM,这个阶段不可中断。
commit 阶段会执行如下的声明周期方法,如下getSnapshotBeforeUpdate\componentDidMount\componentDidUpdate\componentWillUnmount
构建自己的Hook可以将组件逻辑提取到可重用的函数中 把每个组件重复的逻辑单独抽离出来
比较常见的自定义hooks useAsync 封装axiosu useUpdate第一次不渲染数据更新渲染;useForceUpdate:强制更新;useFileView 根据传入 文件类型 URL的路径 返回支持的预览的个文件类型播放器,URL.revokeObjectURL() 静态方法用来释放一个之前已经存在的、通过调用 URL.createObjectURL() 创建的 URL 对象 (Blob 对象 !需要释放内存)。 另一个就是样式button 返回Button 变量样式 变量样式对应的状态
useResizeLine 拖拽 监听 鼠标点击onMouseDown 获取clientX 浏览器的X轴坐标 监听onmousemove 鼠标移动获取clientX 算出偏移量
https://ahooks.js.org/zh-CN/hooks/use-mount
React.memo()、useCallback()、useMemo() 区别及基本使用
https://www.jianshu.com/p/e12013671505
实操题
当用户首次登录成功(注册也是一种可以适用的场景)之后, 服务器端就会生成一个token值,这个值,会在服务器保存token值(保存在数据库中),再将这个token值返回给客户端。最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,由token的前几位+盐以哈希算法压缩成一定长的十六进制字符串,可以防止恶意第三方拼接token请求服务器)
1.都是浏览器存储 2.都存储在浏览器本地 区别: 1.cookie由服务器写入,验证加密 ,CSRF攻击容易获取 sessionStorage以及localStorage都是由前端写入 2.cookie的生命周期由服务器端写入时就设置好的,localStorage是写入就一直存在,除非手动清除,sessionStorage是由页面关闭时自动清除 3.cookie存储空间大小约4kb, sessionStorage及localStorage空间比较大,大约5M 4.3者的数据共享都遵循同源原则,sessionStorage还限制必须是同一个页面 5.前端给后端发送请求时,自动携带cookie, session 及 local都不携带 6.cookie一般存储登录验证信息或者token,localStorage常用于存储不易变动的数据,减轻服务器压力,sessionStorage可以用来监测用户是否是刷新进入页面,如音乐播放器恢复进度条功能
最后还有一个很主要的区别同一浏览器的相同域名和端口的不同页面间可以共享相同的 localStorage,但是不同页面间无法共享sessionStorage的信息。
每次打开页面之后,随机生成一个 id,放在变量里面,开始播放音乐之后,把这个 id 写到 cookie 里去,就是 playerid 这个 cookie,播放音乐的过程中,会不断有事件出发,比如说 timeupdate,事件触发的时候看 cookie 中的 playerid 是不是跟页面自己保存这个变量一致。不一致就暂停播放音乐。
sessionStorage
序列化对象 也是一种存储方式 开辟内存
解析是反序列化对象
JSON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串,数据在传输的过程中只能传输字符串。如果直接传输数据就会变成 [[Prototype]]: Object 无法使用了
使用JSON.parse()进行解析数据
JSON字符串解析
JS数据类型分为两类:一类是基本数据类型,也叫简单数据类型,包含7种类型,分别是Number 、String、Boolean、BigInt、Symbol、Null、Undefined。另一类是引用数据类型也叫复杂数据类型,通常用Object代表,普通对象,数组,正则,日期,Math数学函数都属于Object。
数据分成两大类的本质区别:基本数据类型和引用数据类型它们在内存中的存储方式不同。
基本数据类型是直接存储在栈中的简单数据段,占据空间小,属于被频繁使用的数据。
引用数据类型是存储在堆内存中,占据空间大。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。通常用Object代表,普通对象,数组,正则,日期,Math数学函数都属于Object
加分回答
Symbol每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的
[Symbol('bubing')]:function(){
console.log("我在补兵")
}
undefined 的字面意思就是:未定义的值 。这个值的语义是,希望表示一个变量最原始的状态,而非人为操作的结果 。 这种原始状态会在以下 4 种场景中出现:1、声明一个变量,但是没有赋值2、访问对象上不存在的属性或者未定义的变量3、函数定义了形参,但没有传递实参
null 的字面意思是:空值 。这个值的语义是,希望表示一个对象被人为的重置为空对象,而非一个变量最原始的状态 。 在内存里的表示就是,栈中的变量没有指向堆中的内存对象。
数据的创建方法Symbol(),因为它的构造函数不够完整,所以不能使用new Symbol()创建数据。由于Symbol()创建数据具有唯一性,所以 Symbol() !== Symbol(), 同时使用Symbol数据作为key不能使用for获取到这个key,需要使用Object.getOwnPropertySymbols(obj)获得这个obj对象中key类型是Symbol的key值。
BigInt也是ES6新出的一种数据类型,这种数据类型的特点就是数据涵盖的范围大,能够解决超出普通数据类型范围报错的问题。
使用方法:
-整数末尾直接+n:647326483767797n
-调用BigInt()构造函数:BigInt("647326483767797")
注意:BigInt和Number之间不能进行混合操作
(2)箭头函数:用=>运算符定义的函数.这个是ES6的语法。
(3)方法:在class中定义的函数。
第四种,生成器函数:用 function * 定义的函数。
异步函数:普通函数、箭头函数和生成器函数加上 async 关键字。
闭包就是能够读取其他函数内部变量的函数。
闭包:一个作用域有权访问另一个作用域的局部变量,代码上来看就是子函数访问父函数的局部变量并返回的函数。(一个函数内部创建另一个函数,通过另一个函数来访问这个函数的局部变量,利用闭包突破作用域链)。由于 javascript 的特性,外层的函数无法访问内部函数的变量;而内部函数可以访问外部函数的变量(即作用域链)形成一个闭包环境,需要两个条件:函数嵌套,子函数引用父函数局部变量。 闭包带来的问题:由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。
标记清除法 引用计数法 清除闭包 一般是把 引用类型设置为null
Promise是宏任务(同步执行),只有Promise的回调是异步微任务。
promise存在三种状态pending,fulfilled,rejected,
等待(pending)、已完成(fulfilled)、已拒绝(rejected)
基本使用方法是new Promise((resolve,rejected)=>{}).then(res=>{}).catch(err=>{});调用resolve()会执行then部分,出现错误会执行catch部分,此外promise.all([]).then(rs=>{})还支持顺序执行多个请求,rs的结果是一个请求结果数组。
可以使用Promise构造函数new一个实例,Promise构造函数接收一个函数作为参数,这个函数有两个参数,分别是两个函数 `resolve`和`reject`,`resolve`将Promise的状态由等待变为成功,将异步操作的结果作为参数传递过去;`reject`则将状态由等待转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。实例创建完成后,可以使用`then`方法分别指定成功或失败的回调函数,也可以使用catch捕获失败,then和catch最终返回的也是一个Promise,所以可以链式调用。
1 三个状态 2一旦状态改变,就不会再变,任何时候都可以得到这个结果3 resolve 方法的参数是then中回调函数的参数,reject 方法中的参数是catch中的参数 4. then 方法和 catch方法 只要不报错,返回的都是一个fullfilled状态的promise
加分点 Promise.resolve() :返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。 Promise.reject():返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法。 Promise.all():返回一个新的promise对象,该promise对象在参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。 Promise.any():接收一个Promise对象的集合,当其中的一个 promise 成功,就返回那个成功的promise的值。 Promise.race():当参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
正向代理:“一台代理服务器"代替了"客户端”,去和"目标服务器"进行交互,即代理客户端。
用途:隐藏客户端真实IP,突破限制访问外国网站。
反向代理:“一台代理服务器"代替了"目标服务器”,去和"客户端"进行交互,即代理服务端
用途:隐藏服务器真实IP,提供负载平衡,即指向后端的多台服务器中空闲的一台;
而我们一般在开发中解决跨域是利用反向代理的原理。例如vue项目中配置proxy代理或者nginx配置反向代理。
同源限制、协议、域名、端口、CORS、node中间件、JSONP、postmessage
跨域:当前页面中的某个接口请求的地址和当前页面的地址如果协议、域名、端口其中有一项不同,就说该接口跨域了。
跨域限制的原因:浏览器为了保证网页的安全,出的同源协议策略。
1cors:目前最常用的一种解决办法,通过设置后端允许跨域实现。
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, OPTIONS, POST");
node中间件、nginx反向代理:跨域限制的时候浏览器不能跨域访问服务器,node中间件和nginx反向代理,都是让请求发给代理服务器,静态页面面和代理服务器是同源的,然后代理服务器再向后端服务器发请求,服务器和服务器之间不存在同源限制。
我们公司用的是proxy
客户端发送请求,先经过中间层的处理,将域名 http://localhost:8080 转换为 http://example.com
利用转换后的域名发送请求到server
服务端接收到请求的域名 http://example.com,相应经过中间层的处理,将域名进行转换,发送响应数据给客户端
client 收到和自己相同的域名,继续下一步操作
跨域场景:前后端分离式开发、调用第三方接口
定义:块级格式上下文 **里面的布局不会影响外部** 特点:是一个独立的容器,内部的元素和外部元素互不影响。 形成:根元素、float、position不为relative、display不为none、overflow。 解决的问题:清除浮动、解决塌陷问题
【什么情况下可以让元素产生BFC】
1、float属性不为none
2、position为absolute或fixed
3、display为inline-block、table-cell、table-caption、flex、inline-flex
4、overflow不为visible
【BFC元素具有的特性】
1、在BFC中,盒子从顶部开始垂直地一个接一个排列
2、盒子垂直方向的距离由margin决定。同一个BFC的两个相邻盒子margin会重叠
3、BFC中,margin-left会触碰到border-left(对于从左至右的方式,反之)
4、BFC区域不会与浮动的盒子产生交集,而是紧贴边缘浮动
5、计算BFC高度时,自然会检测浮动的盒子高度
【主要用途】
1、清除内部浮动,父元素设置为BFC可以清除子元素的浮动(最常用overflow:hidden,IE6需加上*zoom:1):计算BFC高度时会检测浮动子盒子高度
2、解决外边距合并问题
3、右侧盒子自适应:BFC区域不会与浮动盒子产生交集,而是紧贴浮动边缘
我们可以通过 box-sizing 修改盒模型,box-sizing border-box content-box
1 清除浮动啊 左边顶宽 右边自适应 下外边距的折叠 放在不同BFC
typeof 判断 不能判断对象类型 typeof [] typeof {} typeof null // 都返回 ‘object’
instanceof找到的是实例在原型链中所有的构造函数,不容易找到直接创建实例的构造函数;
变量.constructor找到的是构造函数只有一个,就是直接创建这个实例的构造函数,所以用constructor找实例的构造函数更严谨。
Object.prototype.toString.call() 不能细分谁是谁的实例
typeof、instanceof、Object.prototype.toString.call()(对象原型链判断方法)、 constructor (用于引用数据类型)
typeof:常用于判断基本数据类型,对于引用数据类型除了function返回’function‘,其余全部返回’object’。 null 是 ‘object’ undefined是‘undefined’ typeof a
instanceof:主要用于区分引用数据类型,a instanceof Object instanceof运算符用于检测对象的原型链上是否存在构造函数的 prototype属性,一层层网上找,有则返回 true,否则返回 false ,除了函数基本数据类型以及 null 直接返回 false,不太适合用于简单数据类型的检测,检测过程繁琐且对于简单数据类型中的undefined, null, symbol检测不出来。 a instanceof Array
constructor:构造函数的属性 undefined和null特殊值不能用a. constructor用于检测引用数据类型,检测方法是获取实例的构造函数判断和某个类是否相同,如果相同就说明该数据是符合那个数据类型的,这种方法不会把原型链上的其他类也加入进来,避免了原型链的干扰。
a.constructor ===Array true
a.constructor === Object false
false Object.prototype.toString.call():适用于所有类型的判断检测,检测方法是Object.prototype.toString.call(数据) 返回的是该数据类型的字符串。 这四种判断数据类型的方法中,各种数据类型都能检测且检测精准的就是Object.prototype.toString.call()这种方法。 加分回答 instanceof的实现原理:验证当前类的原型prototype是否会出现在实例的原型链__proto__上,只要在它的原型链上,则结果都为true。因此,instanceof
在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype
,找到返回true,未找到返回false。 Object.prototype.toString.call()原理:Object.prototype.toString 表示一个返回对象类型的字符串,call()方法可以改变this的指向,那么把Object.prototype.toString()方法指向不同的数据类型上面,返回不同的结果5 jq中判断数据类型的方法 jQuery.isArray();jQuery.isFunction():是否为函数
伪对象选择器>!important>行内样式>id选择器>class选择器>标签选择器>通配选择器>继承
-第一类`!important`,无论引入方式是什么,选择器是什么,它的优先级都是最高的。 -第二类引入方式,行内样式的优先级要高于嵌入和外链,嵌入和外链如果使用的选择器相同就看他们在页面中插入的顺序,在后面插入的会覆盖前面的。 -第三类选择器,选择器优先级:id选择器>(类选择器 | 伪类选择器 | 属性选择器 )> (后代选择器 | 伪元素选择器 )> (子选择器 | 相邻选择器) > 通配符选择器 。 -第四类继承样式,是所有样式中优先级比较低的。 -第五类浏览器默认样式优先级最低。 加分回答 使用!important要谨慎 - 一定要优先考虑使用样式规则的优先级来解决问题而不是 `!important` - 只有在需要覆盖全站或外部 CSS 的特定页面中使用 `!important` - 永远不要在你的插件中使用 `!important` - 永远不要在全站范围的 CSS 代码中使用 `!important` 优先级的比较指的是相同的样式属性,不同样式属性优先级比较失效,比如:在设置`max-width`时注意,已经给元素的`max-width`设置了`!important`但是还不生效,很有可能就是被width覆盖了 举例:`div`最终的宽度还是`200px` div { max-width: 400px !important; height: 200px;background-color: tomato; width: 200px; }
所有异步任务都是在同步任务执行结束之后,从任务队列中依次取出执行。 回调函数是异步操作最基本的方法,比如AJAX回调,回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误,不能直接 return Promise包装了一个异步调用并生成一个Promise实例,当异步调用返回的时候根据调用的结果分别调用实例化时传入的resolve 和 reject方法,then接收到对应的数据,做出相应的处理。Promise不仅能够捕获错误,而且也很好地解决了回调地狱的问题,缺点是无法取消 Promise,错误需要通过回调函数捕获。 Generator 函数是 ES6 提供的一种异步编程解决方案,Generator 函数是一个状态机,封装了多个内部状态,可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。优点是异步语义清晰,缺点是手动迭代Generator
函数很麻烦,实现逻辑有点绕 async/awt是基于Promise实现的,async/awt使得异步代码看起来像同步代码,所以优点是,使用方法清晰明了,缺点是awt 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 awt 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。 加分回答 JS 异步编程进化史:callback -> promise -> generator/yield -> async/awt。 async/awt函数对 Generator 函数的改进,体现在以下三点: - 内置执行器。 Generator 函数的执行必须靠执行器,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。 - 更广的适用性。 yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 awt 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。 - 更好的语义。 async 和 awt,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,awt 表示紧跟在后面的表达式需要等待结果。 目前使用很广泛的就是promise和async/awt
第一种方法:利用对象属性key排除重复项:遍历数组,每次判断对象中是否存在该属性,不存在就存储在新数组中,并且把数组元素作为key,设置一个值,存储在对象中,最后返回新数组。这个方法的优点是效率较高,缺点是占用了较多空间,使用的额外空间有一个查询对象和一个新的数组
function duplicateRemoval(arr) {
//定义接收去重后结果的新数组
let newArr = [];
for(let i = 0;i<arr.length;i++){
//判断新数组中是否存在当前索引为i的原数组元素
if (!newArr.includes(arr[i])) {
//如果不存在,则将其放到新数组的最后位置
newArr.push(arr[i]);
}
}
//返回去重后的新数组
return newArr;
}
第二种方法:利用Set类型数据无重复项:new 一个 Set,参数为需要去重的数组,Set 会自动删除重复的元素,再将 Set 转为数组返回。这个方法的优点是效率更高,代码简单,思路清晰,缺点是可能会有兼容性问题
第三种方法:filter+indexof 去重:这个方法和第一种方法类似,利用 Array 自带的 filter 方法,返回 arr.indexOf(num) 等于 index 的num。原理就是 indexOf 会返回最先找到的数字的索引,假设数组是 [1, 1],在对第二个1使用 indexOf 方法时,返回的是第一个1的索引0。这个方法的优点是可以在去重的时候插入对元素的操作,可拓展性强。
var newArr = arr.filter(function(item,index){
return arr.indexOf(item) === index; // 因为indexOf 只能查找到第一个
});
第四种方法:这个方法比较巧妙,从头遍历数组,如果元素在前面出现过,则将当前元素挪到最后面,继续遍历,直到遍历完所有元素,之后将那些被挪到后面的元素抛弃。这个方法因为是直接操作数组,占用内存较少。
第五种方法:reduce +includes去重:这个方法就是利用reduce遍历和传入一个空数组作为去重后的新数组,然后内部判断新数组中是否存在当前遍历的元素,不存在就插入到新数组中。这种方法时间消耗多,内存空间也有额外占用。 方法还有很多,常用的、了解的这些就可以 加分回答 以上五个方法中,在数据低于10000条的时候没有明显的差别,高于10000条,第一种和第二种的时间消耗最少,后面三种时间消耗依次增加,由于第一种内存空间消耗比较多,且现在很多项目不再考虑低版本浏览器的兼容性问题,所以建议使用第二种去重方法,简洁方便。
let arr = [1, 2, 2, 4, null, null].reduce((accumulator, current) => {
return accumulator.includes(current) ? accumulator : accumulator.concat(current);
}, []);
https://blog.csdn.net/weixin_43797046/article/details/107673635?ops_request_misc=&request_id=&biz_id=102&utm_term=js%E5%88%A4%E6%96%AD%E5%AF%B9%E8%B1%A1%E6%98%AF%E5%90%A6%E7%9B%B8%E7%AD%89&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-107673635.nonecase&spm=1018.2226.3001.4187
Object.getOwnPropertyNames 对象的所有属性名数组
function Person() {
this .name = "KXY" ;
}
Person.prototype = {
constructor: Person,
job: "student" ,
};
var kxy = new Person();
Object.defineProperty(kxy, "sex" , {
value: "female" ,
enumerable: false
});
属性的枚举性会影响以下三个函数的结果:
for…in
Object.keys()
JSON.stringify
undefined == null、undefined !== null 标准回答 undefind 是全局对象的一个属性,当一个变量没有被赋值或者一个函数没有返回值或者某个对象不存在某个属性却去访问或者函数定义了形参但没有传递实参,这时候都是undefined。undefined通过typeof判断类型是'undefined'。undefined == undefined undefined === undefined 。 null代表对象的值未设置,相当于一个对象没有设置指针地址就是null。null通过typeof判断类型是'object'。null === null null == null null == undefined null !== undefined undefined 表示一个变量初始状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。在实际使用过程中,不需要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可。 让一个变量为null,直接给该变量赋值为null即可。 加分回答 null 其实属于自己的类型 Null,而不属于Object类型,typeof 之所以会判定为 Object 类型,是因为JavaScript 数据类型在底层都是以二进制的形式表示的,二进制的前三位为 0 会被 typeof 判断为对象类型,而 null 的二进制位恰好都是 0 ,因此,null 被误判断为 Object 类型。 对象被赋值了null 以后,对象对应的堆内存中的值就是游离状态了,GC 会择机回收该值并释放内存。因此,需要释放某个对象,就将变量设置为 null,即表示该对象已经被清空,目前无效状态。
脱离文档流、盒子塌陷、 影响其他元素排版、伪元素 、overflow:hidden
、标签插入法 标准回答 浮动的作用,设置浮动的图片,可以实现文字环绕图片,设置了浮动的块级元素可以排列在同一行,设置了浮动的行内元素可以设置宽高,同时可以按照浮动设置的方向对齐排列盒子。 设置浮动元素的特点: -设置了浮动,该元素脱标。元素不占位置 -浮动可以进行模式转换(行内块元素) 浮动造成的影响,使盒子脱离文档流,如果父级盒子没有设置高度,需要被子盒子撑开,那么这时候父级盒子的高度就塌陷了,同时也会造成父级盒子后面的兄弟盒子布局受到影响。如果浮动元素后面还有其他兄弟元素,其他兄弟元素的布局也会受到影响。 清除浮动的方法: -伪元素清除浮动:给浮动元素父级增加 .clearfix::after { content: ‘’; display: table; clear: both; } /*兼容IE低版本 */ .clearfix { *zoom: 1; } overflow:hidden:给浮动元素父级增加
overflow:hidden属性 额外标签法:给浮动元素父级增加标签 加分回答 三种清除浮动的特点和影响 -伪元素清除浮动:不会新增标签,不会有其他影响,是当下清除浮动最流行的方法 -
overflow:hidden`:不会新增标签,但是如果父级元素有定位元素超出父级,超出部分会隐藏,在不涉及父级元素有超出内容的情况,overflow:hidden比较常用,毕竟写法方便简洁 -标签插入法:清除浮动的语法加在新增标签上,由于新增标签会造成不必要的渲染,所以这种方法目前不建议使用
1 给受到浮动影响的元素添加overflow:hidden。 触发BFC
2在受到浮动影响的元素前面添加一个空div:clear:both;清除浮动带来的影响。
3 浮动元素的父标签的伪类选择器:after中清除浮动 - 类似于空div的格式。
.box:after{
content:"";
display: block;
clear:both;
}
箭头函数有两种写法,当函数体是单条语句的时候可以省略{}和return。另一种是包含多条语句,不可以省略{}和return。
箭头函数最大的特点就是没有this,所以this是从外部获取就是继承外部的执行上下文中的this,由于没有this关键字所以箭头函数也不能作为构造函数, 同时通过 call()
或 apply()
方法调用一个函数时,只能传递参数(不能绑定this),第一个参数会被忽略。
箭头函数也没有原型和super。不能使用yield关键字,因此箭头函数不能用作 Generator 函数。不能返回直接对象字面量。 加分回答 箭头函数的不适用场景: -定义对象上的方法 当调用 dog.jumps
时,lives
并没有递减。因为 this
没有绑定值,而继承父级作用域。 var dog = { lives: 20, jumps: () => { this.lives–; } } -不适合做事件处理程序 此时触发点击事件,this不是button,无法进行class切换 var button = document.querySelector(‘button’); button.addEventListener(‘click’, () => { this.classList.toggle(‘on’); }); 箭头函数函数适用场景: -简单的函数表达式,内部没有this引用,没有递归、事件绑定、解绑定,适用于map、filter等方法中,写法简洁 var arr = [1,2,3]; var newArr = arr.map((num)=>num*num) -内层函数表达式,需要调用this,且this应与外层函数一致时 let group = { title: “Our Group”, students: [“John”, “Pete”, “Alice”], showList() { this.students.forEach( student => alert(this.title + ': ’ + student) ); } }; group.showList();
1.写法简洁 2.无自己的this,继承上一个作用域的this(全局或上一个函数) 3.内部this无法被改变 4.arguments的特殊性(window下保存,this指向上一个函数则arguments表示上一个函数的参数) 5.不能作为构造函数(无自己的this、constructor) 6.无自己的prototype
function.call(thisArg, arg1, arg2, …)
function.apply(thisArg, [arg1, arg2, …])
fucntion.bind(thisArg, arg1, arg2, …)
首先,call apply bind三个方法都可以用来改变函数的this指向,具体区别如下:
1、fn.call (newThis,params) call函数的第一个参数是this的新指向,后面依次传入函数fn要用到的参数。会立即执行fn函数。
2、fn.apply (newThis,paramsArr) apply函数的第一个参数是this的新指向,第二个参数是fn要用到的参数数组,会立即执行fn函数。
3、bind()方法与前两个不同,前两个方法创建一个新的函数,在调用新函数时,会调用原函数,并指定原函数的 this 值和参数。而bind() 执行的时候并没有调用函数。bind()传入参数的方式和call()一样,都是用参数列表:
obj.sayHello.bind(obj1, ‘设计师’, ‘画画’); // 无输出结果
let person= Person.bind(obj,“周冬雨”,18)
person();
需要返回一个新的函数然后去调用
在大部分编程语言中,变量会被存放在两个地方,栈(stack)和堆(heap)。在 JavaScript 中栈存放的就是值类型的数据和引用类型的地址,而引用类型真正的数据被存放在堆中。
浅拷贝 Object.assign() concat() 解构赋值
Array.from({length:100},(_, i)=>1+(i)) 创建一个1 到一百的
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
lodash deepclone 深拷贝JSON.parse(JSON.stringify()) 浅拷贝 _.clone 展开运算符 对象合并 concat
手写一个深拷贝 hasOwnProperty判断是否有自己的属性
function deepClone(obj) {
if (typeof obj != 'object') return obj;
var temp = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] == 'object') { // 如果obj[key]还是对象则执行递归
temp[key] = deepClone(obj[key]); // 递归
} else {
temp[key] = obj[key];
}
}
}
return temp;
}
react提供的在子节点渲染到父组件以外的DOM节点的Portal
Dialog 对话框
Tooltip 文字提示
Popover 弹出框
const Modal = ({message, isOpen, onClose, children}) => {
if (!isOpen) return null;
return ReactDOM.createPortal(
<div className="modal">
<span>{message}</span>
<button onClick={onClose}>Close</button>
</div>
, document.body)
}
此时这个modal变成了在body节点下面而不是原来的父组件
垃圾回收是指 JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间。
Memory 堆快照检测内存
1 内存管理机制
分配所需要的系统内存空间;
使用分配到的内存进行读或写等操作;
不需要使用内存时,将其空间释放或者归还。
例子(比如说创建变量 系统发现变量不再使用,系统会通过垃圾回收机制的方式来处理掉这些变量所占用的内存)
基础数据类型保存在 栈中 引用数据类型 内存地址存在栈中 指向堆中的对象
1 比如说变量 分为全局变量和局部变量。全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。
谷歌垃圾回收机制
1 遍历 GC Root 中所有对象:能遍历到的是可访问对象 不能遍历就是不可访问对象区分不可访问活动 对象(可回收)和可访问活动对象(保留内存)GC Root => window 对象 DOM树 栈变量
2 第⼆步,回收⾮活动对象(不可访问对象)所占据的内存 统⼀清理内存中所有被标记为可回收的对象
3 第三步 内存整理 频繁回收导致出现大量内存碎片 需要回收
1 标记清除 2标记整理算法 2主副垃圾回收
避免垃圾回收方法 1 对象不用设置null 2数组清空 length=0 3函数多复用
JavaScript 中强引用:对象的引用在 JavaScript 中是强引用,也就是将一个引用对象通过变量或常量保存时,那么这个变量或常量就是强引用,这个对象就不会被回收。
var a = {};
var map = new Map();
map.set(a, '三分钟学前端')
a = null;
console.log(map.keys()) // MapIterator {{}}
console.log(map.values()) // MapIterator {"三分钟学前端"}
JavaScript 中弱引用: WeakMaps 和 WeakSets 是我们在 JavaScript 使用弱引用唯一途径,将一个对象作为键添加到 WeakMap 或 WeakSet 中并不能防止这些对象被回收。
强引用就是 引用对象通过变量去保存 不会被垃圾回收 需要空指针 主动切断回收
弱引用就是 WeakMaps 和 WeakSets 可以被垃圾回收
WeakMap特性
***WeakMap只能将对象作为键名(null除外)
键名引用对象是弱引用
WeakMap不可遍历
强引用:
let a = {name: “eric”, age: 20}
let arr = [a, “other”]
当不需要时,需要手动切断引用,GC才能回收。
a = null;
arr[0] = null;
同理Map也是如此
弱引用:
参与垃圾回收 一旦键key没了就会参与回收
当一个对象被回收后,相关的弱引用也会自动消失
比如
let a = {name: “eric”, age: 20}
let wp = new WeakMap();
wp.set(a, new Array(10 * 1024 * 1024));
此时如果 a = null;
wp里的键名对象和所对应的键值对会自动消失,不用手动删除引用
WeakMap 和 WeakSet 可以提供部分的弱引用功能 并不是真正意义的弱引用 因为他只要键一直存活就会强引用一些内容 ,只有键 没了 设置为null才会被回收 弱引用也消失了
performance 有一个蓝色的图 会有突然凸起 越来越明显 说明内存泄露比较厉害
v-if 导致的内存泄露 实际上 dom 元素在隐藏的时候没有被真实的释放掉 一直在创建 大屏放了几天崩了 。 我们可以用 hide() 方法在将选择框从 DOM 中移除之前做一些清理工作,来解决内存泄露问题。
实时监测档案 接收 转入导出 一个系统大屏 采用定时器轮询请求的方式 同时 四个页面的 每隔20秒有一个转换 。客户端开发完成后交付现场部署使用,已经部署过几个现场。据反馈,在实际使用过程中,存在页面卡顿问题,但关掉客户端重启就会好点,并不会影响业主的使用,因而没有得到过多的关注。 但是是因为大屏一直在运行会导致网页崩溃。
和其他现场对比客户端环境,发现该现场内存过小,因此提议先增加内存观察。内存扩大一倍,发现系统仍然会卡死,只是到达卡死的时间会长点。推测存在内存泄露问题。
看代码 第一时间想到是V-IF 导致内存暴露。根据上面的分析对相应代码进行了修改,原以为问题得到解决了。。结果发现虽然页面没有很快卡死,但是内存占用在逐步上升。客户端放在现场不进行任何操作,只是不断地有实施告警弹窗弹出,放置一夜后发现内存的占比竟然增加了好多。而且发现一旦进行页面操作,内存会持续上升不会释放,终于在我不断的页面操作下,系统在又放置了一夜后,终于因内存溢出而卡死了。。原本以为可以很愉快的度过一个周末的,结果现在整个人都不好了。。这下真的是内存泄露的。。芭比Q了。。
由于 electron 内置的是 chrome 的浏览器内核,我们可以直接在系统中调用谷歌浏览器的开发者工具中 performance 来监控
通过上面的监控效果来看,很明显的存在内存泄露。 继续往下研究。。。不断的换思路,不断的换查看方式。。最终发现了下面的问题。。使用谷歌浏览器中的开发者工具中 memory 来分析。闭包分类直指2第三方库! 发现是和echars 还有流程图的一个第三方组件 。 还有一个就是这个里面怎么有个定时器!!而且这个定时还不会被销毁!实时他的做法用定时器几秒一次调用。定时器里面还引用了外部变量!每一次数据服务都会创建实例,这样的话每次实例都会创建一个不会被销毁的的定时器,每个实例都会引用同一个外部变量!!!这样每次数据请求完,这个实例都不可能会被垃圾回收掉!! 以为好了 但结果放了一夜 页面还是变卡了 一查内存还在泄露。memory中蓝色的柱子线表明这块的创建的东西一直得不到回收!!。当前也是过了几年 模拟了一下线上环境,开了本地去测试,扩大了一下对比范围,memory 对比了一下,很快就找到了然后点击对应的 Object 看发现全部指向第三方库的这个变量。。(线上环境用的都是打包工具生成的文件名和很难找到对应文件)又赶紧去看对应的文件。 。每一次调用数据服务创建的实例对象都会给这个变量对象添加属性,久而久之,这个变量会越来越大,越来越夸张。。而且由于这个对象一直被引用着,那么当初创建他的实例也一直得不到释放。。
transition 标签 app.js
内存泄漏是指在 JavaScript 中,已经分配堆内存地址的对象由于长时间未释放或者无法释放,造成了长期占用内存,使内存浪费,最终会导致运行的应用响应速度变慢以及最终崩溃的情况。这种就是内存泄漏,在日常开发和使用浏览器过程中内存泄漏的场景:
过多的缓存未释放;
闭包太多未释放;
定时器或者回调太多未释放;
太多无效的 DOM 未释放;
全局变量太多未被发现。
1在使用完数据后,及时解除引用(闭包中的变量,DOM 引用,定时器清除)。
2组织好代码逻辑,避免死循环等造成浏览器卡顿、崩溃的问题。
3避免不合理的使用闭包,从而导致某些变量一直被留在内存当中。
原型
原型通常指的是prototype和__proto__这两个原型对象
其中前者叫做显式原型对象,后者叫做隐式原型对象
在js中所有的函数都有一个prototype对象,每一个对象都有__proto__,既然prototype是一个对象,那么他必然也有一个__proto__(在后面会详细解释)
1.所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
2.所有函数都有一个prototype(原型)属性,属性值是一个普通的对
3.所有引用类型的__proto__属性指向它构造函数的prototype
var a = [1,2,3];
a.__proto__ === Array.prototype;
1.原型对象:
在我们创建函数或者构造函数的时候,都会自动添加一个属性prototype 也就是我们所说的原型对象。
原型链
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性,
Parent 中的引用属性会被每个子类示例共享 自己创造构造函数 实例对象 共享构造函数的方法属性
某个构造函数的prototype指向另一个构造函数
//原型链继承
2 function Parent() {
3 this.parentPrototype = "parent prototype"
4 //验证这种继承方法的确定,如果父类示例中存在一个引用类型的属性,将会被所有子类共享
5 this.parentObj = {
6 info: "我是 parent 引用属性parentObj中的 info"
7 }
8 }
9
10 function Children() {
11
12 }
13 //将Children的原型对象指定为Parent的示例,通过原型链,将Parent中的属性赋值给Children示例
14 Children.prototype = new Parent();
15 const a = new Children();
16 console.log(a.parentPrototype); // parent prototype
17 //缺点
18 const b = new Children();
19 //在a示例中改动继承的引用属性
20 a.parentObj.info = "我是a示例中 引用属性parentObj中的 info"
21 //b与a示例共享引用属性
22 console.log(b.parentObj.info); // 我是a示例中 引用属性parentObj中的 info
盗用构造函数就是在子类构造函数中调用父类构造函数(在自己的构造函数中偷偷用父类的构造函数,所以是盗用) ,因此通过使用apply()和call()方法对象冒充继承也可以在新创建的对象上执行构造函数
优点:
1避免了子类示例共享引用属性的情况
2可以在实例化时给Parent构造函数传递参数
缺点:
1如果Parent中存在一个函数,那么每次实例化Children的时候,都会创建一个同样函数,函数的复用性就难以体现
function Box(name){
this.name = name
}
Box.prototype.age = 18
function Desk(name){
Box.call(this, name) // 对象冒充,对象冒充只能继承构造里的信息
}
var desk = new Desk('ccc')
console.log(desk.name) // --> ccc
console.log(desk.age) // --> undefined
优点:
1 避免了子类共享引用属性同时避免了父类构造函数重复对function属性的创建
function Box(name){
this.name = name
}
Box.prototype.run = function (){
console.log(this.name + '正在运行...')
}
function Desk(name){
Box.call(this, name) // 对象冒充
}
Desk.prototype = new Box() // 原型链
var desk = new Desk('ccc')
console.log(desk.name) // --> ccc
desk.run() // --> ccc正在运行...
1 function objFn(o) {
2 o.objFnPrototype = "我是 objFnPrototype"
3 function F() {}
4 F.prototype = o;
5 return new F();
6 }
7
8 let a = objFn({
9 name: "name1"
10 });
11 console.log(a.name); //name1
12 console.log(a.objFnPrototype); //我是 objFnPrototype
缺点:
1和原型链继承一样,parent中的引用属性,会被所有示例共享
vue
function createObje(obj) {
let clone = Object.assign(obj); //接受到对象后,原封不动的创建一个新对象
clone.prototype1 = "我是新增的prototype1"; //在新对象上新增属性,这就是所谓的寄生
return clone; //返回新对象
}
const parent = {
parentPrototype: "parentPrototype"
}
//c实例,就继承了parent的所有属性
let c = createObje(parent);
console.log(c.parentPrototype); //parentPrototype
function inherProto(superType, subType) {
2 //拷贝一个超类的原型副本
3 let proto = {
4 ...superType.prototype
5 };
6 //将原型的超类副本作为子类的原型对象,也就是第一种中的原型链继承方式,只不过继承的是超类原型的副本
7 subType.prototype = proto;
8 //这一步比较迷,官方的说法是,我们在拷贝超类的原型的时候,拷贝的proto对象,将会丢失默认自己的构造函数,也就是superType,
9 //所以我们这里将它的构造函数补全为subType。貌似不做这一步也没啥问题,但是缺了点东西可能会有其他的副作用,所以还是补上
10 proto.constructor = subType;
11
12 }
13
14 function Super() {
15 this.superProto = "super proto";
16 this.colors = ["red", "yelloy"];
17 }
18
19 function Sub() {
20 this.subProto = "sub proto";
21 this.name = "sub name";
22 //这里还是借用构造函数的套路
23 Super.call(this);
24 }
25 Super.prototype.getName = function () {
26 console.log(this.name);
27 }
28 //这里要在定义完Super的属性后执行,因为继承的是超类原型的副本,与Super.prototype是两个对象,在这之后再改变Super.prototype,就已经不会在影响到Sub所继承的副本超类原型对象了
29 inherProto(Super, Sub);
30
31 let a = new Sub();
32 console.log(a.getName);
font-size
中使用是相对于父元素的字体大小,在其他属性中使用是相对于自身的字体大小,如 width。如当前元素的字体尺寸未设置,由于字体大小可继承的原因,可逐级向上查找,最终找不到则相对于浏览器默认字体大小。 rem:相对长度单位,相对于根元素的字体大小,根元素字体大小未设置,使用浏览器默认字体大小。 vw:相对长度单位,相对于视窗宽度的1%。 vh:相对长度单位,相对于视窗高度的1%。 加分回答1vw是窗口宽度的1%;
本质还是rem适配
1 媒体查询PC 不同的移动端尺寸设置不同的 html 字体大小
@media screen and (min-width:321px) and (max-width:375px)
{html{font-size:11px}}
postcss-pxtorem 将项目中的px 自动转换成rem 同时设置基准大小 页面根节点字体大小
html {
font-size: 35px;
} 此时1rem=35px
还有一种做法
类似于lib-flexible /10
比如说flexible 750PX宽 手机 75px 1rem postcss 也需要设置/75
//rem适配
(function () {
var styleN = document.createElement("style");
var width = document.documentElement.clientWidth/16;
styleN.innerHTML = 'html{font-size:'+width+'px!important}';
document.head.appendChild(styleN);
amfe-flexible是按照屏幕宽度的十分之一来设置font-size的,所以我们remUnit的大小设置也应该是设计稿宽度的十分之一,这样css样式才会和设计稿一样. 比如设计稿750 1rem=75px
postcss 将所有css 的px 转换rem 转换比需要设置 一般来说要与flexible 一致 但是需要适配第三方ui组件 可以使用动态对象 三元判断下 传参是不是来源node 第三方组件
2者结合 适配第三方组件的方法 首先知道第三方组件的rootValue 通过postcss unit 动态设置他的px=>rem的比例
html5
语义化标签, header, footer, nav, aside,article,section
增强型表单
视频 video 和音频 audio
Canvas 绘图
SVG绘图
地理定位
拖放 API
WebWorker
WebStorage( 本地离线存储 localStorage、sessionStorage )
WebSocket
css3
1、颜色:新增RGBA、HSLA模式
2、文字阴影:(text-shadow)
3、边框:圆角(border-radius)边框阴影:box-shadow
4、盒子模型:box-sizing
5、背景:background-size,background-origin background-clip(削弱)
6、渐变:linear-gradient(线性渐变):
eg: background-image: linear-gradient(100deg, #237b9f, #f2febd);
radial-gradient (径向渐变)
7、过渡:transition可实现动画
8、自定义动画: animate@keyfrom
9、媒体查询:多栏布局@media screen and (width:800px)
10、border-image
11、2D转换:transform:translate(x,y) rotate(x,y)旋转 skew(x,y)倾斜 scale(x,y)缩放
12、3D转换
13、字体图标:Font-Face
14、弹性布局:flex
如样式A设置 transition: transform 0.5s ; transform-arigin:50% 25%
B的 hover .A中设置 transform:rate(180dap)
api
.wrap {
width: 300px;
height: 300px;
display: grid;
background-color: plum;
}
.box {
width: 100px;
height: 100px;
align-self: center;
justify-self: center;
background-color: powderblue;
}
就比如用户触发一个点击事件,有一个触发的过程
事件捕获-阶段(从上大小,从外到内)—>处于目标事件-阶段---->事件冒泡-阶段(从下到上,从内到外)
window.addEventListener(
"click",
function (event) {
event = event || window.event /*ie*/;
const target = event.target || event.srcElement; /*ie*/ // 拿到事件目标
// 阻止冒泡
// event.stopPropagation()
// event.cancelBubble=true; // ie
// 阻止默认事件
// event.preventDefault(); 提交按钮 阻止提交事件 或者a链接组织跳转
// event.returnValue=false; // ie
},
/* 是否使用捕获,默认是fasle, */ fasle
);
事件委托
简介:事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是
在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的
触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。
举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用
事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事
件触发机制
CommonJS 与 ES6 Module 的差异
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。
CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
html文件都是按顺序执行的,script标签中没有加defer和async时,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。。 而在script标签中写入defer或者async时,就会使JS文件异步加载,即html执行到script标签时,JS加载和文档解析同时进行,而async是在JS加载完成后立即执行JS脚本,阻塞文档解析,而defer则是JS加载完成后,在文档解析完成后执行JS脚本
有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。
有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded事件触发之前完成。
defer与async的区别是:前者要等到整个页面正常渲染结束,才会执行;后者一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。
defer
如果你的脚本代码依赖于页面中的DOM元素(文档是否解析完毕),或者被其他脚本文件依赖。
评论框
代码语法高亮
polyfill.js
async
如果你的脚本并不关心页面中的DOM元素(文档是否解析完毕),并且也不会产生其他脚本需要的数据。
新增 Symbol 类型 表示独一无二的值,用来定义独一无二的对象属性名;
const/let 都是用来声明变量,不可重复声明,具有块级作用域。存在暂时性死区,不存在变量提升。(const 一般用于声明常量);
变量的解构赋值(包含数组、对象、字符串、数字及布尔值,函数参数),剩余运算符(…rest);
模板字符串(${data});
…扩展运算符(数组、对象);;
箭头函数;
Set 和 Map 数据结构;
Proxy/Reflect;
Promise;
async 函数;
Class;
Module 语法(import/export)。
Map
Map对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。构造函数Map可以接受一个数组作为参数。
const m1 = new Map([['a', 111], ['b', 222]])
console.log(m1) // {"a" => 111, "b" => 222}
m1.get('a') // 111
const m2 = new Map([['c', 3]])
const m3 = new Map(m2)
m3.get('c') // 3
m3.has('c') // true
m3.set('d', 555)
m3.get('d') // 555
Set
Set 对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成Set 数据结构。Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
const mySet = new Set(['a', 'a', 'b', 11, 22, 11])
console.log(mySet) // {'a', 'b', 11, 22}
myset.add('c').add({'a': 1})
console.log(mySet) // {'a', 'b', 11, 22, 'c', {a: 1}}
console.log(mySet.size) // 6
mySet.has(22) // true
行内元素 (不能设置宽高,设置宽高无效) a,span,i,em,strong,label
行内块元素:img, input
块元素: div, p, h1-h6, ul,li,ol,dl,table…
知名的空元素 br, hr,img, input,link,meta
requestAnimationFrame 不需要设置时间 requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次 重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率 每秒调用60次, 大约16.7ms
setTimeout()用于在指定的毫秒数后调用函数或计算表达式
setInterval()在播放动画的时,每隔一定时间就调用函数,方法或对象
(1)requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次 重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。
(2)在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回 流,这当然就意味着更少的 CPU、GPU 和内存使用量
(3)requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览 器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停, 有效节省了 CPU 开销。
cancelAnimationFrame()
loadsh
_.debounce(func, [wait=0], [options={}])防抖
_.throttle(func, [wait=0], [options={}]) 节流
防抖
触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
频繁操作点赞和取消点赞,因此需要获取最后一次操作结果并发送给服务器;search搜索联想,用户在不断输入值时…
const debounce=(fn, delay) => {
//记录上一次的延时器
let timer = null;
return () => {
//清除上一次延时器
clearTimeout(timer);
//重新设置新的延时器
timer = setTimeout(() => {
fn()
}, delay)
}
};
节流:
一般是onrize,onscroll等这些频繁触发的函数,比如你想获取滚动条的位置,然后执行下一步动作;鼠标不断点击触发,mousedown(单位时间内只触发一次)…
比如你想获取滚动条的位置,然后执行下一步动作。如果监听后执行的是Dom操作,这样的频繁触发执行,可能会影响到浏览器性能,甚至会将浏览器卡崩。
我们可以规定他多少秒执行一次,这种方法叫函数节流
const throttle=(fn, delay) => {
//记录上一次函数触发的时间
let lastTime = 0;
return () => {
//记录当前函数触发的时间
let nowTime = Date.now();
if (nowTime - lastTime > delay) {
fn();
//同步时间
lastTime = nowTime;
}
}
};
function throttle(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
if (!timeout) {
timeout = setTimeout(function () {
func.apply(context, args)
timeout = null;
}, wait)
}
}
}
function debounce(fn, delay) {
let timer = null;
return function () {
var _this = this; //这里改了
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(_this); //这里改了
}, delay);
};
}
1、加快webpack打包速度
2、减小webpack打包后的文件体积
开发环境性能优化
优化打包构建速度
优化代码调试
加速
1 MFSU
加速热更新
webpack5 新特性 Module Federation 的打包提速方案。
1 Hot Module Replacement 热更新 是指当你对代码修改并保存后,webpack将会对代码进行重新打包,并将改动的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,去实现局部更新页面而非整体刷新页面。
2 externals 排除CDN
3 、优化loader文件的搜索范围
(主要通过配置test、include、exclude、等字段)
4在一些性能开销较大的loder之前添加cache-loader,将结果缓存到磁盘中。
rules: [
{
test: /\.jsx?$/,
use: ['cache-loader','babel-loader']
}
]
5 hard-source-webpack-plugin 首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。 为模块提供中间缓存
代码压缩
1 默认压缩 TerserWebpackPlugin 可以排除console.log
2、抽离公共代码 设置缓存
3使用externals 优化cdn静态资源
4 tree shaking 清楚无用的css .js
生产环境性能优化
优化打包构建速度(与开发环境的优化角度不一样)
优化代码运行的性能
process.env.NODE_ENV === 'production' 判断是不是生产环境
process.env.NODE_ENV === 'develop' 判断是不是开发环境
它分析 webpack 的总打包耗时以及每个 plugin 和 loader 的打包耗时,从而让我们对打包时间较长的部分进行针对性优化。
可以通过 cdn 的形式引入它们,然后将 react、react-dom 从打包列表中排除,这样可以减少打包所需的时间
1 html-webpack-externals-plugin
2 部分webpack 框架 externals 将输入 entre 定位指定的CDN
以下是代码拆分
***提取公共模块***
optimization属性中:splitChunks 提取公共模块 下面的cacheGroups 再可以提取第三方组件
1) splitChunks 设置这个值会自动提取所有公共模块 (或者配置 异步加载的 或者同步的 一般默认用async 异步 就是import 进入的)
如chunks 选项,决定要提取哪些模块
1、默认是 async :只提取异步加载的模块出来打包到一个文件中。
异步加载的模块:通过 import(‘xxx’) 或 require([‘xxx’],() =>{}) 加载的模块。
2、initial:只对入口chunk生效;
3、all:不管异步加载还是同步加载的模块都提取出来,打包到一个文件中;
2)splitChunks 中的核心acheGroups 选项,核心重点,配置提取模块的方案,里面 每一项代表一个提取模块的方案。
下面是 cacheGroups 每项中特有的选项,其余选项和外面一致,若 cacheGroups 每项中有,就按配置的,没有就使用外面配置的;
以下是 加载器 loader
webpack 自身只能解读 JavaScript,对其进行文件的合并、压缩处理。但是我们实际项目中会用到很多类型的文件,如 css、less、jpg、jsx、vue 等等,webpack 本身是处理不了它们的,这个时候就要借助于各种 loader。
webpack 常用 loader,plugin
loader
post-loader px转rem
babel-loader 将 es6 转换成 es5 , ts-loader、vue-loader 缓存babel的编译结果
eslint-loader 配置 enforce: ‘pre’ 这个 loader 最先执行
css-loader、style-loader、postcss-loader、less-loader、sass-loader
file-loader 把文件转换成路径引入, url-loader(比file-loader多了小于多少的能转换成 base64)
image-loader
svg-sprite-loader 处理 svg
thread-loader 开启多进程 ,会在一个单独的 worker 池(worker pool)中运行
cache-loader 缓存一些性能开销比较大的 loader 的处理结果
以下是 插件plugin
插件是用来处理各种任务的,比如代码的压缩,打包优化,等等。比如自动生成 html 文件的插件
webpack.HotModuleReplacementPlugin 热更新 ( 1
保留在完全重新加载页面时丢失的应用程序的状态。
只更新改变的内容,以节省开发时间。
调整样式更加快速,几乎等同于就在浏览器调试器中更改样式。)
terser-webpack-plugin 压缩 js, 可开启多进程压缩、推荐使用 排除console.log()*1
HardSourceWebpackPlugin 缓存(第二次 加载会快很多) *1
mini-css-extract-plugin 提取 CSS 到独立 bundle 文件。 extract-text-webpack-plugin *1
optimize-css-assets-webpack-plugin 压缩 css webpack5 推荐css-minimizer-webpack-plugin *1
html-webpack-plugin 将生成的 css,js 自动注入到 html 文件中,能对 html 文件压缩
copy-webpack-plugin 拷贝某个目录
clean-webpack-plugin 清空某个目录
webpack.DefinePlugin 定义全局变量
purgecss-webpack-plugin 会单独提取 CSS 并清除用不到的 CSS(会有问题把有用的 css 删除)
speed-measure-webpack-plugin 打包构建速度分析、查看编译速度 *1
webpack-bundle-analyzer 打包体积分析 *1
compression-webpack-plugin gzip 压缩
以下是 入口
entry:[“./src/index.js”,“./src/main.js”],
以下是 出口 多个出口 设置动态对象 对象名为入口名
“output”:{//输出的目录
filename:“[name].js”,
path:resolve(__dirname,“build”)
}
IE盒子模型的宽高包括content和padding还有border,标准盒子模型 不包括,
box-sizing:content-box 标准盒模型
box-sizing:border-box IE盒模型
content-box就是用元素的width和height觉得元素的高宽,这就意味着元素的padding和border等不能算在width和height中。
border-box就是用元素内容和padding和border一起决定width和height
行高=高
Margin auto 0
绝对定位 top50%,自身宽度的50%的负值
flex布局 align—center
水平居中的方式
绝对定位
flex布局 juest—center
margin 0 auto
text-align center
display:none
visibility:hidden
opacity:0
设置height、width模型属性为0
position:absolute
clip-path
math.config({number: ‘BigNumber’})
console.log(math--- 0.1+0.2= ${math.eval('0.1+0.2')}
)
parseFloat(1.4000000000000001.toPrecision(12)) === 1.4 // True
理论上用有限的空间来存储无限的小数是不可能保证精确的,但我们可以处理一下得到我们期望的结果
当你拿到 1.4000000000000001 这样的数据要展示时,建议使用 toPrecision 凑整并 parseFloat 转成数字后再显示,如下:
parseFloat(1.4000000000000001.toPrecision(12)) === 1.4 // True
封装成方法就是:
function strip(num, precision = 12) {
return +parseFloat(num.toPrecision(precision));
}
最后还可以使用第三方库,如Math.js、BigDecimal.js
2: 传统的web开发时如何实现MVC模式的
如果前端没有框架,只使用原生的html+js,MVC模式可以这样理解。
将html看成view;
js看成controller,负责处理用户与应用的交互,响应对view的操作(对事件的监听),调用Model对数据进行操作,完成model与view的同步(根据model的改变,通过选择器对view进行操作);
将js的ajax当做Model,也就是数据层,通过ajax从服务器获取数据(按照上面这种方式分层,感觉多少有点强行MVC,因为Model层被弱化了)。
各部分之间的通信都是单向的。
View 传送指令到 ControllerController 完成业务逻辑后,要求 Model 改变状态Model 将新的数据发送到 View,用户得到反馈
MVVM 模型即视图 视图即模型。原来的js 是需要动态去绑定 onchange M模型 V 视图 vm
View负责页面的显示逻辑;Model的数据同步到View显示出来,还负责把View的修改同步回Model。
Model负责存储页面的业务数据以及对相应数据的操作;
ViewModel使用了双向的数据绑定,
实现View与VM保持同步,
3:MVC模式有什么缺点
对 DOM 操作的代价非常高
程序运行缓慢且效率低下
内存浪费严重
应用程序复杂性高,难以分工开发。
vm是view mode的意思。所以mvvm框架是要有一个vm对象,来映射view。也就是vm对象的属性发生改变的时候,对应的视图部分会相对应更新。
双向数据
View 传送指令到 Controller Controller 完成业务逻辑后,要求 Model 改变状态 Model 将新的数据发送到 View,用户得到反馈
vue react区别
不同:
1、函数组件比类组件性能好
因为类组件使用过程中,需要实例化,而函数组件不需要
2、this?声明周期?state?
函数组件没有this,没有生命周期,没有状态state.
类组件有this,有生命周期,有状态state。
这也是有状态组件和无状态组件的区别。
Hook 函数式组件像类式组件一样拥有state、ref、生命周期等属性
1 useState
使函数式组件也能保存状态的一个hook,这个hook的入参是状态的初始值,返回值是一个数组,数组里第一个参数为状态的值,第二个参数为修改状态的方法。
2useEffect
函数式组件用来模拟生命周期的hook,可以模拟组件挂载完成、更新完成、即将卸载三个阶段,即componentDidMount、componentDidUpdate、componentWillUnmount。
useEffect(()=>{
// 这样模拟的是 componentDidMount
}, [])
useEffect(()=>{
// 这样模拟的是componentDidMount 以及当count发生变化时执行componentDidUpdate
}, [count])
useEffect(()=>{
return ()=>{
// 这样模拟的是 componentWillUnmount
}
}, [])
3 useContext 获取公共数据的后代组件 跨越组件层级直接传递变量,实现数据共享。
1 umi.js 配置国际化 locales zh-CN.ts 选择国籍
2lang= locale[lang.toLowerCase()] 动态判断是 哪个国家的text
3 通过createContext.Provider 传递 useContext(createContext())接收
lang[‘属性名’]
import React, { createContext } from ‘react’;
export const Context = createContext();
<Context.Provider value={{selectKeysGrid, setSelectKeysGrid }}>
后代组建selectKeysGrid, setSelectKeysGrid 传递
</Context.Provider>
import { Context } from ‘…/…/…/…/index’;
const { selectKeysGrid, setSelectKeysGrid } = useContext(Context);
(4) useRef
https://blog.csdn.net/bingbing1128/article/details/115495639?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-115495639-blog-118316191.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-115495639-blog-118316191.pc_relevant_aa&utm_relevant_index=2
useRef 是一个对象,它拥有一个 current 属性,并且不管函数组件执行多少次,而 useRef 返回的对象永远都是原来那一个。这个ref对象只有一个current属性,你把一个东西保存在内,它的地址一直不会变。 **useRef变化不会主动使页面渲染
所以我们可以使用useRef代替useS tate来解决那个问题
useRef和createRef区别
createRef会在组件每次渲染的时候重新创建
useRef只会在组件首次渲染时创建
如果要在函数内部使用,那就直接创建后挂ref属性给react元素就行了。
如果要在子组件上使用,除了上面的步骤,还需要使用forwardRef把子组件的函数包起来,然后再传入第二个参数ref,最后挂载ref就可以正常取到DOM了。
当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
你不能在函数组件上使用 ref 属性,因为他们没有实例。
获取子组件的实例(只有类组件可用)
在函数组件中的一个全局变量,不会因为重复 render 重复申明, 类似于类组件的 this.xxx
(5) useReducer
useReducer相当于是useState的升级版,作用与useState类似,都是用来保存状态,但它的不同点在于可以定义一个reducer的纯函数,来处理复杂数据。
usereducer
(6) useCallback 父组件传给子组件的函数
seCallback返回一个函数,当把它返回的这个函数作为子组件使用时,可以避免每次父组件更新时都重新渲染这个子组件
const renderButton = useCallback(
() => (
<Button type="link">
{buttonText}
</Button>
),
[buttonText] // 当buttonText改变时才重新渲染renderButton
);
函数式组件中,每一次更新状态,自定义的函数都要进行重新的声明和定义,如果函数作为props传递给子组件,会造成子组件不必要的重新渲染,有时候子组件并没有使用到父组件发生变化的状态,此时可以使用useCallback来进行性能优化,它会为函数返回一个记忆的值,如果依赖的状态没有发生变化,那么则不会重新创建该函数,也就不会造成子组件不必要的重新渲染。
(7) useMemo 一般传变量
useMemo包裹的变量,相当于对变量做了缓存,当父组件重新渲染时,变量不会改变==》子组件不会重新渲染
useMemo返回的的是一个值,用于避免在每次渲染时都进行高开销的计算。
useMemo也是返回一个记忆的值,如果依赖的内容没有发生改变的话,这个值也不会发生变化,useMemo与useCallback的不同点在于useMemo需要在传入的函数里需要return 一个值,这个值可以是对象、函数,格式如下。
const result = useMemo(() => {
for (let i = 0; i < 100000; i++) {
(num * Math.pow(2, 15)) / 9;
}
}, [num]);
共同作用
在依赖数据发生变化的时候,才会调用传进去的回调函数去重新计算结果,起到一个缓存的作用
两者的区别
useMemo 缓存的结果是回调函数中return回来的值,主要用于缓存计算结果的值,应用场景如需要计算的状态
useCallback 缓存的结果是函数,主要用于缓存函数,应用场景如需要缓存的函数,因为函数式组件每次任何一个state发生变化,会触发整个组件更新,一些函数是没有必要更新的,此时就应该缓存起来,提高性能,减少对资源的浪费;另外还需要注意的是,useCallback应该和React.memo配套使用,缺了一个都可能导致性能不升反而下降。
8 memo
react.memo包裹子组件,在props不变的情况下,子组件是不会二次渲染的
减少子组件渲染次数,即当父组件有多个子组件时,使用memo,可以让没有props变化的子组件不渲染;
memo用于包裹子组件;useCallback和useMemo用于父组件向子组件传值时,即如果是组件内部自己用的函数和变量,不需要使用useCallback和useMemo。而effect 虽然可以对依赖的值监听 但是都会触发子组件更新 前两个是为了抑制更新
memo 可以和useMemo useCallback 组合一起使用
React 把每一个组件当成了一个状态机,组件内部通过state来维护组件状态的变化,当组件的状态发生变化时,React通过虚拟DOM技术来增量并且高效的更新真实DOM。
内存中维护一颗虚拟DOM树,数据变化时(setState),自动更新虚拟 DOM,得到一颗新树,然后 Diff 新老虚拟 DOM 树,找到有变化的部分,得到一个 Change(Patch),将这个 Patch 加入队列,最终批量更新这些 Patch 到 DOM 中。
1 需要虚拟dom 和diff
调和阶段(Reconciler):官方解释。React 会自顶向下通过递归,遍历新数据生成新的 Virtual DOM,然后通过 Diff 算法,找到需要变更的元素(Patch),放到更新队列里面去。
2 fiber
渲染阶段(Renderer) 通过 Fiber 来调度
ReactDOM.render() 和 setState 的时候开始创建更新。
将创建的更新加入任务队列,等待调度。
在 requestIdleCallback 空闲时执行任务。
从根节点开始遍历 Fiber Node,并且构建 WokeInProgress Tree。
生成 effectList。
根据 EffectList 更新 DOM。
setSate接受两个参数,一个是state,另一个是回调函数
注意点:
主要是通过队列机制实现state更新,当执行setState的时候,会将需要更新的state合并后放入状态队列中,而不会立即更新this.state。队列机制主要实现了批量更新state。
如果我们不使用setState更新,而是直接采用this.state来修改,state不会被放入状态队列中,下次调用setState时对状态队列进行合并时,会忽略之前修改的state,就不会达到预期效果。
setState不保证是同步的,也可以认为是异步的,在setState之后,会对state进行diff,判断是否有改变,然后去diff是否要更新UI
setState 方式会 重新渲染组件
this.state的方式会改变值,但不会重新渲染组件(这就是为什么你改变了值,但是页面没有响应你)
1)合成事件,就是react为了解决跨平台,兼容性等问题,自己封装了一套事件机制,代理了原生事件,想在jsx中比较常见的onClick,onChange等,都是合成事件。 类似异步
2)生命周期中,setState是“异步”的
3) 原生事件中,setState是“同步”的
4)异步中调用(setTimeout为例)会同步执行
usestate 拿到最新的值
1
function statusHandleChange(val) {
setModelStatus(val);
// 直接把参数的值 传进去 拿到的就是最新的了
search(val);
}
2
const modelStatusRef = useRef(null);
useEffect(()=>{
// 每次 更新 把值 复制给 modelStatusRef
modelStatusRef.current = modelStatus;
}, [modelStatus]); // 依赖的值 等modelStatus 改变了 才出发里面的值
组件的生命周期有3个主要部分:
1.2.componentWillMount()
componentWillMount()一般用的比较少,它更多的是在服务端渲染时使用。它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时。
1.3.componentDidMount()
组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
1.4.componentWillUnmount ()
在此处完成组件的卸载和数据的销毁。
clear你在组建中所有的setTimeout,setInterval
移除所有组建中的监听 removeEventListener
有时候我们会碰到这个warning:
Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the undefined component.
原因:因为你在组件中的ajax请求返回setState,而你组件销毁的时候,请求还未完成,因此会报warning
解决方法:
componentDidMount() {
this.isMount === true
axios.post().then((res) => {
this.isMount && this.setState({ // 增加条件ismount为true时
aaa:res
})
})
}
componentWillUnmount() {
this.isMount === false
}
2.2.shouldComponentUpdate(nextProps,nextState)
主要用于性能优化(部分更新)
唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新
因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断
2.3.componentWillUpdate (nextProps,nextState)
shouldComponentUpdate返回true以后,组件进入重新渲染的流程,进入componentWillUpdate,这里同样可以拿到nextProps和nextState。
2.4.componentDidUpdate(prevProps,prevState)
组件更新完毕后,react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state。
2.5.render()
render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。
1.父子组件通信
2.跨级组件通信
3.非嵌套组件通信
父子 props 子父 props+回调函数
跨级组件通信 createContext
非嵌套组件通信 (如兄弟组件等)
PubSubJS —》npm add pubsub-js
2.引入 import PubSub from ‘pubsub-js’
若数据从A组件 传给 B组件
(1)A组件中发布消息
PubSub.publish( '消息名',data)
(2)B组件中订阅消息
PubSub.subscribe ('消息名', function(_,data){ });
(3)当B组件中卸载需要取消订阅时执行
PubSub.unsubscribe();
dva redux saga usemodal
受控组件的是指表单元素的控制是交给 React ,表单元素的值是完全交由组件的 state 控制
什么是非受控组件
非受控组件指表单元素的状态并不受 React 组件状态的影响,表单元素的值存储于 DOM 元素中。如果要 React 组件要获取 DOM 元素的值,需要通过绑定 ref 的方式去获取。
1,受控组件:
非受控组件: 用户输入A => input 中显示A
受控组件: 用户输入A => 触发onChange事件 => handleChange 中设置 state.name = “A” => 渲染input使他的value变成A
在React中默认可以完成从state到表单value的动态绑定。
给input提供onchange事件,一旦检测到文本框内容有变化,马上执行onchange事件获取表单的内容。
2,非受控组件:组件的状态更新不依赖于react
①操作DOM获取到你要的数据
ref属性接受一个回调函数,返回一个element节点 , 通过节点获取到数据 ref={(element)=>this.addressElement = element }
② 可以在构造函数里面定义一个变量,通过变量来创建组件引用,就可以获取到这个节点
1 组件包含关系
注意:
1.jsx必须要有根节点
2.正常的普通HTML元素要小写,如果是大写,默认认为是组件
1.有HTML元素构成
2.中间如果需要插入变量用{}
3.{}中间可以使用表达式
4.{}中间表达式中可以使用jsx对象
5.属性和HTML内容一样都是用{}来插入内容
JSX:可理解成html,经过Bable就是运行在浏览器上的代码了。
ReactDOM.render(
element,
document.querySelector(‘#root’)
)
柯里化 可以理解为函数分布传递参数,把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数,也就是说逐步传参,逐步缩小函数的适用范围,逐步求解的过程。
柯里化(currying)又称部分求值。一个柯里化的函数首先会接收一些参数,接收了这些参数后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
具体而言,HOC就是一个函数,且该函数接受一个组件作为参数,并返回一个新组件。
高阶函数 有哪些 filter .map
如延迟组件
进行权限判断:
页面中的按钮权限,在高阶组件中实现权限判断,页面权限判断,进行路由跳转
日志记录:
逻辑提取出来,封装成高阶组件,如果哪个组件需要进行日志处理,在组件名的外面包裹一层日志高阶组件
数据校验:
多个地方都需要进行校验数据,将逻辑拆出来,封装成高阶组件
异常处理:
进行封装异常处理,例如:异常时来个弹窗报错
withAuth.jsx文件
import React, { Component } from 'react';
const withAuth = (WrappedComponent) => {
//返回一个新组件
return class NweComponent extends Component {
render() {
if (this.props.author === "admin") {
return <WrappedComponent {...this.props} />
} else {
return null;
}
}
}
}
export default withAuth;
import React, { Component } from 'react';
import withAuth from './withAuth';
class Button extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (<button >{this.props.children}</button>);
}
}
export default withAuth(Button);
(1)React生命周期中以及事件处理中,为异步。
(2)原生方法(setTimeout,setInnerval,addEventListener )中是同步 。
原理:setState本身并不是异步,只是因为react的性能优化机制体现为异步。在react的生命周期函数或者作用域下为异步,在原生的环境下为同步。 因为每次调用setState都会触发更新,异步操作是为了提高性能,将多个状态合并一起更新,减少re-render调用。
setState的异步并不是说内部由异步代码实现的,其实本身执行的过程和代码都是同步的,只是合
成事件和钩子函数调用的顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的
值,形成了所谓的“异步”,当然也可以通过第二个参数setState(partialState,callback)中的callback
拿到更新后的结果
性能优化机制:在 React 的 setState 函数实现中,会根据一个变量isBatchingUpdates判断是直接更新 this.state 还是放到队列中。isBatchingUpdates 默认是 false,React 在调用事件处理函数之前会将isBatchingUpdates改为true,造成的后果就是由 React 控制的事件处理过程 setState 不会同步更新 this.state。而原生方法不会被React控制。
以下是MVC框架的一些主要问题:
对 DOM 操作的代价非常高
程序运行缓慢且效率低下
内存浪费严重
由于循环依赖性,组件模型需要围绕 models 和 views 进行创建
详细的Redux
mobx写法偏向与oop
对一份数据可以直接进行修改操作,不需要始终返回一个新的数据
并非单一store,可以多个
redux默认使用js原生对象存储数据,而mobx使用可观察对象
Redux 由以下组件组成:
Action – 这是一个用来描述发生了什么事情的对象。
Reducer – 这是一个确定状态将如何变化的地方。
Store – 整个程序的状态/对象树保存在Store中。
View – 只显示 Store 提供的数据。
先简单说一下redux和react是怎么配合的。react-redux提供了connect和Provider两个好基友,它们一个将组件与redux关联起来,一个将store传给组件。组件通过dispatch发出action,store根据action的type属性调用对应的reducer并传入state和这个action,reducer对state进行处理并返回一个新的state放入store,connect监听到store发生变化,调用setState更新组件,此时组件的props也就跟着变化。
如果通过谷歌浏览器react 开发工具
1遵从三个原则
单一事实来源:整个应用的状态存储在单个 store 中的对象/状态树里。单一状态树可以更容易地跟踪随时间的变化,并调试或检查应用程序。
状态是只读的:改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示。
使用纯函数进行更改:为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数。
模块化:抽取业务中的重复逻辑,自定义成hook
组件化:基于使用的UI库,根据业务自底向上封装更复杂的黑盒组件
数据流管理:在hook出现,数据流管理变得更加简便。页面中有自己的数据流,如果是一个公用的数据,可以使用useModel放到全局。
React合成事件是React 模拟原生DOM事件所有能力 的一个事件对象。
根据 W3C规范 来定义合成事件,兼容所有浏览器,拥有与浏览器原声事件相同的接口。合成事件除了拥有和浏览器原生事件相同的接口,包括stopPropagetion()和preventDefault();在React中,所有事件都是合成的,不是原生DOM事件
为什么出现这个技术?
浏览器兼容,实现更好的跨平台。顶层事件代理机制:保证冒泡一致性,可以跨浏览器执行。将不同平台事件模拟成合成事件;
避免垃圾回收。React引入事件池,在事件池中获取事件对象。React事件对象不会被释放掉,而是存入一个数组中;当事件触发,就从这个数组中弹出,避免频繁地创建和销毁(垃圾回收);
方便事件统一管理和事务机制
但是,对大多数事件来说,React 实际上并不会将它们附加到 DOM 节点上。相反,React 会直接在 document 节点上为每种事件类型附加一个处理器。这被称为事件委托。除了在大型应用程序上具有性能优势外,它还使添加类似于 replaying events 这样的新特性变得更加容易。
在React17之前,React是把事件委托在document上的,React17及以后版本不再把事件委托在document上,而是委托在挂载的容器上了
列表渲染的时候加key;
在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行;
合理设计组件,减少props和state
dva
1应用场景 引用第三方网站
windows.open 和a超链接跳转
跳转后用户正在浏览新标签页,但是原来网站的标签页已经被导航到了百度页面。
<a target="_blank" href="" rel="noopener noreferrer nofollow">a标签跳转url</a>
<!--
通过 rel 属性进行控制:
noopener:会将 window.opener 置空,从而源标签页不会进行跳转(存在浏览器兼容问题)
noreferrer:兼容老浏览器/火狐。禁用HTTP头部Referer属性(后端方式)。
nofollow:SEO权重优化,详情见 https://blog.csd n.net/qq_33981438/article/details/80909881
-->
<button onclick='openurl("http://www.baidu.com")'>click跳转</button>
function openurl(url) {
var newTab = window.open();
newTab.opener = null;
newTab.location = url;
}
2iframe
a.如何让自己的网站不被其他网站的 iframe 引用?
// 检测当前网站是否被第三方iframe引用
// 若相等证明没有被第三方引用,若不等证明被第三方引用。当发现被引用时强制跳转百度。
if(top.location != self.location){
top.location.href = 'http://www.baidu.com'
}
// 检测当前网站是否被第三方iframe引用
// 若相等证明没有被第三方引用,若不等证明被第三方引用。当发现被引用时强制跳转百度。
if(top.location != self.location){
top.location.href = ‘http://www.baidu.com’
}
b.如何禁用,被使用的 iframe 对当前网站某些操作?
sandbox是html5的新属性,主要是提高iframe安全系数。iframe因安全问题而臭名昭著,这主要是因为iframe常被用于嵌入到第三方中,然后执行某些恶意操作。
现在有一场景:我的网站需要 iframe 引用某网站,但是不想被该网站操作DOM、不想加载某些js(广告、弹框等)、当前窗口被强行跳转链接等,我们可以设置 sandbox 属性。如使用多项用空格分隔。
allow-same-origin:允许被视为同源,即可操作父级DOM或cookie等
allow-top-navigation:允许当前iframe的引用网页通过url跳转链接或加载
allow-forms:允许表单提交
allow-scripts:允许执行脚本文件
allow-popups:允许浏览器打开新窗口进行跳转
“”:设置为空时上面所有允许全部禁止
你可以这么理解 CSRF 攻击:攻击者盗用了你的身份,以你的名义进行恶意请求。它能做的事情有很多包括:以你的名义发送邮件、发信息、盗取账号、购买商品、虚拟货币转账等。总结起来就是:个人隐私暴露及财产安全问题。
涉及到数据修改操作严格使用 post 请求而不是 get 请求
HTTP 协议中使用 Referer 属性来确定请求来源进行过滤(禁止外域) (也可以设置防盗链)
请求地址添加 token ,使黑客无法伪造用户请求
HTTP 头自定义属性验证(类似上一条)
显示验证方式:添加验证码、密码等
4 接口 referer 设置防盗链
5 XSS/CSS(跨站脚本攻击)
1反射行存储XSS 脚本来自当前 HTTP 请求
当服务器在 HTTP 请求中接收数据并将该数据拼接在 HTML 中返回时
2存储型 XSS
XSS 脚本来自服务器数据库中
攻击者将恶意代码提交到目标网站的数据库中,普通用户访问网站时服务器将恶意代码返回,浏览器默认执行
DOM 型 XSS
该漏洞存在于客户端代码,与服务器无关
类似反射型,区别在于 DOM 型 XSS 并不会和后台进行交互,前端直接将 URL 中的数据不做处理并动态插入到 HTML 中,是纯粹的前端安全问题,要做防御也只能在客户端上进行防御。
a.com可以发文章,我登录后在a.com中发布了一篇文章,文章中包含了恶意代码,,保存文章。这时Tom和Jack看到了我发布的文章,当在查看我的文章时就都中招了,他们的cookie信息都发送到了我的服务器上,攻击成功!这个过程中,受害者是多个人。
Stored XSS漏洞危害性更大,危害面更广。
反射性 (请求)存储型(数据库) dom型
XSS又叫CSS(Cross Site Script),跨站脚本攻击:攻击者在目标网站植入恶意脚本(js / html),用户在浏览器上运行时可以获取用户敏感信息(cookie / session)、修改web页面以欺骗用户、与其他漏洞相结合形成蠕虫等
防御措施(对用户输入内容和服务端返回内容进行过滤和转译)
React 在渲染 HTML 内容和渲染 DOM 属性时都会将 "'&<> 这几个字符进行转义,转义部分源码。 源码大概就是switch
JSX 语法
JSX 实际上是一种语法糖,Babel 会把 JSX 编译成 React.createElement() 的函数调用,最终返回一个 ReactElement 实例,
// JSX
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
// 通过 babel 编译后的代码
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
// React.createElement() 方法返回的 ReactElement
const element = {
$$typeof: Symbol('react.element'),
type: 'h1',
key: null,
props: {
children: 'Hello, world!',
className: 'greeting'
}
...
}
注意到其中有个属性是 t y p e o f ,它是用来标记此对象是一个 R e a c t E l e m e n t , R e a c t 在进行渲染前会通过此属性进行校验,校验不通过将会抛出上面的错误。 R e a c t 利用这个属性来防止通过构造特殊的 C h i l d r e n 来进行的 X S S 攻击,原因是 typeof,它是用来标记此对象是一个 ReactElement,React 在进行渲染前会通过此属性进行校验,校验不通过将会抛出上面的错误。React 利用这个属性来防止通过构造特殊的 Children 来进行的 XSS 攻击,原因是 typeof,它是用来标记此对象是一个ReactElement,React在进行渲染前会通过此属性进行校验,校验不通过将会抛出上面的错误。React利用这个属性来防止通过构造特殊的Children来进行的XSS攻击,原因是typeof 是个 Symbol 类型,进行 JSON 转换后会 Symbol 值会丢失,无法在前后端进行传输。如果用户提交了特殊的 Children,也无法进行渲染,利用此特性,可以防止存储型的 XSS 攻击。
一定要注意 dangerouslySetInnerHTML 不会进行转译
现代大部分浏览器都自带 XSS 筛选器,vue / react 等成熟框架也对 XSS 进行一些防护
即便如此,我们在开发时也要注意和小心
对用户输入内容和服务端返回内容进行过滤和转译
重要内容加密传输
合理使用get/post等请求方式
对于URL携带参数谨慎使用
我们无法做到彻底阻止,但是能增加黑客攻击成本,当成本与利益不符时自然会降低风险
xss.js 白名单输出允许的html标签
6 防御DDoS
CDN 分发
这里不得不提 HTML5 中的一个 API —— Service Worker,它能拦截当前站点产生的所有请求,并能控制返回结果,相当于一个反向代理服务。有了这个黑科技,即可在前端实现 CDN 功能。
我们为静态资源准备多个站点做冗余备份,当 Service Worker 加载资源出错时,可不返回错误给上层页面,而是继续从备用站点加载,直到获得正确结果才返回。这样,只要有一个备用站点可用,资源就不会加载失败。
后端 模块 采用分布式 端口 或者服务器 如果请求过多 暂停服务 过一段时间才会恢复。 不过可以设置 备用模块 如果访问失败 去备用模块访问。
基于web worker(一个独立于JavaScript主线程的独立线程,在里面执行需要消耗大量资源的操作不会堵塞主线程)
在web worker的基础上增加了离线缓存的能力
本质上充当Web应用程序(服务器)与浏览器之间的代理服务器(可以拦截全站的请求,并作出相应的动作->由开发者指定的动作)
创建有效的离线体验(将一些不常更新的内容缓存在浏览器,提高访问体验)
由事件驱动的,具有生命周期
可以访问cache和indexDB
支持推送
并且可以让开发者自己控制管理缓存的内容以及版本
同源指的是我们访问站点的:协议、域名、端口号必须一至,才叫同源。
浏览器默认同源之间的站点是可以相互访问资源和操作DOM的,而不同源之间想要互相访问资源或者操作DOM,那就需要加一些安全策略的限制,俗称同源策略
同源策略主要限制了三个方面:
DOM层面:不同源站点之间不能相互访问和操作DOM
数据层面:不能获取不同源站点的Cookie、LocalStorage、indexDB等数据
网络层面:不能通过XMLHttpRequest向不同源站点发送请求
当然同源策略限制也不是绝对隔离不同源的站点,比如link、img、script标签都没有跨域限制,这让我们开发更灵活了,但是也同样带来了一些安全问题,也就是浏览器网络安全问题,最典型的就是XSS攻击和CSRF攻击
HashRouter
①基于hash模式:页面跳转原理是使用了location.hash、location.replace;和vue router的hash模式实现一致
②比较丑:在域名后,先拼接/#,再拼接路径;也就是利用锚点,实现路由的跳转;如:http://www.abc.com/#/xx
BrowserRouter
①基于history模式:页面跳转原理是使用了HTML5为浏览器全局的history对象新增了两个API,包括 history.pushState、history.replaceState;和vue router的history模式实现一致
②更加优雅: 直接拼接路径;如:http://www.abc.com/xx
③后端需做请求处理: 切换路由后,请求接口路径会发生变化,后端需要配合,做处理
类似 Link 但是会添加当前选中状态(蓝色的链接状态)。
满足条件时提示用户是否离开当前页面。
重定向当前页面,例如登录判断。
路由配置的核心标记,路由匹配时显示对应组件(且只要路由匹配,组件都会显示)
只显示第一个匹配的路由。
———————————
props.location.query
1、动态路由传参
使用prop进行接收 props.match.params.参数名
2、query传参
this.props.history.push({
pathname: ‘路由’,
query:{参数名:参数值}
})
接收:
this.props.location.query.参数名
3、state传参
this.props.history.push({
pathname: ‘路由’,
state:{参数名:参数值}
})
接收:
this.props.location.state.参数名
————————————————
版权声明:本文为CSDN博主「前端Beginners」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45615650/article/details/124864095
—————
< Router>
<Switch>
<Route exact path="/"
render={() => (<Redirect to="/EarlyWarnQuery" />)} //重定向到首页面
/>
<Route path={'/EarlyWarnQuery'} component={(EarlyWarnQuery)} />
// component 可以是任何组件,跨文件夹也可以,只要路径写的对
<Route path={'/Fretail'} component={FileQuery} />
</Switch>
</ Router>
<NavLink key={item.id} to={{ pathname:`/cardDetail/${item.id}`, state: item }}>
<span onClick={this.cardShow} >张三三</span>
</NavLink>
重定向
React-Router 是建立在 history 之上的,常见的history路由方案有三种形式,分别是:
hashHistory
hashHistory 使用 URL 中的 hash(#)部分去创建路由,举例来说,用户访问http://www.example.com/,实际会看到的是http://www.example.com/#/。
hash route 刷新时会丢失 route 传过来的state.因为hashRouter没有使用html5中history的api,无法从历史记录中获取到key和state值,所以当刷新路由后state值会丢失导致页面显示异常。
browserHistory
browserHistory 是使用 React-Router 的应用推荐的 history方案。它使用浏览器中的 History API 用于处理 URL,创建一个像example.com/list/123这样真实的 URL
当刷新页面时,浏览器会向服务器请求,服务器实际会去找根目录下对应的文件,发现找不到,因为实际上我们的服务器并没有这样的 物理路径/文件 或没有配置处理这个路由,所有内容都是通过React-Router去渲染React组件,自然会报404错误。
umi.js 默认是hash route 需要在config.js 设置
history: { type: 'browser', },
前端框架的一般设置跨域代理 proxy对象 react proxy.ts 生产环境 测试环境都可以设置
cors
CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。
浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
postMessage
// 发送消息
const targetWindow = window.open('http://localhost:10001/user');
setTimeout(()=>{
targetWindow.postMessage('来自10002的消息', 'http://localhost:10001')
}, 3000)
window.addEventListener('message', (e) => {
console.log(e.data)
if (event.origin !== "http://localhost:10002")
return;
e.source.postMessage('来自10001的消息', e.origin)
})
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
页面和其打开的新窗口的数据传递
多窗口之间消息传递
页面与嵌套的iframe消息传递
上面三个场景的跨域数据传递
postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
websocket
Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
try {
const socket = new WebSocket(
`${initialState?.businessInfo.socketAddress}${buildTransferUrl}/${importRecordId}`,
);
// Connection opened
socket.addEventListener('open', () => {
socketRef.current = socket;
// socket.send('Hello Server!');
});
socket.addEventListener('close', () => {});
// Listen for messages
socket.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
setProgress(parseInt((data.num / data.total) * 100 + ''));
setShowProgress(data.num + '/' + data.total);
if (data.num >= data.total) {
setTableSpanding(false);
setWebsokcketFlag(false);
message.info(messageInfo);
setMyButtonFlag(true);
}
});
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容
Node中间件代理(两次跨域)
实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。 代理服务器,需要做以下几个步骤:
接受客户端请求 。
将请求 转发给服务器。
拿到服务器 响应 数据。
将 响应 转发给客户端。
nginx反向代理
实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。
使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
JSONP scrpit SRC访问 返回结果是一个函数的调用 数据放在参数中 函数声明在原来的页面中
相同二级域名之间的跨域
document.domain + iframe
该方式只能用于二级域名相同的情况下,比如a.test.com和b.test.com适用于该方式。 只需要给页面添加document.domain ='test.com’表示二级域名都相同就可以实现跨域。
window.name + iframe
就是使用iframe将test2.html加载过来,因为只是为了实现跨域,所以将之隐藏,但是,这时已经完成了最重要的一步,就是将iframe中window.name已经成功设置,但是现在还获取不了,因为是跨域的,所以,我们可以把src设置为当前域的proxy.html。
另外,这里之所以要设置flag,是因为每当改变location的时候,就会重新来一次onload,所以我们希望获取到数据之后,就直接close(),故采用此种方法。
window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
location.hash + iframe
实现原理: a.html欲与c.html跨域相互通信,通过中间页b.html来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
具体实现步骤:一开始a.html给c.html传一个hash值,然后c.html收到hash值后,再把hash值传递给b.html,最后b.html将结果放到a.html的hash值中。 同样的,a.html和b.html是同域的,都是http://localhost:3000;而c.html是http://localhost:4000
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
参考答案:
http1.x 和http2.x主要有以下4个区别:
HTTP2使用的是二进制传送,HTTP1.X是文本(字符串)传送。
二进制传送的单位是帧和流。帧组成了流,同时流还有流ID标示,其中帧对数据进行顺序标识
HTTP2支持多路复用, 在同一个域名下,开启一个TCP的connection,每个请求以stream的方式传输,每个stream有唯一标识,connection一旦建立,后续的请求都可以复用这个connection并且可以同时发送,server端可以根据stream的唯一标识来相应对应的请求。 可以通过流ID来标示究竟是哪个流从而定位到是哪个http请求
HTTP2头部压缩
HTTP2通过gzip和compress压缩头部然后再发送,同时客户端和服务器端同时维护一张头信息表,所有字段都记录在这张表中,这样后面每次传输只需要传输表里面的索引Id就行,通过索引ID查询表头的值
HTTP2支持服务器推送
HTTP2支持在未经客户端许可的情况下,主动向客户端推送内容
TCP的作用是在传输过程把数据拆分成一个个包 然后传过去 接收端按照顺序组合成原始数据。如果一个包没有 就会一直等待 阻塞 产生队头阻塞的问题 HTTP2 引用了多路复用 同一个http请求实现多个http请求传输 影响会更大 三次握手四次挥手 三次握手中间是有三次交互会有很大的消耗
http3 是放弃了TCP协议 采用了UDP quic协议 实现快速握手 不会阻塞、
https和http的区别:1、https的端口是443,而http的端口是80,且两者的连接方式不同;2、http传输是明文的,而https是用ssl进行加密的,https的安全性更高;3、https是需要申请证书的,而http不需要。
为什么数据传输是用对称加密?
首先:非对称加密的加解密效率是非常低的,而 http 的应用场景中通常端与端之间存在大量的交互,非对称加密的效率是无法接受的。
加密过程:
加密: 原文+密钥 = 密文
解密:密文-密钥 = 原文
非对称是公钥+私钥
常见的对称加密算法: DES, AES, 3DES等
HTTPS 在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段。
“证书 – 为公钥加上数字签名”
1、HTTPS 协议需要到 CA (Certificate Authority,证书颁发机构)申请证书,一般免费证书较少,因而需要一定费用。(以前的网易官网是http,而网易邮箱是 https 。)
2、HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
3、HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)
参考答案:
http缓存的分类:
根据是否需要重新向服务器发起请求来分类,可分为(强制缓存,协商缓存) 根据是否可以被单个或者多个用户使用来分类,可分为(私有缓存,共享缓存) 强制缓存如果生效,不需要再和服务器发生交互,而协商缓存不管是否生效,都需要与服务端发生交互。下面是强制缓存和协商缓存的一些对比:
1.1、强制缓存
强制缓存在缓存数据未失效的情况下(即Cache-Control的max-age没有过期或者Expires的缓存时间没有过期),那么就会直接使用浏览器的缓存数据,不会再向服务器发送任何请求。强制缓存生效时,http状态码为200。这种方式页面的加载速度是最快的,性能也是很好的,但是在这期间,如果服务器端的资源修改了,页面上是拿不到的,因为它不会再向服务器发请求了。这种情况就是我们在开发种经常遇到的,比如你修改了页面上的某个样式,在页面上刷新了但没有生效,因为走的是强缓存,所以Ctrl + F5一顿操作之后就好了。 跟强制缓存相关的header头属性有(Pragma/Cache-Control/Expires), Pragma和Cache-control共存时,Pragma的优先级是比Cache-Control高的。
1.2、协商缓存
Last-Modify搭配If-Modify-Since:浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-modify是该资源的最后修改时间;当浏览器再次请求该资源时,request的请求头中会包含If-Modify-Since,该值服务端header中返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存
Etag搭配If-None-Match:web服务器响应请求时,会在header中加一个Etag用来告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。则再次向web服务器请求时带上头If-None-Match (Etag的值)。web服务器收到请求后将If-None-Match与Etag进行比对,决定是否命中协商缓存;
当第一次请求时服务器返回的响应头中没有Cache-Control和Expires或者Cache-Control和Expires过期还或者它的属性设置为no-cache时(即不走强缓存),那么浏览器第二次请求时就会与服务器进行协商,与服务器端对比判断资源是否进行了修改更新。如果服务器端的资源没有修改,那么就会返回304状态码,告诉浏览器可以使用缓存中的数据,这样就减少了服务器的数据传输压力。如果数据有更新就会返回200状态码,服务器就会返回更新后的资源并且将缓存信息一起返回。跟协商缓存相关的header头属性有(ETag/If-Not-Match 、Last-Modified/If-Modified-Since)请求头和响应头需要成对出现
1.3、私有缓存(浏览器级缓存)
私有缓存只能用于单独的用户:Cache-Control: Private
1.4、共享缓存(代理级缓存)
共享缓存可以被多个用户使用: Cache-Control: Public
1.5 Service Workers
cache stroage
必须是https
1,首先要注册service
2,监听install,拿到需要缓存的文件
3,下次用户访问的时候可以通过拦截请求方式查询是否存在缓存,存在缓存的话就可以直接读取文件,否则请求。
注册、安装成功(安装失败)、激活、运行、销毁。
事件:install、activate、message、fetch、push、async。
由于是离线缓存,所以在首次注册、二次访问、服务脚本更新等会有不同的生命周期。
首次 安装-激活-销毁
安装:执行过程即是 installing 过程,此时会触发 install 事件,并执行 installEvent 的 waitUtil 方法。执行完毕后,当前状态是 installed。
激活:立即进入 activating 状态;并触发 activate 事件,处理相关事件内容。执行完成后,变成 activated 状态。
销毁: 当安装失败或进程被关闭时。
激活后 可以监听fetch 请求数据缓存 或者缓存对应的文件列表
再次访问 在上一次服务未销毁时,二次访问页面,直接停留在激活运行状态:
脚本更新 ,每次访问都会被下载一次。并且至少每 24 小时会被下载一次。为的是避免错误代码一直被运行。下载后,会比对是否更新,如果更新,就会重新注册安装 serivceworker,安装成功后会处于 waiting 状态。
{
urlPattern: /.css./,
handler: ‘CacheFirst’,
options: {
cacheName: ‘seed-css’,
expiration: {
maxEntries: 30, //最多缓存30个,超过的按照LRU原则删除
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
},
},
},
5.26 协商缓存原理,谁跟谁协商,如何协商?
参考答案:
协商缓存: 向服务器发送请求,服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的response header通知浏览器从缓存中读取资源;
服务器和请求协商,根据请求头携带的参数进行协商
浏览器首次加载资源成功时,服务器返回200,此时浏览器不仅将资源下载下来,而且把response的header(里面的date属性非常重要,用来计算第二次相同资源时当前时间和date的时间差)一并缓存;
下一次加载资源时,首先要经过强缓存的处理,cache-control的优先级最高,比如cache-control:no-cache,就直接进入到协商缓存的步骤了,如果cache-control:max-age=xxx,就会先比较当前时间和上一次返回200时的时间差,如果没有超过max-age,命中强缓存,不发请求直接从本地缓存读取该文件(这里需要注意,如果没有cache-control,*****会取expires的值,时间代表这这个资源的失效时间,看是否直接过期),过期的话会进入下一个阶段,协商缓存
协商缓存阶段,则向服务器发送header带有If-None-Match和If-Modified-Since的请求,服务器会比较Etag,如果相同,If-None-Match的值设为false,返回状态304,命中协商缓存,返回304;如果不一致则有改动,直接返回新的资源文件带上新的Etag值并返回200;
协商缓存第二个重要的字段是,If-Modified-Since,如果客户端发送的If-Modified-Since的值跟服务器端获取的文件最近改动的时间,一致则命中协商缓存,返回304;不一致则返回新的last-modified和文件并返回200;
谷歌中200 from disk cache和from memory cache 硬盘缓存 内存缓存
火狐是 304
强缓存:
1、先查找内存,如果内存中存在,从内存中加载;
2、如果内存中未查找到,选择硬盘获取,如果硬盘中有,从硬盘中加载;
3、如果硬盘中未查找到,那就进行网络请求;
4、加载到的资源缓存到硬盘和内存;
6、服务器处理请求,浏览器接收HTTP响应。
一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
某些服务器不能精确的得到文件的最后修改时间。
get用来获取数据,post用来提交数据
get参数有长度限制(受限于url长度,具体的数值取决于浏览器和服务器的限制,最长2048字节),而post无限制
get请求的数据会附加在url之 ,以 " ? "分割url和传输数据,多个参数用 "&"连接,而post请求会把请求的数据放在http请求体中。
get是明文传输,post是放在请求体中,但是开发者可以通过抓包工具看到,也相当于是明文的。
get请求会保存在浏览器历史记录中,还可能保存在web服务器的日志中
1 ,URL解析
从输入URL到页面加载的主干流程如下:
1、浏览器的地址栏输入URL并按下回车。
2、浏览器查找当前URL的DNS缓存记录。
(1、器中输入www.baidu.com域名,操作系统会先查hosts件是否有记录,有的话就会把相对应映射的IP返回。
2、hosts文件没有就去查本地dns解析器有没有缓存。(这个我没答上来)
3、然后就去找我们计算机上配置的dns服务器上有或者有缓存,就返回
4、还没有的话就去找根DNS服务器(全球13台,固定ip地址),然后判断.com域名是哪个服务器管理,如果无法解析,就查找.baidu.com服务器是否能解析,直到查到www.baidu.com的IP地址)
2, 缓存检查 (资源缓存 强缓存 协商缓存 所有资源请求都是get请求,)
3、DNS解析URL对应的IP。
4、根据IP建立TCP连接(三次握手)。
第三次握手是为了防止失效的请求连接到达服务器,让服务器错误的打开连接。第二次握手,主机B还不能确认主机A已经收到确认请求,也是说B认为建立好连接,开始发数据了,结果发出去的包一直A都没收到,那攻击B就很容易了,我专门发包不接收,服务器很容易就挂了。
必须三次是因为数据传输是单向的 因为TCP是一个双向传输协议,只有经过第三次握手,才能确保双向都可以接收到对方的发送的数据。第一次请求网络没收到 服务器没有确认再发送一遍 建立了双向 但是又发送了一次
(1 第一次握手由客户端发送资源包给到服务端,若该过程正常,则得出结论:服务端接收、客户端发送服务正常
2 第二次握手由服务端发送资源包给到客户端,若该过程正常,则得出结论:服务端发送、客户端接收服务正常
3 第三次握手由客户端发送资源包给到服务端,若该过程正常,则得出结论:服务端接收、客户端发送服务正常)
5、HTTP发起请求。
浏览器首次加载资源成功时,服务器返回200,此时浏览器不仅将资源下载下来,而且把response的header(里面的date属性非常重要,用来计算第二次相同资源时当前时间和date的时间差)一并缓存;
下一次加载资源时,首先要经过强缓存的处理,cache-control的优先级最高,比如cache-control:no-cache,就直接进入到协商缓存的步骤了,如果cache-control:max-age=xxx,就会先比较当前时间和上一次返回200时的时间差,如果没有超过max-age,命中强缓存,不发请求直接从本地缓存读取该文件(这里需要注意,如果没有cache-control,会取expires的值,来对比是否过期),过期的话会进入下一个阶段,协商缓存
协商缓存阶段,则向服务器发送header带有If-None-Match和If-Modified-Since的请求,服务器会比较Etag,如果相同,命中协商缓存,返回304;如果不一致则有改动,直接返回新的资源文件带上新的Etag值并返回200;
协商缓存第二个重要的字段是,If-Modified-Since,如果客户端发送的If-Modified-Since的值跟服务器端获取的文件最近改动的时间,一致则命中协商缓存,返回304;不一致则返回新的last-modified和文件并返回200;
谷歌中200 from disk cache和from memory cache 硬盘缓存 内存缓存
火狐是 304
强缓存:
1、先查找内存,如果内存中存在,从内存中加载;
2、如果内存中未查找到,选择硬盘获取,如果硬盘中有,从硬盘中加载;
3、如果硬盘中未查找到,那就进行网络请求;
4、加载到的资源缓存到硬盘和内存;
6、服务器处理请求,浏览器接收HTTP响应。
7、渲染页面,构建DOM树。
构建DOM树(DOM tree):从上到下解析HTML文档生成DOM节点树(DOM tree),也叫内容树(content tree);
构建CSSOM(CSS Object Model)树:加载解析样式生成CSSOM树;
执行JavaScript:加载并执行JavaScript代码(包括内联代码或外联JavaScript文件);
构建渲染树(render tree):根据DOM树和CSSOM树,生成渲染树(render tree);
渲染树:按顺序展示在屏幕上的一系列矩形,这些矩形带有字体,颜色和尺寸等视觉属性。
布局(layout):根据渲染树将节点树的每一个节点布局在屏幕上的正确位置;
绘制(painting):遍历渲染树绘制所有节点,为每一个节点适用对应的样式,这一过程是通过UI后端模块完成;
8、关闭TCP连接(四次挥手)。
从输入URL到页面加载的主干流程如下:
TCP协议经历了数据的发送之后 去尝试断开连接 此时会经历四次挥手过程
第一次 是客户端向服务器发送一个fin包 去进行连接断开的请求
第二次挥手 是服务器发给客户端 ack包 用来确认上一个客户端发送断开请求的报文
第三次挥手 服务器发给客户端 服务器的数据发送完毕 那么向客户端发送一个fin包
第四次挥手 是客户端发送给服务器ack包 用来确认上一个服务器发送断开的报文
重排/回流(Reflow):当DOM的变化影响了元素的几何信息,浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。表现为重新生成布局,重新排列元素。
重绘(Repaint): 当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。表现为某些元素的外观被改变
单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分
重排和重绘代价是高昂的,它们会破坏用户体验,并且让UI展示非常迟缓,而相比之下重排的性能影响更大,在两者无法避免的情况下,一般我们宁可选择代价更小的重绘。
『重绘』不一定会出现『重排』,『重排』必然会出现『重绘』。
原文链接:https://blog.csdn.net/CathyleeQ/article/details/123364051
HTML文档结构层次尽量少,最好不深于六层;
脚本尽量后放,放在前即可;
少量首屏样式内联放在标签内;
样式结构层次尽量简单;
在脚本中尽量减少DOM操作,尽量缓存访问DOM的样式信息,避免过度触发回流;
减少通过JavaScript代码修改元素样式,尽量使用修改class名方式操作样式或动画;
动画尽量使用在绝对定位或固定定位的元素上;
隐藏在屏幕外,或在页面滚动时,尽量停止动画;
尽量缓存DOM查找,查找器尽量简洁;
涉及多域名的网站,可以开启域名预解析
前端
1 Vuex是什么,每个属性是干嘛的,如何使用 ?
Vuex是集中管理项目公共数据的。Vuex 有state、mutations 、getters、actions、module属性。 state 属性用来存储公共管理的数据。 mutations 属性定义改变state中数据的方法, 注意:不要在mutation中的方法中写异步方法ajax,那样数据就不可跟踪了 。 getters 属性可以认为是定义 store 的计算属性。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。 action属性类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。 moudle属性是将store分割成模块。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块,从上至下进行同样方式的分割 使用方法: state :直接以对象方式添加属性 mutations :通过store.commit
调用 action:通过 store.dispatch
方法触发 getters:直接通过store.getters.调用 加分回答 可以使用mapState、mapMutations、mapAction、mapGetters一次性获取每个属性下对应的多个方法。 VueX在大型项目中比较常用,非关系组件传递数据比较方便。
1创建阶段
constructor
在方法内部通过super关键字获取来自父组件的props
在该方法中,通常的操作为初始化state状态或者在this上挂载方法
getDerivedStateFromProps 当传入的type发生变化的时候,更新state 不更新传null
1无条件的根据 prop 来更新内部 state,也就是只要有传入 prop 值, 就更新 state
2只有 prop 值和 state 值不同时才更新 state 值
执行时机:组件创建和更新阶段,不论是props变化还是state变化,也会调用
在每次render方法前调用,第一个参数为即将更新的props,第二个参数为上一个状态的state,可以比较props 和 state来加一些限制条件,防止无用的state更新
componentWillReceiveProps弃用 16
componentWillReceiveProps在初始化render的时候不会执行,它会在Component接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染。这个东西十分好用,但是一旦用错也会造成十分严重的后果
componentWillMount 将要装载,在render之前调用;
render
类组件必须实现的方法,用于渲染DOM结构
componentDidMount
组件挂载到真实DOM节点后执行,其在render方法之后执行
2 更新
getDerivedStateFromProps
该方法介绍同上
shouldComponentUpdate
用于告知组件本身基于当前的props和state是否需要重新渲染组件
componentDidUpdate
执行时机:组件更新结束后触发
在该方法中,可以根据前后的props和state的变化做相应的操作,如获取数据,修改DOM样式等
3 卸载
componentWillUnmount
此方法用于组件卸载前,清理一些注册是监听事件,或者取消订阅的网络请求等
通过extends关键字实现类的继承
class sup {
constructor(name) {
this.name = name
}
printName() {
console.log(this.name)
}
}
class sub extends sup{
constructor(name,age) {
super(name) // super代表的事父类的构造函数
this.age = age
}
printAge() {
console.log(this.age)
}
}
let jack = new sub('jack',20)
jack.printName() //输出 : jack
jack.printAge() //输出 : 20
在上面的例子中,可以看到通过super关键字实现调用父类,super代替的是父类的构建函数,使用super(name)相当于调用sup.prototype.constructor.call(this,name)
子类是没有自己的this对象的,它只能继承父类的this对象,然后对其进行加工
而super()就是将父类中的this对象继承给子类的,没有super()子类就得不到this对象
如果先调用this,再初始化super(),同样是禁止的行为
class Button extends React.Component {
constructor(props) {
super(props); // 没传入 props
console.log(props); // {}
console.log(this.props); // {}
// ...
}
在React中,类组件基于ES6,所以在constructor中必须使用super
在调用super过程,无论是否传入props,React内部都会将porps赋值给组件实例porps属性中
如果只调用了super(),那么this.props在super()和构造函数结束之间仍是undefined
async/await 是 Generator 函数的语法糖,并对 Generator 函数进行了改进。它是基于 Generator 和 promise 实现的。
异步编程
function *gen(){
}
let a=gen()
1 调用方法需要 a.Next()
2 yield ‘ABC’ yield 可以算作是函数代码的分隔符 通过next 方法执行 一次执行一个yield
3 传参的话 传在next()
1async 可以写多个 一个依赖另一个
2 promise
三种状态 不可以逆 中断
3 generator
语法上,Generator 函数是一个状态机,封装了多个内部状态。
async只是generator的一个语法糖,他相当于*,await相当于yield
async 和 await,比起星号和 yield,语义更清楚了
但是,调用Generator函数后,函数并不执行,返回的也不是函数执行后的结果,而是一个指向内部状态的指针对象。
下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。即:每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
generator函数内有两个重要方法,1 yield表达式 2.next()
Generator 函数是分段执行的,yield表达式是暂停执行的标记,而 next方法可以恢复执行
统一处理副作用函数 异步
当action发出之后,reducer立即算出state,整个过程是一个同步的操作
那么如果需要支持异步操作,或者支持错误处理、日志监控,这个过程就可以用上中间件
Redux中,中间件就是放在就是在dispatch过程,在分发action进行拦截处理
redux-thunk: 解决redux中的异步问题,将异步问题放在action中进行操作。
redux-saga:同样是解决redux中的异步问题,不同于redux-thunk的是,将异步问题的逻辑单独的拆分出来放到另一个文件进行管理。
中间件 redux-thunk redux-saga(DVA)
前者的缺点 dispatch一面有一个方法 在别的地方调用 方法里面还有一个还有dispathch
export default ()=>(dispatch)=>{
fetch('/api/goodList',{ //fecth返回的是一个promise
method: 'get',
dataType: 'json',
}).then(function(json){
var json=JSON.parse(json);
if(json.msg==200){
dispatch({type:'init',data:json.data});
}
},function(error){
console.log(error);
});
};
从这个具有副作用的action中,我们可以看出,函数内部极为复杂。如果需要为每一个异步操作都如此定义一个action,显然action不易维护
saga
1 声明 声明Effect
redux-saga中的api有take、put、all、select这些,在redux-saga中将这些api都定义为Effect。在Effect执行后,当函数resolved时返回一个描述对象,然后saga根据这个描述对象恢复执行generator中的函数。
action1(plain object)——>redux-saga监听—>执行相应的Effect方法——>返回描述对象—>恢复执行异步和副作用函数—>action2(plain object)
1 call方法是一个Effect类方法
yield call
发送 api 请求
2 yield put
发送对应的 dispatch,触发对应的 action 获取在reduce中的调用函数
3 fork 创建一个新的进程或者线程,并发发送请求。无阻塞 可以理解为异步
function* fetch_user() {
const [users, todos] = [
yield fork(fetchResource, ‘https://jsonplaceholder.typicode.com/users’),
yield fork(fetchResource, ‘https://jsonplaceholder.typicode.com/todos’)
]
}
takeEvery :监听相应的动作并执行相应的方法 每一次
take: take(Action | ‘*’) 告诉 middleware 等待一个指定或者满足匹配符 * 的 Action
call:调用异步方法,并且让它同步执行
fork: 调用异步方法,并且是异步执行 无阻塞
4 all 和fork相似 也是并发多个 action
select: 查询当前state的值
5 takeEvery和takeLatest
takeEvery和takeLatest用于监听相应的动作并执行相应的方法,是构建在take和fork上面的高阶api,比如要监听login动作,好用takeEvery方法可以:前者 监听action 每一次都触发 后者只触发最后一次
顾名思义takeEvery监听每一次对应action的派发,而takeLatest监听最后一次action的派发,并自动取消之前已经在启动且任在执行的任务。 这个和我们的防抖很类似。
function* increment() {
yield put({ type: ‘increment’ })
}
function* watchIncrement() {
yield takeLatest(‘increment_saga’, increment)
}
6 cancel 取消当前的生成器函数执行
一旦触发就会监听监听相应的动作并执行相应的方法
7 race 类似于promise
在React项目中,样式语言无论是用scss或less,如果想让 样式 仅作用在某个组件,而不影响全局,一般都会把样式文件进行模块化,即打包后每个class名都会被自动加上一串唯一的序列号。
产生局部作用域的唯一方法,就是使用一个独一无二的class的名字,不会与其他选择器重名。这就是 CSS Modules 的做法。
.main {
width: 100px;
:global {
.ant-popover-title{
color: red;
}
}
}
1>、混入(Mixins)——class中的class;
*** 1 定义一个class 在另一个class中调用
.alert {
font-weight: 700;
}
.highlight(@color: red) {
font-size: 1.2em;
color: @color;
}
.heads-up {
.alert;
.highlight(red);
}
2>、参数混入——可以传递参数的class,就像函数一样;
混合名(变量){规则集} .class (参数)
2.1> 映射
#colors() {
primary: blue;
secondary: green;
}
.button {
color: #colors[primary];
border: 1px solid #colors[secondary];
}
3>、嵌套规则——Class中嵌套class,从而减少重复的代码;
4>、运算——CSS中用上数学;
5>、颜色功能——可以编辑颜色;
6>、名字空间(namespace)——分组样式,从而可以被调用;
7>、作用域——局部修改样式; 因为是嵌套结构 变量类似于let
$color: black;
.scoped {
$bg: blue;
$color: white;
color: $color;
background-color:$bg;
}
.unscoped {
color:$color;
}
8>、JavaScript 赋值——在CSS中使用JavaScript表达式赋值。
@red: #c00;
strong {
color: @red;
}
作为选择器和属性名:@{selector的值}的形式
变量的延迟加载,及其作用域{}范围
@m:margin;
@selector:#wrap;
*{
@{m}: 0;
padding: 0;
background: red url(@url);
//也可以写为 background: red url(“@{url}”);
}
@{selector}{}
//演示变量的延迟加载,及其作用域{}范围
@var: 0;
.class {
@var: 1;
.brass {
@var: 2;
three: @var;//这个编译完为3
@var: 3;
}
one: @var;//这个编译完为1
}
8> 继承
.parentClass{
color:red;
}
.subClassOne{
&:extend(.parentClass);
}
.subClassTwo:extend(.parentClass){
}
//编译为
.parentClass,
.subClassOne,
.subClassTwo {
color: red;
}
简而言之,在TailwindCSS中,有许多小类代表CSS声明。因此,当您要创建组件时,则需要使用其中的一些小类来创建您要引用的组件。
<div className="md:mt-8 flex w-full flex-col items-center">
<ShoppingCartHeader subtotal={1639.97} save={290} />
</div>
断点前缀 最小宽度 CSS
sm 640px @media (min-width: 640px) { … }
md 768px @media (min-width: 768px) { … }
lg 1024px @media (min-width: 1024px) { … }
xl 1280px @media (min-width: 1280px) { … }
2xl 1536px @media (min-width: 1536px) { … }
悬浮 focus:outline-none hover:bg-purple-700
间距 自定义
className里面拼写各个小样式的名称即可
宽高为0 只设置border-width: 角头那一边设置为0
.border {
width: 0;
height: 0;
border-style: solid;
border-width: 0 50px 50px;
border-color: transparent transparent #d9534f;
// border-color: rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) #d9534f;
}
setState在合成事件和钩子函数中是异步的,在原生事件和setTime中都是同步的
setState的异步并不是说内部由异步代码实现的,其实本身执行的过程和代码都是同步的,只是合
成事件和钩子函数调用的顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的
值,形成了所谓的“异步”,当然也可以通过第二个参数setState(partialState,callback)中的callback
拿到更新后的结果。
单行文本溢出
text-overflow:规定当文本溢出时,显示省略符号来代表被修剪的文本
white-space:设置文字在一行显示,不能换行
overflow:文字长度超出限定宽度,则隐藏超出的内容
overflow设为hidden,普通情况用在块级元素的外层隐藏内部溢出元素,或者配合下面两个属性实现文本溢出省略
white-space:nowrap,作用是设置文本不换行,是overflow:hidden和text-overflow:ellipsis生效的基础
text-overflow属性值有如下:
clip:当对象内文本溢出部分裁切掉
ellipsis:当对象内文本溢出时显示省略标记(…)
多行文本溢出
基于高度截断
伪元素+固定高度 (此方法适用于所有浏览器 可与js配合)
<style>
.demo {
position: relative;
line-height: 18px;
height: 36px;
overflow: hidden;
word-break: break-all;
}
.demo::after {
content: "...";
font-weight: bold;
position: absolute;
bottom: 0;
right: 0;
padding: 0 20px 1px 45px;
/* 为了展示效果更好 省略号前 文字若隐若现 白标*/
background: -webkit-gradient(linear, left top, right top, from(rgba(255, 255, 255, 0)), to(white), color-stop(50%, white));
background: -moz-linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
background: -o-linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
background: -ms-linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
background: linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
}
</style>
<body>
<div class="demo">
床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光床前明月光
</div>
</body>
基于行数截断
浏览器私有前缀 类似于 display:flex 高度撑开 (兼容性问题 只适合 webkit内核浏览器)
-webkit-line-clamp: 2:用来限制在一个块元素显示的文本的行数,为了实现该效果,它需要组合其他的WebKit属性)
display: -webkit-box:和1结合使用,将对象作为弹性伸缩盒子模型显示
-webkit-box-orient: vertical:和1结合使用 ,设置或检索伸缩盒对象的子元素的排列方式
overflow: hidden:文本溢出限定的宽度就隐藏内容
text-overflow: ellipsis:多行文本的情况下,用省略号“…”隐藏溢出范围的文本
<style>
p {
width: 400px;
border-radius: 1px solid red;
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
<p>
这是一些文本这是一些文本这是一些文本这是一些文本这是一些文本
这是一些文本这是一些文本这是一些文本这是一些文本这是一些文本
</p >
遍历对象的每一个属性深度对比是非常浪费性能的
React 使用列表的key来进行对比,如果不指定,就默认为 index 下标
如果不加key 默认index 会发生一种情况 比如[A B ]添加C 会默认 C 代替A A代替B 然后新增B 消耗性能
直接加一个key 的话 就相当于执行一个unshift 如果是index 那没会有重新赋值的过程 消耗性能
1.key是虚拟dom对象的标识,在更新显示时起到重要的作用
2.当状态中的数据发生变化的时候,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则见3
3.当旧虚拟DOM找到了与新虚拟DOM相同的Key:发现虚拟DOM中是否发生改变,如果发生改变那生成新的真实DOM,随后替换旧的真实DOM,如果发现没有改变那就直接使用之前的真实DOM
旧虚拟DOM未找到与新虚拟DOM相同的key,根据数据创建新的真实DOM,随后渲染到页面当中
4.用index作为key可能会存在当数据量过大的时候重新去新旧DOM作比较的时候会多次重新生成真实DOM,造成不必要的DOM刷新问题,而且随着数据发生变化数据的索引值会发生变化让对应的DOM元素更新界面产生问题
5.对于key可以用每条数据的唯一标识,一般常用id作为唯一标识
如果直接用ref 去表示子组件:ref作为子组件的属性,获取的是该子组件
1 没有使用forwardRef时,父组件传入子组件ref属性,此时ref指向的是子组件本身
forwardRef,用于将 ref 转发 如转发给孙子
引用传递(Ref forwading)是一种通过组件向子组件自动传递 引用ref 的技术。对于应用者的大多数组件来说没什么作用。但是对于有些重复使用的组件,可能有用。例如某些input组件,需要控制其focus,本来是可以使用ref来控制,但是因为该input已被包裹在组件中,这时就需要使用Ref forward来透过组件获得该input的引用。
const Profile = forwardRef(function (props,ref){
return <h2 ref={ref}>Profile</h2>
})
原先表示子组件的ref 用来形容了子组件中的一个ref 被穿透了
PureComponent其实就是一个继承自Component的子类,会自动加载shouldComponentUpdate函数。当组件需要更新的时候,shouldComponentUpdate会对组件的props和state进行一次浅比较。如果props和state都没有发生变化,那么render方法也就不会出发,当然也就省去了之后的虚拟dom的生成和对比,在react性能方面得到了优化。
React提供了Refs帮助开发者可以直接操作DOM节点
1 回调函数
2 string 方法 直接是一个dom实例
3 createRef (可以获取元素实例 组件的话可以获取组件实例)
createRef 当ref属性用于普通 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性 每一次render都更新 如果在函数式 应该是null
createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用
类是组件 子组件的this 就是ref的真实属性 子传父即可 父收到值 创建一个ref接受 就可以控制
forwardRef可以操作函数式组件的dom
需要注意的是ref只会 标记对应的组件 比如connect(zujian ) 对应的是connect
export default connect(mapStateToProps,mapDispatchToProps,null,{forwardRef: true})(AiMap); 可以解决这个问题
hooks 中
返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialValue )。
返回的 ref 对象在组件的整个生命周期内保持不变
当更新 current 值时并不会 re-render ,这是与 useState 不同的地方 不会随组件更新而更新
更新 useRef 是 side effect (副作用),所以一般写在 useEffect 或 event handler 里
useRef 类似于类组件的 this
为了解决跨浏览器兼容性问题,React 会将浏览器原生事件(Browser Native Event)封装为合成事件(SyntheticEvent)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外有意思的是,React 并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理。这样 React 在更新 DOM 的时候就不需要考虑如何去处理附着在 DOM 上的事件监听器,最终达到优化性能的目的 。
createElement是创建一个React元素,jsx即是这个函数的语法糖;它的函数签命是React.createElement(type, [props], […children])
cloneElement,不同的是它传入的第一个参数element是一个 React 元素,而不是标签名或组件
cloneElement是拷贝一个React元素,可选择在修改它的props后,再返回一个新的React元素;它的函数签命是React.cloneElement(element, [props], […children]): ReactElement,这个函数常结合React.Children.map一起使用,修改props.children的props,来进行一些额外的操作,如下
ReactElement() 创建的是react 实例对象 该方法比较简单,就是初始化了一个对象,并将其标记为React.Element对象
Render Props 复用
给一个组件传入一个prop,这个props是一个函数,函数的作用是用来告诉这个组件需要渲染什么内容,那么这个prop就成为render prop。,能够封装固定的逻辑,动态地确定需要渲染的内容。
个人的理解就是在父组件用 写一个占位函数 这个函数给子组件 然后子组件渲染它 这样实现父组件通过自己控制子组件 实现逻辑复用
父 <A render={() => <h3>h3标签</h3>} />
子 {this.props.render(this.state.count)}
缺点 多级嵌套```
` <Fetch
url={`/api/${item}`}
render={data =>
<Fetch
url={`/api/${data}`}
render={data => <Entry data={data} />}
/>
}
/>`
hoc 同理 也可以用来复用 同时装饰
export default withFetch(“/api/fruits”)(App); 这个APP组件具有hoc的作用
高阶组件的不足
// 函数
const withMouse = (Component) => {
return class extends React.Component {
state = { x: 0, y: 0 }
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
<Component {...this.props} mouse={this.state}/>
</div>
)
}
}
}
// 组件
const App = (props) => {
const { x, y } = props.mouse
return (
<div style={{ height: '100%' }}>
<h1>The mouse position is ({x}, {y})</h1>
</div>
)
}
//通过函数,输出一个新的组件,即高阶组件HOC
const AppWithMouse = withMouse(App)
ReactDOM.render(<AppWithMouse/>, document.getElementById('root'))
HOC的约定
将不相关的 props传递给被包裹的组件(HOC应透传与自身无关的 props)
最大化可组合性
包装显示名称以便调试
缺点 父传子的 属性 可能被中间的hoc组件属性覆盖 父传hoc传子 上面的高阶组件,增强了 App 组件,让 App 组件可以通过 this.props.data 拿到请求来的数据。假设我们使用 App 时也可能给它传一个 data 属性
第三种hoos复用 见上
三者之间的差异
1、高阶组件可以做的事情,hooks 都可以去做,而且 hooks 的使用比高级组件更灵活也更方便,但是hooks 只能用在函数组件下
2、高阶组件或hook,通常用在单一的逻辑复用
比如实时获取当前滚动条位置,或定义state,副作用处理等,都是单一的逻辑
3、 render props 通常是一个完整的功能复用,只是该功能中视图或部分视图需要由使用者定义
比如,弹窗功能,路由功能 等
使用 XHR 发送一个 json 请求一般是这样:
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function() {
console.log(xhr.response);
};
xhr.onerror = function() {
console.log("Oops, error");
};
xhr.send();
fetch是一种XMLHttpRequest的一种替代方案,在工作当中除了用ajax获取后台数据外我们还可以使用fetch、axios来替代ajax
默认返回一个promise对象
fetch(url).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
}).catch(function(e) {
console.log("Oops, error");
});
prop-types的主要作用:对props中数据类型进行检测及限制
var 声明的变量存在提升;
var没有块级作用域,let和const存在块级作用域;
var允许重复声明,let和const在同一作用域不允许重复声明;
var和let声明变量可以修改,const是常量不能改变 引用类型可以修改。
var let const
函数作用域 块级作用域 块级作用域
变量提升 不存在变量提升 不存在变量提升
值可更改 值可更改 值不可更改
不建议用var
1 var 可以重复声明
2 没有块级作用域 循环外面也可以访问
3 由var定义的顶级变量会作为系统属性被自动添至全局变量,但由let定义的变量就不会。
const codeMessage: Record
200: ‘服务器成功返回请求的数据。’,
201: ‘新建或修改数据成功。’,
202: ‘一个请求已经进入后台排队(异步任务)。’,
204: ‘处理成功。’,
400: ‘发出的请求有错误,服务器没有进行新建或修改数据的操作。’,
401: ‘用户没有权限(令牌、用户名、密码错误)。’,
403: ‘用户得到授权,但是访问是被禁止的。’,
404: ‘发出的请求针对的是不存在的记录,服务器没有进行操作。’,
406: ‘请求的格式不可得。’,
410: ‘请求的资源被永久删除,且不会再得到的。’,
422: ‘当创建一个对象时,发生一个验证错误。’,
500: ‘服务器发生错误,请检查服务器。’,
502: ‘网关错误。’,
503: ‘服务不可用,服务器暂时过载或维护。’,
504: ‘网关超时。’,
301 moved permanently,永久性重定向,表示资源已被分配了新的 URL
302 found,临时性重定向,表示资源临时被分配了新的 URL
303 see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源
304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况
};
1 分布式架构、一体化架构和微服务架构
2 采用了哪些设计模式
3 项目部署前,团队之间是否互相review (代码评审)
1做一个适配ant pro-table 的二次拖拽式组件 改变width 时保存用户的个人信息
2 权限控制这一块参与的比较多
3 3Dthree 开发
1)1 初始化创建场景 const scene = new THREE.Scene(); 后面加的相机 立方体各种add进去
2) 2 设置渲染器的大小 创建坐标系
3) 3 设计相机的宽高比 角度 观察点 (可以控制 第一视觉 和第三视觉 已经可以拖动)
4) 4 环境光 地板和墙(require 可以设置本地图片纹理)
5) 5创建一个档案架 一般使用for循环
6 比如说 有2个 4层 2楼 xyz 密集架间距
(宽度/100- (隔板宽*每行几个+1 :隔板) )/每行几个 长宽高 同理
每层每列循环
每列放置
for (let j = 0; j < this.state.currentRoom.compactShelvesLayer; j += 1) {
// 每列
for (let i = 0; i < this.state.currentRoom.compactShelvesColumn; i += 1) {
if (geoMinBox) {
const newGeoMinBox = geoMinBox.clone();
newGeoMinBox.position.set(
x + 0.08 + i * (0.08 + minLatticeWidth),
y + 0.05 + j * (minLatticeHeight + 0.05),
0,
);
minLatticeArr.push(newGeoMinBox);
} else {
const [minLattice] = threeHelper.createBlankMesh(
new THREE.BoxGeometry(minLatticeWidth, minLatticeHeight, depth),
);
geoMinBox = minLattice;
minLattice.position.set(
x + 0.08 + i * (0.08 + minLatticeWidth),
y + 0.05 + j * (minLatticeHeight + 0.05),
0,
);
minLatticeArr.push(minLattice);
}
}
}
然后设置档案架中间的空隙
然后设置下材质颜色啊 什么的
把这个档案架添加到场景中
这个场景是用于放置3d物体,可以理解为一个世界。
x width,y height,z deep
(1) BoxGeometry是四边形的原始几何类
用于拍摄场景,并把相机拍摄的内容传输的渲染器
## 立方相机 6个面的相机 正交相机 远近一致
## 透视相机 由远到近 类似于人眼(开发使用)
### fov 视野角度 => 上下眼皮距离 扇形视野角度
### aspect 长宽比
### near 近端面
### far 远端面
## 立体相机 类似于 3D画面 2个透视相机
把相机传输的内容输出到页面,一般是在canvas中完成
x.render(Scene,Camera)
常见的材质类型
## 基础网格材质(MeshBasicMaterial)
1 对光照无反应,无法反射光照
## 基础线条材质(LineBasicMaterial)
一种用于绘制线框样式几何体的材质
## Lambert网格材质(MeshLambertMaterial)
一种非光泽表面的材质,没有镜面高光。
对光照有反应 必须要有光
木头或石头场景
## Phong网格材质(MeshPhongMaterial)
一种用于具有镜面高光的光泽表面的材质。
对光照有反应 必须要有光
常用于玻璃和金属
## 平行光(DirectionalLight)
平行光是沿着特定方向发射的光。这种光的表现像是无限远,从它发出的光线都是平行的
投射投影 模拟太阳光
## 点光源(PointLight)
从一个点向各个方向发射的光源。一个常见的例子是模拟一个灯泡发出的光。
投射投影 模拟灯光
## AmbientLight 环境光
环境光会均匀的照亮场景中的所有物体。
环境光不能用来投射阴影,因为它没有方向。
4 内存泄露排查
音视频流 WebRTC 连接 UDP连接
浏览器调用系统的摄像头 API getUserMedia 获得媒体流,注意要打开浏览器的摄像头限制
navigator.getUserMedia 第二个参数成功的回调 返回媒体流
1 MediaDevices.getUserMedia({video: true, audio: false },第二个参数成功的回调 返回媒体流)
2 startPeerConnection(媒体流) 建立传输视频数据所需要的 ICE 通信路径
dom = new RTCPeerConnection(config);
3 进行一些通信方法
4 通信通道建立后 ,把e.stream给video的url
git 方法
初始化仓库——git init
克隆现有的仓库 ——git clone
将在工作区更改的文件 提交到 暂存区 ——git add 文件名
将暂存区的内容,添加到当前分支。——add commit -m "内容的描述"了;
时刻掌握仓库当前的状态(比如改动那些文件,但是还没有提交add或者commit)——git status
用git status只能查看仓库的状态,但是查询不到具体修改了什么文件内容,需要用git diff这个命令查看相较于上一次暂存都修改了些什么内容了(是工作区与已经commit或者暂存区的区别。) ——git diff
撤销对工作区修改,工作区的修改会消失。——git restore – 文件名
用最近的git提交的版本,覆盖掉本地的工作区的修改。(git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”)—— git checkout – 文件名
清空add命令向暂存区提交的关于file文件的修改(Ustage),这个命令仅改变暂存区,并不改变工作区。——git reset HEAD–文件名
版本回退,用HEAD表示当前版本,上一个版本就是HEAD,上上一个版本就是HEAD,当然往上100个版本写100个比较容易数不过来,所以写成HEAD~100。 ——git reset --hard HEAD^
查看,当前分支的,所有的commit记录(从这个仓库创建起的所有commit记录)——git log
如果觉得git log 输出的信息很乱——git log --pretty=oneline
记录你的每一次命令(包括commit,切换分支等)——git reflog
git checkout 创建分支
0 git pull 或者 git fetch 然后手动选择合并 如git merge
1 git add .
2 git commit-m
3 git tag <标签名> 如果是定板的bug修改
4 git push
5 coding 远程代码合并 申请 coding自检代码 组长审核 审核通过自动化脚本合并
频繁切换 git stash --all 保存你的修改,或者通过 git commit -m a “WIP” 做一个临时提交
git worktreeadd…/test_demo2 test_feature 进行 跨分支开发 git work tree
项目 亮点
1websocket 心跳机制
1、心跳机制是每隔一段时间会向服务器发送一个数据包:
告诉服务器(后台)自己还活着,同时客户端(浏览器)会确认服务器端是否还活着
2、如果还活着的话,就会回传一个数据包给客户端
3、服务端断开连接了。客户端需要重连~
比如说
1 onopen 生命周期时只需心跳检测 heartCheck.start(); 一般来说是定时器每隔一段时间发送一个ping 消息 。然后在onmessage 中间听 拿到返回一个pong消息说明连接正常 此时重置心跳监听 心跳重置定时器设为null 然后另一个定时器 如果超过一定时间还没重置,说明后端主动断开了 这个时候close关闭
断线重连(我们测试的环境是断开网络连接),断开网络后,心跳包无法发送出去,所以如果当前时间距离上次成功心跳的时间超过20秒,说明连接已经出现问题了,此时需要关闭连接。
在close 中有webSocket.reconnect(); 定时器控制没重连一直重连
2 并发控制
//一般自己写一个Promise.all+队列思想 1 传入一串url数组,和允许的最大并发数,返回一个Promise,
// 这个Promise (有一个数组需要初始化队列请求,在此处限制队列长度<=maxNum,并且每个请求都会被放入队列中,比如说并发是3 那么现在这个数组长度是三,
// 初始化数组 可以用Array.form 第一个参数设置下长度 第二个参数设置数组的每一个元素是一个async awat方法 这个方法用while循环判断data长度 每一次通过data.pop()拿到最后一位的值
// 最终每一个数组的元素返回promise 异步请求放在这里 外面用pomise.all 把所有的promise放在一个数组里面 并发执行 得到结果后返回结果数组
图片上传 一般是一个 file对象 或者是多个file对象
Formdate 对象 formData.append(key,value)
转换成blob对象 new Blob([‘’],{type:‘text/html’})创建一个.html文件 保存在浏览器内存中
转换成file对象 new file([blob],‘name’,{type:‘text/html’})
3 大文件上传 分片上传
前段需要做的
1文件进行分片 首先需要判断后端确定字段 文件的唯一标识,分片索引,分片总数,文件名称
文件的唯一标识 一般是通过第第三方库md5摘要计算 总分片数=文件大小/每片大小 再向上取整
1 一般来说前端会定义好每一片的大小 然后计算下 // 需要上传多少次
const chunks = Math.ceil(file.size / chunkSize);
2 然后new Formdata 把后端要传的东西加进去 最关键就是每一段上传的file 一般有文件唯一的key值 还有就是当前第几个索引 ,每一个分片大小,总片数,总文件大小
3 每一片的file 计算一般是 把当前索引传过来 有一个start位置 索引*每一片大小的 一个end位置 是start+每一片索引大小 或者总的文件size
4最关键一步就是截取分片数据 file.slice(start,end) 把这个传给后端
5还有一个关键的点 check方法 每一次上传文件前 文件唯一值 去判断一个是第一次传还是已经传完了 还是知道他是哪一片 只要发现当前索引数小于总的索引 给他+1 继续递归
一般来说后端判断下 前端传过去的索引值是不是等于文件总的分片数
滑块验证
容器宽度 图片宽度
第一种是随机生成图片缺口 在滑块之内,容器之中
img.style.backgroundPosition =-left+‘px’
给滑块input事件 滑动条的滑动 就是他的一个偏移 偏移 和原来的偏移相减如果是5以内就是成功
假设我们有两个web服务器,我们需要使用keycloak来对我们的资源进行保护,只有用户登录以后才能访问到这两个服务器的资源,否则就要跳转到登录页面。所以我们要在两个服务之前加一个gateway层,在这一层对用户请求进行拦截,验证用户是否已经登录(了解OAuth2的话,就知道这里就是验证accessToken),如果没有的话,就要引导用户去到keycloak登录页面,认证以后再跳转回到要访问的页面。
SSO是为了解决一个用户在鉴权服务器登陆过一次以后,可以在任何应用中畅通无阻,一次登陆,多系统访问,操作用户是实打实的该应用的官方用户,用户的权限和分域以鉴权服务器的存储为准。比如说Cas 耶鲁大学
OAuth2.0 解决的是通过令牌获取某个系统的操作权限,因为有 clientId 的标识,一次登陆只能对该系统生效,第三方应用的操作用户不是鉴权系统的官方用户,授权权限鉴权中心可以做限制。
1 首先进入我们系统如果是登陆之后的 页面 一般会判断有没有token 没有的话去login 授权页获取
2 进入授权页 会判断 props.location 传过来的code授权码有没有 如果没有 返回重定向到一个指定的认证中心 并带上自己的url 作为回调地址
3 授权页 输入账号密码认证成功后 返回一个授权码code 根据回调的url 页面回去
4 我们这边根据传过来的code 去调用自己的接口 后端通过这个code 以及指定认证的url oauth 解析请求accessToken等 返回一个accessToken 给我 这个token 就可以获取各种保护资源。
URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。
几个G的文件
IndexedDB
类似于noSql 数据库 key-value存储
一般用第三方库进行封装 localforage
localforage.setItem(key,value) key 一般策略进行存储localforage.getItem
var store = localforage.createInstance({
name: “nameHere”
});
var otherStore = localforage.createInstance({
name: “otherName”
});
// Setting the key on one of these doesn’t affect the other.
store.setItem(“key”, “value”);
otherStore.setItem(“key”, “value2”);
//localforage.removeItem(‘somekey’).then(function() {
// Run this code once the key has been removed.
console.log(‘Key is cleared!’);
}).catch(function(err) {
// This code runs if there were any errors
console.log(err);
});
localforage.clear()
通过server worker 或者web worker 避免阻塞
注册 然后是安装 监听ajax fetch 等
this.addEventListener('install', function (event) {
console.log('install');
event.waitUntil(
caches.open('sw_demo').then(function (cache) {
return cache.addAll([
'/style.css',
'/panda.jpg',
'./main.js'
])
}
));
});
这种方法只能缓存指定的资源,无疑是不实用的,所以我们需要针对用户发起的每一个请求进行缓存。
缓存指定静态资源
web worker
主线程//主线程
var myWorker = new Worker(‘worker.js’, { name : ‘myWorker’});//Worker 线程
主线程调用worker.postMessage()方法,向 Worker 发消息。
主线程监听子线程的返回
worker.onmessage = function (event) {
console.log(‘Received message’ + event.data);
doSomething();
}
按钮权限控制
1 封装按钮button 支持 点击触发modal表单 删除验证,点击事件防抖,下拉按钮等(该组件connect 调用dva 获取 当前路由按钮数据state 与组件props传值比较)每次调用 判断传值btnJurisdiction 这是一个权限属性 有的话就是全局权限按钮 没有就是正常的button)
2 对于action的监听主要是路由跳转是调用接口 调用接口在dva中配置 通过 subscriptions 一个属性里面history.listen 订阅监听路由 然后dispath调用异步方法 。后端协助 路由跳转调用接口 返回对应菜单路由的配置按钮 更新store 然后转给封装的组件,如果传过来的权限属性 没有包括这个路由按钮那么就返回null 不显示)
先会有一个资源配置 给每一个路由配置开放性按钮,然后去每一个角色,配置权限,配置的时候一般是选择该角色的路由 然后每一个路由下面有选择选择 复选框tree。 (路由模块也是比较复杂)
4 菜单路由配置按钮时 按钮数据源来于前端代码存储 限制死 ant-d 自动完成输入下拉框 后端负责保存对应菜单 按钮配置 以及角色路由和按钮配置 形成一个闭环
路由权限控制
1 menu通过后端 请求返回该用户需要的菜单 树形结点类型的数据 生成菜单结构
2 地址栏输入 比较保存在dva全局中的菜单数据 使用history.block 拦截 类似于 路由拦截beforeeach
3 系统管理保存 用户路由权限的做法 一般是前后台一致 比如前端除了代码中的配置 路由path 名称自己输入 还要保存到后端。 UMI框架自带的路由一般是一种固定路由, 前端约束的路由path name 以及固定父子关系。 这里是改成了资源路由类型 用户纯输入路由的name 以及排序所在父级中文名。用户只需获取 config/routes 的name (写了个递归 判断了下权限路由 下面的路由push 到一个数组 然后递归下把每一个tree格式数组铺平) 传给后端数据其实还是一个path 一个name 一些其他的
4 配完之后在角色那里配置 传给后端 形成闭环
5 后面菜单跳转的时候 回来最开始 这里有一个主意的点 要自定义菜单点击方法 不能用他原来的方法 点击的的时候让他push 或者弄个link跳转
第一层 行业管理 设置多个行业 这个行业是固定。(高校,档案馆) 行业下面的一层是平台 可以理解为某一个具体的甲方 或者(甲方所具有的多功能系统) 在下面 注册的一些配置 比如系统网段局域网政务网 滑块登录 主题一些可以做的个性化需求之间的区分等 区分角色管理 配置权限(细化按钮 路由) 菜单 里面再配置(每一个菜单的按钮权限 ) 用户这边组织机构区分
面试遇到的
setState作为react中的重要部分,将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。
setState通过一个队列机制实现state更新。当执行setState时,会将需要更新的state合并后放入状态对列,而不会立刻更新this.state,队列机制可以高效地批量更新state。如果不通过setState而直接修改this.state的值,那么该state将不会被放入状态队列中,当下次调用setState 并对状态队列进行合并时,将会忽略之前直接被修改的 state,而造成无法预知的错误。
更新队列的同时需要 shouldComponentUpdate 的状态来判断是否需要更新组件
在js引擎中对变量的存储主要有两种位置,堆内存和栈内存。
和java中对内存的处理类似,栈内存主要用于存储各种基本类型的变量,包括Boolean、Number、String、Undefined、Null,**以及对象变量的指针,前端培训这时候栈内存给人的感觉就像一个线性排列的空间,每个小单元大小基本相等。
基础数据类型 存在栈中 引用数据类型存储在 堆中 引用数据类型的指针存放在栈中
在数据结构中,栈是一种可以实现“先进后出”(或者称为“后进先出”)的存储结构。假设给定栈 S=(a0,a1,…,an-1),则称 a0 为栈底,an-1 为栈顶。进栈则按照 a0,a1,…,an-1 的顺序进行进栈;而出栈的顺序则需要反过来,按照“后存放的先取,先存放的后取”的原则进行,则 an-1 先退出栈,然后 an-2 才能够退出,最后再退出 a0。
堆则是一种经过排序的树形数据结构,“先进先出” 常用来实现优先队列等。假设有一个集合 K={k0,k1,…,kn-1},把它的所有元素按完全二叉树的顺序存放在一个数组中,并且满足:
则称这个集合 K 为最小堆(或者最大堆)。
堆是一种特殊的完全二叉树。其中,节点是从左到右填满的,并且最后一层的树叶都在最左边(即如果一个节点没有左儿子,那么它一定没有右儿子);每个节点的值都小于(或者都大于)其子节点的值。
1、栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放;
2、堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
*GC回收的时候 因为频繁分配和释放(malloc / free)不同大小的堆空间势必会造成内存空间的不连续,从而造成大量碎片,导致程序效率降低;而对栈来讲,则不会存在这个问题。所以在回收可访问对象后需要 是释放在这些碎片内存
堆:内存中,存储的是引用数据类型,引用数据类型无法确定大小,堆实际上是一个在内存中使用到内存中零散空间的链表结构的存储空间,堆的大小由引用类型的大小直接决定,引用类型的大小的变化直接影响到堆的变化
栈:是内存中存储值类型的,大小为2M,超出则会报错,内存溢出
不管是在 updateFunctionComponent 还是 updateClassComponent 中,最后都会调用 reconcileChildren 调和子树。
React Diff 的入口函数为reconcileChildren,reconcileChildren内部会通过 current === null 区分当前 fiber 节点是 mount 还是 update,再分别执行不同的工作:
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes
) {
if (current === null) {
// 对于mount的组件
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
// 对于update的组件
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
);
}
}
首次渲染调用的是 mountChildFibers,之后就是 reconcileChildFibers,其实这两个方法是一样的,唯一的区别是生成这个方法的时候的一个参数不同。这个参数主要用于初次渲染时候的优化。 。
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);
ChildReconciler 方法很长,其中有一些内部方法。这些内部方法依序分析。
function ChildReconciler(shouldTrackSideEffects) {
...
return reconcileChildFibers;
}
ChildReconciler 方法返回的是内部方法 reconcileChildFibers。该方法第一个入参是 workInProgress,第二个是当前 Fiber 节点的第一个子节点,第三个参数是计算得到的 children。第二个参数在首次渲染是为 null,因为首次渲染的时候 Fiber 都没有建立子节点。
reconcileChildFibers 根据新节点的不同类型,进行不同的处理。
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
// 判断顶层元素是否是 REACT_FRAGMENT_TYPE,并且没有 key
// 就是判断 用的
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
// type 为REACT_FRAGMENT_TYPE是不需要任何更新的,直接渲染子节点即可
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
// Handle object types
// newChild 是否是对象
const isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
switch (newChild.$$typeof) {
// 正常创建的 ReactElement
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
// 通过 ReactDOM.createPortal 创建的
// 模态框就是这么实现的
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
}
}
// 是个文本节点
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
expirationTime,
),
);
}
// 是个数组
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
// 不是数组,但是可遍历
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
if (isObject) {
throwOnInvalidObjectType(returnFiber, newChild);
}
// 这些都是错误处理,忽略
if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
// If the new child is undefined, and the return fiber is a composite
// component, throw an error. If Fiber return types are disabled,
// we already threw above.
switch (returnFiber.tag) {
case ClassComponent: {
}
// Intentionally fall through to the next case, which handles both
// functions and classes
// eslint-disable-next-lined no-fallthrough
case FunctionComponent: {
const Component = returnFiber.type;
invariant(
false,
'%s(...): Nothing was returned from render. This usually means a ' +
'return statement is missing. Or, to render nothing, ' +
'return null.',
Component.displayName || Component.name || 'Component',
);
}
}
}
// Remaining cases are all treated as empty.
// 删除旧节点的内容
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
placeSingleChild 判断是否是第一次渲染,如果是的话增加Placement副作用,后期需要挂载 DOM
function placeSingleChild(newFiber: Fiber): Fiber {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.effectTag = Placement
}
return newFiber
}
来了单节点
对于正常的 ReactElement,调用 reconcileSingleElement 方法。
第一个while循环的目的明显就是找到老的children和新的children中第一个key和节点类型相同的节点,直接复用这个节点,然后删除老的children中其他的(我们无法保证新的children是单个节点的时候老的children也是单个的,所以要用遍历。)
注意key为null我们也认为是相等,因为单个节点没有key也是正常的。
如果找了一圈没发现,那么就把老的children都删了,重新为新的children创建节点。
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
// 新节点的 key
const key = element.key;
// 老的第一个子节点
let child = currentFirstChild;
// 遍历老节点,通过 child = child.sibling 遍历
// 从当前已有的所有子节点中,找到可以复用的 fiber 对象,并删除它的 兄弟节点
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
// 存在老的 child key 和新的 key 相同
if (child.key === key) {
// 如果节点类型未改变的话
if (
child.tag === Fragment
? element.type === REACT_FRAGMENT_TYPE
: child.elementType === element.type ||
// Keep this check inline so it only runs on the false path:
(__DEV__
? isCompatibleFamilyForHotReloading(child, element)
: false)
) {
// 复用 child,删除它的兄弟节点
// 因为旧节点它有兄弟节点,新节点只有它一个
deleteRemainingChildren(returnFiber, child.sibling);
// 复制 fiber 节点,并重置 index 和 sibling
// existing就是复用的节点
const existing = useFiber(
child,
element.type === REACT_FRAGMENT_TYPE
? element.props.children
: element.props,
expirationTime,
);
// coerceRef 的作用是把规范化 ref,因为 ref 有三种形式,string ref 要转换成方法。
existing.ref = coerceRef(returnFiber, child, element);
// 设置父节点
existing.return = returnFiber;
// 达成复用,直接返回了
return existing;
} else {
// 类型不同,删除旧节点
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
// key 都不相同,直接删
deleteChild(returnFiber, child);
}
child = child.sibling;
}
// 执行到这里,child 一定是 null,那就是要自己建立子节点
if (element.type === REACT_FRAGMENT_TYPE) {
// 创建Fragment类型的 fiber 节点
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
expirationTime,
element.key,
);
created.return = returnFiber;
return created;
} else {
// 创建 Element 类型的 fiber 节点
const created = createFiberFromElement(
element,
returnFiber.mode,
expirationTime,
);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
}
}
deleteRemainingChildren 循环调用了 deleteChild。deleteChild 用于删除单个节点,其实是为要删除的子节点们做 Deletion 标记,用于在 commit 阶段正式删。
ed until the complete phase. Once we implement
// resuming, this may not be true.
const last = returnFiber.lastEffect;
// 将节点挂载到父节点的 Effect 链上
if (last !== null) {
last.nextEffect = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
// 当前节点添加 Deletion 标记,以便在 commit 阶段进行删除
childToDelete.nextEffect = null;
childToDelete.effectTag = Deletion;
}
useFiber 用于复用旧的 fiber 节点,重置了 index 和 sibling。怎么实现复用的呢,从 createWorkInProgress 可以看出,React 并没有直接将旧的 Fiber 节点返回给新的,因为此时旧的 Fiber 节点已经完成了渲染,对其进行直接操作会很危险,所以 useFiber 复用的节点其实是 alternate,对 alternate 的任何更新不会影响当前 Fiber, 而且 alternate 和 current 是互相引用的,互相作为对方的 alternate,能够一致被复用,效率很高,这个策略叫做 double buffer
React 会准备 fiber 树的两个版本(新版本和旧版本),当新版本的某一新节点在旧版本上有时,可以复用旧 fiber 的属性,而不是重新创建新的节点。
新旧 fiber 树相互复用的思路来源于doubleBuffer。
双缓冲机制
双缓存机制是一种在内存中构建并直接替换的技术。协调的过程中就使用了这种技术。
在React中同时存在着两棵fiber tree。一棵是当前在屏幕上显示的dom对应的fiber tree,称为current fiber tree,而另一棵是当触发新的更新任务时,React在内存中构建的fiber tree,称为workInProgress fiber tree。
current fiber tree和workInProgress fiber tree中的fiber节点通过alternate属性进行连接。
currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;
React应用的根节点中也存在current属性,利用current属性在不同fiber tree的根节点之间进行切换的操作,就能够完成current fiber tree与workInProgress fiber tree之间的切换。
function useFiber(
fiber: Fiber,
pendingProps: mixed,
expirationTime: ExpirationTime,
): Fiber {
// We currently set sibling to null and index to 0 here because it is easy
// to forget to do before returning it. E.g. for the single child case.
const clone = createWorkInProgress(fiber, pendingProps, expirationTime);
clone.index = 0;
clone.sibling = null;
return clone;
}
export function createWorkInProgress(
current: Fiber,
pendingProps: any,
expirationTime: ExpirationTime,
): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
// We already have an alternate.
// Reset the effect tag.
workInProgress.effectTag = NoEffect;
// The effect list is no longer valid.
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
workInProgress.childExpirationTime = current.childExpirationTime;
workInProgress.expirationTime = current.expirationTime;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
// Clone the dependencies object. This is mutated during the render phase, so
// it cannot be shared with the current fiber.
const currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
expirationTime: currentDependencies.expirationTime,
firstContext: currentDependencies.firstContext,
responders: currentDependencies.responders,
};
// These will be overridden during the parent's reconciliation
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
return workInProgress;
}
当节点无法复用的时候,调用 createFiberFromElement 方法创建新的 Fibre 节点,该方法根据节点的类型不同,创建不同类型的 Fiber
源码 略
如果新节点是个字符串或是数字,调用 reconcileSingleTextNode 方法。文字节点的对比比较简单粗暴,直接找老的children中的第一个节点,如果是文字节点就复用,如果不是就删除全部老的节点,创建新的文字节点。
reconcileChildren 遇到新的子节点是 array 时,调用 reconcileChildrenArray 方法。reconcileChildrenArray 中比较了新旧 Fiber 节点是否能够复用。主要关注 React 是如何尽量少的遍历节点的。
多节点
1 首先进行一轮 for 循环,同时遍历新旧节点
如果 key 都相同,则需要判断新的元素优先遍历完成,还是旧的 fiber 节点优先遍历完成
如果 key 不同,则提前结束当前 for 循环,将剩下的 fiber 节点存入 map 中,继续遍历剩下的新的 element,从 map 中查找是否能复用
reconcileChildrenArray 比较数组的方法很简单,首先逐个对比(相同位置对比)两个数组,如果相等则继续,如果有任何一个不等,那么跳出循环。如果老的数组全部被复用,那么补齐新数组,如果新数组已经完成,那么删除老数组中多余的部分。如果新数组没有完全生成,老数组也没有完全复用,那么创建一个 map,用于存放未被复用的老数组,然后遍历剩余的新数组,检查是否能从老数组中得到可复用的部分,有则复用,没有则新建。最后老的没有匹配到的都要删除。
为了更简单的理解这个过程,我画了个例子。0_01 中,0 表示老数组,01 表示这个对象的 key。最初有 01 02 03 04 这 4 个 fiber 对象,组成老数组,发生变化后,新的数组的变成饿了 01 02 04,也就是删除了 03。注意 1_01 中 前半部分的 1 代表新数组,后半部分代表 key。前缀 2 表示最终返回的数组。
我在例子中使用了相同的方格来示新旧的数组,其实它们是完全不一样的,老的 children 是 Fiber 对象组成的链,新的childern 是React Element组成的 array。reconcileChildrenArray 目的就是复用老的 children 的 Fiber 链。但是,新的 Fiber 链不是直接复用老的 Fiber 链,而是复用了 fiber.alternate。这种双缓存策略使得 fiber 对象可以一直被交替使用。
双缓冲机制
双缓存机制是一种在内存中构建并直接替换的技术。协调的过程中就使用了这种技术。
在React中同时存在着两棵fiber tree。一棵是当前在屏幕上显示的dom对应的fiber tree,称为current fiber tree,而另一棵是当触发新的更新任务时,React在内存中构建的fiber tree,称为workInProgress fiber tree。
current fiber tree和workInProgress fiber tree中的fiber节点通过alternate属性进行连接。
currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;
React应用的根节点中也存在current属性,利用current属性在不同fiber tree的根节点之间进行切换的操作,就能够完成current fiber tree与workInProgress fiber tree之间的切换。
在协调阶段,React利用diff算法,将产生update的React element与current fiber tree中对应的节点进行比较,并最终在内存中生成workInProgress fiber tree。随后Renderer会依据workInProgress fiber tree将update渲染到页面上。同时根节点的current属性会指向workInProgress fiber tree,此时workInProgress fiber tree就变为current fiber tree。
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
var oldFiber = currentFirstChild;
var newIdx = 0;
// 第一步:for循环同时遍历新旧节点
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
// 如果key不同,则直接结束当前for循环
if (oldFiber.key !== newChildren[newIdx].key) {
break;
}
oldFiber = oldFiber.sibling;
}
// 第二步:判断新的element是否已经遍历完成
if (newIdx === newChildren.length) {
// 如果新的element优先遍历完成,则将剩下的旧的fiber节点全部删除
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild; // dom diff结束
}
// 第三步:判断旧的fiber节点是否已经遍历完成
if (oldFiber === null) {
// 如果旧的fiber节点优先遍历完成,则遍历剩下的新的element元素,并创建新的fiber节点
for (; newIdx < newChildren.length; newIdx++) {
var _newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
}
return resultingFirstChild; // dom diff 结束
}
// 程序执行到这里,说明新旧节点都还没遍历完成,并且存在至少一个节点key不同的场景
// 第四步:将剩下的旧的fiber节点存到map中,使用key做为键,如果key不存在,则使用oldFiber.index做为键
var existingChildren = mapRemainingChildren(returnFiber, oldFiber);
for (; newIdx < newChildren.length; newIdx++) {
// 从map中查找是否能复用旧的fiber节点
var _newFiber2 = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx]
);
if (_newFiber2.alternate) {
// 如果可以复用,则将旧的fiber从existingChildren中删除
existingChildren.delete(
_newFiber2.key === null ? newIdx : _newFiber2.key
);
}
}
// 第五步:最后,将existingChildren中的节点全部标记为删除并添加到父节点的副作用链表中
existingChildren.forEach(function (child) {
return deleteChild(returnFiber, child);
});
return resultingFirstChild;
}
什么是Fiber
在 React 中,Fiber 就是 React 16 实现的一套新的更新机制,让 React 的更新过程变得可控,避免了之前采用递归需要一气呵成影响性能的做法。
1.React Fiber 中的时间分片
把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。
React Fiber 把更新过程碎片化,每执行完一段更新过程,就把控制权交还给 React 负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务;
现在 部分浏览器 已经实现了对应的API ——requestIdleCallback
页面是一帧一帧绘制出来的,当每秒绘制的帧数(FPS)达到 60 时,页面是流畅的,小于这个值时,用户会感觉到卡顿。
1s 60 帧,所以每一帧分到的时间是 1000/60 ≈ 16 ms。所以我们书写代码时力求不让一帧的工作量超过 16ms。
requestIdelCallbackAPI。对于不支持这个API 的浏览器,React 会加上 pollyfill。
在上面我们已经知道浏览器是一帧一帧执行的,在两个执行帧之间,主线程通常会有一小段空闲时间,requestIdleCallback可以在这个空闲期(Idle Period)调用空闲期回调(Idle Callback),执行一些任务。
低优先级任务由requestIdleCallback处理;
高优先级任务,如动画相关的由requestAnimationFrame处理;
requestIdleCallback可以在多个空闲期调用空闲期回调,执行任务;
requestIdleCallback方法提供 deadline,即任务执行限制时间,以切分任务,避免长时间执行,阻塞UI渲染而导致掉帧;
***这个方案看似确实不错,但是怎么实现可能会遇到几个问题:
如何拆分成子任务?
一个子任务多大合适?
怎么判断是否还有剩余时间?
有剩余时间怎么去调度应该执行哪一个任务?
没有剩余时间之前的任务怎么办?
为了做到这些,我们首先需要一种方法将任务分解为单元。从某种意义上说,这就是 Fiber,Fiber 代表一种工作单元。
但是仅仅是分解为单元也无法做到中断任务,因为函数调用栈就是这样,每个函数为一个工作,每个工作被称为堆栈帧,它会一直工作,直到堆栈为空,无法中断。(执行栈会一直执行任务 直到任务完成 不可中断)
所以我们需要一种增量渲染的调度,那么就需要重新实现一个堆栈帧的调度,这个堆栈帧可以按照自己的调度算法执行他们。另外由于这些堆栈是可以自己控制的,所以可以加入并发或者错误边界等功能。
因此 Fiber 就是重新实现的堆栈帧,本质上 Fiber 也可以理解为是一个虚拟的堆栈帧,将可中断的任务拆分成多个子任务,通过按照优先级来自由调度子任务,分段更新,从而将之前的同步渲染改为异步渲染。
所以我们可以说 Fiber 是一种数据结构(堆栈帧),也可以说是一种解决可中断的调用任务的一种解决方案,它的特性就是时间分片(time slicing)和暂停(supense)。
Fiber 是如何工作的
ReactDOM.render() 和 setState 的时候开始创建更新。
将创建的更新加入任务队列,等待调度。
在 requestIdleCallback 空闲时执行任务。
从根节点开始遍历 Fiber Node,并且构建 WokeInProgress Tree。
生成 effectList。
根据 EffectList 更新 DOM。
下面是一个详细的执行过程图:
第一部分从 ReactDOM.render() 方法开始,把接收的 React Element 转换为 Fiber 节点,并为其设置优先级,创建 Update,加入到更新队列,这部分主要是做一些初始数据的准备。
第二部分主要是三个函数:scheduleWork、requestWork、performWork,即安排工作、申请工作、正式工作三部曲,React 16 新增的异步调用的功能则在这部分实现,这部分就是 Schedule 阶段,前面介绍的 Cooperative Scheduling 就是在这个阶段,只有在这个解决获取到可执行的时间片,第三部分才会继续执行。具体是如何调度的,后面文章再介绍,这是 React 调度的关键过程。
第三部分是一个大循环,遍历所有的 Fiber 节点,通过 Diff 算法计算所有更新工作,产出 EffectList 给到 commit 阶段使用,这部分的核心是 beginWork 函数,这部分基本就是 Fiber Reconciler ,包括 reconciliation 和 commit 阶段。
在第二部分,进行 Schedule 完,获取到时间片之后,就开始进行 reconcile。
Fiber Reconciler 是 React 里的调和器,这也是任务调度完成之后,如何去执行每个任务,如何去更新每一个节点的过程,对应上面的第三部分。
reconcile 过程分为2个阶段(phase):
(可中断)render/reconciliation 通过构造 WorkInProgress Tree 得出 Change。
(不可中断)commit 应用这些DOM change。
reconciliation 阶段
在 reconciliation 阶段的每个工作循环中,每次处理一个 Fiber,处理完可以中断/挂起整个工作循环。通过每个节点更新结束时向上归并 Effect List 来收集任务结果,reconciliation 结束后,根节点的 Effect List里记录了包括 DOM change 在内的所有 Side Effect。
render 阶段可以理解为就是 Diff 的过程,得出 Change(Effect List),会执行声明如下的声明周期方法:
[UNSAFE_]componentWillMount(弃用)
[UNSAFE_]componentWillReceiveProps(弃用)
getDerivedStateFromProps
shouldComponentUpdate
[UNSAFE_]componentWillUpdate(弃用)
render
由于 reconciliation 阶段是可中断的,一旦中断之后恢复的时候又会重新执行,所以很可能 reconciliation 阶段的生命周期方法会被多次调用,所以在 reconciliation 阶段的生命周期的方法是不稳定的,我想这也是 React 为什么要废弃 componentWillMount 和 componentWillReceiveProps方法而改为静态方法 getDerivedStateFromProps 的原因吧。
commit 阶段
commit 阶段可以理解为就是将 Diff 的结果反映到真实 DOM 的过程。
在 commit 阶段,在 commitRoot 里会根据 effect的 effectTag,具体 effectTag 见源码 ,进行对应的插入、更新、删除操作,根据 tag 不同,调用不同的更新方法。
commit 阶段会执行如下的声明周期方法:
getSnapshotBeforeUpdate
componentDidMount
componentDidUpdate
componentWillUnmount
P.S:注意区别 reconciler、reconcile 和 reconciliation,reconciler 是调和器,是一个名词,可以说是 React 工作的一个模块,协调模块;reconcile 是调和器调和的动作,是一个动词;而 reconciliation 只是 reconcile 过程的第一个阶段。
Fiber Tree 和 WorkInProgress Tree
React 在 render 第一次渲染时,会通过 React.createElement 创建一颗 Element 树,可以称之为 Virtual DOM Tree,由于要记录上下文信息,加入了 Fiber,每一个 Element 会对应一个 Fiber Node,将 Fiber Node 链接起来的结构成为 Fiber Tree。它反映了用于渲染 UI 的应用程序的状态。这棵树通常被称为 current 树(当前树,记录当前页面的状态)。
在后续的更新过程中(setState),每次重新渲染都会重新创建 Element, 但是 Fiber 不会,Fiber 只会使用对应的 Element 中的数据来更新自己必要的属性,
Fiber Tree 一个重要的特点是链表结构,将递归遍历编程循环遍历,然后配合 requestIdleCallback API, 实现任务拆分、中断与恢复。
这个链接的结构是怎么构成的呢,这就要主要到之前 Fiber Node 的节点的这几个字段:
// 单链表树结构
{
return: Fiber | null, // 指向父节点
child: Fiber | null,// 指向自己的第一个子节点
sibling: Fiber | null,// 指向自己的兄弟结构,兄弟节点的return指向同一个父节点
}
每一个 Fiber Node 节点与 Virtual Dom 一一对应,所有 Fiber Node 连接起来形成 Fiber tree, 是个单链表树结构,如下图所示:
对照图来看,是不是可以知道 Fiber Node 是如何联系起来的呢,Fiber Tree 就是这样一个单链表。
当 render 的时候有了这么一条单链表,当调用 setState 的时候又是如何 Diff 得到 change 的呢?
采用的是一种叫双缓冲技术(double buffering),这个时候就需要另外一颗树:WorkInProgress Tree,它反映了要刷新到屏幕的未来状态。
WorkInProgress Tree 构造完毕,得到的就是新的 Fiber Tree,然后喜新厌旧(把 current 指针指向WorkInProgress Tree,丢掉旧的 Fiber Tree)就好了。
这样做的好处:
能够复用内部对象(fiber)
节省内存分配、GC的时间开销
就算运行中有错误,也不会影响 View 上的数据
每个 Fiber上都有个alternate属性,也指向一个 Fiber,创建 WorkInProgress 节点时优先取alternate,没有的话就创建一个。
创建 WorkInProgress Tree 的过程也是一个 Diff 的过程,Diff 完成之后会生成一个 Effect List,这个 Effect List 就是最终 Commit 阶段用来处理副作用的阶段。
:Fiber的执行流程
用户操作引起setState被调用以后,先调用enqueueSetState方法,该方法可以划分成俩个阶段(个人理解),第一阶段Data Preparation,是初始化一些数据结构,比如fiber,updateQueue,update。
新的update会通过insertUpdateIntoQueue方法,根据优先级插入到队列的对应位置,ensureUpdateQueues方法初始化俩个更新队列,queue1和current.updateQueue对应,queue2和current.alternate.updateQueue对应。
第二阶段,Fiber Reconciler,就开始进行任务分片调度,scheduleWork首先更新每个fiber的优先级,这里并没有updatePriority这个方法,但是干了这件事。当fiber.return === null,找到父节点,把所有diff出的变化(side effect)归结到root上。
requestWork,首先把当前的更新添加到schedule list中(addRootToSchedule),然后根据当前是否为异步渲染(isAsync参数),异步渲染调用。scheduleCallbackWithExpriation方法,下一步高能!!
scheduleCallbackWithExpriation这个方法在不同环境,实现不一样,chrome等浏览器中使用requestIdleCallback API,没有这个API的浏览器中,通过requestAnimationFrame模拟一个requestIdCallback,来在浏览器空闲时,完成下一个分片的工作,注意,这个函数会传入一个expirationTime,超过这个时间活没干完,就放弃了。
执行到performWorkOnRoot,就是fiber文档中提到的Commit Phase和Reconciliation Phase俩阶段。
第一阶段Reconciliation Phase,在workLoop中,通过一个while循环,完成每个分片任务。
performUnitOfWork也可以分成俩阶段,蓝色框表示。beginWork是一个入口函数,根据workInProgress的类型去实例化不同的react element class。workInProgress是通过alternate挂载一些新属性获得的。
实例化不同的react element class时候会调用和will有关的生命周期方法。
completeUnitOfWork是进行一些收尾工作,diff完一个节点以后,更新props和调用生命周期方法等。
然后进入Commit Phase阶段,这个阶段不能被打断。
————————————————
版权声明:本文为CSDN博主「吴迪98」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43606158/article/details/89425297
//from 1 usefrom 2 form onfinsih
什么是fiber
那么如何理解react中的fiber呢,两个层面来解释:
从运行机制上来解释,fiber是一种流程让出机制,它能让react中的同步渲染进行中断,并将渲染的控制权让回浏览器,从而达到不阻塞浏览器渲染的目的。
从数据角度来解释,fiber能细化成一种数据结构,或者一个执行单元。,每一个被创建的虚拟dom都会被包装成一个fiber节点,它具备如下结构:
我们可以结合这两点来理解,react会在跑完一个执行单元后检测自己还剩多少时间(这个所剩时间下文会解释),如果还有时间就继续运行,反之就终止任务并记录任务,同时将控制权还给浏览器,直到下次浏览器自身工作做完,又有了空闲时间,便再将控制权交给react,以此反复。
const fiber = {
stateNode,// dom节点实例
child,// 当前节点所关联的子节点
sibling,// 当前节点所关联的兄弟节点
return// 当前节点所关联的父节点
}
()()()()1 任务调度 scheduleWork
React 创建了update,并且将 update 放入 updateQueue 中,接下来就是任务调度的过程。任务调度的起点是 scheduleWork 方法
一般先要进行调度的检查防止出现死循环。这个其实很好理解,就是你在render中进行setstate操作的时候 会有Maximum update depth exceeded报错
中断
scheduleWork 更新 安排工作
在此时他会比较fiber的expirationTime要小于当前的expirationTime,说明它的优先级要比当前的低,此时提高优先级(把当前过期时间变成fiber的过期时间)。向上遍历去更新expirationTime。
当新的scheduleCallback的优先级更高时,中断当前任务cancelCallback(existingCallbackNode)、
requestIdleCallback这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,
此外checkForInterruption如果当前fiber的优先级更高,需要打断当前执行的任务,立即执行该fiber上的update,则更新interruptedBy。enableUserTimingAPI 这个变量在生产环境下的值是false,所以这个函数其实是为开发方便准备的,没有太大意义。。
()()
同步模式 => 是否是初次挂载 => 是 => 直接调用renderRoot渲染更新
同步模式 => 是否是初次挂载 => 否 => 调用scheduleCallbackForRoot进行callback调度
异步模式 => 获取优先级 => 调用scheduleCallbackForRoot进行callback调度
一次更新的产生,并不只影响当前 Fibre 对象,而是会影响到所有上层的 Fiber 节点,修改了当前 Fiber 对象的 expirationTime,修改了 Fiber 父节点 的 childExpirationTime。 2. 首次渲染,React 不会做任何调度,直接 renderRoot,因为首次渲染是没有调度的必要的。
在每处理完成一个 Fiber 节点时,会检查时间片是否到时,如果到了,则会中断此次 “渲染”
等到下一次进来的时候,可以直接从 workInProgress 上次中断的 Fiber 节点开始处理即可
fiber在渲染中每次都会经历协调Reconciliation与提交Commit两个阶段。
协调阶段:这个阶段做的事情很多,比如fiber的创建diff对比等等都在这个阶段。在对比完成之后即等待下次提交,需要注意的是这个阶段可以被暂停。
提交阶段:将协调阶段计算出来的变更一次性提交,此阶段同步进行且不可中断(优先保证渲染)。
那么接下来我将从源码角度,给大家展示下react是如何创建fiber节点,Reconciliation(diff)是如何对比,以及前文提到的剩余时间是如何运转的。
React 做任务调度的时候,如果任务没有延期,会调用 requestHostCallback(flushWork) 方法来执行任务。
React是怎么知道浏览器会有空闲时间呢?***
1在umi中 的plugin 中有一个 usemodal 类似于hook
方法一 app.ts文件中 定义他的initialState 后续使用是const { initialState, setInitialState } = useModel(‘@@initialState’);
此外就是自定义usemodal
如定义一个
const { isShowLeft, setIsShowLeft } = useModel(‘publicModel’, (ret) => ({
isShowLeft: ret.isShowLeft,
setIsShowLeft: ret.setIsShowLeft,
}));
微信小程序 登录授权
1、当用户进入微信小程序时,首先我们先判断用户是否授权过此小程序 wx.getSetting( sucsess:res=>{//调用成功的回调函数 res.authSetting[‘scope.userInfo’]
2、如果没有授权,我们通过一个按钮来实现授权登录 授权
3、通过bindgetuserinfo事件,我们可以获取到个人的信息、加密偏移数据、加密用户信息(e.detail获取)
getuserinfo(e){
console.log(e.detail);//可以获取到个人的信息、加密偏移数据、加密用户信息
}
4.用户可以授权登录,也可以取消授权
wx.login({//登录
success: (res) => {//成功的回调
// console.log(res.code);//获取临时登录凭证code
if (res.code) {//当有临时登录凭证code码时,我们请求登录接口
//请求登录接口
}
}
})
5、根据登录接口返回的code码,判断用户是否时新用户
通过这个code 去调用自己的登录 如果说没有用户那么 会去注册 注册需要userinfo里面的
encryptedData: user.encryptedData,
iv: user.iv,
code: res.code 一般来讲成功之后会返回token 保存在wx.setStorage(里面)
6、当用户注册成功后,在调登录接口,保存token。在有些页面需要使用token
wx.setStorage({//保存token
data: token,
key: ‘token’
})
7、在步骤1中,当我们授权过时,我们要看token是否存在
8、当token存在时,我们直接执行逻辑代码
9、当token不存在时,我们就需要登录,登录后判断返回的code码,在根据code码判断用户是否是新用户。最后保存token
h5
1需要公众号配置Oauth 网页授权之后回调的页面
页面授权
页面重定向去引导开发者打开 微信的授权oauth2 页面 一般来讲是一串url 后面带了code自己的重定向页面
回到我们页面之后 比如说通过props.location.query 他的code授权码
这个授权码有很多操作 比如可以掉我们自己的一个登录接口 后端返回用户信息以及access_token在将这个token配到接口请求体里面