目录
请求头,响应头
HTTP版本、状态码
web安全
浏览器缓存、本地存储
渲染页面方式SSR、SEO搜索引擎优化
浏览器http请求
同步 js标签跨域、url
异步
web-worker(创建分线程):适用于计算密集型任务
index.js为加载到html页面中的主线程(js文件)
work.js为在index中创建的分线程
异步ajax、websock协议
ajax是异步的技术术语,最早的api是xhr(XMLHttpRequest)
axios
同构:同样的代码在nodejs端,浏览器端都可用
在浏览器用xhr,Node.js中使用Node的内置http模块
fetch es6 api(基于promise)
宏任务,微任务,事件循环event loop
url:?(分割url和参数) &(参数分隔符 )#(锚点,id)
&:参数顺序/无效参数不影响
urlObj.searchParams.set
多值参数
编/解码
JS编码/解码:decodeURIxxx
base64:如jZXNzL2luZGV4I
编码:Buffer.from(str).toString('base64')
解码:Buffer.from(str, 'base64').toString()
不要混用编码方法
现代浏览器自动处理部分编码
应用
浏览器的自动URL解析
保护特殊字符:illegal base64 data at input byte
404:参数丢失
window.location属性值
打开url
跨域
LocalStorage、SessionStorage、Cookie
不通过服务器:
CORS 禁用:chrome.exe --disable-web-security --user-data-dir="C:\chrome_temp"跨窗口通信 API(Window.postMessage):目标窗口监听,origin
window.addEventListener("message", receiveMessage, false);
receiveMessage(event){event.data,event.orgin,event.source.postMessage,}
jsonp:script>,js、css,img静态资源,仅get方式,
现在浏览器兼容性高了,逐步淘汰了
代理服务器:服务器间不用同源
Nginx反向代理:类似cors字段设置+proxy_pass
proxy
应用:vue.config.js
应用:本地页面跨域请求
cors(浏览器IE10以下不支持)
服务端Access-Control-Allow-Origin
preflight预检查:跨域复杂请求前,OPTIONS 请求
简单请求:GET/HEAD/POST+Content-Type:text/plain,multipart/form-data,application/x-www-form-urlencoded
报错
localhost/127.0.0.1与本机IP
应用
当服务端没有设置跨域时,可以将json伪装成text/plain
websocket:HTML5新特性,TCP,实时通信
兼容性不好,只适用于主流浏览器和IE10+
应用:webpack热更新
透传【开发业务】
url
请求头,响应头
HTTP版本、状态码
web安全
浏览器缓存、本地存储
渲染页面方式SSR、SEO搜索引擎优化
浏览器http请求
服务 / Server: 一直运行的程序,等待被访问
同步 js标签跨域、url
img src,link href>
异步
web-worker(创建分线程):适用于计算密集型任务
当在 HTML 页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成。
web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。
Web Worker 线程无法直接访问 DOM(文档对象模型)元素、操作页面内容,也不能访问浏览器的Window和 Document 对象。这是为了防止 Web Worker影响主页面的交互
index.js为加载到html页面中的主线程(js文件)
work.js为在index中创建的分线程
index.js: 创建分线程 var w =new webwork('work.js')//创建 Web Worker 对象 向子线程发送数据 w.postmesage('数据') work.js: onmessage = function(ev) { console.log(ev);//接受主线程发送过来的ev.data数据 this.postMessage('数据')//通过 postmesage('数据') 向主线程发送数据 } 通过w.onmessage=function(ev){ev.data} ev.data 接受 a 的值. w.terminate();//终止 Web Worker异步ajax、websock协议
ajax是异步的技术术语,最早的api是xhr(XMLHttpRequest)
axios
同构:同样的代码在nodejs端,浏览器端都可用
在浏览器用xhr,Node.js中使用Node的内置http模块
// 在浏览器中,根据其Content-Type头信息,自动转换数据 axios.get('/api/data').then(response => { // response.data 将是一个JavaScript对象 }); // 在Node.js中手动设置响应数据类型 axios.get('/api/data', { responseType: 'json' }).then(response => { // response.data 将是一个JavaScript对象 });- axios 新版本也支持了fetch
- 第三方库都是基于原生API的,所以axios也还是基于xhr的
fetch es6 api(基于promise)
【JavaScript】爆肝 2 万字!一次性搞懂 Ajax、Fetch 和 Axios 的区别~
宏任务,微任务,事件循环event loop
url:?(分割url和参数) &(参数分隔符 )#(锚点,id)
http://example.com/page?param1=value1¶m2=value2#section1
| ? | 分隔实际的URL和参数 |
| & | URL中指定的参数间的分隔符 |
| = | 左边为参数名、右边参数值 |
| # | 锚点(Anchor),用于标识文档中的特定位置或元素, 仅在客户端(前端)使用,并且由浏览器处理,不发送到服务器(后端) 指示浏览器滚动到具有 |
&:参数顺序/无效参数不影响
urlObj.searchParams.set
URL 构造函数在解析无效 URL 时会抛出异常duo
try { let urlObj = new URL(url); urlObj.searchParams.set('meishi_appid', id); // 更新或创建参数 global.viewList[id].view.webContents.loadURL(urlObj.href); } catch (error) { global.viewList[id].view.webContents.loadURL(url); } 等同于encodeURIComponent + ? const title = encodeURIComponent(res.fileName); let newUrl = `${url}?title=${title}`;多值参数
| 写法 | 示例 |
|---|---|
逗号分隔(单参数) |
|
重复同名参数 |
|
其他分隔符,具体看接口文档 |
|
JSON 字符串,post请求更常见 |
|
编/解码
URL 规范(RFC 3986)规定,URL 只能包含以下字符:
保留字符:
A-Z、a-z、0-9、-、_、.、~特殊字符:
!、*、'、(、)、;、:、@、&、=、+、$、,、/、?、#、[、]中文等非 ASCII 字符必须经过编码(Percent-Encoding)才能使用(
%xx形式)
JS编码/解码:decodeURIxxx
参数值:用
encodeURIComponent/URL编码/百分号编码(编码更彻底)。
如https%3A%2F%2F = https://
完整 URL:用
encodeURI(保留/、?等结构字符)。
base64:如jZXNzL2luZGV4I
编码:Buffer.from(str).toString('base64')
解码:Buffer.from(str, 'base64').toString()
如aHR0cHM6...=bpm...
不要混用编码方法
错误的解码(如用decodeURI解码encodeURIComponent的结果)会导致乱码。
现代浏览器自动处理部分编码
在浏览器地址栏输入中文时,Chrome/Firefox 会自动编码(但代码中仍需手动处理)。
如英文逗号可以不编码。userNames=xxx,yyy
浏览器地址栏直接输入、回车,通常就能用,不用手动写成%2C。
但以下情况建议编码(或必须用编码):
| 字符/内容 | 原因 |
|---|---|
| 有特殊含义,会破坏 URL 结构 |
空格 | 可能被转成 |
中文、特殊符号 | 必须编码 |
用户名里本身带逗号 | 会和分隔符冲突,需确认接口规则 |
应用
浏览器的自动URL解析
// 浏览器看到这个URL会自动解析token参数,可能会重新编码 const normalUrl = res.fileUrl + '?token=' + res.token; // 成功方案:整个URL被编码,浏览器不会解析内部结构,把这个当作一个普通的编码字符串处理 const encodedUrl = encodeURIComponent(res.fileUrl + '?token=' + res.token);保护特殊字符:illegal base64 data at input byte
// 内层编码:保护token中的特殊字符 const safeToken = encodeURIComponent(res.token); // 外层编码:保护整个URL结构 const fullEncodedUrl = encodeURIComponent(res.fileUrl + '?token=' + safeToken); // 普通URL - 浏览器会解析参数 window.open('https://example.com/file.xlsx?token=abc+def=123'); // 浏览器可能把 + 解析为空格,把 = 当作参数分隔符404:参数丢失
SSO 跳转或后端重定向时,hash 后的参数丢失,业务页面拿不到实际参数,直接 404。
首次打开时,未登录会走 SSO 跳转链路,hash 丢失概率大。
登录后,页面可能直接渲染,不再走 SSO 跳转,所以正常。
URL 超长被截断,或参数顺序导致 hash 被提前截断。
window.location属性值
window的全局对象,表示当前页面http://www.example.com/path/index.html
window.location.href:获取/设置 url
window.location.orgin:协议、主机名和端口号部分
//https://www.example.com:8080/page.html // :// : //https%3A%2F%2Fwww.example.com%3A8080。 encodeURIComponent(window.location.origin) //encodeURIComponent用于将字符串中的特殊字符(空格、&、+、= 、?)转换为编码形式,确保URL中不包含任何无效字符 //查询参数时 或者 动态参数时 需要encodeURIComponent const url = 'https://example.com/api?param=' + encodeURIComponent(queryParam); window.location.href =`https://www.example.com/path/to/resource.html/domain=${location.host}&req=${encodeURIComponent(location.pathname)}&protocol=https${location.hash}`window.location.protocol: 协议http
window.location.host:主机+端口(host:8080)/IP地址(127.123.32.1唯一)/域名(www.example.com助记)
window.location.hostname:主机host
window.location.port:端口8080
window.location.pathname: 资源路径path/index.html,资源index.html
window.location.hash:
window.location.search: 搜索
var searchParams = new URLSearchParams(window.location.search); console.log(searchParams.get('name')); // 输出 "John"打开url
同源页面,自动携带所有相关Cookie到新页面,都能复用登录态。
"同基域"页面+SSO系统(携带父域Cookie),也能复用登录态。
非同源
| 打开方式 | 技术原理 | 是否继承登录态 |
|---|---|---|
location.href = | 同源导航 | ✅ |
window.open(url) | 同源弹出窗口 | ✅ |
<ahref='' target="_blank"> | 同源新标签页 | ✅ |
| 地址栏直接输入 | 全新的浏览器实例 | ❌ |
| 复制链接粘贴 | 全新的浏览会话 | ❌ |
跨域
跨域是浏览器内核特有的安全保护机制,命令行数据传输工具 curl不受 CORS限制。
同源策略:URL 的协议、域名和端口号完全一致,查询参数不影响同源,共享Cookie、LocalStorage
LocalStorage、SessionStorage、Cookie
| 特性 | LocalStorage | SessionStorage | Cookie |
|---|---|---|---|
| 数据范围 | 同源域名 | 同源 + 路径 + 标签页【同路径】 | 可通过 Domain【域名】/Path【路径】 灵活配置 |
| 持久性 | 持久,需手动或代码清除 | 标签页关闭即清除 | 可设置过期时间 (Expires/Max-Age) |
| 跟随请求 | 否 | 否 | 是(自动在 HTTP 头中发送) |
| 可访问性 | 仅客户端 JavaScript | 仅客户端 JavaScript | 客户端和服务器均可访问 |
同域:通常指主域名相同 ,不关心协议和端口,不是标准术语
不通过服务器:
CORS 禁用:chrome.exe --disable-web-security --user-data-dir="C:\chrome_temp"
跨窗口通信 API(Window.postMessage):目标窗口监听,origin
window.addEventListener("message", receiveMessage, false);
receiveMessage(event){event.data,event.orgin,event.source.postMessage,}
http://localhost:3000
<!DOCTYPE html> <html> <head> <script> window.addEventListener("DOMContentLoaded", (event) => { // 获取目标窗口的引用 const targetWindow = document.getElementById("targetFrame").contentWindow; // 给按钮添加点击事件处理函数 document.getElementById("sendButton").addEventListener("click", () => { // 向目标窗口发送消息 targetWindow.postMessage("你好,来自页面1!", "http://localhost:4000"); }); }); // 接收消息的处理函数 function receiveMessage(event) { // 确保消息来自预期的源 if (event.origin !== "http://localhost:4000") { return; } console.log("接收到的消息:", event.data); } // 监听消息事件 window.addEventListener("message", receiveMessage, false); </script> </head> <body> <button id="sendButton">发送消息</button> <iframe id="targetFrame" src="http://localhost:4000"></iframe> </body> </html>http://localhost:4000
<!DOCTYPE html> <html> <head> <script> // 监听消息事件 window.addEventListener("message", receiveMessage, false); // 接收消息的处理函数 function receiveMessage(event) { // 确保消息来自预期的源 if (event.origin !== "http://localhost:3000") { return; } console.log("接收到的消息:", event.data); // 向页面1发送回应消息 event.source.postMessage("你好,来自页面2!", event.origin); } </script> </head> <body> <h1>页面2</h1> </body> </html>jsonp:script>,js、css,img静态资源,仅get方式,
现在浏览器兼容性高了,逐步淘汰了
JSONP(JSON WithPadding)是利用<script src=XXX>跨域
因为是动态创建script标签,所以它只支持get请求,不支持post请求
代理服务器:服务器间不用同源
正向代理主要是用来解决访问限制问题;
反向代理则是提供负载均衡、安全防护等作用。
Nginx反向代理:类似cors字段设置+proxy_pass
前端应用运行在http://localhost:3000,跨域访问后端 API ,它运行在http://localhost:8000
server { #HTTP 协议来监听请求,应该使用 listen 80; listen 80; server_name localhost; location /api { proxy_pass http://localhost:8000; # 允许来自前端应用的跨域请求 add_header 'Access-Control-Allow-Origin' 'http://localhost:3000'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; # 支持 OPTIONS 请求,用于预检请求 if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' 'http://localhost:3000'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; # 响应预检请求,直接返回 204 No Content add_header 'Content-Length' 0; add_header 'Content-Type' 'text/plain charset=UTF-8'; return 204; } } }proxy
应用:vue.config.js
const configStatic = { proxy: { '/test': { target: 'http://www.xx.cn',// 设置目标服务器的地址 changeOrigin: true, pathRewrite: { '^/test': '/' } } } module.exports = configStatic应用:本地页面跨域请求
浏览器有同源策略 / 跨域(CORS):
- 页面在:
http://10.253.70.42:3000 - 接口在:
https://xiaomi.77.com - 域名、端口、协议不同 → 浏览器可能禁止页面里的 JS 直接
fetch外站接口
常见解决办法:本机起一个代理(proxy)
浏览器 → 同源:http://本机:3000/proxy?url=真实接口
本机服务 → 服务端去请求 →https://xiaomi.77.com...
本机服务 → 把结果还给浏览器
浏览器只和「本机 3000」说话,跨域由服务器完成(服务器不受浏览器 CORS 限制)。
//index.html const targetUrl = `https://xiaomi.77.com/updateGrayUser?${params}`; const url = `/proxy?url=${encodeURIComponent(targetUrl)}`; fetch(url)- 托管页面:让
index.html能通过http://打开(不是file://) - 代理转发:页面不能直接跨域访问外站时,由本机代为请求
// server.js 靠本机的 Node进程(服务端)去请求外站,绕过浏览器的跨域限制 // require 加载 Node.js 自带的能力,不用额外安装。 const http = require('http'); const https = require('https'); const fs = require('fs'); const path = require('path'); const { URL } = require('url'); const PORT = 3000; function proxy(targetUrl, res) { //由 Node 去请求外站 https.get(targetUrl, (proxyRes) => { let data = ''; proxyRes.on('data', (chunk) => { data += chunk; }); proxyRes.on('end', () => { res.writeHead(proxyRes.statusCode || 200, { 'Content-Type': proxyRes.headers['content-type'] || 'application/json; charset=utf-8', 'Access-Control-Allow-Origin': '*', }); res.end(data); }); }).on('error', (err) => { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: err.message })); }); } http.createServer((req, res) => { const url = new URL(req.url, `http://${req.headers.host}`); if (url.pathname === '/proxy') { const targetUrl = url.searchParams.get('url'); if (!targetUrl) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'missing url parameter' })); } return proxy(targetUrl, res); } const file = path.join(__dirname, url.pathname === '/' ? 'index.html' : url.pathname.slice(1)); if (!file.startsWith(__dirname)) { res.writeHead(403).end('Forbidden'); return; } fs.readFile(file, (err, data) => { if (err) { res.writeHead(404).end('Not Found'); return; } const type = path.extname(file) === '.html' ? 'text/html; charset=utf-8' : 'text/plain'; res.writeHead(200, { 'Content-Type': type }).end(data); }); }).listen(PORT, '0.0.0.0', () => { console.log(`http://localhost:${PORT}`); });
cors(浏览器IE10以下不支持)
服务端Access-Control-Allow-Origin
preflight预检查:跨域复杂请求前,OPTIONS 请求
预检请求是指浏览器在发送跨域请求之前,会先发送一个OPTIONS 请求,用于询问服务器是否接受实际的请求。
预检请求主要用于对复杂请求(例如包含自定义请求头或使用非简单标头字段的请求)进行安全验证。
简单请求:GET/HEAD/POST+Content-Type:text/plain,multipart/form-data,application/x-www-form-urlencoded
条件2:除了被允许的自定义请求头(例如:X-PINGOTHER),请求头仅包含一些简单标头字段,如:Accept、Accept-Language、Content-Language、Content-Type,Range。
其中Content-Type 的值仅限于下列三者之一:
- text/plain无格式正文(可以有效避免XSS漏洞)
- multipart/form-data(键值对型数据)
- application/x-www-form-urlencoded(URLencoded)(默认)
报错
localhost/127.0.0.1与本机IP
将 `localhost` 改为本机 IP 后出现 CORS 跨域无法登录,原因如下:
1. **同源策略限制**:浏览器的同源策略要求协议、域名、端口完全一致。`localhost` 和 `127.0.0.1` 或本机 IP(如 `192.168.x.x`)在浏览器看来是不同的源。
2. **Cookie 不共享**:如果你的登录态(如 session、token、cookie)是在 `localhost` 下设置的,切换到本机 IP 时,cookie 不会自动带上,导致认证失效,接口返回未登录。
3. **CORS 配置问题**:后端如果只允许 `localhost` 跨域,换成 IP 后请求来源变了,CORS 校验失败,浏览器拦截请求。
应用
当服务端没有设置跨域时,可以将json伪装成text/plain
原理:
包装成简单请求,以躲过预检查。但是有些客户端或者代理还是会预检查
安全:
浏览器对于 JSON 响应的默认处理机制是自动将其解析为 JavaScript 对象。
当浏览器接收到响应时,如果响应的 Content-Type 是application/json或没有指定 Content-Type,浏览器会自动将响应体解析为JSON,JavaScript 对象,如果是恶意代码可能会被执行,导致安全漏洞或攻击。
通过将 JSON 响应伪装成 text/plain 类型,可以避免浏览器将响应自动解析为 JavaScript 对象的默认行为。
这样一来,JavaScript 代码需要手动对响应进行解析,确保数据的安全性和正确性。这种伪装可以提供额外的安全层,防止对于响应的自动解析可能带来的安全风险。
//请求头 GET /example HTTP/1.1 Host: example.com Accept: text/plain //响应头 HTTP/1.1 200 OK Content-Type: text/plain //响应体 {"foo": "bar"}跨源资源共享(CORS) - HTTP | MDN
Cross-Origin Resource Sharing (CORS) - HTTP | MDN
websocket:HTML5新特性,TCP,实时通信
兼容性不好,只适用于主流浏览器和IE10+
应用:webpack热更新
HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信
WebSocket和HTTP都是应用层协议,都基于TCP 协议。
WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了
原生WebSocket API使用起来不太方便
Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
本地文件socket.html向localhost:3000发生数据和接受数据:
// socket.html <script> let socket = new WebSocket('ws://localhost:3000'); socket.onopen = function () { socket.send('我爱你');//向服务器发送数据 } socket.onmessage = function (e) { console.log(e.data);//接收服务器返回的数据 } </script>// server.js let express = require('express'); let app = express(); let WebSocket = require('ws');//记得安装ws let wss = new WebSocket.Server({port:3000}); wss.on('connection',function(ws) { ws.on('message', function (data) { console.log(data); ws.send('我不爱你') }); })透传【开发业务】
透传 (Transparent Transmission):数据从一个系统/模块传递到另一个系统/模块,中间不做任何处理或修改
参数是否敏感?
↓
是 → 使用POST请求体/加密传输
否 → 参数是否简单且量小?
↓
是 → 使用URL参数(跨域跨端)
否 → 数据是否复杂或量大?
↓
是 → 使用状态管理/存储(Vuex、LocalStorage)
否 → 是否需要持久化?
↓
是 → LocalStorage/SessionStorage
否 → 是否临时性通信?
↓
是 → 事件总线/全局状态
url
// 假设当前URL:https://example.com/path?source=ehr&extra=xxx const urlParams = new URLSearchParams(window.location.search); // window.location.search = "?source=ehr&extra=xxx" // urlParams.get('source') → "ehr" ✅ //URLSearchParams 构造函数不会解析完整 URL,但是如果字符串起始位置有 ? 的话会被去除。 const paramsString1 = "http://example.com/search?query=%40"; const searchParams1 = new URLSearchParams(paramsString1); console.log(searchParams1.has("query")); // false console.log(searchParams1.has("http://example.com/search?query")); // true