react 16做了哪些更新
- react作为一个ui库,将前端编程由传统的
命令式
编程转变为声明式
编程,即所谓的数据驱动视图,但如果简单粗暴的操作,比如讲生成的html直接采用innerHtml替换,会带来重绘重排
之类的性能问题。为了尽量提高性能,React团队引入了虚拟dom,即采用js对象来描述dom树,通过对比前后两次的虚拟对象,来找到最小的dom操作(vdom diff),以此提高性能。 - 上面提到的reactDom diff,在react 16之前,这个过程我们称之为
stack reconciler
,它是一个递归的过程,在树很深的时候,单次diff时间过长会造成JS线程持续被占用,用户交互响应迟滞,页面渲染会出现明显的卡顿,这在现代前端是一个致命的问题。所以为了解决这种问题,react 团队对整个架构进行了调整,引入了fiber架构,将以前的stack reconciler替换为fiber reconciler
。采用增量式渲染
。引入了任务优先级(expiration)
和requestIdleCallback
的循环调度算法,简单来说就是将以前的一根筋diff更新,首先拆分成两个阶段:reconciliation
与commit
;第一个reconciliation
阶段是可打断的,被拆分成一个个的小任务(fiber),在每一侦的渲染空闲期做小任务diff。然后是commit阶段,这个阶段是不拆分且不能打断的,将diff节点的effectTag一口气更新到页面上。 - 由于reconciliation是可以被打断的,且存在任务优先级的问题,所以会导致commit前的一些生命周期函数多次被执行, 如componentWillMount、componentWillReceiveProps 和 componetWillUpdate,但react官方已申明这些问题,并将其标记为unsafe,在React17中将会移除
- 由于每次唤起更新是从根节点(RootFiber)开始,为了更好的节点复用与性能优化。在react中始终存
workInprogressTree
(future vdom) 与oldTree
(current vdom)两个链表,两个链表相互引用。这无形中又解决了另一个问题,当workInprogressTree生成报错时,这时也不会导致页面渲染崩溃,而只是更新失败,页面仍然还在
React hooks原理
在React 16前,函数式组件不能拥有状态管理?因为16以前只有类组件有对应的实例,而16以后Fiber 架构的出现,让每一个节点都拥有对应的实例,也就拥有了保存状态的能力。
Hooks的本质就是闭包
和两级链表
。
闭包是指有权访问另一个函数作用域中变量或方法
的函数,创建闭包的方式就是在一个函数内创建闭包函数,通过闭包函数访问这个函数的局部变量, 利用闭包可以突破作用链域的特性,将函数内部的变量和方法
传递到外部。
hooks 链表
一个组件包含的hooks 以链表的形式存储在fiber节点的memoizedState属性上,currentHook链表就是当前正在遍历的fiber节点的。nextCurrentHook 就是即将被添加到正在遍历fiber节点的hooks的新链表
let currentHook: Hook | null = null;
let nextCurrentHook: Hook | null = null;
type Hooks = {
memoizedState: any, // 指向当前渲染节点 Fiber
baseState: any, // 初始化 initialState, 最新的state
baseUpdate: Update | null,
// 当前需要更新的 Update ,每次更新完之后,会赋值上一个 update,方便 react 在渲染错误的边缘,数据回溯
queue: UpdateQueue | null,// 可以让state变化的,即update或dispach产生的update
next: Hook | null, // link 到下一个 hooks
}
state链表
其实state 链表不是hooks独有的,类操作的setState也存在。
- memoizedState,cursor 是存在哪里的?如何和每个函数组件一一对应的?
react 会生成一棵组件树(或Fiber 单链表),树中每个节点对应了一个组件,hooks 的数据就作为组件的一个信息,存储在这些节点上,伴随组件一起出生,一起死亡。 - 为什么只能在函数最外层调用 Hook?
memoizedState 是按 hook定义的顺序来放置数据的,如果 hook 顺序变化,memoizedState 并不会感知到。
- 自定义的 Hook 是如何影响使用它的函数组件的?
共享同一个 memoizedState,共享同一个顺序。 - “Capture Value” 特性是如何产生的?
每一次 ReRender 的时候,都是重新去执行函数组件了,对于之前已经执行过的函数组件,并不会做任何操作。
react setState 异步更新
setState 实现原理
setState 通过一个队列机制来实现 state 更新,当执行 setState() 时,会将需要更新的 state 浅合并后放入 状态队列,而不会立即更新 state,队列机制可以高效的批量更新 state。如果不通过setState,直接修改this.state 的值,则不会放入状态队列,当下一次调用 setState 对状态队列进行合并时,之前对 this.state 的修改将会被忽略,造成无法预知的错误。
setState()有的同步有的异步?
在React中, 如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state 。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。
原因: 在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。
setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
调用风险
当调用 setState 时,实际上是会执行 enqueueSetState
方法,并会对 partialState
及 _pendingStateQueue
队列进行合并操作,最终通过 enqueueUpdate
执行 state 更新。
而 performUpdateIfNecessary
获取 _pendingElement
、 _pendingStateQueue
、_pendingForceUpdate
,并调用 reaciveComponent
和 updateComponent
来进行组件更新。
但,如果在 shouldComponentUpdate
或 componentWillUpdate
方法里调用 this.setState 方法,就会造成崩溃。 这是因为在 shouldComponentUpdate
或 componentWillUpdate
方法里调用 this.setState
时,this._pendingStateQueue!=null
,则 performUpdateIfNecessary
方法就会调用 updateComponent
方法进行组件更新,而 updateComponent
方法又会调用 shouldComponentUpdate
和componentWillUpdate
方法,因此造成循环调用,使得浏览器内存占满后崩溃。
React Fiber
掉帧:在页面元素很多,且需要频繁刷新的场景下,React 15 会出现掉帧的现象,其根本原因,是大量的同步计算任务阻塞了浏览器的 UI 渲染。默认情况下,JS 运算、页面布局和页面绘制都是运行在浏览器的主线程当中,他们之间是互斥的关系。如果 JS 运算持续占用主线程,页面就没法得到及时的更新。当我们调用setState
更新页面的时候,React 会遍历应用的所有节点,计算出差异,然后再更新 UI,整个过程不能被打断。如果页面元素很多,整个过程占用的时机就可能超过 16 毫秒,就容易出现掉帧的现象。
如何解决主线程长时间被 JS 运算?将JS运算切割为多个步骤,分批完成。在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间进行页面的渲染。等浏览器忙完之后,再继续之前未完成的任务。
React 15 及以下版本通过递归的方式进行渲染,使用的是 JS 引擎自身的函数调用栈,它会一直执行到栈空为止。而Fiber
实现了自己的组件调用栈,它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。实现方式是使用了浏览器的requestIdleCallback
window.requestIdleCallback()会在浏览器空闲时期依次调用函数,这就可以让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这些延迟触发但关键的事件产生影响。函数一般会按先进先调用的顺序执行,除非函数在浏览器调用它之前就到了它的超时时间。
React 框架内部的运作可以分为 3 层:
- Virtual DOM 层,描述页面长什么样。
- Reconciler 层,负责调用组件生命周期方法,进行 Diff 运算等。
- Renderer 层,根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative。
Fiber 表征reconciliation阶段所能拆分的最小工作单元,其实指的是一种链表树,它可以用一个纯 JS 对象来表示:
const fiber = {
stateNode: {}, // 节点实例
child: {}, // 子节点
sibling: {}, // 兄弟节点
return: {}, // 表示处理完成后返回结果所要合并的目标,通常指向父节点
};
Reconciler区别
- 以前的 Reconciler 被命名为
Stack Reconciler
。Stack Reconciler 运作的过程是不能被打断的,必须一条道走到黑; - Fiber Reconciler 每执行一段时间,都会将控制权交回给浏览器,可以分段执行;
从Stack Reconciler
到Fiber Reconciler
,源码层面其实就是干了一件递归改循环的事情。
scheduling(调度)
scheduling(调度)是fiber reconciliation的一个过程,主要是进行任务分配,达到分段执行。任务的优先级有六种:
- synchronous,与之前的Stack Reconciler操作一样,同步执行
- task,在next tick之前执行
- animation,下一帧之前执行
- high,在不久的将来立即执行
- low,稍微延迟执行也没关系
- offscreen,下一次render时或scroll时才执行
优先级高的任务(如键盘输入)可以打断优先级低的任务(如Diff)的执行,从而更快的生效。
Fiber Reconciler 在执行过程中,会分为 2 个阶段:
- 阶段一,生成 Fiber 树,得出需要更新的节点信息。这一步是一个渐进的过程,可以被打断。
- 阶段二,将需要更新的节点一次过批量更新,这个过程不能被打断。
阶段一可被打断的特性,让优先级更高的任务先执行,从框架层面大大降低了页面掉帧的概率。
参考:
HOC 与render props区别
Render Props: 把将要包裹的组件作为props属性传入,然后容器组件调用这个属性,并向其传参
实现方式:
- 通过
props.children(props)
,props.children返回的是UI元素。
JSX 标签中的所有内容都会作为一个children
prop 传递给RenderProps
组件。因为RenderProps
将{props.children}
渲染在一个中,被传递的这些子组件最终都会出现在输出结果中。// 定义 const RenderProps = props =>
{props.children(props)}// 调用{() => <>Hello RenderProps>} - 通过props中的任何函数, 自行定义传入内容
// 定义 const LoginForm = props => { const flag = false; const allProps = { flag, ...props }; if (flag) { return <>{props.login(allProps)}> } else { return <>{props.notLogin(allProps)}> } } // 调用
LOGIN
} noLogin={() =>NOT LOGIN
} />优点:
1、支持ES6
2、不用担心props命名问题,在render函数中只取需要的state
3、不会产生无用的组件加深层级
4、render props模式的构建都是动态的,所有的改变都在render中触发,可以更好的利用组件内的生命周期。HOC: 接受一个组件作为参数,返回一个新的组件的函数。
class Home extends React.Component { // UI } export default Connect()(Home);
高阶组件由于每次都会返回一个新的组件,对于react来说,这是不利于diff和状态复用的,所以高阶组件的包装不能在render 方法中进行,而只能像上面那样在组件声明时包裹,这样也就不利于动态传参。
优点:
1、支持ES6
2、复用性强,HOC为纯函数且返回值为组件,可以多层嵌套
3、支持传入多个参数,增强了适用范围
缺点:
1、当多个HOC一起使用时,无法直接判断子组件的props是哪个HOC负责传递的
2、多个组件嵌套,容易产生同样名称的props
3、HOC可能会产生许多无用的组件,加深了组件的层级总的来说,render props其实和高阶组件类似,就是在puru component上增加state,响应react的生命周期。
React 通信
react的数据流是单向的,最常见的就是通过props由父组件向子组件传值。
- 父向子通信: 传入props
- 子向父通信:父组件向子组件传一个函数,然后通过这个函数的回调,拿到子组件传过来的值
- 父向孙通信:利用context传值。
React.createContext()
- 兄弟间通信:
1、找一个相同的父组件,既可以用props传递数据,也可以用context的方式来传递数据。
2、用一些全局机制去实现通信,比如redux等
3、发布订阅模式react合成事件
React 合成事件(SyntheticEvent)是 React 模拟原生 DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。
为什么要使用合成事件?
- 进行浏览器兼容,实现更好的跨平台
React 采用的是顶层事件代理机制,能够保证冒泡一致性,可以跨浏览器执行。React 提供的合成事件用来抹平不同浏览器事件对象之间的差异,将不同平台事件模拟合成事件。 - 避免垃圾回收
事件对象可能会被频繁创建和回收,因此 React 引入事件池,在事件池中获取或释放事件对象。即 React 事件对象不会被释放掉,而是存放进一个数组中,当事件触发,就从这个数组中弹出,避免频繁地去创建和销毁(垃圾回收)。 - 方便事件统一管理和事务机制
实现原理
在 React 中,“合成事件”会以事件委托方式绑定在document
对象上,并在组件卸载(unmount)阶段自动销毁绑定的事件。合成事件和原生事件
当真实 DOM 元素触发事件,会冒泡到
document
对象后,再处理 React 事件;所以会先执行原生事件,然后处理 React 事件;最后真正执行document
上挂载的事件。合成事件和原生事件最好不要混用。 原生事件中如果执行了
stopPropagation
方法,则会导致其他React
事件失效。因为所有元素的事件将无法冒泡到document
上,所有的 React 事件都将无法被注册。合成事件的事件池
合成事件对象池,是 React 事件系统提供的一种性能优化方式。合成事件对象在事件池统一管理,不同类型的合成事件具有不同的事件池。
react与vue区别
1. 监听数据变化的实现原理不同
Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化。
React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。2. 数据流不同
Vue1.0中可以实现两种双向绑定:父子组件之间props可以双向绑定;组件与DOM之间可以通过v-model双向绑定。
Vue2.x中父子组件之间不能双向绑定了(但是提供了一个语法糖自动帮你通过事件的方式修改)。
React一直不支持双向绑定,提倡的是单向数据流,称之为onChange/setState()模式。3. HoC和mixins
Vue组合不同功能的方式是通过mixin,Vue中组件是一个被包装的函数,并不简单的就是我们定义组件的时候传入的对象或者函数。
React组合不同功能的方式是通过HoC(高阶组件)。4. 模板渲染方式的不同
模板的语法不同,React是通过JSX渲染模板, Vue是通过一种拓展的HTML语法进行渲染。
模板的原理不同,React通过原生JS实现模板中的常见语法,比如插值,条件,循环等。而Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的,比如 v-if 。举个例子,说明React的好处:react中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以我们import 一个组件完了之后,还需要在 components 中再声明下。
5. 渲染过程不同
Vue可以更快地计算出Virtual DOM的差异,这是由于它会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
React当状态被改变时,全部子组件都会重新渲染。通过shouldComponentUpdate这个生命周期方法可以进行控制,但Vue将此视为默认的优化。6. 框架本质不同
Vue本质是MVVM框架,由MVC发展而来;
React是前端组件化框架,由后端组件化发展而来。性能优化
1. 静态资源使用 CDN
CDN是一组分布在多个不同地理位置的 Web 服务器。当服务器离用户越远时,延迟越高。
2. 无阻塞
头部内联的样式和脚本会阻塞页面的渲染,样式放在头部并使用link方式引入,脚本放在尾部并使用异步方式加载
3. 压缩文件
压缩文件可以减少文件下载时间。
- 在 webpack 可以使用如下插件进行压缩:
- JavaScript:UglifyPlugin
- CSS :MiniCssExtractPlugin
- HTML:HtmlWebpackPlugin
- 使用 gzip 压缩。通过向 HTTP 请求头中的 Accept-Encoding 头添加 gzip 标识来开启这一功能。
4. 图片优化
- 图片懒加载
- 响应式图片:浏览器根据屏幕大小自动加载合适的图片。
- 降低图片质量:方法有两种,一是通过 webpack 插件
image-webpack-loader
,二是通过在线网站进行压缩。
5. 减少重绘重排
- 降低 CSS 选择器的复杂性
- 使用 transform 和 opacity 属性更改来实现动画
- 用 JavaScript 修改样式时,最好不要直接写样式,而是替换 class 来改变样式。
- 如果要对 DOM 元素执行一系列操作,可以将 DOM 元素脱离文档流,修改完成后,再将它带回文档。推荐使用隐藏元素(display:none)或文档碎片(DocumentFragement),都能很好的实现这个方案。
6. 使用 requestAnimationFrame 来实现视觉变化
7. webpack 打包, 添加文件缓存
index.html
设置成no-cache
,这样每次请求的时候都会比对一下index.html
文件有没变化,如果没变化就使用缓存,有变化就使用新的index.html
文件。
其他所有文件一律使用长缓存,例如设置成缓存一年maxAge: 1000 * 60 * 60 * 24 * 365
。
前端代码使用 webpack 打包,根据文件内容生成对应的文件名,每次重新打包时只有内容发生了变化,文件名才会发生变化。- max-age: 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。在这个时间前,浏览器读取文件不会发出新请求,而是直接使用缓存。
- 指定 no-cache 表示客户端可以缓存资源,每次使用缓存资源前都必须重新验证其有效性
输入url后发生了什么
- DNS域名解析;
- 建立TCP连接(三次握手);
- 发送HTTP请求;
- 服务器处理请求;
- 返回响应结果;
- 关闭TCP连接(四次握手);
- 浏览器解析HTML;
- 浏览器布局渲染;
1. DNS域名解析: 拿到服务器ip
客户端收到你输入的域名地址后,它首先去找本地的hosts文件,检查在该文件中是否有相应的域名、IP对应关系,如果有,则向其IP地址发送请求,如果没有,再去找DNS服务器。
2. 建立TCP链接: 客户端链接服务器
TCP提供了一种可靠、面向连接、字节流、传输层的服务。对于客户端与服务器的TCP链接,必然要说的就是『三次握手』。“3次握手”的作用就是
双方都能明确自己和对方的收、发能力是正常的
。客户端发送一个带有SYN标志的数据包给服务端,服务端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息,最后客户端再回传一个带ACK标志的数据包,代表握手结束,连接成功。
SYN —— 用于初如化一个连接的序列号。
ACK —— 确认,使得确认号有效。
RST —— 重置连接。
FIN —— 该报文段的发送方已经结束向对方发送数据。客户端:“你好,在家不。” -- SYN
服务端:“在的,你来吧。” -- SYN + ACK
客户端:“好嘞。” -- ACK3. 发送HTTP请求
4. 服务器处理请求
5. 返回响应结果
6. 关闭TCP连接(需要4次握手)
为了避免服务器与客户端双方的资源占用和损耗,当双方没有请求或响应传递时,任意一方都可以发起关闭请求。
关闭连接时,服务器收到对方的FIN报文时,仅仅表示客户端不再发送数据了但是还能接收数据,而服务器也未必全部数据都发送给客户端,所以服务器可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
客户端:“兄弟,我这边没数据要传了,咱关闭连接吧。” -- FIN + seq
服务端:“收到,我看看我这边有木有数据了。” -- ACK + seq + ack
服务端:“兄弟,我这边也没数据要传你了,咱可以关闭连接了。” - FIN + ACK + seq + ack
客户端:“好嘞。” -- ACK + seq + ack7. 浏览器解析HTML
浏览器需要加载解析的不仅仅是HTML,还包括CSS、JS,以及还要加载图片、视频等其他媒体资源。
浏览器通过解析HTML,生成DOM树,解析CSS,生成CSSOM树,然后通过DOM树和CSSPOM树生成渲染树。渲染树与DOM树不同,渲染树中并没有head、display为none等不必显示的节点。
浏览器的解析过程并非是串连进行的,比如在解析CSS的同时,可以继续加载解析HTML,但在解析执行JS脚本时,会停止解析后续HTML,会出现阻塞问题。
8. 浏览器渲染页面
根据渲染树布局,计算CSS样式,即每个节点在页面中的大小和位置等几何信息。HTML默认是流式布局的,CSS和js会打破这种布局,改变DOM的外观样式以及大小和位置。最后浏览器绘制各个节点,将页面展示给用户。
replaint:屏幕的一部分重画,不影响整体布局,比如某个CSS的背景色变了,但元素的几何尺寸和位置不变。
reflow: 意味着元素的几何尺寸变了,需要重新计算渲染树。
参考:
Babel Plugin与preset区别
Babel是代码转换器,比如将ES6转成ES5,或者将JSX转成JS等。借助Babel,开发者可以提前用上新的JS特性。
原始代码 --> [Babel Plugin] --> 转换后的代码
Plugin
实现Babel代码转换功能的核心,就是Babel插件(plugin)。Babel插件一般尽可能拆成小的力度,开发者可以按需引进, 既提高了性能,也提高了扩展性。比如对ES6转ES5的功能,Babel官方拆成了20+个插件。开发者想要体验ES6的箭头函数特性,那只需要引入
transform-es2015-arrow-functions
插件就可以,而不是加载ES6全家桶。Preset
可以简单的把Babel Preset视为Babel Plugin的集合。想要将所有ES6的代码转成ES5,逐个插件引入的效率比较低下, 就可以采用Babel Preset。比如
babel-preset-es2015
就包含了所有跟ES6转换有关的插件。Plugin与Preset执行顺序
可以同时使用多个Plugin和Preset,此时,它们的执行顺序非常重要。
- 先执行完所有Plugin,再执行Preset。
- 多个Plugin,按照声明次序顺序执行。
- 多个Preset,按照声明次序逆序执行。
比如
.babelrc
配置如下,那么执行的顺序为:- Plugin:transform-react-jsx、transform-async-to-generator
- Preset:es2016、es2015
{ "presets": [ "es2015", "es2016" ], "plugins": [ "transform-react-jsx", "transform-async-to-generator" ] }
webpack hash区别
hash一般是结合CDN缓存来使用,通过webpack构建之后,生成对应文件名自动带上对应的MD5值。如果文件内容改变的话,那么对应文件哈希值也会改变,对应的HTML引用的URL地址也会改变,触发CDN服务器从源服务器上拉取对应数据,进而更新本地缓存。
- hash
hash是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改。同一次构建过程中生成的哈希都是一样的。
output:{ path:path.join(__dirname, '/dist'), filename: 'bundle.[name].[hash].js', }
- chunkhash
根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响。
output:{ path:path.join(__dirname, '/dist/js'), filename: 'bundle.[name].[chunkhash].js', }
采用chunkhash,项目主入口文件Index.js及其对应的依赖文件Index.css由于被打包在同一个模块,共用相同的chunkhash。由于公共库是不同的模块,有单独的chunkhash。所以Index文件的更改不会影响公共库。如果index.js更改了代码,css未改变,由于该模块发生了改变,导致css文件会重复构建。
- contenthash
根据文件内容创建出唯一 hash。当文件内容发生变化时,[contenthash] 才会发生变化。
output: { filename: '[name].[contenthash].js', chunkFilename: '[name].[contenthash].js', path: path.resolve(__dirname, '../dist'), }
typeof 如何判断的
js 在底层存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息:
对象: 000
浮点数: 010
字符串: 100
布尔: 110
整数: 1
null:所有机器码均为0
undefined:用 −2^30 整数来表示// JavaScript 诞生以来便如此 typeof null === 'object';
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于
null
代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null
也因此返回"object"
。怎样开发和部署前端代码
为了进一步提升网站性能,会把静态资源和动态网页分集群部署,静态资源会被部署到CDN节点上,网页中引用的资源也会变成对应的部署路径。当需要更新静态资源的时候,同时也会更新html中的引用。
如果同时改了页面结构和样式,也更新了静态资源对应的url地址,现在要发布代码上线,是先上线页面,还是先上线静态资源?
- 先部署页面,再部署资源:在二者部署的时间间隔内,如果有用户访问页面,就会在新的页面结构中加载旧的资源,并且把这个旧版本的资源当做新版本缓存起来,其结果就是:用户访问到了一个样式错乱的页面,除非手动刷新,否则在资源缓存过期之前,页面会一直执行错误。
- 先部署资源,再部署页面:在部署时间间隔之内,有旧版本资源本地缓存的用户访问网站,由于请求的页面是旧版本的,资源引用没有改变,浏览器将直接使用本地缓存,这种情况下页面展现正常;但没有本地缓存或者缓存过期的用户访问网站,就会出现旧版本页面加载新版本资源的情况,导致页面执行错误,但当页面完成部署,这部分用户再次访问页面又会恢复正常了。
这个奇葩问题,起源于资源的 覆盖式发布,用 待发布资源 覆盖 已发布资源,就有这种问题。解决它也好办,就是实现 非覆盖式发布。用文件的摘要信息来对资源文件进行重命名,把摘要信息放到资源文件发布路径中,这样,内容有修改的资源就变成了一个新的文件发布到线上,不会覆盖已有的资源文件。上线过程中,先全量部署静态资源,再灰度部署页面,整个问题就比较完美的解决了。
大公司的静态资源优化方案,基本上要实现这么几个东西:
- 配置超长时间的本地缓存 —— 节省带宽,提高性能
- 采用内容摘要作为缓存更新依据 —— 精确的缓存控制
- 静态资源CDN部署 —— 优化网络请求
- 更改资源发布路径实现非覆盖式发布 —— 平滑升级
大数相加
function add(a, b){ const maxLength = Math.max(a.length, b.length); a = a.padStart(maxLength, 0); b = b.padStart(maxLength, 0); let t = 0; let f = 0; let sum = ""; for (let i = maxLength - 1; i >= 0; i--) { t = parseInt(a[i]) + parseInt(b[i]) + f; f = Math.floor(t / 10); sum = `${t % 10}${sum}`; } if (f === 1){ sum = "1" + sum; } return sum; }
斐波那契数列求和
function fib(n) { if (n <= 0) { return 0; } let n1 = 1; let n2 = 1; let sum = 1; for(let i = 3; i <= n; i++) { [n1, n2] = [n2, sum]; sum = n1 + n2; } return sum; };