浏览器的内存模型
- 浏览器进程(主进程)
- 网络进程(子进程)
- 渲染进程(子进程)
每打开一个新的标签页 都会由主进程开启子进程 其中渲染进程开启渲染主线程 在其中执行所有的前端代码
渲染主线程
浏览器内最繁忙的线程 里面的执行机制——事件循环
事件循环
先微后宏
- 微任务
process.nextTick(与普通微任务有区别,在微任务队列执行之前执行)
new Promise().then(回调)等。 - 宏仁务
web worker
setTimeout
setInterval
setImmediate
script(整体代码)
I/O 操作等
现在的新说法:因为任务的类型越来越多 同样的任务需要放在同一个队列 所以宏仁务的概念改成了队列优先级:(保留微任务的说法)微任务>交互队列(点击事件)>延时队列(定/延时器)
URL到整个页面加载完毕显示在屏幕上的整个流程
主要是2个过程:网络(获取HTML文档)+渲染(解析HTML文档)
网络的过程:
当网络线程获取了HTML文档后,会产生1个渲染任务,传递到渲染主线程的消息队列;
在事件循环机制的作用下,渲染主线程拿出消息队列的该渲染任务并执行
渲染的过程:
页面展示过程
DNS 预解析
DNS Prefetching
:具有此属性的域名不需要用户点击链接就在后台解析,而域名解析和内容载入是串行的网络操作,减少等待时间,提升用户体验
浏览器对网站第一次的域名DNS解析查找流程依次为:
DNS预解析的实现
- meta标签告知浏览器需要DNS预解析:
<meta http-equiv="x-dns-prefetch-control" content="on" />
- link标签强制对DNS预解析:
<link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />
注意:dns-prefetch需慎用,多页面重复DNS预解析会增加重复DNS查询次数
垃圾回收机制(GC)
- 标记清除
- 引用计数
常见的内存泄露
- 闭包
- 全局变量
- 事件绑定没有移除
- 定时器setInterval() 、setTimeout()
- jQuery里对 DOM 的引用
- 控制台日志 console
- 彼此引用的对象产生的循环(需要手动断开引用)
解决方法:
- 重复测试单一脚本
- 在开发者工具里手动GC
- 借助浏览器的任务管理器分析内存占用
跨域
出于浏览器的同源策略Sameoriginpolicy限制:
协议
(protocol),主机/域名
(host)和端口号
(port)三者任一不同
解决方法
- JSONP
- CORS
- WebSockets
- WebPack协议跨域
- Proxy代理
- Nginx反向代理
- 跨文档通信API:window.postMessage()
- 设置document.domain解决无法读取非同源网页的 Cookie问题(仅限主域相同,子域不同的跨域应用场景)
解决方法
- JSONP
- CORS(必须与服务器一起配置)
- WebSockets
- WebPack协议跨域
- Proxy代理
- Nginx反向代理
- 跨文档通信API:window.postMessage()
- 设置document.domain解决无法读取非同源网页的 Cookie问题(仅限主域相同,子域不同的跨域应用场景)
JSONP实现原理
后端将数据转换成JSON格式然后包装成函数,通过js传递给前端进行对应的函数解析(利用了浏览器对标签的限制较小)
- 缺点:只能进行GET请求
- 优点:兼容性好,在一些古老的浏览器中都可以运行
// 定义解析函数
function jsonpCallback(data) {
console.log(data);
}
// 动态创建script标签并设置src
function fetchJSONP(url) {
const script = document.createElement('script');
script.src = url;
script.onload=function(){
script.remove();
};
document.body.appendChild(script);
}
// 调用JSONP请求
fetchJSONP('http://example.com/api/data?callback=jsonpCallback');
const http = require('http');
const url = require('url');
const server = http.createServer((req, res) => {
const queryData = url.parse(req.url, true).query;
const callback = queryData.callback;
// 数据
const data = {
name: 'John',
age: 30
};
// 将数据转换成JSONP格式
const jsonData = JSON.stringify(data);
const responseText = `${callback}(${jsonData})`;
// 设置响应头
res.writeHead(200, {'Content-Type': 'application/javascript'});
res.end(responseText);
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Ngnix反向代理
监听localhost主机的2222端口;
api的所有请求都转发到3333端口,同时加上header请求头
iframe 跨域登录的优点
- 用户体验:用户可以在不离开当前页面的情况下,完成另一个域上的登录操作,提供了更为流畅的用户体验。
- 安全性:由于登录操作是在iframe中进行的,可以限制敏感信息的暴露,通过设置相应的安全策略(如CSP),减少XSS攻击的风险。
- 隔离性:iframe的内容与父页面是隔离的,这意味着即使iframe中的页面出现安全问题,也不会直接影响到父页面的安全。
- 统一认证:对于拥有多个子域的大型网站,可以通过iframe跨域登录实现单点登录(SSO),用户在一个域登录后,可以无缝访问其他子域。
- 易于实现:相比于其他跨域认证方案,iframe跨域登录实现起来较为简单,只需在父页面中嵌入一个指向登录页面的iframe即可。
- 维护性:当需要更新登录逻辑时,只需在登录页面进行更改,而不需要修改所有使用iframe的页面。
然而,需要注意的是,虽然iframe跨域登录有其优点,但也存在一些潜在的安全风险,例如点击劫持(Clickjacking)攻击,因此需要采取适当的安全措施来防范这些风险。
LocalStorage
、SessionStorage
、Cookie
、Session
、Token
LocalStorage | SessionStorage | Cookie | Session | Token | |
---|---|---|---|---|---|
存储位置 | 客户端 | 客户端/服务端 | |||
存储类型 | 字符串 | 任意类型 | |||
增/改、删、查、清 | setItem()、getItem()、removeItem()、clear() | 都是操作document.cookie | / | ||
存储大小 | 10M | 5M | 4K | / | |
兼容性 | H5 | H4 / H5 | / | ||
语法 | 简易 | 复杂 | / | ||
有效期 | 永久:需手动清除 | 仅当前会话有效 | 在设置的有效期后过期 | 由服务器管理 | / |
与服务端通信 | × | 携带在HTTP请求头信息中 | 无需额外措施 | 需要额外措施,如 CSRF Token | |
页面共享 | √ | 不同顶级窗口不共享 | 同域名不同端口可共享 | 不可直接访问 | JS可直接访问 |
易用性 | 可以接受原生/封装接口,对Object/Array支持更好 | 原生接口不友好,需要手动封装 | 不可直接访问 | ||
适用场景 | 不需要与服务端通信的数据 | 需要与服务端通信的数据 | |||
数据传输 | / | 在请求头传输 | 在请求头/请求体传输 | / | |
前端访问 | JS可直接访问 | 不可直接访问 | JS可直接访问 | ||
CSRF防护 | 需要额外措施,如设置同源策略 | / | 需要额外措施,如 CSRF Token | / | 需要额外措施,如 CSRF Token |
安全性 | 相对较高,但仍存在 XSS 攻击风险 | 低,容易受到 XSS 攻击 | 相对较低,容易受到 CSRF、XSS 攻击 | 相对较高,但仍存在 XSS 攻击风险 | 高,提供 CSRF 防护机制 |