Skip to content

浏览器 ​

浏览器进程 ​

进程 进程有多个线程
js 线程和 gui 线程互斥,js 线程运行时会阻塞 gui 线程
事件触发线程归属于浏览器而不是 js 引擎,用来控制事件循环
当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾(事件循环)

渲染流程 ​

渲染流程

DOM 树渲染到 script 标签后就会停止,js 引擎开始工作

渲染流程

多个带 async 属性的标签,不能保证加载的顺序,多个带 defer 属性的标签,按照加载顺序执行

事件循环 ​

浏览器环境 ​

js 引擎是单线程的,只有一个 FILO 的执行栈。
遇到异步操作放入 task 队列或者 microtask 队列,执行完执行栈后先执行 microtask 队列中微任务,再执行 task 队列中宏任务队列。
宏任务执行结束后会对页面重新渲染
微任务会在一个宏任务执行结束,下一个宏任务执行前执行

事件循环

宏任务是由宿主环境发起的,比如浏览器、Node 等
微任务是由 JS 引擎发起的
Object.observe 观察对象变化
MutationObserver 观察 DOM 变化
requestAnimationFrame 在浏览器在下次页面重绘,渲染之前调用,不一定在每一轮 Event Loop 中调用
requestIdleCallback 是在浏览器渲染后有空闲时间时执行,在页面渲染之后调用,如果 requestIdleCallback 设置了第二个参数 timeout,则会在超时后的下一帧强制执行

js
Promise.resolve().then(() => {
  console.log(1)
  setTimeout(() => {
    // 嵌套后只能在下一轮宏任务执行
    console.log(2)
  })
})
setTimeout(() => {
  console.log(3)
})
// 1, 3, 2

node 环境 ​

事件循环

  • timers 阶段:这个阶段执行 timer(setTimeout、setInterval)的回调
  • I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
  • idle, prepare 阶段:仅 node 内部使用
  • poll 阶段:获取新的 I/O 事件, 适当的条件下 node 将阻塞在这里
  • check 阶段:执行 setImmediate() 的回调
  • close callbacks 阶段:执行 socket 的 close 事件回调

微任务会在事件循环的各个阶段之间执行
process.nextTick 独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行

垃圾回收 ​

标记清除 👍 ​

从根对象出发,找出所有从根开始引用的对象,然后找出这些对象引用的对象,找出未被引用的对象进行回收。

引用计数 👎 ​

回收引用次数为零的对象。如果循环引用或者互相引用就无法被回收

V8 分代式垃圾回收 ​

v8回收

新加入的对象都会存放到使用区,当使用区快被写满时,就需要执行一次垃圾清理操作
当开始进行垃圾回收时,新生代垃圾回收器会对使用区中的活动对象做标记,标记完成之后将使用区的活动对象复制进空闲区并进行排序,随后进入垃圾清理阶段,即将非活动对象占用的空间清理掉。最后进行角色互换,把原来的使用区变成空闲区,把原来的空闲区变成使用区
当一个对象经过多次复制后依然存活,它将会被认为是生命周期较长的对象,随后会被移动到老生代中,采用老生代的垃圾回收策略进行管理
老生代就是用标记清除

内存泄漏的情况 ​

全局变量
闭包
定时器
DOM 节点的引用
未声明直接赋值的变量将会变成全局变量,在页面关闭前不会被回收。
this 也有可能创建全局变量

js
function foo() {
  this.variable = "potential accidental global"
} // foo 调用自己,this 指向了 window,通过严格模式避免
foo()

跨域 ​

协议、域名、端口有一个不同就是跨域

proxy ​

使用 node 或者 nginx 直接代理请求,最方便

JSONP ​

通过 script 标签提供一个回调函数从地址获取数据,只限于 get 请求。

js
<script src="http://www.api.com/login?id=114514&callback=action"></script>
<script>
  const action = (res) => {
  // 处理数据
}
</script>

CORS ​

服务端设置 Access-Control-Allow-Origin 字段允许跨域
携带 cookie 需要 withCredentials 字段

简单请求

请求方法满足是以下三种方法,

  • HEAD
  • GET
  • POST

Content-Type 的值仅限于下列三者

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

复杂请求 ​

会发起 option 预检请求

  • Session Cookie - 浏览器关闭,cookies 就会被删除
  • Persistent Cookie - 持久型 Cookie
  • Secure Cookie - 只有 HTTPS 可以访问,确保 cookie 被加密
  • HttpOnly Cookie - 只能在 http(https)请求上传递,无法从 js 获取
  • Third-party Cookie - 在 a 网站请求 b 网站,希望带上 b 得 cookie,需要浏览器端带上 withCredentials,服务端开启 cors,并在 cookie 上设置 SameSite

SameSite ​

  • Strict - 最为严格,完全禁止第三方 Cookie
  • Lax - 只有导航到目标网址的 Get 请求会携带 Lax
  • None - 必须同时设置 Secure 属性

缓存 ​

页面的缓存状态由 header 决定,根据 header 确定是否命中

缓存

强缓存 ​

命中则直接从本地获取缓存,不向服务端请求
200 OK (from memory cache)

协商缓存 ​

没有命中(如过期)则向服务器请求,若服务端未更新则重定向至本地缓存
304 Not Modified

header 字段 ​

Cache-Control: max-age/s-maxage/public/private/no-cache/no-store/must-revalidate
Expires: 缓存过期时间
Last-modified: 服务器端文件的最后修改时间
Etag: hash 字符串,标识资源的状态,由服务端产生

浏览器缓存 - Javascript 编程基础 - SegmentFault 思否

事件捕获冒泡 ​

捕获冒泡

peventDefault 阻止默认事件,如超链接
stopPropagation 阻止冒泡

Event 的 target currentTarget ​

target:点击的元素
currentTarget:触发的元素

iframe 通信 ​

url 传参 ​

痛过 query、hash 通知子页面,window.onhashchange 感知 hash 变化

postMessage ​

window.open 也可以使用 postMessage

js
window.postMessage("Hello world!", "http://127.0.0.1:5500/child.html")

window.addEventListener("message", function (event) {
  // 判断消息是否来自可信任的源
  if (event.origin === "http://127.0.0.1:5500/child.html") {
    console.log("message: " + event.data)
  }
})

直接获取 DOM ​

会受到同源策略限制

js
document.getElementById().contentWindow.document

window.parent.document

Web Workers ​

js
const myWorker = new Worker("worker.js")
myWorker.postMessage("asd")
myWorker.onmessage = (e) => {
  console.log(e.data)
}
myWorker.terminate()

// worker.js
onmessage = (e) => {
  console.log(e.data)
  postMessage("qwe")
}
this.close()

与主线程的区别 ​

Web Workers 和主线程之间的一个关键区别是 Web Workers 没有访问 DOM 或 UI 的权限。这意味着它不能直接操作页面上的 HTML 元素或与用户交互。实际上,Web Workers 被设计用于执行不需要直接访问 UI 的任务,例如数据处理、图像操作或计算
另一个区别是,Web Workers 被设计为在与主线程分离的沙箱环境中运行,这意味着它们对系统资源的访问受到限制,并且不能访问某些 API,如 localStorage 或 sessionStorage API。不过,它可以通过消息传递系统与主线程进行通信,从而允许两个线程之间交换数据

服务器发送事件 Server-Sent Event ​

js
// http 长连接
const evtSource = new EventSource("//api.example.com/ssedemo.php", { withCredentials: true })

// onopen onerror
evtSource.onmessage = (e) => {
  console.log(e.data)
}

evtSource.close()

与 WebSocket 比较 ​

  • 数据推送方向:SSE 是服务器向客户端的单向通信,服务器可以主动推送数据给客户端。而 WebSocket 是双向通信,允许服务器和客户端之间进行实时的双向数据交换。

  • 连接建立:SSE 使用基于 HTTP 的长连接,通过普通的 HTTP 请求和响应来建立连接,从而实现数据的实时推送。WebSocket 使用自定义的协议,通过建立 WebSocket 连接来实现双向通信。

  • 兼容性:由于 SSE 基于 HTTP 协议,它可以在大多数现代浏览器中使用,并且不需要额外的协议升级。WebSocket 在绝大多数现代浏览器中也得到了支持,但在某些特殊的网络环境下可能会遇到问题。

事件委托 + DocumentFragment 优化大量节点 ​