os
GC
V8引擎垃圾回收算法
分代式垃圾回收
- 新生代:新生代中的对象为存活时间较短的对象
- 老生代:老生代中的对象为存活时间和数量较多的对象
按照对象的存活时间将内存的垃圾回收进行不同的分代,然后分别对不同分代的内存实行更高效的算法
scavenge 算法(具体实现采用了Cheney算法)—》通过牺牲空间换时间适合生命周期短的新生代
Cheney算法是采用复制方式实现的垃圾回收算法,它将堆内存一分为二,每一部分空间成为semispace。在这两个semispace空间中,只有一个处于使用中,另一个处于闲置状态。处于使用中的成为From空间,处于闲置状态的空间成为To空间。
分配对象时,首先从From空间进行分配,当开始垃圾回收时候,会检查From空间中的存活对象,这些存活对象将被复制到To空间中,而非存活的对象占用的空间将会被释放。完成复制之后,From和To角色互换
- 新生代中的对象是否已经经历多次 Scavenge 算法,如果经历过的话,会将对象从新生代空间移到老生代空间中。
- To 空间的对象占比大小超过 25 %。在这种情况下,为了不影响到内存分配,会将对象从新生代空间移到老生代空间中。
在老生代中,以下情况会先启动标记清除算法:
- 空间中对象超过一定限制
- 空间不够新生代对象转移到老生代中
- 某一个空间没有分块的时候
Mark-Sweep & Mark-Compact
标记清除算法和标记压缩算法Mark-Sweep
在标记阶段遍历堆中的所有对象,并标记活着的对象,在随后的清除阶段中,只清除没有被标记的对象。
而清除之后出现内存的不连续,需要Mark-Compact
进行整理
增量标志
垃圾回收的三种算法都需要将应用逻辑暂停下来,代执行完垃圾收集后再恢复执行应用逻辑,成为“全停顿”,带来体验的影响。
V8从标记阶段入手,将一口气停顿完成的动作改为增量标记。就是拆分为许多小的“步进”,每做完一“步进”,就让JavaScript应用逻辑执行一小会。交替直至完成。
Cache
缓存
缓存具有优先级,根据其位置不同可分为下面。浏览器查找缓存的时候,依次根据优先级查找,如果都没有找到的时候,便会发起网络请求:
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
- network request
Service Worker
navigation.serviceWorker
Service Worker 是运行在浏览器的另一个独立线程
- 传输协议必须是HTTPS(Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。)
步骤:
- 注册Server Worker
1 | navigator.serviceWorker.register('worker.js').then(e => console.log('success')).catch(e => console.log('error')) |
- 监听
install
事件,缓存需要的文件
1 | // worker.js |
- 在下次用户访问的时候就可以通过拦截请求的方式查询是否有缓存,如果存在缓存就使用缓存,如果没有就去请求数据
- 当Service Worker没有命中缓存的时候,会根据缓存优先度去查找数据。(当时无论是从Memory Disk中还是从网络中获取数据,浏览器都是显示是从Service Worker中获取的)
Memory Cache
Memory Cache 就是内存中的缓存,它会随着进程的释放而释放。持续时间很短,存储量少
Disk Cache
Disk Cache 是存储在硬盘中的缓存,它较于Memory Cache具有时效性长(根据http返回的header设置时间),容量大的特点。
Push Cache
Push Cache 是HTTP/2.0 的内容 ,当上面三种缓存都没命中的时候,才会触发这种缓存。
它的缓存时间很短,只在一个会话(session)中存在,一点会话结束就会被释放
HTTP/2 push is tougher than I thought
- 所有的资源都能被推送,但是 Edge 和 Safari 浏览器兼容性不怎么好
- 可以推送 no-cache 和 no-store 的资源
- 一旦连接被关闭,Push Cache 就被释放
- 多个页面可以使用相同的 HTTP/2 连接,也就是说能使用同样的缓存
- Push Cache 中的缓存只能被使用一次
- 浏览器可以拒绝接受已经存在的资源推送
- 你可以给其他域名推送资源
缓存策略
通常浏览器的缓存策略分为两种:协商缓存 和 强缓存
强缓存
强缓存代表在缓存期间不需要请求,直接返回200
- Expires:HTTP/1.0产物,表示资源在某个时间后过期,并且受制于系统时间
- Cache-Control:max-age:30 优先级高于Expires,表示该资源在30s后过期,需要再次请求。
Cache-Control常见指令:
- public 响应可以同时被客户端和代理服务器缓存
- private 相应只能被客户端缓存
- max-age 缓存存活时间
- s-max-age 覆盖max-age,作用一样,只在代理服务器生效
- no-store 不缓存任何响应
- no-cache 资源被缓存,但是立即失效,下次请求验证资源是否过期
- max-stale ns内,即使缓存过期,也使用该缓存
- max-fresh 希望在30s内获取最新响应
max-age=0, no-cache 等同 must-revalidate
协商缓存
如果缓存过期了,就需要发起请求验证资源是否过期。如果资源没有改变,服务器端返回304表示无更新,并更新缓存有效期
- Last-Modified
- ETag
Last-Modified 和 If-Modified-Since
Last-Modified 表示本地文件最后的修改日期,其中If-Modified-Since
会将Last-Modified
的值发送给服务器,询问服务器在该日期后资源是否有更新,如果有就将新的资源发送回来,如果没有就返回304
弊端:
- 如果在本地打开了文件,即使没有修改也会造成
Last-Modified
的值被修改,从而导致服务端不能命中缓存导致发送相同资源 - 因为
Last-Modified
只能以秒计算,所以如果在1s内修改了文件,服务端会认为资源还是有效的,从而不能发送正确的资源
ETag 和 If-None-Match
ETag类似于文件指纹,优先级高于Last-Modified
If-None-Match
会将当前的ETag发送给服务端,如果有变动就发送新的资源回来
ETag
语法:
1 | ETag: W/"<ETag value>" |
- W 为可选值(大小写敏感),表示使用弱验证器
表示位于双引号的字符串。没有明确指定生成ETag值的方法,通常使用内容的hash以及最后修改时间的时间戳的hash值,或简单地使用版本号。
Apache将文件索引节(inode),大小(size)和最后修改时间作为输入求值得到
在3.23版本后移除inode,只留下大小和时间戳
- ETag可以避免“空中碰撞”:
如果正在编辑文章的时候,当前wiki内容被hash,然后将ETag内容放入响应中(服务端)
将更改保存到wiki页面的时候(发布数据),POST请求可以附带含有ETag值的If-Match
来检查是否最新版本。
如果不是最新版本,意味着文档已经被编辑,抛出412提示条件判断错误。
- ETag可以缓存未更改的资源
如果用户再次访问已经过期的含有ETag资源的时候,客户端就会发送一个含有If-None-Match
的字段到服务端
服务端判断两个ETag的值是否一致,如果一致则返回304状态码,告诉客户端缓存可用
如果服务端同时设置了ETag和Last-Modified,那么这两个谁的优先度高?
一般来说,ETag的优先度会比Last-Modified优先度高
from https://www.rfc-editor.org/rfc/rfc7232.txt, RFC 7232:
A recipient MUST ignore If-Modified-Since if the request contains an
If-None-Match header field; the condition in If-None-Match is
considered to be a more accurate replacement for the condition in
If-Modified-Since, and the two are only combined for the sake of
interoperating with older intermediaries that might not implement
If-None-Match.
What if both If-Modified-Since and If-None-Match are present in HTTP headers
What takes precedence: the ETag or Last-Modified HTTP header?
既然有了 ETag,那么 Last-Modified 还有存在的必要吗?
有必要存在:
- 兼容性考虑
- ETag计算需要耗费性能,对于要求不高的资源可以使用
Last-Modified
- ETag在分布式系统中,可能不同的系统产生不同的ETag,从而导致ETag不统一
vary
这个是告知代理服务器在缓存文件的时候要区分vary
字段里面的header,他会根据vary指定的字段不同而缓存不同的文件(比如一个是使用过gzip压缩过的文件,另外一个不是通过gzip压缩过的文件)
没有缓存策略的情况
如果没有缓存策略的情况下,浏览器会采用采用一个启发式算法:去响应头中的Date
减去Last-Modifiled
的值的10%作为缓存时间
实际场景
- 通常HTML不缓存或者缓存时间很短,所以可以通过打包工具,对文件名进行处理,当文件改变的时候就会更改文件名,从而使其发起新的请求。设置其他缓存为一个很长的时间。
- 对于频繁变动的文件,可以设置
Cache-Control: no-cache
使浏览器每一次都验证资源的有效性,这样做虽然不能减少请求数量,但是可以减少数据的传输。
Reference
cross origin
跨域
如果协议、域名或者端口号有一个不同就是跨域。一般使用“javascript:;
”以及”about blank
“打开的页面会继承源。但是data:
协议的不继承。
在IE中,端口号不在同源策略中。两个高度互信的范围不遵循授权范围。
可以使用document.domain
修改
同源策略
主要是用来防止CSRF(跨站请求伪造Cross Site Request Forgery)攻击的
没有同源策略的情况下,A网站可以被其他来源的内容Ajax访问到内容。如果保留着登录态,其他来源的Ajax就可以访问到你的全部信息
请求跨域了,但是请求是成功发送了。只是浏览器将响应结果拦截了。
1)无法读取不同源的 Cookie、LocalStorage 和 IndexDB 。
2)无法获得不同源的DOM 。
3)不能向不同源的服务器发送ajax请求。
4)websocket 不受影响
<script>、<img>、<iframe>、<link>
解决跨域问题
根据使用场景差异分为:
- 同一主域:使用document.domain
- 不同主域:使用window.name/ Location.hash/ JSONP / CORS/ postmessage / 图片img/ web socket/ 反向代理
document.domain
适用于二级域名相同的情况,只要设置两个页面中的document.domain
相同就可以实现跨域请求
例如 mail.qq.com qq.com
store.google.com google.com
JSONP
利用script标签,GET请求,callback
淘宝和天猫cookie的传递,是利用JSONP来实现的(天猫通过访问淘宝的一个JSONP服务器,然后返回之后将cookie改写)
原理:
- 前端通过script标签进行跨域
- 将前端的方法通过参数来传递到服务器(例如callback),然后服务器注入参数之后返回,实现服务器与客户端的通信
- 只支持GET方法,只能使用全局方法(可以解决)
注意事项:
如果由参数,需要进行encodeURLComponent()
优点:兼容性好,URL限制参数,错误处理机制不好,预料外返回难以处理。script标签的onerror
CORS跨域资源共享(根本解决方法)
需要后端浏览器和后端同时支持,
服务端设置 Access-Control-Allow-Origin
- 简单请求
- 使用以下请求:
POST
、HEAD
、GET
- 首部集合:Accept、Accept-Language Content-Language
Content-Type
为以下:text/plain
、multipart/form-data
、application/x-www-form-urlencoded
- 请求中的XMLHttpRequestUpload对象没有注册任何事件监听器
- 请求中没有使用ReadableStream对象
- 使用以下请求:
- 复杂请求
- 不满足上面的请求即为复杂请求
对于复杂请求,首先会触发一个OPTIONS
预检请求,通过这个请求来知道服务器是否允许跨域请求。
服务器会返回一个HTTP字段Acess-Control-Allow-Origin
,表明该资源可以被设置的外域访问
预检请求通过OPTIONS
方法发起一个请求到服务器,以获知服务器是否允许该实际请求。(可以避免跨域请求对服务器的用户数据产生未预期的影响),请求同时携带了两个首部字段
1 | Access-Control-Request-Method: POST |
其中xhr中请求要设置withCredentials=true
才能携带cookie
缺点:非简单请求需要多一次请求,需要服务端设置相应,兼容性问题
postMessage
这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息
1 | // 发送消息端 |
targetWindow:
- window.open
- window.opener
- HTMLFrameElement.contentWindow
- window.parent
- widnow.frames
缺点,兼容性不好,ie9只对iframe使用,不能对其他tab使用
(优酷)
location.hash
打开一个隐藏的iframe,src到跨域地址,iframe通过改变hash来通讯
优点:双向通讯
缺点:通过url,不够安全,大小限制,需要通过onhashchange事件,兼容性问题
window.name
原理:一个window生命周期内,window name属性共享,注意XSS注入
过程:
- a页面打开b页面iframe
- b加载完之后将数据写进去window.name,然后跳转到和a同域的代理页面(同域才能操作)
- 读取iframe.contentWindow.name得到跨域data
优点:传输的数据量大:2M,支持性好,支持GET和POST
缺点: 需要请求一次Proxy页面,Content Security Policy(CSP),内容安全策略(HTTP设置),X-Frame-Options设置
代理服务器
服务端没有同源限制
Websocket
ws不实行同源政策
图片Ping
只能单向,通常用来上报一些数据
new technology
performance
网络部分
- 减少http请求
- 图片使用sprite,使用图片地图(map/area),使用伪协议(data://),合并css和JavaScript文件(但是抽离公共部分),按需加载
- 缓存:设置expires、cache-control、Etag、CDN等缓存,解决文件更新问题(文件名改变),使用外部css和JavaScript文件
- 加快http请求
- 使用压缩gzip选项
- 使用HTTP/2 或者 HTTP/3(多路复用,UDP)
- 预加载、DNS预解析
- 压缩JavaScript和css文件,抽离公共部分
- 减少重定向
- 无阻塞加载脚本
- 客户端
- scirpt标签放在body最后,link放在head(前面),script会阻塞,它可以使用document.write来改写后面的,所以脚本必须顺序执行(可以使用无阻塞并行下载async、或延迟下载defer==保证顺序执行)
- JavaScript运行优化(如动画使用requestAnimationFrame),利用引擎优化性能
- 预渲染
- css解析性能
- 渲染性能
- onload后加载(拆分代码)
JS性能优化
在V8引擎下,引入了TurboFan
编译器,他会在特定的情况下进行优化,将代码编译成执行效率更高的Machine Code
在这一过程中,JS会首先被解析为AST(抽象语法树),解析过程略慢。
Ignition会将AST转化成Bytecode,TurboFan负责编译出优化后的Machine Code,在执行效率上,Machine Code优于 Bytecode
- 对于函数,避免声明嵌套函数(类也是函数),这样会造成函数的重复解析
1 | function test1() { |
编译为 Machine Code的情况:
- 如果一个函数被多次调用,并且参数一直传入同一个类型,那么V8就会认为该段代码可以编译成Machine Code。因为Machine Code 固定了类型,不需要再执行很多判断逻辑了。如果此时传入的参数类型发生改变,那么Machine Code就会被DeOptimized为Bytecode。DeOptimized次数较少就应该要保证传入的类型一致
另外编译器会对函数进行 Lazy-Compile(预解析),当函数没有执行的时候,会对函数进行一次预解析,直到代码被执行以后才会被解析编译。而对于马上调用的函数来说,预解析其实是多余的。其实给函数套上括号就可以了
1 | (function test() { |
图片优化
计算图片大小:
对于一个100px * 100px
的图片,图像中有10000px
的点,每一个px
有4
个通道(rgba
),每一个通道1
个字节(1byte = 8bit
),所以该图片大小为 10000 * 4 / 1024
图片加载优化
- 不用图片
- 移动端适配,裁剪加载
- 小图使用base64
- 使用雪碧图
- 选择正确的图片格式:
- webp
- 小图使用png
- 大图使用jpeg
- 懒加载
DNS预解析
1 | <link rel="dns-prefetch" href="" > |
函数节流和防抖
预加载
1 | <link rel="preload" href=""> |
预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载
预渲染
1 | <link rel="prerender" href="http://example.com"> |
懒加载
首先将图片上的src属性设置为loading的图片,然后在图片的data-src上设置真实的src地址。
首先加载前n个图片,然后记录最后加载到的数据索引
监控window的滚动事件,如果索引所在的地方距离可视范围一定距离的时候,执行加载程序。可以使用函数节流和防抖的方式。
另一种方法, IntersectionObserver 方法(优先级低)
第三种方法,对img都订阅事件,当滚动结束的时候才开始加载(从视口开始加载)节流,当出现在视口才开始加载
对于页面:import、ajax
懒执行
CDN
我们可以将静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限
可以考虑使用多个 CDN 域名。并且对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie,平白消耗流量。
编写高效的JavaScript
- 执行上下文,执行上下文中变量标识符在作用域链中位置越深,查找和访问它所需要的时间就越长。所以使用局部变量是JavaScript读写最快的标识符
- 使用with会使作用域链加深,第二个使try-catch语句中的catch语句,其行为类似于with语句,也是在作用域链上增加了一个对象。该对象包含了由catch指定命名的异常对象
- 高效存储:从字面量中和局部变量中读取值的开销小,但是在对象中读取数据和数组开销大。所以在对象属性或数组属性存储为局部变量是一种好方法。
- 流控制:大量if/else之类出现的,按频率排序,也可以拆分成几个分支(二分查找),switch简化了多重判断的结构,提上了性能
- 循环:可以将变量从length变成0,若执行过长的循环可以考虑使用异步
简化CSS选择符
css选择符是从右边开始的
- 避免了通配选择符
- 不要限定ID选择符,页面中一个ID只能对应一个对象,如:
#id
而不是li#id
- 不要限定类选择符,而是根据实际情况进行拓展
- 避免使用后代选择符,处理后代选择符开销是最高的。应该使用子选择符
- 避免使用标签-子选择符:如果像
#id > li > a
,这样基于标签的子选择符,那么应该使用一个类来关联每个标签元素。如果可以尽量使用类名。 - 不要试图编写长的选择符
- 依靠继承
react
MVC
model
用来封装与应用程序的业务逻辑相关的数据以及对数据的处理方法,会有一个或多个视图监听此模型。一旦模型的数据发生变化,模型将通知有关的视图。(有操作数据的方法)
view
视图,当模型发生变化的时候,视图相应得到刷新自己的机会
controller
定义用户界面对用户输入的相应方式,起到不同层面的组织作用,用于控制应用程序的流程,它处理用户行为和数据model
的改变。它的职责为进行Model和View之间的协作(路由、输入预处理等)的应用逻辑(application logic)。
其中:view
和model
之间是观察者模式,view
观察model
,实现在model
上注册,以便view
可以了解在数据model
上发生的改变view
和controller
是策略模式,view
可以使用controller
的方法
Controller和View都依赖Model层
调用关系:
用户的对View操作以后,View捕获到这个操作,会把处理的权利交移给Controller(Pass calls);Controller会对来自View数据进行预处理、决定调用哪个Model的接口;然后由Model执行相关的业务逻辑;当Model变更了以后,会通过观察者模式(Observer Pattern)通知View;View通过观察者模式收到Model变更的消息以后,会向Model请求最新的数据,然后重新更新界面。
需要注意的地方:
- View是把控制权交移给Controller,Controller执行应用程序相关的应用逻辑(对来自View数据进行预处理、决定调用哪个Model的接口等等)。
- Controller操作Model,Model执行业务逻辑对数据进行处理。但不会直接操作View,可以说它是对View无知的。
- View和Model的同步消息是通过观察者模式进行,而同步操作是由View自己请求Model的数据然后对视图进行更新。
缺点:
- View无法组件化。View是强依赖特定的Model的
MVP
Model View Presenter 为MVC的一种衍生模式
切入点:解决controller
和view
的捆绑关系,将其进行改造,使view
不仅拥有UI组件的结构,还拥有处理用户事件的能力。此时view
不能调用model
的方法,所以只能让presenter
取更新model
,在通过观察者模式更新view
。
相比与传统MVC,解耦了model
和view
,完全分离视图和模型,使职责划分更加清晰。可以将view
抽象出来做成组件。
(controller
可以在view
中复用)。
应用逻辑主要集中在presenter
这一层中。缺点:手动更新
调用关系
和MVC模式一样,用户对View的操作都会从View交移给Presenter。Presenter会执行相应的应用程序逻辑,并且对Model进行相应的操作;而这时候Model执行完业务逻辑以后,也是通过观察者模式把自己变更的消息传递出去,但是是传给Presenter而不是View。Presenter获取到Model变更的消息以后,通过View提供的接口更新界面。
MVVM
最重要的使数据绑定,data-binding。
view和model不知道彼此的存在,同MVP一样,将view和model清晰地分离开,如果viewmodel的属性值改变了,这些新值通过数据绑定自动传递给view,反过来,viewmodel会暴露model中数据和特定状态给view。
此时的model是单纯的数据,不包含对数据的操作
Vue双向数据绑定:通过Object.defineProperty来实现绑定
flux
Flux将一个应用分成四个组成部分:
- View:视图
- Action:一个对象,视图发出的信息,包含一个type以及数据
- Dispatcher:接收Actions,执行回调函数
- Store:数据层,用来存放应用状态,提醒Views更新页面
Predictable
特点:单项数据流动
control view
用来保存状态,监听store的变化,然后数据转发给子组件。
子组件不包含所有的状态,为纯组件。
dispatcher
将action派发到store,dispatcher只有一个,而且是全局的。
Virtual DOM
router control
lifecycle
communication
HOC & mixins
event
React
element & component
element是由component生成的,element是一个对象,component是一个构造函数。
element是immutable(不能改变)的,一旦创建了就不能更改element的children、attributes等属性。
state & props
props只能读,不能更改。所有的React Component 必须像纯函数那样对待他们的props
this.props
与 this.state
可能被异步更新,所以不能依靠他们来计算下一个state
,要使用回调函数:
1 | this.setState((state, props) => ({counter: state.counter + props.counter})) |
dataflow
top-down/ unidirectional 数据流
state只能被特定的组件所拥有,所有的state数据更新只能影响这个组件树下层的组件
Event
React的event使用驼峰写法,而不是全部小写
将一个函数传给下一个组件的事件,而不是字符串
(不能return false来阻止默认行为,只能使用preventDefault来)
React的事件是一个合成事件。所以不需要担心浏览器兼容性
key
注意key相同可能引发错误,使用index作为key也可能引发错误。
以下三个条件满足的情况下中可以使用index作为key:
- list中的数据不会改变(即不会重新计算)
- list不会过滤或者重新保存(如排序)
- list中item没有唯一的id
diff算法·
基于假设:
- 两个不同type的element会生成不同的树
- 可以增加一个key prop来指定哪个element是不变的
virtual-dom
尝试去最小化回流/重绘步骤,从而在大型且复杂的项目中得到更好的性能
virtual-dom ---> fiber
stack-reconciler
fiber
Inside Fiber: in-depth overview of the new reconciliation algorithm in React
the-how-and-why-on-reacts-usage-of-linked-list-in-fiber
Reconciliation
Accessibility
Lazy & Suspense
lazy接收一个含有import
语句的回调,这个回调返回一个promise,(promise中必须有一个包含组件default export)
1 | const Component = React.lazy(() => import('./component')) |
ErrorBoundary
1 | class ErrorBoundary extends Component { |
Error boundaries 不捕获下面的错误
- event handle
- 异步代码
- 服务器端渲染
- Error boundaries 自己抛出的错误
只有class组件才有error boundaries,与catch工作原理类似
在React 16 之后, 如果errors没有被catch,那么会把整个react组件树卸载。
Context
const MyContext = React.createContext(defaultValue)
defaultValue
只有在一个组件上方没有任何的provider
的时候使用。
Context.Provider
1 | <Context.Provider value={/* some value */} /> |
其中上方的value一般不使用字面量对象,一般使用this.state。因为如果是使用字面量对象,每次rerender的时候就会重新创建一个对象
Class.contextType
Context.Consumer
Ref
ref 不是props的属性,如果需要实现穿越组件传递ref,需要使用下面的api
1 | React.forwardRef(function(props, ref) {...}) |
React.forwardRef
接收一个render函数,这个函数接收两个props、ref
使用React.createRef
不能使用在函数组件上,因为函数组件没有实例。
高阶组件
与复合组件区别:复合组件
一般而言,高阶组件是一个接收一个组件并且返回另外一个组件的函数
主要的作用是让不同组件之间共用逻辑
通常,高阶组件是一个纯函数,没有副作用
注意不要更改原组件
注意配置displayName
不要将高阶组件放在render
函数里面,performance以及内部state会消失(React会认为他们是不同的组件)
注意key以及ref的传递
1 | function logProps(WrappedComponent) { |
与mixin区别
mixin是通过使用Object.defineProperty
来实现多个组件之间方法的共用。
问题:
- 破坏了原有组件的封装,不能改变state,或者将state提升的时候很麻烦
- 命名冲突,mixin很难去取出或者移除
- 增加复杂性:增加越来越多的mixin的时候,引入越来越多的方法,造成代码逻辑复杂,不易维护。
性能
- HOC不要在render定义
- 使用data-,代替bind来传参数
- 注意移除event Listener
- 使用
production
版本 - 使用
shouldComponentUpdate
- key
1 | class CounterButton extends React.PureComponent { |
jsx
因为jsx解释出来是这样的:
1 | <div id="2"> |
编译后:
1 | React.createElement('div', |
所以要在作用域内引入React
才能正常工作
1 | // 等价 |
children以及直接使用引号的html是没有转义的,因此可以像写HTML那样使用
false
,null
,undefined
,true
会被忽略
Protals
React可以在render中将element挂载在其他的节点下:
1 | render() { |
使用这个API可以正常使用Context之类的功能,因为它作为一个portal存在于React tree中,(无论它处于哪个DOM tree中)
Hook
1 | import React, { useState } from 'react' |
Hook:
- 很难在组件之间重用一个有状态的逻辑(wrapper hell),不需要改变组件之间的关系重用逻辑
- 复杂组件很难被理解
- 兼容性
state hook
如果在要给render中多次调用useState,React会以相同的顺序调用他们
Hook就是一个钩子能够让你hook进去React函数组件的state和lifecycle(Hook不能在class中工作)
effect hook
获取数据,订阅,或者修改DOM这种副作用的时候使用
React默认在render之后执行effects(包括第一次render)
useEffect可以return一个函数作为callback
这个方法可以告诉React在render完成之后应该干什么,返回的函数称为clearup,React在组件unmount的时候运行这个clearup函数(React都会运行effect之前的clearup函数)==》原因:
如果props改变了,那么就可能会出现bug
可以告诉React跳过这个effect,使用第二个参数(数组)。
1 | useEffext(() => document.title = `click ${count} times`, [count]) |
rules
- 不要再循环、条件或者嵌套函数中使用hook
- 只在React函数组件中使用
React怎么识别出哪个state是哪个useState调用的?
React根据hook被调用的顺序来判断的。(因为每次render的时候他们的顺序都不会发生改变)
前端路由的实现
- hash模式,通过改变hash,监听hashchange事件来进行页面跳转
- history模式,通过使用history中的新功能:history.putState和history.replaceState来改变URL(通过History模式改变URL不会引起页面的刷新,只会更新浏览器的历史纪录。)如果用户点击后退按钮,会触发popState事件
Vue 和 React 的区别
- Vue修改状态要简单一点,React需要使用setState来改变状态,并且需要手动优化
- React使用了JSX,完全可以通过JS来控制页面,更加的灵活。Vue使用了模板语法,相比于JSX来说没有那么灵活,但是可以脱离工具链,通过直接编写render函数就能够在浏览器中运行。