js基础
数据类型5+1
基本类型:number
、string
、undefined
、boolean
、null
;
引用类型:Object
;
基本类型添加了两个新成员:symbol
、BigInt
;
基本类型和引用类型区别:前者按值访问,因此为我们操作的就是存储在变量中的实际值;后者不能直接操作对象所在的内存空间,在操作对象时,实际操作的是该对象的引用。
基本类型存储在栈
中;引用类型的 ‘变量’名称 存储在栈
中,同时存储指针,该指针指向堆
中的真实的值;(栈
、堆
概念可以直接搜索 "js中栈和堆的概念和区别")
参考:阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版
参考书:犀牛书
this指向
箭头函数:this继承包裹箭头函数的第一个普通函数中的this;
普通函数:new(this指向新对象)——bind/apply/call(this是强制指向的this)——this指向函数上下文对象——this指向window;
参考书:红宝书
闭包
一个函数引用另外一个函数内部变量就会形成闭包,闭包在平时开发中非常的多见;理解作用域链创建和使用的细节对理解闭包非常重要;
参考书:红宝书
原型
每个对象被创建开始,就与另一个对象关联,从另一个对象上继承其属性,这里的另一个对象就是原型;
原型存在的意义就是组成原型链。当范文一个对象属性时,先从对象本身找,找不到就去对象原型上找,如果还找不到,就去对象原型的原型上找,如此类推,直到找到为止;这条由对象及原型组成的查找链条就是原型链;
参考书:红宝书
继承
组合继承:通过 call
修改子类this
,将子类原型指向 new 父类()
;
寄生组合继承:通过 call
修改子类this
,将子类原型指向父类原型(Object.create(父类.prototype)) ,并将子类原型构造函数指向子类;
参考书:红宝书
模块化
模块化特点:复用性、可维护性;
commonJs
最早作为标准在NodeJs中引入, 作为NodeJs模块化标准;在commonJs
中,require
首次加载就会执行整个脚本,在内存中生成一个对象缓存下来,下次加载时可直接从缓存获取;
对于循环加载,只输出已执行的部分。还未执行部分不输出;
最新ES6的 ES Module,是静态加载;脚本被加载时,成为一个指向被加载模块的引用,不会缓存;
并发和并行
区别:并发是两个或多个事件在同一时间间隔内发生;并行是指两个或多个事件在同一时刻内发生; 并行通过提升硬件能力,只需要多核CPU即可达到; 并发中最常听到的就是高并发,在一定时间,服务器的吞吐量和QPS每秒响应请求数都是一定的。而在这样的情况下,要解决高并发的问题,最简单当然是提升机器性能,加内存,加硬盘,升级网速。当然,通过架构层面设计,也可以做点东西,部署多台机器,添加负载均衡层,将请求均匀;将数据库分库分表并读写分离;引入消息中间件等;缓存集群引入;
异步方案
回调函数:在浏览器端,异步问题就是Ajax网络请求响应异步,而回调函数可以一定程度解决这个响应异步的问题,缺点是:不易维护、不易读、不能直接return;
Generator:封装多个内部状态的异步解决方案,生成器函数;会返回一个迭代器,配合next()函数,可以控制开始、暂停和恢复代码; 场景:控制流管理、异步操作;
Promise:解决异步操作方案,包含异步操作结果的对象;内部存在三种状态,一旦开始不可更改状态;问题:无法取消,错误需要回调捕获;Promise是链式调用,每次调用then之后返回一个Promise,并且是一个全新的Promise; Promise实现依据Promise/A+规范—— 三种状态:pedding、fulfilled、rejected; (Promise封装的Ajax为例子)进行异步处理流:执行Promise回调函数,由于异步操作,内部状态未变化。继续执行then回调,内部状态未变化,回调队列中then回调暂时不执行。此时,异步执行完成(网络请求响应),状态变化,Promise内部调用resolve函数,回调队列执行;
async/await:异步解决方案,需要配套使用;函数添加async后,函数会返回一个Promise;await内部实现了Generator,await是异步操作,后来的表达式不返回Promise的话,就会包装成Promise.resolve(返回值 ),然后去执行函数外的同步代码;
事件循环
一个事件循环又一个货多个任务队列;一个任务队列是一组任务;
1. 每个事件循环是一个正在运行的任务,它可以是一个任务或null;
2. 每个事件循环具有microtask队列(微任务队列),最初为空;
3. 每个事件循环都有一个执行微任务检查点的布尔值,该布尔值最初为false;
1. js的事件循环如何环执行?
浏览器事件循环执行顺序:
- 首先执行script脚本同步代码,属于宏任务;
- 当执行完当前所有同步任务后,执行栈为空,查询是否有异步任务代码需要执行;
- 执行当前宏任务下的所有微任务;
- 微任务执行完之后,如有必要会渲染页面;
- 然后开始下一轮事件循环;
2. 宏任务和微任务有哪些?
宏任务:script、setTimeout、setInterval、setImmediate、I/O、UI Render; 微任务:process.nextTick、promise、MutationObsever;
3. Nodejs的事件循环和浏览器事件循环区别?
Nodejs和浏览器端,宏任务和微任务交替执行,调用顺序基本相同。Nodejs执行会进行阶段区分,分为6个阶段:参考:Node.js 事件循环文档,每进入某个阶段,会从回调队列中取出函数执行。当队列为空或回调函数达到系统设定阈值,就进入下一个阶段。
异步方案
可以把ajax异步回调作为浏览器端的最初的异步解决方案,异步回调 ———> Promise实例 ——> Generator ——> async/await
,大致是一个这样的线,我们需要关注的可能主要就是Promise 和 async/await的方案。
1. Promise内部实现了解嘛?它的具体工作流程是怎么样的?
Promise是异步解决方案,包含异步操作结果的对象,特点:内部存在三种状态,状态一旦变化不可更改状态;Promise是链式调用,每次调用then回调函数都会返回一个新的Promise,并且是一个全新的Promise;
Promise异步处理流:执行Promise实例回调函数,由于异步请求或者操作,此时状态未变化。继续执行then回调,由于状态未变化回调会被放入回调数组中。直到Promise实例中异步请求或者操作完成,状态发生变化,调用resolve函数,回调数组遍历执行,then回调函数拿到相关数据
。
2. 说一说async/await
参考:
浏览器渲染原理
浏览器是多进程应用,每打开一个Tab页,就相当于创建了一个独立的浏览器进程。浏览器进程有:Browser进程(主进程)、插件进程、GPU进程、渲染进程(浏览器内核)。主进程只有一个是负责协调应用,同时默认每个Tab页面一个渲染进程,互不影响。我们常说的Js线程、GUI渲染线程就包含在渲染进程之中,还包括异步http请求线程、事件触发线程等。
1. 地址栏输入域名地址之后发生了什么?
1. DNS服务查询域名对应的目标服务IP地址;
2. 根据目标IP地址查找到目标服务器,并建立TCP连接;
3. 服务器响应返回数据或文本信息等;
4. 客户端获取到数据之后进行页面渲染;
2. 浏览器渲染的流程?
1. 请求html文件处理html标记构建DOM树;
2. 请求css文件处理css标记构建CSSOM树;
3. 将DOM和CSSOM合并成一个渲染树;
4. 根据渲染树布局,以计算每个节点的几何信息;
5. 将各节点绘制在屏幕上;
存在阻塞的css资源时,浏览器会延迟JS的执行和DOM的构建;
3. 重绘和回流?
重绘
是当节点需要更改外观而不影响布局;回流
是布局或几何属性需要改变;
参考:
网络协议
关于TCP和UDP的介绍、TCP的三次握手和四次挥手就不细说,相关文章可以参考:传输层协议TCP和UDP ,TCP三次握手和四次挥手;
http协议是常用的网络传输协议,全称是超文本传输协议,它规定了http请求和响应的具体结构,当然还包含其他东西,例如:缓存、文件类型、参数、请求类型、状态等。它是建立在传输层TCP协议之上的,TCP握手成功之后,才可以进行网络数据传输。
HTTP/HTTPS
1. 什么是http协议?它是怎么样的?
http是TCP/IP协议应用层协议,主要负责数据或文本图片等内容的传输,它是建立在传输层TCP协议之上的。http分为请求报文和响应报文,从Web客户端发往Web服务器的HTTP报文称为请求报文(request message)。从服务器发往客户端的报文称为响应报文
,报文分为:起始行、 首部字段、主体等;
2. http和https的区别?
http和https从字面的区别就是一个s,这个s就是SSL/TCL加密协议。说到加密协议就绕不开加密技术,在SSL/TCL加密协议中既有用到非对称加密,也有用到对称加密。SSL/TCL加密协议相当于是在应用层和传输层之间加了一层,可称为加密层。 大致流程:客户端向服务器端索要加密证书获取公钥等证书信息;双方协商生成"对话密钥";双方采用"对话密钥"进行加密通信。非对称加密保证"对话密钥"的安全传输,对称加密保证客户端和服务端对数据的加解密。
3. http网络缓存如何配置?
如果需要开启强缓存和协商缓存,可在服务端nginx web服务器进行对应的配置,开启对应的网络缓存。其他服务端web服务器也可配置。(配置细节网上一大堆)
4. 强缓存和协商缓存的区别?
强缓存:Cache-Control,常用属性有max-age、no-cache、no-store等。max-age表示相对时间内的缓存,在这个相对时间内不会再去请求对应资源;no-cache表示不意味着不缓存,它的意思是在使用缓存资源之前,它必须经过服务器的检查; no-store表示不缓存。
协商缓存:Last-modified 和 ETag,这两个类似,可以理解为文件标识。也可以将ETag(你选择的版本ID)或者Last-modified日期添加到响应首部中。客户端下次获取资源时,他会分别通过If-None-Match(与ETage对应)和If-Modified-Since(与Last-Mofied对应)两个请求首部将值发送给服务器。如果服务器发现两次值都是对等的,就是返回一个HTTP 304。它们之间的区别:ETag 只要资源变化,它就会改变;Last-modified 不识别秒单位里的修改。
如何使用 ————— 前端中保证HTML资源是最新的,设置如:max-age=300、ETag、Last-modified,当然也可考虑使用no-cache、ETag、Last-modified配合。而CSS和JS找资源已经被注入到HTML中,资源文件地址通常使用哈希值加入文件名中保证资源是最新,css和js文件可设置:max-age=31536000或last-modified 配合使用。
5. 了解http2.0嘛?为什么说http2.0更好?
http2.0基于二进制分帧层,http2.0可以在共享TCP连接的基础上同时发送请求和响应。在http1.x中,是通过文本的方式传输数据,基于文本的方式传输数据存在很多缺陷,文本的表现形式有多样性,因此要做到健壮性考虑的场景必然有很多,但是二进制则不同,只有0和1的组合,因此选择了二进制传输,实现方便且健壮。
为了保证http不受影响,那就需要在应用层(HTTP2.0)和传输层(TCP or UDP)之间增加一个二进制分帧层。在二进制分帧层上,http2.0会将所有传输的信息分为更小的消息和帧,并采用二进制格式编码,其中http1.x的首部信息会被封装到Headers帧,而Request Body则封装到Data帧。在传输中会共用一个TCP流(TCP连接中的一个虚拟通道,可以承载双向的消息),不至于重复连接。
参考:
前端框架
前端框架目前市面主流就是React和Vue,对于框架的使用和学习,前期建议多翻翻文档,中期根据自己在使用过程中遇到的问题学习,后期就可以考虑翻源码了。由于工作原因,我对react了解更多,所以分享主要就是React。 React和Vue作为前端框架在本质上做的是相同的事,在浏览器和开发操作之间加了一个中间层,来进项目的辅助管理和开发。
React框架
1. JSX本质是什么,它和JS的关系是什么?为什么使用JSX?
JSX
是类HTML的语法结构,实质是JS
的语法扩张。它语法结构简洁,通俗易懂,对于研发效率和体验有大的提升。
2. JSX背后的功能模块是什么,这个功能模块做了哪些事情?
JSX
通过babel
语法转换之后,实际就是通过React.createElement
函数来创建React元素。createElement
接收三个参数type
类型,config
配置,children
子元素。通过createElement
就可创建出虚拟DOM
对象。
3. react16.3新旧生命周期,消失的旧生命周期有哪些?
去掉ComponentWillMount
和CompoentWillReceiveProps
升级为getDeicvedStateFromProps
,保证生命周期更加单一,更可控;去掉ComponentWillUpdate新增getSnapshotDeforeUpdate;
4. React团队为什么要去掉旧的生命周期函数?
React16+引入Fiber
架构,Fiber
会将一个大的更新任务拆解为多个小任务,而且它是可中止,可恢复的。React16+生命周期被划分为render
和commit
两个阶段。render
阶段在执行过程中允许被打断,而commit阶段操作涉及真实DOM渲染,是不可打断的。
`ComponentWillMount`、
`ComponentWillUpdate`、
`ComponentWillReceiveProps`
这些生命周期,它们都处于render
阶段,而在Fiber
架构中,render
阶段会被打断,重复被执行。在这些生命周期可能习惯做的事情可能有:setState、异步请求、操作真实DOM等。而在Fiber异步渲染控制下,这些生命周期可能会导致非常严重的bug(例如在这些废弃的生命周期中调用支付接口)。
5. React组件数据如何流动?实现数据通信的方案有哪些?
react是自上而下的单向组件数据流
1.父-子组件通过prop属性传递数据,子-父组件可通过函数;
2.兄弟组件共享同一个父组件,达到兄弟组件通信;
3.Context API实现全局通信;(目前还没试过)
4.redux数据状态容,进行可预测的状态管理;
5.发布/订阅模式实现任意组件通信;
6. 为什么是React Hooks?
相对于Class
组件,函数组件更加轻量,更加符合UI=render(data)特点。同时在Fiber架构的加持下,Hooks的实现不是问题。配合函数组件的发展,Hooks应运而生,从而是函数组件真正把数据和渲染绑定到一起。当然Hooks也还是存在部分不足:部分周期不存在;不能很好的消化“复杂”,组件的拆分和组织是一个大的挑战,不易把握。
7. 为什么Hooks执行顺序如此重要?
Hooks本质是链表。例如 使用useText、useState创建state时,hook创建的state会以单链表形式保存,更新时,函数组件重新调用,hooks会依次遍历单链表,读取数据并更新,这一过程完全按照创建时的顺序来的。因此当更新时,位置一旦改变,执行顺序被替换,运行就会出现bug。
8. 调和(协调)和diff的关系或区别?
调和指的是虚拟DOM映射到真实DOM的过程。调和过程并不能和diff画等号。调和是“使一致”的过程,而diff是“找不同”的过程,它只是“使一致”过程中的一个环节。(当然常说的调和相关问题多半就是diff过程的)
9. react的diff逻辑和思路?
1.因为时间复杂度的原因,diff过程只针对同层的节点作比较;
2.对于同类型的组件,才有进一步对比的必要性;
3.对于列表组件,通过key属性来维持节点的稳定性,避免总是生产新节点;
10. setState的工作流是怎么样的?
非并发(concurrent
)模式:setState
会出现异步和同步的现象。在生命周期和合成事件中是同步,而在setTimeout、setInterval、DOM原生函数等函数中是同步的。那么这是为什么尼?在合成事件或生命周期执行时,批量更新的任务锁就被开启了,我们所做的setState操作会被放入到批量更新队列中,直到函数执行完,批量更新的任务锁才会被关闭。批量更新的任务锁是一个同步操作,而一旦你在setTimeout函数使用setState,此时setTimeout函数回调会被放入下一个宏任务执行,而当setState执行时,批量更新的任务锁时关闭的,它就不会放入到批量更新队列中,而是直接执行。
并发(concurrent
)模式:setState
不会出现异步和同步的现象。因为存在时间切片,只要当前时间片没有结束,依旧可以将多个 setState 合并成一个,即使是在setTimeout中被调用。而对于超过当前时间片的操作,会通过MessageChannel
放入到下一个宏任务中继续执行。(MessageChannel
接收消息的时机比 Promise 所在的 microTask 要晚,但是早于 setTimeout)
11. Stack Reconciler栈调和 有怎么样的局限性?
浏览器中Js线程和渲染线程是互斥的。这两个线程不能穿插执行,必须串行。而当Js线程长时间占用主线程,那么渲染线程的更新就不得不长时间的等待,这时就会导致页面卡顿。
Stack Reconciler
栈调和是一个同步递归过程,虚拟DOM树diff算法遍历是深度优先遍历。由于它是同步的,不可在被打断。当处理结构复杂,体量庞大的虚拟DOM树时,Stack Reconciler
时间会很长,以为这Js主线程长时间占用主线程,进而导致上述中说道的渲染卡顿/页面卡死。
12. 说一说Fiber架构?
特点:可中断、可恢复、存在优先级。
Scheduler ————> Reconciler ————> Renderer
更新优先级 找不同 渲染不同
在Fiber
架构模式下,每个更新任务会被赋予一个优先级。当然有任务A进入调度器,这个任务优先级更高,而Reconciler
中已有任务B在执行,那么,Reconciler
会将任务B终止,更高优先级的任务A被推入Reconciler
。当A任务完成之后,新一轮调度会将之前中断的任务B重新推入Reconciler
,继续它的渲染之旅。
render开始 ——————> (工作单元| 工作单元 | 工作单元) ——————> commit提交渲染
13. ReactDOM.render调用栈的初始化阶段、render阶段
初始化阶段:会创建root对象这个对象挂载_internalRoot
属性,而_internalRoot
也就是FiberRoot
。FiberRoot
的本质是一个FiberRootNode
对象,其中包含current属性,current对象是一个FiberNode实例。current对象就是一个Fiber节点,并是Fiber树的头部节点;确定Fiber的优先级,结合优先级创建当前Fiber的update对象,并将其入队调度FiberRoot;接下来进入render阶段;(此时相当于只有一个Fiber头部节点)
render阶段:通过createWorkInProgress函数,创建rootFiber节点的副本workInProgress节点树(即current节点的副本节点),他们通过alternate互相引用;接着会触发beginWork函数,进而实现对新的Fiber节点的创建。循环遍历,组件元素Fiber会不断被创建(每个元素节点对应一个Fiber节点),直到创建到最后一个为止,此时Fiber树(单链表)基本完成;
重点: 此时已经遍历到了单链表的最底部节点,然后会由下自上的依次生成真实DOM节点,同时被它的父组件副作用链,这个副作用链也是一个单链表,直遍历到根节点,此时的根节点上的副作用链就包含的全部的DOM更新。那么剩下的只需要拿到root下的副作用链更新即可了。
参考:
性能优化
可使用Chrome Lighthouse进行性能测评,根据测评结果也可以得出一些改进的点: react框架层面可以做的优化方向核心点:减少重新 render 的次数;减少计算的量,主要是减少重复计算。有下列具体方案:
1. 拆分公共组件和业务组件,根据具体业务分离,降低组件颗粒度;
2. shouldComponentUpdate中拦截非不要渲染;
3. 对于简单的数据类型,可考虑使用React.PureComponent;
4. 函数组件考虑使用React.meno,React.memo 与 React.PureComponent 非常相似;(版本React 16.6.0)
5. React Hooks组件使用useCallback缓存函数,避免每次返回一个新的函数;(版本React v16.8)
6. React Hooks组件使用useMemo缓存计算值;(版本React v16.8)
一个前端项目下通用的优化技巧:使用缓存、节流、压缩、按需加载、全局管理等方法或技巧。如下:
1. 避免频繁渲染更新,即使必须的情况下,也需要考虑是否使用节流函数;
2. 对于长列表页面,考虑翻页加载、点击下一页或者虚拟长列表,避免数据量过大,渲染卡顿;
3. 统一控制项目中的定时器函数,避免定时器偷跑;
4. 拆分Js文件,可考虑按需加载,提升加载速度;
5. 保证首屏必要数据渲染,可增加过渡图片,提升用户体验;
6. 对于后端接口多余数据可考虑清洗数据,只保留必要数据;
7. 避免过多的数据请求,可考虑使用数据缓存,提升用户体验;
8. 对于大图考虑CDN图片加载,也可考虑图片懒加载;
9. 避免使用过多css选择器嵌套;
10. 代码文件gzip压缩等,服务器相关缓存配置;
以上更多的是各种技巧和原则,如果要知道具体的标准,可以参考google为Web性能表现提供的文档developers.google.cn/Web Performance
设计模式
参考文章: JavaScript设计模式