一、前端举行收集要求的关注点
大多数状况下,在前端提议一个收集要求我们只需关注下面几点:
- 传入基础参数(
url
,要求体式格局) - 要求参数、要求参数范例
- 设置要求头
- 猎取相应的体式格局
- 猎取相应头、相应状况、相应效果
- 异常处置惩罚
- 照顾
cookie
设置 - 跨域要求
二、前端举行收集要求的体式格局
-
form
表单、ifream
、革新页面 -
Ajax
– 异步收集要求的开山鼻祖 -
jQuery
– 一个时期 -
fetch
–Ajax
的替代者 -
axios、request
等浩瀚开源库
三、关于收集要求的疑问
-
Ajax
的涌现处理了什么题目 - 原生
Ajax
怎样运用 -
jQuery
的收集要求体式格局 -
fetch
的用法以及坑点 - 怎样准确的运用
fetch
- 怎样挑选适宜的跨域体式格局
带着以上这些题目、关注点我们对几种收集要求举行一次全面的剖析。
四、Ajax的涌现处理了什么题目
在Ajax
涌现之前,web
顺序是如许事变的:
这类交互的的缺点是不言而喻的,任何和服务器的交互都须要革新页面,用户体验异常差,Ajax
的涌现处理了这个题目。Ajax
全称Asynchronous JavaScript + XML
(异步JavaScript
和XML
)
运用Ajax
,网页运用能够疾速地将增量更新显如今用户界面上,而不须要重载(革新)全部页面。
Ajax
自身不是一种新手艺,而是用来形貌一种运用现有手艺鸠合完成的一个手艺计划,浏览器的XMLHttpRequest
是完成Ajax
最重要的对象(IE6
以下运用ActiveXObject
)。
只管X
在Ajax
中代表XML
, 但由于JSON
的许多上风,比方越发轻量以及作为Javascript
的一部份,如今JSON
的运用比XML
越发广泛。
五、原生Ajax的用法
这里重要剖析XMLHttpRequest
对象,下面是它的一段基础运用:
var xhr = new XMLHttpRequest();
xhr.open('post','www.xxx.com',true)
// 吸收返回值
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 ){
if(xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
console.log(xhr.responseText);
}
}
}
// 处置惩罚要求参数
postData = {"name1":"value1","name2":"value2"};
postData = (function(value){
var dataString = "";
for(var key in value){
dataString += key+"="+value[key]+"&";
};
return dataString;
}(postData));
// 设置要求头
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
// 异常处置惩罚
xhr.onerror = function() {
console.log('Network request failed')
}
// 跨域照顾cookie
xhr.withCredentials = true;
// 发出要求
xhr.send(postData);
下面离别对XMLHttpRequest
对象经常使用的的函数、属性、事宜举行剖析。
函数
open
用于初始化一个要求,用法:
xhr.open(method, url, async);
-
method
:要求体式格局,如get、post
-
url
:要求的url
-
async
:是不是为异步要求
send
用于发送 HTTP
要求,即挪用该要领后HTTP
要求才会被真正发出,用法:
xhr.send(param)
-
param
:http要求的参数,能够为string、Blob
等范例。
abort
用于停止一个ajax
要求,挪用此要领后readyState
将被设置为0
,用法:
xhr.abort()
setRequestHeader
用于设置HTTP
要求头,此要领必需在 open()
要领和 send()
之间挪用,用法:
xhr.setRequestHeader(header, value);
getResponseHeader
用于猎取http
返转头,假如在返转头中有多个一样的称号,那末返回的值就会是用逗号和空格将值分开的字符串,用法:
var header = xhr.getResponseHeader(name);
属性
readyState
用来标识当前XMLHttpRequest
对象所处的状况,XMLHttpRequest
对象老是位于以下状况中的一个:
值 | 状况 | 形貌 |
---|---|---|
0 | UNSENT | 代办被竖立,但还没有挪用 open() 要领。 |
1 | OPENED | open() 要领已被挪用。 |
2 | HEADERS_RECEIVED | send() 要领已被挪用,而且头部和状况已可获得。 |
3 | LOADING | 下载中; responseText 属性已包含部份数据。 |
4 | DONE | 下载支配已完成。 |
status
示意http
要求的状况, 初始值为0
。假如服务器没有显式地指定状况码, 那末status
将被设置为默许值, 即200
。
responseType
示意相应的数据范例,并许可我们手动设置,假如为空,默以为text
范例,能够有下面的取值:
值 | 形貌 |
---|---|
"" | 将 responseType 设为空字符串与设置为"text" 雷同, 是默许范例 (实际上是 DOMString )。 |
"arraybuffer" | response 是一个包含二进制数据的 JavaScript ArrayBuffer 。 |
"blob" | response 是一个包含二进制数据的 Blob 对象 。 |
"document" | response 是一个 HTML Document 或 XML XMLDocument ,这取决于吸收到的数据的 MIME 范例。 |
"json" | response 是一个 JavaScript 对象。这个对象是经由过程将吸收到的数据范例视为 JSON 剖析获得的。 |
"text" | response 是包含在 DOMString 对象中的文本。 |
response
返回相应的正文,返回的范例由上面的responseType
决议。
withCredentials
ajax
要求默许会照顾同源要求的cookie
,而跨域要求则不会照顾cookie
,设置xhr
的withCredentials
的属性为true
将许可照顾跨域cookie
。
事宜回调
onreadystatechange
xhr.onreadystatechange = callback;
当readyState
属性发作变化时,callback会被触发。
onloadstart
xhr.onloadstart = callback;
在ajax
要求发送之前(readyState==1
后, readyState==2
前),callback
会被触发。
onprogress
xhr.onprogress = function(event){
console.log(event.loaded / event.total);
}
回调函数能够猎取资本总大小total
,已加载的资本大小loaded
,用这两个值能够盘算加载进度。
onload
xhr.onload = callback;
当一个资本及其依靠资本已完成加载时,将触发callback
,平常我们会在onload
事宜中处置惩罚返回值。
异常处置惩罚
onerror
xhr.onerror = callback;
当ajax
资本加载失利时会触发callback
。
ontimeout
xhr.ontimeout = callback;
当进度由于预定时候到期而停止时,会触发callback
,超时时候可运用timeout
属性举行设置。
六、jQuery对Ajax的封装
在很长一段时候里,人们运用jQuery
供应的ajax
封装举行收集要求,包含$.ajax、$.get、$.post
等,这几个要领放到如今,我依旧觉得很有用。
$.ajax({
dataType: 'json', // 设置返回值范例
contentType: 'application/json', // 设置参数范例
headers: {'Content-Type','application/json'},// 设置要求头
xhrFields: { withCredentials: true }, // 跨域照顾cookie
data: JSON.stringify({a: [{b:1, a:1}]}), // 通报参数
error:function(xhr,status){ // 毛病处置惩罚
console.log(xhr,status);
},
success: function (data,status) { // 猎取效果
console.log(data,status);
}
})
$.ajax
只吸收一个参数,这个参数吸收一系列设置,其本身封装了一个jqXHR
对象,有兴致能够浏览一下jQuary-ajax 源码
经常使用设置:
url
当前页地点。发送要求的地点。
type
范例:String
要求体式格局 ("POST"
或 "GET"
), 默以为 "GET"
。注重:别的 HTTP
要求要领,如 PUT
和 DELETE
也能够运用,但仅部份浏览器支撑。
timeout
范例:Number
设置要求超时时候(毫秒)。此设置将掩盖全局设置。
success
范例:Function
要求胜利后的回调函数。
jsonp
在一个jsonp
要求中重写回调函数的名字。这个值用来替代在"callback=?"
这类GET
或POST
要求中URL
参数里的"callback"
部份。
error 范例:Function
。要求失利时挪用此函数。
注重:源码里对毛病的剖断:
isSuccess = status >= 200 && status < 300 || status === 304;
返回值除了这几个状况码都邑进error
回调。
dataType
"xml": 返回 XML 文档,可用 jQuery 处置惩罚。
"html": 返回纯文本 HTML 信息;包含的 script 标签会在插进去 dom 时实行。
"script": 返回纯文本 JavaScript 代码。不会自动缓存效果。除非设置了 "cache" 参数。注重:在长途要求时(不在同一个域下),一切 POST 要求都将转为 GET 要求。(由于将运用 DOM 的 script标签来加载)
"json": 返回 JSON 数据 。
"jsonp": JSONP 花样。运用 JSONP 情势挪用函数时,如 "myurl?callback=?" jQuery 将自动替代 ? 为准确的函数名,以实行回调函数。
"text": 返回纯文本字符串
data
范例:String
运用JSON.stringify
转码
complete
范例:Function
要求完成后回调函数 (要求胜利或失利以后均挪用)。
async
范例:Boolean
默许值: true
。默许设置下,一切要求均为异步要求。假如须要发送同步要求,请将此选项设置为 false
。
contentType
范例:String
默许值: "application/x-www-form-urlencoded"
。发送信息至服务器时内容编码范例。
键值对如许组织在平常的状况下是没有什么题目的,这里说的平常是,不带嵌套范例JSON
,也就是 简朴的JSON
,形如如许:
{
a: 1,
b: 2,
c: 3
}
然则在一些庞杂的状况下就有题目了。 比方在 Ajax
中你要传一个庞杂的 json
对像,也就说是对象嵌数组,数组中包含对象,你如许传: application/x-www-form-urlencoded
这类情势是没有办法将庞杂的 JSON
组织成键值对情势。
{
data: {
a: [{
x: 2
}]
}
}
能够用以下体式格局通报庞杂的json
对象
$.ajax({
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify({a: [{b:1, a:1}]})
})
七、jQuery的替代者
近年来前端MV*
的发展壮大,人们越来越少的运用jQuery
,我们不能够零丁为了运用jQuery
的Ajax api
来零丁引入他,无可避免的,我们须要寻觅新的手艺计划。
尤雨溪在他的文档中引荐人人用axios
举行收集要求。axios
基于Promise
对原生的XHR
举行了异常全面的封装,运用体式格局也异常的文雅。别的,axios
一样供应了在node
环境下的支撑,可谓是收集要求的首选计划。
将来一定还会涌现更优异的封装,他们有异常全面的斟酌以及细致的文档,这里我们不多做精细精美,我们把关注的重点放在更底层的APIfetch
。
Fetch API
是一个用用于接见和支配HTTP管道的壮大的原生 API。
这类功用之前是运用 XMLHttpRequest完成的。Fetch供应了一个更好的替代要领,能够很容易地被其他手艺运用,比方 Service Workers。Fetch还供应了单个逻辑位置来定义其他HTTP相干观点,比方CORS和HTTP的扩大。
可见fetch
是作为XMLHttpRequest
的替代品涌现的。
运用fetch
,你不须要再分外加载一个外部资本。但它还没有被浏览器完整支撑,所以你依然须要一个 polyfill
。
八、fetch的运用
一个基础的 fetch要求:
const options = {
method: "POST", // 要求参数
headers: { "Content-Type": "application/json"}, // 设置要求头
body: JSON.stringify({name:'123'}), // 要求参数
credentials: "same-origin", // cookie设置
mode: "cors", // 跨域
}
fetch('http://www.xxx.com',options)
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson); // 相应数据
})
.catch(function(err){
console.log(err); // 异常处置惩罚
})
Fetch API
供应了一个全局的fetch()
要领,以及几个辅佐对象来提议一个收集要求。
fetch()
fetch()
要领用于提议猎取资本的要求。它返回一个 promise
,这个 promise
会在要求相应后被 resolve
,并传回 Response
对象。
Headers
能够经由过程 Headers()
组织函数来竖立一个你本身的 headers
对象,相当于 response/request
的头信息,能够使你查询到这些头信息,或许针对差别的效果做差别的支配。
var myHeaders = new Headers();
myHeaders.append("Content-Type", "text/plain");
Request
经由过程 Request()
组织函数能够竖立一个Request
对象,这个对象能够作为fetch
函数的第二个参数。
Response
在fetch()
处置惩罚完promises
以后返回一个Response
实例,也能够手动竖立一个Response
实例。
九、fetch polyfill源码剖析
由于fetch
是一个异常底层的API
,所以我们没法进一步的探讨它的底层,然则我们能够借助它的polyfill
探讨它的基础原理,并找出个中的坑点。
代码构造
由代码可见,polyfill
重要对Fetch
API供应的四大对象举行了封装:
fetch 封装
代码异常清楚:
- 组织一个
Promise
对象并返回 - 竖立一个
Request
对象 - 竖立一个
XMLHttpRequest
对象 - 掏出
Request
对象中的要求url
,要求方发,open
一个xhr
要求,并将Request
对象中存储的headers
掏出赋给xhr -
xhr onload
后掏出response
的status
、headers
、body
封装Response
对象,挪用resolve
。
异常处置惩罚
能够发明,挪用reject
有三种能够:
- 1.要求超时
- 2.要求失利
注重:当和服务器竖立简介,并收到服务器的异常状况码如404、500
等并不能触发onerror
。当收集故障时或要求被阻挠时,才会标记为 reject
,如跨域、url
不存在,收集异常等会触发onerror
。
所以运用fetch当吸收到异常状况码都是会进入then而不是catch。这些毛病要求每每要手动处置惩罚。
- 3.手动停止
能够在request
参数中传入signal
对象,并对signal
对象增加abort
事宜监听,当xhr.readyState
变成4
(相应内容剖析完成)后将signal对象的abort事宜监听移撤除。
这示意,在一个fetch
要求终了之前能够挪用signal.abort
将其停止。在浏览器中能够运用AbortController()
组织函数竖立一个掌握器,然后运用AbortController.signal
属性
这是一个试验中的功用,此功用某些浏览器尚在开辟中
Headers封装
在header对象中保护了一个map
对象,组织函数中能够传入Header
对象、数组、一般对象范例的header
,并将一切的值保护到map
中。
之前在fetch
函数中看到挪用了header
的forEach
要领,下面是它的完成:
可见header
的遍历即其内部map
的遍历。
别的Header
还供应了append、delete、get、set
等要领,都是对其内部的map
对象举行支配。
Request对象
Request
对象吸收的两个参数即fetch
函数吸收的两个参数,第一个参数能够直接通报url
,也能够通报一个组织好的request
对象。第二个参数即掌握差别设置的option
对象。
能够传入credentials、headers、method、mode、signal、referrer
等属性。
这里注重:
- 传入的
headers
被看成Headers
组织函数的参数来组织header对象。
cookie处置惩罚
fetch函数中另有以下的代码:
if (request.credentials === 'include') {
xhr.withCredentials = true
} else if (request.credentials === 'omit') {
xhr.withCredentials = false
}
默许的credentials
范例为same-origin
,即可照顾同源要求的coodkie。
然后我发明这里polyfill的完成和MDN-运用Fetch以及许多材料是不一致的:
mdn: 默许状况下,fetch 不会从服务端发送或吸收任何 cookies
因而我离别试验了下运用polyfill
和运用原生fetch
照顾cookie的状况,发如今不设置credentials
的状况下竟然都是默许照顾同源cookie
的,这和文档的申明说不一致的,查阅了许多材料后都是说fetch
默许不会照顾cookie,下面是运用原生fetch
在浏览器举行要求的状况:
然后我发如今MDN-Fetch-Request已指出新版浏览器credentials
默许值已更改成same-origin
,旧版依旧是omit
。
确切MDN-运用Fetch这里的文档更新的有些不实时,误人子弟了…
Response对象
Response
对象是fetch
挪用胜利后的返回值:
回忆下f
etch中对
Response`的支配:
xhr.onload = function () {
var options = {
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || '')
}
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
var body = 'response' in xhr ? xhr.response : xhr.responseText
resolve(new Response(body, options))
}
Response
组织函数:
可见在组织函数中重要对options
中的status、statusText、headers、url
等离别做了处置惩罚并挂载到Response
对象上。
组织函数内里并没有对responseText
的明白处置惩罚,末了交给了_initBody
函数处置惩罚,而Response
并没有主动声明_initBody
属性,代码末了运用Response
挪用了Body
函数,实际上_initBody
函数是经由过程Body
函数挂载到Response
身上的,先来看看_initBody
函数:
可见,_initBody
函数依据xhr.response
的范例(Blob、FormData、String...
),为差别的参数举行赋值,这些参数在Body
要领中获得差别的运用,下面详细看看Body
函数还做了哪些其他的支配:
Body
函数中还为Response
对象挂载了四个函数,text、json、blob、formData
,这些函数中的支配就是将_initBody中获得的差别范例的返回值返回。
这也说清楚明了,在fetch
实行终了后,不能直接在response
中猎取到返回值而必需挪用text()、json()
等函数才猎取到返回值。
这里另有一点须要申明:几个函数中都有相似下面的逻辑:
var rejected = consumed(this)
if (rejected) {
return rejected
}
consumed函数:
function consumed(body) {
if (body.bodyUsed) {
return Promise.reject(new TypeError('Already read'))
}
body.bodyUsed = true
}
每次挪用text()、json()
等函数后会将bodyUsed
变量变成true
,用来标识返回值已读取过了,下一次再读取直接抛出TypeError('Already read')
。这也遵照了原生fetch
的准绳:
由于Responses对象被设置为了 stream 的体式格局,所以它们只能被读取一次
十、fetch的坑点
VUE
的文档中对fetch
有下面的形貌:
运用
fetch
另有许多别的注重事项,这也是为何人人现阶段照样更喜好
axios
多一些。固然这个事变在将来能够会发作转变。
由于fetch
是一个异常底层的API
,它并没有被举行许多封装,另有许多题目须要处置惩罚:
- 不能直接通报
JavaScript
对象作为参数 - 须要本身推断返回值范例,并实行相应猎取返回值的要领
- 猎取返回值要领只能挪用一次,不能屡次挪用
- 没法一般的捕捉异常
- 老版浏览器不会默许照顾
cookie
- 不支撑
jsonp
十一、对fetch的封装
要求参数处置惩罚
支撑传入差别的参数范例:
function stringify(url, data) {
var dataString = url.indexOf('?') == -1 ? '?' : '&';
for (var key in data) {
dataString += key + '=' + data[key] + '&';
};
return dataString;
}
if (request.formData) {
request.body = request.data;
} else if (/^get$/i.test(request.method)) {
request.url = `${request.url}${stringify(request.url, request.data)}`;
} else if (request.form) {
request.headers.set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
request.body = stringify(request.data);
} else {
request.headers.set('Content-Type', 'application/json;charset=UTF-8');
request.body = JSON.stringify(request.data);
}
cookie照顾
fetch
在新版浏览器已最先默许照顾同源cookie
,但在老版浏览器中不会默许照顾,我们须要对他举行一致设置:
request.credentials = 'same-origin'; // 同源照顾
request.credentials = 'include'; // 可跨域照顾
异常处置惩罚
当吸收到一个代表毛病的 HTTP 状况码时,从 fetch()返回的 Promise 不会被标记为 reject, 纵然该 HTTP 相应的状况码是 404 或 500。相反,它会将 Promise 状况标记为 resolve (然则会将 resolve 的返回值的 ok 属性设置为 false ),仅当收集故障时或要求被阻挠时,才会标记为 reject。
因而我们要对fetch
的异常举行一致处置惩罚
.then(response => {
if (response.ok) {
return Promise.resolve(response);
}else{
const error = new Error(`要求失利! 状况码: ${response.status}, 失利信息: ${response.statusText}`);
error.response = response;
return Promise.reject(error);
}
});
返回值处置惩罚
对差别的返回值范例挪用差别的函数吸收,这里必需提早推断好范例,不能屡次挪用猎取返回值的要领:
.then(response => {
let contentType = response.headers.get('content-type');
if (contentType.includes('application/json')) {
return response.json();
} else {
return response.text();
}
});
jsonp
fetch
自身没有供应对jsonp
的支撑,jsonp
自身也不属于一种异常好的处理跨域的体式格局,引荐运用cors
或许nginx
处理跨域,详细请看下面的章节。
fetch封装好了,能够兴奋的运用了。
嗯,axios真好用…
十二、跨域总结
谈到收集要求,就不得不提跨域。
浏览器的同源战略限定了从同一个源加载的文档或剧本怎样与来自另一个源的资本举行交互。这是一个用于断绝潜伏歹意文件的重要平安机制。平常不许可差别源间的读支配。
跨域前提:协定,域名,端口,有一个差别就算跨域。
下面是处理跨域的几种体式格局:
nginx
运用nginx
反向代办完成跨域,参考我这篇文章:前端开辟者必备的nginx学问
cors
CORS
是一个W3C
规范,全称是”跨域资本共享”(Cross-origin resource sharing)
。它许可浏览器向跨源服务器,发出XMLHttpRequest
要求。
服务端设置 Access-Control-Allow-Origin
就能够开启 CORS
。 该属性示意哪些域名能够接见资本,假如设置通配符则示意一切网站都能够接见资本。
app.all('*', function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
next();
});
jsonp
script
标签的src
属性中的链接能够接见跨域的js
剧本,应用这个特征,服务端不再返回JSON
花样的数据,而是返回一段挪用某个函数的js
代码,在src
中举行了挪用,如许完成了跨域。
jquery
对jsonp
的支撑:
$.ajax({
type : "get",
url : "http://xxxx"
dataType: "jsonp",
jsonp:"callback",
jsonpCallback: "doo",
success : function(data) {
console.log(data);
}
});
fetch、axios
等并没有直接供应对jsonp
的支撑,假如须要运用这类体式格局,我们能够尝试举行手动封装:
(function (window,document) {
"use strict";
var jsonp = function (url,data,callback) {
// 1.将传入的data数据转化为url字符串情势
// {id:1,name:'jack'} => id=1&name=jack
var dataString = url.indexof('?') == -1? '?': '&';
for(var key in data){
dataString += key + '=' + data[key] + '&';
};
// 2 处置惩罚url中的回调函数
// cbFuncName回调函数的名字 :my_json_cb_名字的前缀 + 随机数(把小数点去掉)
var cbFuncName = 'my_json_cb_' + Math.random().toString().replace('.','');
dataString += 'callback=' + cbFuncName;
// 3.竖立一个script标签并插进去到页面中
var scriptEle = document.createElement('script');
scriptEle.src = url + dataString;
// 4.挂载回调函数
window[cbFuncName] = function (data) {
callback(data);
// 处置惩罚完回调函数的数据以后,删除jsonp的script标签
document.body.removeChild(scriptEle);
}
document.body.appendChild(scriptEle);
}
window.$jsonp = jsonp;
})(window,document)
postMessage跨域
postMessage()
要领许可来自差别源的剧本采纳异步体式格局举行有限的通讯,能够完成跨文本档、多窗口、跨域音讯通报。
//捕捉iframe
var domain = 'http://scriptandstyle.com';
var iframe = document.getElementById('myIFrame').contentWindow;
//发送音讯
setInterval(function(){
var message = 'Hello! The time is: ' + (new Date().getTime());
console.log('blog.local: sending message: ' + message);
//send the message and target URI
iframe.postMessage(message,domain);
},6000);
//相应事宜
window.addEventListener('message',function(event) {
if(event.origin !== 'http://davidwalsh.name') return;
console.log('message received: ' + event.data,event);
event.source.postMessage('holla back youngin!',event.origin);
},false);
postMessage
跨域适用于以下场景:同浏览器多窗口间跨域通讯、iframe
间跨域通讯。
WebSocket
WebSocket
是一种双向通讯协定,在竖立衔接以后,WebSocket
的 server
与 client
都能主意向对方发送或吸收数据而不受同源战略的限定。
function WebSocketTest(){
if ("WebSocket" in window){
alert("您的浏览器支撑 WebSocket!");
// 翻开一个 web socket
var ws = new WebSocket("ws://localhost:3000/abcd");
ws.onopen = function(){
// Web Socket 已衔接上,运用 send() 要领发送数据
ws.send("发送数据");
alert("数据发送中...");
};
ws.onmessage = function (evt) {
var received_msg = evt.data;
alert("数据已吸收...");
};
ws.onclose = function(){
// 封闭 websocket
alert("衔接已封闭...");
};
} else{
// 浏览器不支撑 WebSocket
alert("您的浏览器不支撑 WebSocket!");
}
}
文中若有毛病,迎接在批评区斧正,感谢浏览。
引荐人人运用Fundebug,一款很好用的BUG监控东西~