Appearance
前端网络请求
表单提交
表单提交是前端项目中最原始的网络请求
html
<form id="myForm" enctype="application/x-www-form-urlencoded">
<label for="name">姓名:</label>
<input type="text" id="name" name="name" /><br /><br />
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" /><br /><br />
<label for="message">留言:</label><br />
<textarea id="message" name="message"></textarea><br /><br />
<input type="submit" value="提交" />
</form>
enctype
表单enctype
属性用于指定在提交表单数据时所使用的编码类型
application/x-www-form-urlencoded
,这是最常用的编码类型。在该编码类型下,表单数据会以键值对的形式进行 URL 编码,并将数据包含在请求的正文中。这是 HTML5 规范中指定的默认编码类型,这种编码格式将键值对的参数用&
连接起来,如果有空格,将空格转换为+
加号;有特殊符号,将特殊符号转换为ASCII HEX
值
multipart/form-data
,将数据编码成一整条消息,其中包含了多个 Parts,每个 Part 都包含头信息部和 type 等内容,通过boundary
进行分割。如果要发送大量的二进制数据(non-ASCII),使用application/x-www-form-urlencoded
是很浪费的,因此使用表单上传文件时往往需要指定为该编码格式
text-plain
,纯文本,浏览器不进行任何编码
application/json
,JSON数据类型,主要用于 AJAX 或 Fetch API 发送 JSON 格式的数据时使用,需要注意的是表单提交不支持这种数据类型,写在这里只是为了方便对比
针对不同的enctype
,服务端需要按照对应的格式才能解析请求体的内容
URL编码
相关面试题
为什么会存在url编码
url 长度过长在 android 中报错,如何处理
js
var originalString = 'Hello World!';
var encodedString = encodeURIComponent(originalString);
console.log(encodedString);
// 输出:Hello%20World%21
var decodedString = decodeURIComponent(encodedString);
console.log(decodedString);
// 输出:Hello World!
URL编码是一种将URL中的特殊字符转换为URL安全格式的过程。URL中允许使用一部分特殊字符(如?、&、=、/等),但某些字符在URL中具有特殊含义,或者在不同的上下文中可能被解析为其他目的。因此,为了确保URL的正确性和一致性,需要对URL进行编码。
主要原因包括以下几点:
特殊字符的含义:URL中的某些字符具有特殊含义,用于表示URL的结构、参数等信息。例如,?用于分隔URL和查询参数,&用于分隔多个查询参数,=用于分隔参数的键和值等。如果URL中包含这些特殊字符本身作为普通字符使用,而不是作为其特定含义,就需要对它们进行编码,以避免歧义和解析错误。
非ASCII字符和非安全字符:URL中可以包含非ASCII字符(如汉字、特殊符号等)和非安全字符(如空格、引号、尖括号等)。为了在传输过程中避免出现问题,这些字符需要被编码成安全的ASCII字符。URL编码使用了一种叫做百分号编码(Percent Encoding)的方式,将非ASCII字符和非安全字符转换为%xx的形式,其中xx代表字符的ASCII码值。
URL长度限制:某些网络环境对URL的长度有限制,如果URL中包含较长的特殊字符或非ASCII字符,可能会导致URL超过限制而出现问题。通过URL编码,可以将较长的字符转换为固定长度的编码形式,从而减小URL长度并确保其有效性。
总之,URL编码的目的是确保URL的正确性、一致性和安全性,避免歧义和解析错误,以及适应各种网络环境和限制。编码后的URL可以保证特殊字符和非ASCII字符在传输过程中能够正确处理,同时遵守URL的规范和标准。
XHR
如何封装一个可控制并发数量限制的网络请求方法?
之前遇见的一个面试题目是手写代码,封装 ajax,碰到这种面试官就赶紧跑路吧~
js
var xhr = new XMLHttpRequest()
var url = '/api'
xhr.open("GET", url, false)
xhr.onreadystatechange = function () {
// 这里的函数异步执行,可参考之前 JS 基础中的异步模块
if (xhr.readyState == 4) {
if (xhr.status == 200) {
alert(xhr.responseText)
}
}
}
xhr.send(null)...
在绝大多数情况下,前端开发都不需要再手动编写 XHR 代码了。
Axios
Axios 是一个流行的 JavaScript 库,用于进行 HTTP 请求。它是基于 Promise 的异步操作库,可以在浏览器和 Node.js 环境中使用。Axios 提供了简洁而强大的 API,可以轻松地发送 HTTP 请求,并处理响应数据。
在浏览器端,Axios底层使用的是XMLHttpRequest
k
使用 Axios,你可以发送各种类型的 HTTP 请求,如 GET、POST、PUT、DELETE 等。它还支持设置请求头、发送请求参数、处理响应数据、处理错误等功能。
Axios 还提供了拦截器(interceptors)功能,可以在请求发送前或响应返回后对请求进行拦截和处理。
Axios 的主要优点之一是它的易用性和灵活性。它可以与各种前端框架(如 React、Vue.js、Angular)和后端服务器(如 Node.js、Express)无缝集成,也是 SSR 项目等前端同构项目中数据请求的常用方法。
Fetch
了解 fetch 吗?fetch 如何解决跨域问题?如何取消 fetch 请求?能否监听 fetch 的上传进度?
参考:Fetch-MDN 文档
Fetch API 是一种现代的网络请求 API,提供了更简洁和功能强大的方式来发送网络请求。Fetch 的核心在于对 HTTP 接口的抽象,包括 Request,Response,Headers,Body,
js
var url = "/index.php"
fetch(url, {
method: "POST",
body: JSON.stringify({code:'1'}).
}).then(res=>{
return res.text()
}).then(res=>{
console.log(res)
})
只有 POST 方式可以配置 body 参数
兼容性
fetch 本身存在浏览器兼容性的问题,此外由于 fetch 依赖 Promise,而 promise 本身也存在兼容性问题。
一种常规的 fetch-polyfill 的思路是:首先判断浏览器是否原生支持 fetch,否则结合 Promise 使用 XMLHttpRequest 的方式来实现。
cookie
fetch 可以手动控制是否需要在请求中携带 cookie,手动配置credentials
,其取值有
- omit:
默认值,忽略 cookie 的发送 - same-origin: 表示 cookie 只能同域发送,不能跨域发送
- include: cookie 既可以同域发送,也可以跨域发送
测试发现在 Chrome72 中 credentials 的默认值貌似已经调整为same-origin
响应错误的处理
fetch 返回的是一个 Promise,其抛出 reject 的机制为:在某些错误的 http 状态下如 400、500 等不会 reject,相反它会被 resolve;只有网络错误会导致请求不能完成时,fetch 才会被 reject;因此需要在 resolve 做一层判断
js
fetch(url)
.then(function (response) {
if (response.ok) {
return response.json();
}
throw new Error("Network response was not ok.");
})
.catch(function (error) {
console.log(error.message);
});
不支持 timeout 和 abort
fetch 并没有提供请求超时时间的配置项,不过可以通过下面思路实现 fetch 的 timeout 功能
js
var oldFetchfn = window.fetch;
window.fetch = function (url, opts) {
return new Promise((resolve, reject) => {
// 超过timeout时间仍未响应,则抛出超时的错误
var timeoutTimer = setTimeout(() => {
reject(new Error("fetch timeout"));
}, opts.timeout);
oldFetchfn(url, opts).then(
(res) => {
clearTimeout(timeoutTimer);
resolve(res);
},
(err) => {
clearTimeout(timeoutTimer);
reject(err);
}
);
});
};
或者简单实用Promise.race[request, timeout]
来模拟超时
另外,根据 Promise 指导规范标准,promise 实例是不能 abort 的,这表示在通过 fetch 发送请求之后,无法中断请求,根据上面的思路,我们可以手动实现 abort
js
var oldFetchfn = fetch;
window.fetch = function (input, opts) {
return new Promise(function (resolve, reject) {
var p = oldFetchfn(input, opts).then(resolve, reject);
p.abort = function () {
reject(new Error("fetch abort"));
};
return p;
});
};
尽管上面实现了类似于 timeout 和 abort 的功能,但需要注意
- 这里实现的 timeout 并不是“请求连接超时”的配置项,而包含了请求连接、服务器处理、响应回来直至改变 promise 状态的这一整段时间
- 这里实现的 abort 功能,只是忽略了这次请求的响应,因为即使调用 abort,实际上本次请求也不会被 abort 掉,仍旧会发送到服务端
不支持 jsonp
jsonp 只是一种实现跨域的方法,而不是 xhr 和 fetch 这样的协议。fetch 不支持 jsonp 是理所应当的。
不支持监听进度
在 xhr 中,可以通过下面方式获取进度
js
xhr.upload.onprogress = () => {}; //上传的progress事件
xhr.onprogress = () => {}; //下载的progress事件
在 fetch 并,并没有提供相关的事件,因此fetch 是不支持 progress 的;不过在 fetch 中,response.body
是一个可读字节流对象,因此可以用来模拟 progresss,详情可参考2016 - the year of web streams
js
fetch(url).then((response) => {
var reader = response.body.getReader();
var bytesReceived = 0;
reader.read().then(function processResult(result) {
if (result.done) {
console.log("Fetch complete");
return;
}
bytesReceived += result.value.length;
console.log("Received", bytesReceived, "bytes of data so far");
return reader.read().then(processResult);
});
});
fetch跨域
与 ajax 相同,可以通过 CORS 解决 fetch 的跨域问题,服务器通过Access-Control-Allow-Origin
响应头来允许指定的源进行跨域。
除此之外,fetch 还可以通过mode
配置项,设置请求的模式,
- same-origin,该模式不允许跨域的,发送的请求需要遵守同源策略
- cors,该模式支持跨域请求,顾名思义它是以 CORS 的形式跨域;当然该模式也支持同域请求
- no-cors,该模式用于跨域请求但是服务器不带 CORS 响应头,其作用是运行浏览器发送此次请求,但无法访问响应的内容,与 img 标签类似
WebSocket
由于 http 存在一个明显的弊端(消息只能有客户端推送到服务器端,而服务器端不能主动推送到客户端),导致如果服务器如果有连续的变化,这时只能使用轮询,而轮询效率过低,并不适合。于是 WebSocket 被发明出来。
- 支持双向通信,实时性更强;
- 可以发送文本,也可以二进制文件;
- 协议标识符是 ws,加密后是 wss ;
- 较少的控制开销。连接创建后,ws 客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有 2~10 字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的 4 字节的掩码。而 HTTP 协议每次通信都需要携带完整的头部;
- 支持扩展。ws 协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)
- 无跨域问题。
为什么 websocket 可以跨域
websocket 的请求报文中,有一个字段是 Origin,表示该请求的请求源(origin),即发自哪个域名。是因为有了 Origin 这个字段,所以浏览器没有对 websocket 执行同源策略显示,因为服务器可以根据这个字段,判断是否许可本次通信。
那么,如何保证 websocket 的安全性呢?
作为一项新技术,WebSockets 旨在从一开始就支持跨域场景。任何编写服务器逻辑的人都应该意识到跨域请求的可能性,并执行必要的验证,而无需使用浏览器端强大的同源策略。
跨域
参考:浏览器中的跨域