react中的异步实现

事件循环

由于 JS 是单线程非阻塞的,所有的任务都需要在这个线程中来处理。当需要执行异步任务时主线程会先挂起这个任务,当异步任务执行完毕之后主线程会根据规则执行回调。当任务处理完毕之后, JS 会将这个事件加入队列中,这个队列即被称为事件队列。

进程和线程

  • 进程: CPU 在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序。
  • 线程: 进程中的更小单位,描述了执行一段指令所需的时间。

在浏览器中,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。

JS 运行的时候会阻止 UI 渲染,这是因为 JS 可以修改 DOM,如果在 JS 执行的时候 UI 线程还在工作,就可能导致不能安全的渲染 UI。这其实也是一个单线程的好处,得益于 JS 是单线程运行的,可以达到节省内存,节约上下文切换时间,没有锁的问题的好处。

对于锁的问题,形象的来说就是当我读取一个数字 15 的时候,同时有两个操作对数字进行了加减,这时候结果就出现了错误。解决这个问题也不难,只需要在读取的时候加锁,直到读取完毕之前都不能进行写入操作。

执行栈

所有的 JS 代码在运行时都是在执行上下文中进行的。JS 中有三种执行上下文:

  • 全局执行上下文,默认的,浏览器中是 window 对象,nodejs 中是 global,并且 this 在非严格模式下指向它们。
  • 函数执行上下文,JS 的函数每当被调用时会创建一个上下文。
  • Eval 执行上下文,eval 函数会产生自己的上下文。

栈是一种数据结构,具有先进后出的原则。JS 中的执行栈就具有这样的结构,当引擎第一次遇到 JS 代码时,会产生一个全局执行上下文并压入执行栈,每遇到一个函数调用,就会往栈中压入一个新的上下文。引擎执行栈顶的函数,执行完毕,弹出当前执行上下文。

微任务和宏任务

异步任务被分为两种类型,微任务和宏任务

微任务
  • Promise.then
  • MutationObserver
  • process.nextTick
宏任务
  • script(整体代码)
  • setTimout
  • setInterval
  • setImmediate
  • MessageChannel
  • requestAnimationFrame
  • postMessage
  • I/O
  • UI 交互事件

事件循环

Event Loop(事件循环)中,每一次循环称为 tick, 每一次 tick 的任务如下:

  1. 执行栈选择最先进入队列的宏任务(通常是 script 整体代码),如果有则执行
  2. 检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列
  3. 更新 render(每一次事件循环,浏览器都可能会去更新渲染)
  4. 重复以上步骤

宏任务 > 所有微任务 > 宏任务,如下图所示:

事件循环

  1. 将所有任务看成两个队列:执行队列与事件队列。
  2. 执行队列是同步的,事件队列是异步的,宏任务放入事件列表,微任务放入执行队列之后,事件队列之前。
  3. 当执行完同步代码之后,就会执行位于执行列表之后的微任务,然后再执行事件列表中的宏任务

react 中的异步实现

react 中的异步是通过宏任务来实现的,优先使用 setImmediate ,然后使用 MessageChannel,若都不支持则使用 setTimeout 实现

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
30
31
let schedulePerformWorkUntilDeadline
if (typeof localSetImmediate === 'function') {
// Node.js and old IE.
// There's a few reasons for why we prefer setImmediate.
//
// Unlike MessageChannel, it doesn't prevent a Node.js process from exiting.
// (Even though this is a DOM fork of the Scheduler, you could get here
// with a mix of Node.js 15+, which has a MessageChannel, and jsdom.)
// https://github.com/facebook/react/issues/20756
//
// But also, it runs earlier which is the semantic we want.
// If other browsers ever implement it, it's better to use it.
// Although both of these would be inferior to native scheduling.
schedulePerformWorkUntilDeadline = () => {
localSetImmediate(performWorkUntilDeadline)
}
} else if (typeof MessageChannel !== 'undefined') {
// DOM and Worker environments.
// We prefer MessageChannel because of the 4ms setTimeout clamping.
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = performWorkUntilDeadline
schedulePerformWorkUntilDeadline = () => {
port.postMessage(null)
}
} else {
// We should only fallback here in non-browser environments.
schedulePerformWorkUntilDeadline = () => {
localSetTimeout(performWorkUntilDeadline, 0)
}
}
  1. 判断是否存在 setImmediate,如果存在则使用 setImmediate,不存在下一步
  2. 判断是否支持 MessageChannel,如果支持则创建一个消息通道,通过消息通道实现宏任务,否则进行下一步
  3. 回落到 setTimeout 实现异步

说明

  1. 优先选择 MessageChannel 而不是 setTimeout 的原因是由于 setTimeout 有一个最小 4ms 的等待时间,在无宏任务执行时,这个时间会浪费掉
  2. 不选择微任务的原因是,使用微任务的方式时,当前任务任然占据了主线程而没有释放出来,达不到把主线程还给渲染线程的目的
edge 浏览器测试参考

事件循环

参考

前端框架

DOM 操作时代

代表库 jQueryzepto

  • 简化选择器
  • 简化 DOM 操作
  • 简化 AJAX 操作
  • 统一事件绑定
  • 兼容性实现
  • 延时对象($.Deferred

DOM 操作的一般流程

  1. DOM 加载
  2. js 脚本加载
  3. js脚本执行,发送异步请求获取数据
  4. 操作 DOM 进行数据渲染
  5. DOM 节点进行事件绑定

DOM 操作流程

MV* 交互模式

MVC 模式

DOM 交互部分的内容分为数据模型、视图和事件控制三部分

  • Model:存放请求的数据结果和数据对象
  • View: 页面 DOM 的更新与修改
  • Controller: 根据前端路由条件调用不同的 ModelView渲染不同的数据内容

用户的操作直接通过 Controller 控制

MVP 模式

Model-View-Presenter,将 DOM 交互部分的内容分为数据模型、视图和 Presenter 三部分

  • Model:存放请求的数据结果和数据对象(仅提供数据)
  • View: 页面 DOM 的更新与修改(仅提供视图模板)
  • Presenter: 根据前端路由条件调用不同的 ModelView渲染不同的数据内容(主要的逻辑处理)

PresenterView 的绑定是双向的,Presenter 的改变会改变 ViewView 的改变也会触发 Presenter,所有的逻辑调用数据和渲染视图 View 模板都在 Presenter 中完成,同时用户在 View 层操作的改变反馈到 Presenter 改变 Model并渲染新的 View 视图。

  • 优点:只需关注 Presenter 的逻辑
  • 缺点:内容较重

MVVM 模式

自动化的 MVP 框架,用 ViewModel 代替 PresenterModel 的调用和 ViewViewModel 自动触发完成
用户操作时, ViewModel 捕获数据变化,将变化反应到 View 上。 ViewModel 的数据操作最终在页面上以 Directive 的形式体现,通过对 Directive 的识别来渲染数据或绑定事件。

通用基本设计
  • Directive: 指令,即自定义执行函数
  • Filter: 过滤器,对传入的初始数据信进行处理,然后把处理结果交给下一步(DirectiveFilter)。
  • 表达式:控制页面内容按照具体条件展示
  • ViewModel: 实现传入的 Model 数据在内存中存放,也提供一些基本的读取或修改数据的 API
  • 数据变更检查: 即数据变化自动触发其它操作,通过手动触发、脏检测、对象劫持、Proxy
数据变更检查
  • 手动触发: 通过在数据对象上定义 getset 方法,调用时手动触发 getset 来获取和修改数据,改变后主动触发 getsetView 层的重新渲染功能
  • 脏检测: 在 ViewModel 对象的某个属性值发生变化时找到与这个属性值相关的所有元素,然后比较数据变化,如果有变化则进行 Directive 指令调用,对这个元素进行重新扫描渲染
  • 前端数据对象劫持: 使用 Object.definePropertyObject.definePropertiesViewModel 数据对象进行属性 getset 的监听,当有数据读取和赋值时则扫描节点元素,运行指定对应节点的 Directive 指令。
  • ES6 Proxy:类似与 Object.definePropertyObject.defineProperties

与native交互

Hybird App

Native App 引用基础上结合了 Web App 应用所形成的模式,一般通过 webview 的模式引入

特点

  • 系统资源较少,包括 CPU 、内存、网卡、网络链接等。
  • 支持更新的浏览器特性,不用考虑 IE 的兼容性问题
  • 可以实现离线应用,通过新的浏览器特性或 Native 的文件读取机制进行文件级的文件缓存和离线更新
  • 不同机型设备的兼容问题
  • 可以调用客户端 Native 的能力,例如摄像头、定位、传感器、本地文件访问等。

WebNative 的协议调用

通过 URI 请求

在系统中注册一个 Schema 协议的 URI ,这个 URI 可以在系统的任意地方调起一段原生方法或一个原生的界面

`web` 通过 `URI` 请求 `Native` 流程

通过 addJavascriptInterface 注入方法到页面中调用

通过 addJavascriptInterface 方法向页面中注入一个全局对象以供调用

注入对象

1
2
3
4
5
6
7
8
// 声明 ws 实例
WebSettings ws = webView.getSetting();
// 开启执行 JavaScript 脚本
ws.setJavaScriptEnabled(true);
// 加载页面
ws.loadUrl("xxx");
// 注入全局对象
ws.addJavascriptInterface(new Object(), "native")

调用

1
2
3
4
5
6
7
<html>
...
<script>
// 调用注入的 native 方法
native.method()
</script>
</html>

NativeWeb 的调用

  • 安卓:通过 webView.loadUrl 方法实现
  • iOS :通过 stringByEvaluatingJavaScriptFromString 方法实现

html 定义方法

1
2
3
4
5
6
7
8
<html>
...
<script>
function log(msg) {
console.log(msg)
}
</script>
</html>

原生调用

1
2
3
4
5
6
7
8
// 声明 ws 实例
WebSettings ws = webView.getSetting();
// 开启执行 JavaScript 脚本
ws.setJavaScriptEnabled(true);
// 加载页面
ws.loadUrl("xxx");
// 调用 js 方法
webView.loadUr("javascript: log('hello world')")

JSBridge

通信规则: jsBridge://className:callbackMethod/methodName?jsonObj

安卓实现

通过 prompt 方式调用。 addJavascriptInterface 存在安全漏洞,可以通过 JavascriptInterface 来解决,但是其存在兼容性问题。所以通过 webView.setWebChromeClient 来实现: JavaScript 在执行 alertprompt 时, Native 端会自动触发 onJsAlertonJsPrompt 的方法回调函数,由于 alert 比较常用,所以可以通过重写 onJsPrompt 的方法实现对 Native 端方法的调用

1
2
3
4
5
6
7
8
// 设置 prompt 监听
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsPrompt(WebView wv, String url, String msg, String defaultValue, JsPromptResult res) {
res.confirm(JSBridge.callJsPrompt(MainActivity.this, wv, msg));
return true
}
});

iOS 实现

通过 iframe 的方式调用

调用流程

  1. 安卓通过 prompt(jsBridge://className:callbackMethod/methodName?jsonObj) 调用,iOS 通过 iframe 方式调用
  2. Native 解析协议,调用对应的 className 对象中的 methodName 方法,并把对应的参数 jsonObj 序列化后作为方法的参数
  3. 执行完成后调用 JavaScript 回调函数返回结果

调用流程

实时协议

websocket

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

实现

nodejs 中实现 websocket 服务

优点

  • 较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有 2 至 10 字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的 4 字节的掩码。相对于 HTTP 请求每次都要携带完整的头部,此项开销显著减少了。
  • 更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少;即使是和 Comet 等类似的长轮询比较,其也能在短时间内更多次地传递数据。
  • 保持连接状态。与 HTTP 不同的是,Websocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而 HTTP 请求可能需要在每个请求都携带状态信息(如身份认证等)。
  • 更好的二进制支持。Websocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容。
  • 可以支持扩展。Websocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。
  • 更好的压缩效果。相对于 HTTP 压缩,Websocket 在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。

长轮询

HTTP 请求设置一个较长的超时等待时间,网络请求可以维持一个较长的时间来等待返回结果,如果在等待的时间内有结果会立即返回结果,如果没有结果就会超时自动断开链接,等待下一次请求。

长轮询流程

短轮询

每隔一段时间定时向服务端发起一次请求拉取数据。

短轮询流程

参考

计算机网络-HTTPS

HTTPS 协议是通过加入 SSL 层来加密 HTTP 数据进行安全通信的

HTTPS 建立链接的过程

  1. 客户端发起请求,携带客户端支持加密算法,同时携带随机串 1
  2. 服务端收到请求后与自己支持的加密算法进行比对,从双方都支持的加密算法中选择一个返回,同时返回域名相关的公钥和证书签名信息(包括证书时间、日期、颁发机构)及随机串 2
  3. 客户端收到响应后验证证书的合法性和公钥的正确性(通过请求证书颁发机构来验证证书的合法性)
  4. 证书验证通过后,利用公钥加密一个随机字符串作为后续传输数据的密钥,同时加入利用公钥加密随机串 1 和随机串 2 组成的握手信息,然后发送给服务端
  5. 服务端获取到信息后利用私钥进行解密得到客户端生成的随机字符串把它作为后续传输的密钥,利用密钥对传输的随机串 1 和随机串 2 进行加密,然后返回
  6. 客户端利用上述生成的随机串对返回的信息进行解密,然后验证其合法性,后续传输就是堆成加密传输

HTTPS 获取密钥过程

参考文档

网络安全

前端攻击

XSS

跨站脚本攻击,由插入页面处理的数据未经处理导致。

  • 存储型 XSS :提交的数据未经处理存储到数据库中,然后从数据库中获取数据插入页面导致的
  • 反射型 XSS :通过 URL 提取参数未经处理直接插入到页面导致
  • MXSS :渲染 DOM 时插入了攻击脚本(模板渲染)

通过字符转义的方式把特殊的符号转化为安全的 HTML 字符(如 <、>等)

SQL 注入攻击

结构性查询语言注入攻击,页面提交的数据直接拼接到 SQL 语句进行操作导致,通过对查询数据进行合法性校验即可避免

CSRF

跨站请求伪造,非源站点按照源站点数据格式提交非法数据给源站点的一种攻击方式。大部分网站是通过 COOKIE 的方式来验证登录的用户信息,由于浏览器的策略导致请求时会自动携带相关 COOKIE,当非源站点请求源站点数据时也会携带相关 COOKIE,此时构造一个非法的请求也会请求成功。解决办法就是通过在页面中预先植入 TOKEN ,在接口请求时再手动携带上相关的值,由于浏览器的安全策略限制非源站点无法读取相关数据进而导致请求失败

网络劫持

网络资源请求在请求过程中因为人为的攻击导致没有加载到预期的资源内容。

DNS 劫持

攻击者通过篡改 DNS 服务器的域名解析记录,导致用户无法访问正确的网络 IP ,而是访问篡改后的错误 IP 地址。

HTTP 劫持

在用户浏览器和访问的目的服务器之间建立的网络数据传输通道中从网关或防火墙层上监视特定的数据信息,当满足特定条件时,就会在正常的数据包中插入或篡改网络数据包(如 ISP 在部分第三方网站页面中植入广告)。可以通过 HTTPS 协议来预防

浏览器 WEB 安全控制

X-XSS-Protection

防止反射型 XSS 问题的发生,浏览器层面增强前端网页的安全性。

1
2
3
4
X-XSS-Protection: 0 # 禁止XSS过滤。
X-XSS-Protection: 1 # 启用XSS过滤(通常浏览器是默认的)。 如果检测到跨站脚本攻击,浏览器将清除页面(删除不安全的部分)。
X-XSS-Protection: 1; mode=block # 启用XSS过滤。 如果检测到攻击,浏览器将不会清除页面,而是阻止页面加载。
X-XSS-Protection: 1; report=<reporting-uri> # 启用XSS过滤。 如果检测到跨站脚本攻击,浏览器将清除页面并使用CSP report-uri (en-US)指令的功能发送违规报告。

Strict-Transport-Security

配置浏览器和服务器之间安全通信的机制,防止中间者攻击(强制使用 HTTPS 协议,普通协议无效)

1
2
3
Strict-Transport-Security: max-age=<expire-time> # 设置在浏览器收到这个请求后的<expire-time>秒的时间内凡是访问这个域名下的请求都使用HTTPS请求。
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains # 如果这个可选的参数被指定,那么说明此规则也适用于该网站的所有子域名。
Strict-Transport-Security: max-age=<expire-time>; preload # 查看 预加载 HSTS 获得详情。不是标准的一部分。

Content-Security-Policy

开发者定义的安全策略性声明,浏览器只可以加载指定可信域名来源的内容(包括脚本、图片、iframefontstyle 等)

Access-Control-Allow-Origin

决定哪些网站可以访问当前服务器资源,通过定义通配符可以让所有网站来访问当前网站的所有资源。若 Access-Control-Allow-Origin*,则 Access-Control-Allow-Credentials 无效。若想 Access-Control-Allow-Credentials 有效(即请求携带 Cookie),则必须明确配置来源。

参考文档

计算机网络-http协议

HTTP

无状态、无连接的应用层协议

HTTP 1.0

浏览器和服务器保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接,服务器处理完成后立即断开 TCP 连接(无连接),服务器不跟踪每个客户端也不记录过去的请求(无状态)。

HTTP 1.1

发布于 1999 年,默认使用文本格式传输数据

长链接

默认包含 Connection: keep-alive 头,可以让客户端和服务端在一段时间内可以复用这个链接,无需再次建立链接。链接复用发生在应用层,且复用是串行的

支持请求管道化

基于 HTTP1.1 的长连接,使得请求管线化成为可能。管线化使得请求能够“并行”传输。虽然 HTTP1.1 支持管道化,但是服务器必须按照客户端请求的先后顺序依次回送相应的结果,以保证客户端能够区分出每次请求的响应内容。

HTTP1.1 支持管道化链接图

由于“管道化”技术存在各种各样的问题,所以很多浏览器要么根本不支持它,要么就直接默认关闭。

协议扩展切换

支持在请求头部域消息中包含 Upgrade 头并让客户端通过头部标识令服务器知道它能够支持其它备用通信协议的一种机制,服务器根据客户端请求的其它协议进行切换,切换后使用备用协议与客户端进行通信

缓存控制

新增 Cache-Control 字段,支持 max-age 用来表示相对过期时间;服务器也可以通过 EtagLast-Modified 来判断是否从浏览器中加载文件,此时缓存的控制和判断将决定响应状态码是 200 还是 304。参考浏览器缓存章节

部分内容传输优化

引入了 range 头域支持超文本文件的部分传输。如允许请求一个文件的起始位置和偏移长度来进行文件内容的部分传输

Host 头处理

请求消息和响应消息都支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request

错误通知的管理

新增了 24 个错误状态响应码,如 409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。

HTTP 2.0

  • 采用完全的二进制格式来传输数据,其在网络中以帧的形式传播,多个帧在网络中形成了帧的传输网络流,即流式传播的。
  • 使用 TCP 复用的方式来降低网络请求链接的建立和关闭的开销,多个请求可以通过一个链接来进行并发完成。复用发生在传输层,是帧的多路复用,不同文件的传输可以在一个 TCP 连接中一起同时进行流式传播
  • 支持传输流的优先级和流量控制机制
  • 支持服务端推送

二进制分帧

通过在应用层和传输层之间增加一个二进制分帧层,突破了 HTTP1.1 的性能限制、改进传输性能。

二进制分帧结构图

多路复用(连接共享)

  • 流(stream):已建立连接上的双向字节流。
  • 消息:与逻辑消息对应的完整的一系列数据帧。
  • 帧(frame):HTTP2.0 通信的最小单位,每个帧包含帧头部,至少也会标识出当前帧所属的流(stream id)。

多路复用(连接共享)

所有的 HTTP2.0 通信都在一个 TCP 连接上完成,这个连接可以承载任意数量的双向数据流。每个数据流以消息的形式发送,而消息由一或多个帧组成。这些帧可以乱序发送,然后再根据每个帧头部的流标识符(stream id)重新组装。

头部压缩

使用 encoder 来减少需要传输的 header 大小,通讯双方各自 cache 一份 header fields 表,既避免了重复 header 的传输,又减小了需要传输的大小。高效的压缩算法可以很大的压缩 header,减少发送包的数量从而降低延迟。

在头部压缩技术中,客户端和服务器均会维护两份相同的静态字典和动态字典。在静态字典中,包含了常见的头部名称以及头部名称与值的组合。静态字典在首次请求时就可以使用。那么现在头部的字段就可以被简写成静态字典中相应字段对应的 index。而动态字典跟连接的上下文相关,每个 HTTP/2 连接维护的动态字典是不尽相同的。动态字典可以在连接中不听的进行更新。原本完整的 HTTP 报文头部的键值对或字段,由于字典的存在,现在可以转换成索引 index,在相应的端再进行查找还原,也就起到了压缩的作用。

参考

commonjs

commonjs 规范

  • 模块引用:通过 require 方法把模块引入上下文
  • 模块定义:模块中通过 exports 属性导出模块的方法和变量,module 代表模块自身
  • 模块标识:传递给 require 方法的参数

node 实现

引入模块的流程
  1. 路径分析
  2. 文件定位
  3. 编译执行
模块分类
  1. 核心模块:Node 源代码的编译过程中,编译进了二进制执行文件。Node 启动时已经被加载入内存中
  2. 文件模块:运行时动态加载

模块缓存

  1. 引入过的模块都会进行缓存
  2. 缓存优先

路径分析和文件定位

模块路径

Node 中在定位文件模块的具体文件时制定的查找策略,根据查找规则生成查找路径数组,查找规则如下

  1. 查找当前目录下的 node_modules 目录
  2. 父目录下的 node_modules 目录
  3. 沿着路径向上逐级递归直到找到根目录的 node_modules 目录
模块标志符分析
  • 核心模块
  • 相对路径模块和绝对路径模块:转化为真实路径作为索引来查找和缓存模块
  • 非路径形式的文件模块
文件定位
  • 文件扩展名分析:Node 会按照 .js.json.node的次序依次尝试扩展名,尝试过程中会阻塞式的判断文件是否存在
  • 目录分析与包:分析标识符得到一个文件夹时,Node 会按照 package.jsonindex.jsindex.jsonindex.node来查找文件,若查找到 package.json 会提取 main 指定的文件来进行定位

模块编译

定位到具体的文件之后,Node 会构建一个模块对象,然后根据路径载入并编译

1
2
3
4
5
6
7
8
9
10
11
12
function Module(id, parent) {
this.id = id
this.parent = parent
this.exports = {}
this.filename = null
this.loaded = false
this.children = []

if (parent && parent.children) {
parent.children.push(this)
}
}
不同扩展名的载入方式
  • .js 文件:通过 fs 模块同步读取文件后编译执行
  • .node 文件:c/c++ 写的扩展文件,通过 dlopen() 方法加载编译生成的文件
  • .json 文件:通过 fs 模块同步读取文件后返回 JSON.parse 的结果
  • 其它类型的文件:当作 .js 文件来处理
注: 可以通过 require.extensions[".ext"] 的方式来扩展方式,但是已不推荐
JavaScript 模块的编译

Node 对获取的 JavaScript 文件内容做了包装,在头部添加了 (function(exports, require, module, __filename, __dirname){\n,在尾部添加了 \n}),包装之后的代码会通过 vm 原生的 runInThisContext() 方法执行,返回一个 function 对象,然后将当前模块对象的 exports 属性、require 方法、 module(模块对象自身)、模块定位中的完整文件路径和目录作为参数传递给这个函数执行,执行之后模块的 exports 属性被返回给了调用方

注: exports 属性是作为形参传入的,直接赋值会改变形参的引用,通过 module.exports 赋值采用迂回的方案不改变形参的引用
c/c++ 模块的编译

Node 调用 process.dlopen 方法进行加载和执行,模块的 exports 对象和 .node 模块产生联系并返回给调用者

  • 优点: 执行效率高
  • 缺点:门槛高
JSON 文件的编译

Node 利用 fs 模块同步读取 JSON 文件内容之后,调用 JSON.parse 方法得到对象,然后赋值给对象的 exports 属性供外部调用

浏览器基础

浏览器组成

用户界面

包括浏览器中可见的地址输入框、浏览器前进返回按钮、打开书签、打开历史记录等用户可操作的功能选项

浏览器引擎

可以在用户界面和渲染引擎之间传送指令或在客户端本地缓存中读写数据等,是浏览器各个部分通信的核心。

渲染引擎(内核)

解析 DOM 文档和 CSS 规则并将内容排版到浏览器中显示有样式的界面

网络

开启网络线程发送请求或下载资源文件

UI 后端

绘制基本的浏览器窗口内的控件,比如组合选择框、按钮、输入框等

JavaScript 引擎

解析和执行 JavaScript 脚本,比如 V8 引擎

持久化数据存储

数据持久化存储,涉及 cookielocalStorage 等客户端存储技术

渲染引擎

  1. 解析 HTML 构建 DOM树:将 HTML 元素标签解析成由多个 DOM 元素对象节点组成的具有节点父子关系的 DOM 树结构
  2. 构建渲染树:按顺序提取 DOM 元素对象的样式数据,构建带样式描述的 DOM 渲染树对象
  3. 渲染布局阶段:根据节点的大小和位置把元素绘制在页面上,主要是布局属性(例如: postion、float、margin、padding 等)生效
  4. 绘制渲染树:将节点的背景、颜色、文本等样式信息应用到节点上,主要是元素的内部显示样式(例如: color、background、text-shadow 等)生效

gecko 内核渲染流程

先解析 HTM,生成内容 Sink (Content Sink 可以认为是构建 DOM 结构树的工厂方法),再开始解析 CSS

gecko内核渲染流程

webkit 内核渲染流程

HTML 和 CSS 的解析是并行的

webkit内核渲染流程

持久化技术

  • HTTP 文件缓存
  • localStorage
  • sessionStorage
  • cookie
  • webSQL
  • Application cache
  • cacheStorage
  • flash 缓存