• 首页

  • 归档

  • 关于我
H i , e v e r b r e z
Hi, everbrez

If I could be the hero

02月
22
NetWork

os

发表于 2019-02-22 • 字数统计 2059

os

阅读全文 »
02月
22
NetWork

WebSocket

发表于 2019-02-22 • 字数统计 642

WebSocket是一种在单个TCP连接上进行全双工通信的协议,是一个持久化的协议

阅读全文 »
02月
20
JavaScript

Nodejs

发表于 2019-02-20 • 字数统计 804

严格来说,一切动作都是事件,这就是事件驱动的思想

阅读全文 »
02月
17
JavaScript

GC

发表于 2019-02-17 • 字数统计 649

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应用逻辑执行一小会。交替直至完成。

阅读全文 »
02月
17
Cache

Cache

发表于 2019-02-17 • 字数统计 1925

缓存

缓存具有优先级,根据其位置不同可分为下面。浏览器查找缓存的时候,依次根据优先级查找,如果都没有找到的时候,便会发起网络请求:

  1. Service Worker
  2. Memory Cache
  3. Disk Cache
  4. Push Cache
  5. network request

Service Worker

navigation.serviceWorker

Service Worker 是运行在浏览器的另一个独立线程

  • 传输协议必须是HTTPS(Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。)

步骤:

  1. 注册Server Worker
1
navigator.serviceWorker.register('worker.js').then(e => console.log('success')).catch(e => console.log('error'))
  1. 监听install事件,缓存需要的文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// worker.js
self.addEventListener('install', (e) => {
e.waitUntil(
caches.open('my-cache').then(cache => {
return cache.addAll(['./index.html', './index.js'])
})
)
})

// 拦截请求
self.addEventListener('fetch', (e) => {
e.respondWith(
cache.match(e.request).then(response => {
if (responese) {
return responese
}
})
)
})
  1. 在下次用户访问的时候就可以通过拦截请求的方式查询是否有缓存,如果存在缓存就使用缓存,如果没有就去请求数据
  2. 当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,只留下大小和时间戳

  1. ETag可以避免“空中碰撞”:

如果正在编辑文章的时候,当前wiki内容被hash,然后将ETag内容放入响应中(服务端)

将更改保存到wiki页面的时候(发布数据),POST请求可以附带含有ETag值的If-Match来检查是否最新版本。

如果不是最新版本,意味着文档已经被编辑,抛出412提示条件判断错误。

  1. 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 还有存在的必要吗?

有必要存在:

  1. 兼容性考虑
  2. ETag计算需要耗费性能,对于要求不高的资源可以使用Last-Modified
  3. ETag在分布式系统中,可能不同的系统产生不同的ETag,从而导致ETag不统一

vary

这个是告知代理服务器在缓存文件的时候要区分vary字段里面的header,他会根据vary指定的字段不同而缓存不同的文件(比如一个是使用过gzip压缩过的文件,另外一个不是通过gzip压缩过的文件)

没有缓存策略的情况

如果没有缓存策略的情况下,浏览器会采用采用一个启发式算法:去响应头中的Date减去Last-Modifiled的值的10%作为缓存时间

实际场景

  1. 通常HTML不缓存或者缓存时间很短,所以可以通过打包工具,对文件名进行处理,当文件改变的时候就会更改文件名,从而使其发起新的请求。设置其他缓存为一个很长的时间。
  2. 对于频繁变动的文件,可以设置Cache-Control: no-cache使浏览器每一次都验证资源的有效性,这样做虽然不能减少请求数量,但是可以减少数据的传输。

Reference

HTTP 缓存

HTTP Cache(google-developer)

阅读全文 »
02月
17
JavaScript

cross origin

发表于 2019-02-17 • 字数统计 1204

跨域

如果协议、域名或者端口号有一个不同就是跨域。一般使用“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>

解决跨域问题

根据使用场景差异分为:

  1. 同一主域:使用document.domain
  2. 不同主域:使用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改写)

原理:

  1. 前端通过script标签进行跨域
  2. 将前端的方法通过参数来传递到服务器(例如callback),然后服务器注入参数之后返回,实现服务器与客户端的通信
  3. 只支持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
2
Access-Control-Request-Method: POST
Access-Control-Request-Headers: ...(实际请求携带的自定义首部字段)

其中xhr中请求要设置withCredentials=true才能携带cookie

缺点:非简单请求需要多一次请求,需要服务端设置相应,兼容性问题

postMessage

这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息

1
2
3
4
5
6
7
8
9
10
11
12
// 发送消息端
window.parent.postMessage('message', 'http://test.com')
// 接收消息端
var mc = new MessageChannel()
mc.addEventListener('message', event => {
var origin = event.origin || event.originalEvent.origin
if (origin === 'http://test.com') {
console.log('验证通过')
}
})

targetWindow.postMessage(data, targetOrigin) // targetOrigin类似(https://google.com)

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注入

过程:

  1. a页面打开b页面iframe
  2. b加载完之后将数据写进去window.name,然后跳转到和a同域的代理页面(同域才能操作)
  3. 读取iframe.contentWindow.name得到跨域data

优点:传输的数据量大:2M,支持性好,支持GET和POST

缺点: 需要请求一次Proxy页面,Content Security Policy(CSP),内容安全策略(HTTP设置),X-Frame-Options设置

代理服务器

服务端没有同源限制

Websocket

ws不实行同源政策

图片Ping

只能单向,通常用来上报一些数据

阅读全文 »
02月
17
JavaScript

new technology

发表于 2019-02-17 • 字数统计 14

PWA

WebAssembly / WebComponent

CSS-in-JS / JSS

typeScript

parcel VS webpack

yarn VS npm

阅读全文 »
02月
17
JavaScript

performance

发表于 2019-02-17 • 字数统计 1509

网络部分

  1. 减少http请求
    1. 图片使用sprite,使用图片地图(map/area),使用伪协议(data://),合并css和JavaScript文件(但是抽离公共部分),按需加载
    2. 缓存:设置expires、cache-control、Etag、CDN等缓存,解决文件更新问题(文件名改变),使用外部css和JavaScript文件
  2. 加快http请求
    1. 使用压缩gzip选项
    2. 使用HTTP/2 或者 HTTP/3(多路复用,UDP)
    3. 预加载、DNS预解析
    4. 压缩JavaScript和css文件,抽离公共部分
    5. 减少重定向
    6. 无阻塞加载脚本
  3. 客户端
    1. scirpt标签放在body最后,link放在head(前面),script会阻塞,它可以使用document.write来改写后面的,所以脚本必须顺序执行(可以使用无阻塞并行下载async、或延迟下载defer==保证顺序执行)
    2. JavaScript运行优化(如动画使用requestAnimationFrame),利用引擎优化性能
    3. 预渲染
    4. css解析性能
    5. 渲染性能
    6. onload后加载(拆分代码)

JS性能优化

在V8引擎下,引入了TurboFan编译器,他会在特定的情况下进行优化,将代码编译成执行效率更高的Machine Code

在这一过程中,JS会首先被解析为AST(抽象语法树),解析过程略慢。

v8

Ignition会将AST转化成Bytecode,TurboFan负责编译出优化后的Machine Code,在执行效率上,Machine Code优于 Bytecode

  1. 对于函数,避免声明嵌套函数(类也是函数),这样会造成函数的重复解析
1
2
3
function test1() {
function test2() {}
}

编译为 Machine Code的情况:

  1. 如果一个函数被多次调用,并且参数一直传入同一个类型,那么V8就会认为该段代码可以编译成Machine Code。因为Machine Code 固定了类型,不需要再执行很多判断逻辑了。如果此时传入的参数类型发生改变,那么Machine Code就会被DeOptimized为Bytecode。DeOptimized次数较少就应该要保证传入的类型一致

另外编译器会对函数进行 Lazy-Compile(预解析),当函数没有执行的时候,会对函数进行一次预解析,直到代码被执行以后才会被解析编译。而对于马上调用的函数来说,预解析其实是多余的。其实给函数套上括号就可以了

1
2
3
(function test() {
// some code
})

图片优化

计算图片大小:

对于一个100px * 100px的图片,图像中有10000px的点,每一个px有4个通道(rgba),每一个通道1个字节(1byte = 8bit),所以该图片大小为 10000 * 4 / 1024

图片加载优化

  1. 不用图片
  2. 移动端适配,裁剪加载
  3. 小图使用base64
  4. 使用雪碧图
  5. 选择正确的图片格式:
    1. webp
    2. 小图使用png
    3. 大图使用jpeg
  6. 懒加载

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

  1. 执行上下文,执行上下文中变量标识符在作用域链中位置越深,查找和访问它所需要的时间就越长。所以使用局部变量是JavaScript读写最快的标识符
  2. 使用with会使作用域链加深,第二个使try-catch语句中的catch语句,其行为类似于with语句,也是在作用域链上增加了一个对象。该对象包含了由catch指定命名的异常对象
  3. 高效存储:从字面量中和局部变量中读取值的开销小,但是在对象中读取数据和数组开销大。所以在对象属性或数组属性存储为局部变量是一种好方法。
  4. 流控制:大量if/else之类出现的,按频率排序,也可以拆分成几个分支(二分查找),switch简化了多重判断的结构,提上了性能
  5. 循环:可以将变量从length变成0,若执行过长的循环可以考虑使用异步

简化CSS选择符

css选择符是从右边开始的

  1. 避免了通配选择符
  2. 不要限定ID选择符,页面中一个ID只能对应一个对象,如:#id而不是li#id
  3. 不要限定类选择符,而是根据实际情况进行拓展
  4. 避免使用后代选择符,处理后代选择符开销是最高的。应该使用子选择符
  5. 避免使用标签-子选择符:如果像#id > li > a,这样基于标签的子选择符,那么应该使用一个类来关联每个标签元素。如果可以尽量使用类名。
  6. 不要试图编写长的选择符
  7. 依靠继承
阅读全文 »
02月
17
JavaScript

react

发表于 2019-02-17 • 字数统计 2986

MVC

MVC

MV*

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请求最新的数据,然后重新更新界面。

需要注意的地方:

  1. View是把控制权交移给Controller,Controller执行应用程序相关的应用逻辑(对来自View数据进行预处理、决定调用哪个Model的接口等等)。
  2. Controller操作Model,Model执行业务逻辑对数据进行处理。但不会直接操作View,可以说它是对View无知的。
  3. View和Model的同步消息是通过观察者模式进行,而同步操作是由View自己请求Model的数据然后对视图进行更新。

缺点:

  1. 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来实现绑定

vue的双向数据绑定

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

react-key

注意key相同可能引发错误,使用index作为key也可能引发错误。

以下三个条件满足的情况下中可以使用index作为key:

  • list中的数据不会改变(即不会重新计算)
  • list不会过滤或者重新保存(如排序)
  • list中item没有唯一的id

diff算法·

diff-algorithm

基于假设:

  1. 两个不同type的element会生成不同的树
  2. 可以增加一个key prop来指定哪个element是不变的

virtual-dom

尝试去最小化回流/重绘步骤,从而在大型且复杂的项目中得到更好的性能

virtual-dom ---> fiber

virtual-dom

stack-reconciler

fiber

完全理解React Fiber

React 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

checklist

Lazy & Suspense

lazy接收一个含有import语句的回调,这个回调返回一个promise,(promise中必须有一个包含组件default export)

1
2
3
4
5
6
7
8
9
const Component = React.lazy(() => import('./component'))

function MyComponent() {
return (
<Suspense fallback={<div>loading</div>}>
<Component />
</Suspense>
)
}

ErrorBoundary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class ErrorBoundary extends Component {
constructor(props) {
super(props)
this.state = {
error: false,
msg: ''
}
}

static getDerivedStateFromError(err) {
return {
error: true,
msg: error.message
}
}

componentDidCatch() {

}

render() {
const { error, msg } = this.state
return (
<>
{error ? <div>error: {msg}</div> : this.props.children}
</>
)
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
React.forwardRef(function(props, ref) {...})

function logProps(Component) {
class logProps extends React.Component {
render() {
const {forwardedRef, ...rest} = this.props
return <Component ref={forwardedRef} {...rest} />
}
}

return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />
})
}

React.forwardRef接收一个render函数,这个函数接收两个props、ref

使用React.createRef不能使用在函数组件上,因为函数组件没有实例。

高阶组件

与复合组件区别:复合组件

一般而言,高阶组件是一个接收一个组件并且返回另外一个组件的函数

主要的作用是让不同组件之间共用逻辑

通常,高阶组件是一个纯函数,没有副作用

注意不要更改原组件

注意配置displayName

不要将高阶组件放在render函数里面,performance以及内部state会消失(React会认为他们是不同的组件)

注意key以及ref的传递

1
2
3
4
5
6
7
8
9
10
11
function logProps(WrappedComponent) {
return class extends React.Component {
//... some method
render () {
const { extraProps, ...passThroughProps } = this.props
return (
<WrappedComponent injectProp={injectProp} {...passThroughProps} /> //传递props以及插入props
)
}
}
}

与mixin区别

mixins-considered-harmful

mixin是通过使用Object.defineProperty来实现多个组件之间方法的共用。
问题:

  1. 破坏了原有组件的封装,不能改变state,或者将state提升的时候很麻烦
  2. 命名冲突,mixin很难去取出或者移除
  3. 增加复杂性:增加越来越多的mixin的时候,引入越来越多的方法,造成代码逻辑复杂,不易维护。

性能

  1. HOC不要在render定义
  2. 使用data-,代替bind来传参数
  3. 注意移除event Listener
  4. 使用production版本
  5. 使用shouldComponentUpdate
  6. key
1
2
3
class CounterButton extends React.PureComponent {
// PureComponent封装了shouldComponentUpdate所需要的逻辑,对props和state进行浅比较
}

jsx

因为jsx解释出来是这样的:

1
2
3
4
5
6
<div id="2">
233
{value}
<Foo />
<></>
</div>

编译后:

1
2
3
4
5
React.createElement('div', 
{id: '2'},
'233',
value,
React.createElement(Foo, null), React.createElement(React.Fragment, null))

所以要在作用域内引入React才能正常工作

1
2
3
// 等价
<MyComponent message="&lt;3" />
<MyComponent message={'<3'} />

children以及直接使用引号的html是没有转义的,因此可以像写HTML那样使用

false,null,undefined,true会被忽略

Protals

React可以在render中将element挂载在其他的节点下:

1
2
3
render() {
return ReactDOM.createPortal(this.props.children, domNode)
}

使用这个API可以正常使用Context之类的功能,因为它作为一个portal存在于React tree中,(无论它处于哪个DOM tree中)

Hook

1
2
3
4
5
6
7
8
import React, { useState } from 'react'

function Test() {
const [count, setCount] = useState(0)
return (
<div>{count}</div>
)
}

Hook:

  1. 很难在组件之间重用一个有状态的逻辑(wrapper hell),不需要改变组件之间的关系重用逻辑
  2. 复杂组件很难被理解
  3. 兼容性

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
2
useEffext(() => document.title = `click ${count} times`, [count])
useEffect(() => {/*do something*/}, []) //空数组说明这个sideEffect不依靠于state,所以不需要在re-render的时候更新

rules

  1. 不要再循环、条件或者嵌套函数中使用hook
  2. 只在React函数组件中使用

React怎么识别出哪个state是哪个useState调用的?

React根据hook被调用的顺序来判断的。(因为每次render的时候他们的顺序都不会发生改变)

前端路由的实现

  1. hash模式,通过改变hash,监听hashchange事件来进行页面跳转
  2. history模式,通过使用history中的新功能:history.putState和history.replaceState来改变URL(通过History模式改变URL不会引起页面的刷新,只会更新浏览器的历史纪录。)如果用户点击后退按钮,会触发popState事件

Vue 和 React 的区别

  1. Vue修改状态要简单一点,React需要使用setState来改变状态,并且需要手动优化
  2. React使用了JSX,完全可以通过JS来控制页面,更加的灵活。Vue使用了模板语法,相比于JSX来说没有那么灵活,但是可以脱离工具链,通过直接编写render函数就能够在浏览器中运行。
阅读全文 »
02月
17
JavaScript

storage

发表于 2019-02-17 • 字数统计 347

浏览器的储存主要包括:cookie,localStorage,sessionStorage,indexDB

阅读全文 »
1234567

everbrez

你能抓到我么?

everbrez
最喜欢的作品
最喜欢的女孩子
最喜欢的游戏
路人女主的养成方法、我的青春恋爱物语果然有问题
加藤惠
Minecraft

博客已萌萌哒运行(●'◡'●)ノ♥

© 2021 Hi, everbrez. 由 Hexo 强力驱动. Theme By Sagiri v0.0.74. 站点地图.

Made with by everbrez.