Skip to content

Latest commit

 

History

History
214 lines (138 loc) · 15.8 KB

深入.md

File metadata and controls

214 lines (138 loc) · 15.8 KB

深入理解

JS

加载第三方 JS

从网站开发者的角度来看, 第三方 JS 相比第一方 JS 有如下几个不同之处:

  • 下载速度不可控
  • JS 地址域名与网站域名不同
  • 文件内容不可控
  • 不一定有强缓存(Cache-Control/Expires)

所以要考虑下如何在有很多第三方 JS 的情况下, 保证他们不影响到网站自己的加载速度. 我们可以异步加载这些第三方 JS 代码.

  • 异步加载 JS,动态创建一个 script 标签, 然后设置其 src 和 async 属性, 再插入到页面中

观察者模式 vs 发布-订阅模式

  • 在观察者模式中, 观察者是知道 Subject 的, Subject 一直保持对观察者进行记录. 然而, 在发布订阅模式中, 发布者和订阅者不知道对方的存在. 它们只有通过消息代理进行通信.
  • 在发布订阅模式中, 组件是松散耦合的, 正好和观察者模式相反.
  • 观察者模式大多数时候是同步的, 比如当事件触发, Subject 就会去调用观察者的方法. 而发布-订阅模式大多数时候是异步的(使用消息队列).
  • 观察者 模式需要在单个应用程序地址空间中实现, 而发布-订阅更像交叉应用模式.

浏览器和 Node 事件循环的区别

分 Node11 之前和 Node11 之后,在 Node11 之后浏览器事件和 Node 事件是一样的了

  • 每执行一个宏任务就执行完微任务队列
  • 常见的 task(宏任务) 比如:setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染等.
  • 常见的 micro-task 比如: new Promise().then(回调)、MutationObserver(html5 新特性) 等.

React

diff 算法

由数据驱动的 DOM 节点映射问题,是 diff 算法主要解决的问题.

由于 React render方法返回的不是真正的 DOM 节点, 而是轻量级的 Javascript 对象, 简称虚拟 DOM,React 就是使用这些虚拟 DOM 来计算出需要实现 UI 更新所需要的最少 DOM 操作.

一般采用这几种方法:

  • 同层对比,React 只会逐层对比两个虚拟树,即 root 节点和 root 节点对比,child1 和 child2 节点对比(Element Diff).
  • 通过给节点key属性来快速找到节点(Tree Diff).
  • React 应用由很多自定义组件组成,最终转化为了div或其他标签的虚拟树,diff 算法只对相同组件类型的组件进行匹配,即如果一个menu组件被一个header组件替换,React 不会对比而是直接删除在创建(整个删除+替换)(Component Diff).

算法首先会对新旧两棵树进行一个深度优先的遍历, 这样每个节点都会有一个序号. 在深度遍历的时候, 每遍历到一个节点, 我们就将这个节点和新的树中的节点进行比较, 如果有差异, 则将这个差异记录到一个对象中.

在对列表元素进行对比的时候, 由于 TagName 是重复的, 所以我们不能使用这个来对比. 我们需要给每一个子节点加上一个 key, 列表对比的时候使用 key 来进行比较, 这样我们才能够复用老的 DOM 树上的节点.

参考React Diff 算法

React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) , 那么 O(n^3) 和 O(n) 是如何计算出来的

精读《DOM diff 原理详解》

由于左树中任意节点都可能出现在右树, 所以必须在对左树深度遍历的同时, 对右树进行深度遍历, 找到每个节点的对应关系, 这里的时间复杂度是 O(n²), 之后需要对树的各节点进行增删移的操作, 这个过程简单可以理解为加了一层遍历循环, 因此再乘一个 n.

如何减少 diff 的复杂度,就只能减少对比情况,一般为:

  • 同层级节点对比时,如果左树节点缺少,右树节点则直接删除;如果右树节点缺少,则右树节点增加.

React Fiber 做了什么

为了给⽤户制造⼀种应⽤很快的'假象', 我们不能让⼀个程序⻓期霸占着资源. 你可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执⾏视作操作系统的'进程', 我们需要通过某些调度策略合理地分配 CPU 资源, 从⽽提⾼浏览器的⽤户响应速率, 同时兼顾任务执⾏效率.

Fiber类似于 es6 的Generator函数,是一种协程(控制流程的让出机制).

Fiber 的架构有两个主要阶段:渲染(render)提交(commit).

渲染阶段,主要是得到由 jsx 装换的fiber节点树,并表示需要在下一阶段执行的副作用(Effect),副作用可以是,改变 DOM 或调用生命周期方法,或是state 和 props 更新.

渲染阶段是可以异步执行的,具体操作如下:

  • React 渲染的过程可以被中断, 可以将控制权交回浏览器, 让位给⾼优先级的任务, 浏览器空闲后再恢复渲染.
  • 把渲染更新过程拆分成多个子任务, 每次只做一小部分, 做完看是否还有剩余时间, 如果有继续下一个任务;如果没有, 挂起当前任务, 将时间控制权交给主线程, 等主线程不忙的时候在继续执行. 这种策略叫做 Cooperative Scheduling(合作式调度), 操作系统常用任务调度策略之一.
  • 通过深度遍历搜索(DFS)来遍历链表结构的虚拟 DOM 节点.

而提交阶段是同步执行的,因为在此阶段,需要执行 Fiber 的副作用,例如 DOM 的更新,是用户可见的变化.

参考:

React Hook 相关

  • useState适合少量状态定义,即在一个 hook 组件内少量定义,少量定义的好处在于可以容易观察代码变化,对于复杂的状态,可以考虑状态管理

  • useContext,Context 在使用时需要注意,当 Provider 的值发生变化时,调用它的(一般是子组件)组件会触发重渲染,所以一旦定义的状态比较多时,每个状态的改变都会使订阅它的组件重渲染. 避免的方法是,定义多个 Context 并只存储少量数据,或者第三方库

  • useCallback,接收一个内联回调函数参数和一个依赖项数组(子组件依赖父组件的状态, 即子组件会使用到父组件的值) , useCallback 会返回该回调函数的 memoized 版本, 该回调函数仅在某个依赖项改变时才会更新

  • useMemo,把创建函数和依赖项数组作为参数传入 useMemo, 它仅会在某个依赖项改变时才重新计算 memoized 值. 这种优化有助于避免在每次渲染时都进行高开销的计算

  • useEffect,接收一个函数, 该函数会在组件渲染到屏幕之后才执行, 该函数有要求:要么返回一个能清除副作用的函数, 要么就不返回任何内容.

    每次渲染都有自己的 Props , State, Effects,所以在当前的渲染下,Effect 拿到的是定义它的那次渲染中的 props 和 state,不是上次渲染改变的.

React 受控组件和非受控组件

在 HTML 的表单元素中, 它们通常自己维护一套 state, 并随着用户的输入自己进行 UI 上的更新, 这种行为是不被我们程序所管控的. 而如果将 React 里的 state 属性和表单元素的值建立依赖关系, 再通过 onChange 事件与 setState()结合更新 state 属性, 就能达到控制用户输入过程中表单发生的操作. 被 React 以这种方式控制取值的表单输入元素就叫做受控组件.

高阶组件

高阶组件是把组件作为参数传入到组件中并返回一个新的组件, 在 React 中一般用来复用组件逻辑

其他

TCP 三次握手和四次挥手

三次是最少的安全次数, 两次不安全, 四次浪费资源

  • 第一次 client => server 只能 server 判断出 client 具备发送能力
  • 第二次 server => client client 就可以判断出 server 具备发送和接受能力. 此时 client 还需让 server 知道自己接收能力没问题于是就有了第三次
  • 第三次 client => server 双方均保证了自己的接收和发送能力没有问题
  • MSL(Maximum Segment Lifetime), 译为“报文最大生存时间”. RFC 793 中规定 MSL 为 2 分钟, 实际应用中常用的是 30 秒, 1 分钟和 2 分钟等.
  • 为什么是 2MSL, 即两倍的 MSL, TCP 的 TIME_WAIT 状态也称为 2MSL 等待状态.

性能优化

参考文章:

性能 浏览器缓存机制:强缓存和协商缓存

  • 静态处理

    • webpack:压缩, 合并, MD5

    • http 缓存

      • 强缓存:在用户第一次访问页面后进行缓存,在过期时间之前不会再去请求.

        expires:设置一个过期时间, 缺点: 浏览器时间和服务端时间不一致 cache-control:设置一个时间的长度(在当前时间基础上, 再过多长时间过期), 解决了 expires 的问题;

      • 协商缓存:发送请求时携带缓存标识,服务器根据缓存标识决定是否使用缓存的过程.

        协商缓存生效, 返回 304, 服务器告诉浏览器资源未更新, 则再去浏览器缓存中访问资源 协商缓存失效, 返回 200 和请求结果

        etag:请求带上 if-none-match 一个 由文件内容生成的 hash 值, 判断的是文件内容 last-modified:请求 if-modified-since, 服务端对比时间, 是否一样, 一样说明文件没有修改过

    • 离线缓存

      • localstorage:同步 sessionstorage indexDb:异步 websql:异步 作用: 缓存我们的业务代码 basket.js 资源通过 ajax 请求, 然后帮你缓存起来 localForage 包装了 localstorage, indexDb, websql 的缓存 Service Worker 基于 web work 的 web work: 可以新开启一个线程, 可以干一些计算密集型的操作, 不会阻塞主线程. 任务执行完了之后会发一个信号给主线程. 但是不能操作 dom 后端同时更改一个资源, 有锁的机制. Service Worker:也是在另外一个线程做事. 可以拦截请求
  • 渲染优化

    • 解析 HTML -> 解析 CSS -> 结合 HTML CSS 生成位置进行渲染 -> 生成布局在浏览器中渲染 -> 对每个节点进行重绘
    • 减少重排次数,对节点的操作应该在最底层
    • 使用 CSS3 操作

前端工程化

工程化解决的是小团队在不断的发展中,遇到的不规范或是选型失败带来的开发和项目实现中的困难.

工程化由几个大的过程组成,分别是:

  • 制订规范化的前端工作流, 并规范统一项目的模块化开发和前端资源, 让代码的维护和互相协作更加容易更加方便
  • 复用团队经验
  • 关注新技术的发展,并尝试与现有项目进行实验

对于工程的生命周期,大体是由四个阶段: 开发 -> 测试 -> 部署 -> 维护,并通过系统化、严格约束、可量化的方法使之成为工程化.

从输入 URL 到页面加载完成的过程中都发生了什么事情

  • 输入地址后,浏览器首先对地址进行检查,判断协议类型,如果是别的协议比如file就展示文件,如果是http就开始安全检查,然后发送请求
  • 请求的发送有两方面组成,一个是DNS查询,一个是Socket发送数据
    • DNS 查询经历了 `缓存查询 -> 系统 DNS 查询 -> ISP DNS 缓存 -> 根域名服务缓存
    • 发送时,HTTP 链接一般使用TCP协议,经过三次握手请求(注意 TCP 协议需要保持顺序,并且 TCP 协议没有 IP,IP 地址是由上一层的IP协议定义的)
  • 请求到达之后,一般先进入服务器的负载均衡机器,可以分发或防御一些恶意请求,缓存或监控
  • 到达Web Server后,由后端处理相应的请求,然后返回结果
  • 浏览器接收后,首先处理返回的状态码,根据不同的状态码处理不同的结果,然后对返回结果压缩,然后根据响应资源的MIME类型解析内容
  • HTML 解析后,CSS 解析,然后构建树,进行重排重绘等一系列操作,在进行 JS 编译工作,然后执行

参考: 从输入 URL 到页面加载完成的过程中都发生了什么事情?

参考: BAT 高频面试题:浏览器输入 URL 回车之后发生了什么?

什么是 MVVM 比之 MVC 有什么区别 什么又是 MVP

MVC、MVP 和 MVVM 是三种常见的软件架构设计模式, 主要通过分离关注点的方式来组织代码结构, 优化我们的开发效率.

比如说我们实验室在以前项目开发的时候, 使用单页应用时, 往往一个路由页面对应了一个脚本文件, 所有的页面逻辑都在一个脚本文件里. 页面的渲染、数据的获取, 对用户事件的响应所有的应用逻辑都混合在一起, 这样在开发简单项目时, 可能看不出什么问题, 当时一旦项目变得复杂, 那么整个文件就会变得冗长, 混乱, 这样对我们的项目开发和后期的项目维护是非常不利的.

MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构. 其中 View 负责页面的显示逻辑, Model 负责存储页面的业务数据, 以及对相应数据的操作. 并且 View 和 Model 应用了观察者模式, 当 Model 层发生改变的时候它会通知有关 View 层更新页面. Controller 层是 View 层和 Model 层的纽带, 它主要负责用户与应用的响应操作, 当用户与页面产生交互的时候, Co ntroller 中的事件触发器就开始工作了, 通过调用 Model 层, 来完成对 Model 的修改, 然后 Model 层再去通知 View 层更新.

MVP 模式与 MVC 唯一不同的在于 Presenter 和 Controller. 在 MVC 模式中我们使用观察者模式, 来实现当 Model 层数据发生变化的时候, 通知 View 层的更新. 这样 View 层和 Model 层耦合在一起, 当项目逻辑变得复杂的时候, 可能会造成代码的混乱, 并且可能会对代码的复用性造成一些问题. MVP 的模式通过使用 Presenter 来实现对 View 层和 Model 层的解耦. MVC 中的 Controller 只知道 Model 的接口, 因此它没有办法控制 View 层的更新, MVP 模式中, View 层的接口暴露给了 Presenter 因此我们可以在 Presenter 中将 Model 的变化和 View 的变化绑定在一起, 以此来实现 View 和 Model 的同步更新. 这样就实现了对 View 和 Model 的解耦, Presenter 还包含了其他的响应逻辑.

MVVM 模式中的 VM, 指的是 ViewModel, 它和 MVP 的思想其实是相同的, 不过它通过双向的数据绑定, 将 View 和 Model 的同步更新给自动化了. 当 Model 发生变化的时候, ViewModel 就会自动更新;ViewModel 变化了, View 也会更新. 这样就将 Presenter 中的工作给自动化了. 我了解过一点双向数据绑定的原理, 比如 vue 是通过使用数据劫持和发布订阅者模式来实现的这一功 能.

如何跨标签页通信

参考前端跨页面通信