当前位置: 首页 > news >正文

4.2、浏览器请求详解(ajax、fetch、axios使用,手写ajax)

浏览器请求详解

  • 1.ajax
    • 手写ajax
  • 2. fetch
    • 2.1 Response
    • 2.2 定制 HTTP 请求
    • 2.3 option API
    • 2.4 fetch cancel
  • 3.axios
    • 3.1 基本使用
    • 3.2 响应数据
    • 3.3 创建实例(封装)
    • 3.4 拦截器
    • 3.5 完整的请求配置
  • 总结

在浏览器端发送网络请求的常见方式:

  1. ajax;
  2. fetch;
  3. axios;

1.ajax

Ajax 是一个技术统称,是一个概念模型,它囊括了很多技术,并不特指某一技术,它很重要的特性之一就是让页面实现局部刷新。
特点:

  • 局部刷新页面,无需重载整个页面。

简单来说,Ajax 是一种思想,XMLHttpRequest 只是实现 Ajax 的一种方式。其中 XMLHttpRequest 模块就是实现 Ajax 的一种很好的方式。

手写ajax

利用 XMLHttpRequest 模块实现 Ajax。
1、创建异步对象

let xmlHttp;
if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, SafarixmlHttp = new XMLHttpRequest();
} else {// code for IE6, IE5xmlHttp = new ActiveXObject('Microsoft.XMLHTTP');
}

创建的这个异步对象上有很多属性和方法,常用的有

  1. onreadystatechange:监听异步对象请求状态码readyState的改变,每当readyState改变时,就会触发onreadystatechange事件;
  2. readyState:请求状态码(readyState表示异步对象目前的状态,状态码从0到4):
    0: 表示请求未初始化,还没有调用 open();
    1: 服务器连接已建立,但是还没有调用 send();
    2: 请求已接收,正在处理中(通常现在可以从响应中获取内容头);
    3: 请求处理中,通常响应中已有部分数据可用了,没有全部完成;
    4: 当readyState状态码为4时,表示请求已完成;此阶段确认全部数据都已经解析完毕,可以通过异步对象的属性获取对应数据;
  3. status:http状态码
    http状态码表示成功的http状态码有
    xmlHttp.status >= 200 && xmlHttp.status < 300 || xmlHttp.status == 304
  4. responseText:后台返回的字符串形式的响应数据;
  5. responseXML:后台返回的XML形式的响应数据;

2、设置请求方式和请求地址
创建异步对象之后,通过open()方法设置ajax请求方式和请求地址 格式:

xmlHttp.open(“GET/POST”,“ajax-get.txt”,true)

第一个参数:请求的类型;GET 还是 POST;
第二个参数:表示请求的文件的地址url;
第三个参数:设置请求方法是不是异步async,true为异步,false为同步。AJAX存在的意义就是发异步请求,所以第三个参数永远传true;

注意:有个问题,就是IE中的缓存问题
在IE浏览器中,如果通过Ajax发送GET请求,那么IE浏览器认为,同一个URL只有一个结果,如果地址没有发生变化,它就会把上一次返回的结果,直接返回。这样我们不能实时的拿到变化后的数据。如果要想我们拿到实时数据,必须保证每次的URL都是不一样的,有两种方式:

  1. Math.random();
  2. new Date().getTime();

即在请求地址后面拼接上?t=随机数或者1970.01.01至当前的毫秒数 所以在IE中通过ajax发送get请求时,可以设置请求地址为:

xmlHttp.open("GET","ajax-get.txt?t=" + (new Date().getTime()),true);
//或
xmlHttp.open("GET","ajax-get.txt?t=" + Math.random(),true);

3、发送请求
直接通过异步对象的send()发送请求

xmlHttp.send();

特别注意的是: 如果发送POST请求,使用setRequestHeader()来添加 HTTP请求头,并在send()方法中传递要发送的数据:

xmlHttp.open("POST","ajax_test.html",true); 
xmlHttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); 
xmlHttp.send("fname=Henry&lname=Ford");

4、通过onreadystatechange监听状态变化

当异步对象的readyState发生改变,会触发onreadystatechange函数,当readyState变成为4时,表示当前状态是请求完毕的状态,同时当http的响应码status为200到300之间(包括200和300)或为304时,表示ajax请求成功;当http状态码不是200到300之间的数也不是304时,表示请求不成功

//4.监听状态变化
xmlHttp.onreadystatechange = () => {// 判断当前状态改变是请求完毕的状态吗if (xmlHttp.readyState === 4) {if (xmlHttp.status >= 200 && xmlHttp.status < 300 || xmlHttp.status == 304) {console.log("成功的接收到服务器返回的数据");}else{console.log("不成功!");}   }
}  

5、处理返回的结果

如果成功,可通过异步对象的responseText属性来获取服务器返回的字符串

接下来,我们来封装一个方法ajax()用于发送请求

ajax()方法调用:

ajax({type: 'GET',url: 'http://localhost:3000/posts',timeout: 1000,//方法设置超时时间data:[{name:'张三'}]success: data => {console.log('success', data);},error: err => {console.log('error', err);},
});

实现法ajax():

const ajax = option => {//  0.将data对象转换成字符串//处理objconst objToString = data => {data.t = new Date().getTime();let res = [];for (let key in data) {//需要将key和value转成非中文的形式,因为url不能有中文。使用encodeURIComponent();res.push(encodeURIComponent(key) + ' = ' + encodeURIComponent(data[key]));}return res.join('&');};let str = objToString(option.data || {});//  1.创建一个异步对象xmlHttp;var xmlHttp, timer;if (window.XMLHttpRequest) {xmlHttp = new XMLHttpRequest();} else if (xmlHttp) {// code for IE6, IE5xmlHttp = new ActiveXObject('Microsoft.xmlHttp');}//  2.设置请求方式和请求地址;// 判断请求的类型是POST还是GETif (option.type.toLowerCase() === 'get') {xmlHttp.open(option.type, option.url + '?' + str, true);//  3.发送请求;xmlHttp.send();} else {xmlHttp.open(option.type, option.url, true);// 注意:在post请求中,必须在open和send之间添加HTTP请求头:setRequestHeader(header,value);xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');//  3.发送请求;xmlHttp.send(str);}//  4.监听状态的变化;xmlHttp.onreadystatechange = function () {clearInterval(timer);debugger;if (xmlHttp.readyState === 4) {if ((xmlHttp.status >= 200 && xmlHttp.status < 300) || xmlHttp.status == 304) {//  5.处理返回的结果;option.success(xmlHttp.responseText); //成功后回调;} else {option.error(xmlHttp.responseText); //失败后回调;}}};//判断外界是否传入了超时时间if (option.timeout) {timer = setInterval(function () {xmlHttp.abort(); //中断请求clearInterval(timer);}, option.timeout);}
};

ajax只是一种异步请求的方式,并不特指某一种具体的实现方式,但随着使用这种方式实现网络请求时内部又包含请求的情况,就会出现回调地狱,这也是XHR的诟病之一,因此,后来才催生了更加优雅的请求方式。

2. fetch

Fetch 是在 ES6 出现的,它使用了 ES6 提出的 Promise 对象。它是 XMLHttpRequest 的替代品。
有人把它与 Ajax 作比较,其实这是不对的,我们通常所说的Ajax 是指使用 XMLHttpRequest 实现的 Ajax,所以真正应该和 XMLHttpRequest 作比较。

Fetch 是一个 API,它是真实存在的,它是基于 Promise 的。
建议阅读:MDN

fetch()的功能与 XMLHttpRequest 基本相同,但有三个差异:

  1. fetch使用 Promise,不使用回调函数,因此大大简化了写法,写起来更简洁;
  2. fetch采用模块化设计,API 分散在多个对象上(Response 对象、Request 对象、Headers 对象),更合理一些;相比之下,XMLHttpRequest 的 API 设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码;
  3. fetch通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。XMLHTTPRequest 对象不支持数据流,所有的数据必须放在缓存里,不支持分块读取,必须等待全部拿到后,再一次性吐出来;

在用法上,fetch()接受一个 URL 字符串作为参数,默认向该网址发出 GET 请求,返回一个 Promise 对象。它的基本用法如下。

fetch(url).then(...).catch(...)

下面是一个demo,从服务器获取 JSON 数据。

fetch('https://api.github.com/users/ruanyf').then(response => response.json()).then(json => console.log(json)).catch(err => console.log('Request Failed', err)); 

fetch()接收到的response是一个 Stream 对象,response.json()是一个异步操作,取出所有内容,并将其转为 JSON 对象。
Promise 可以使用 await 语法改写,使得语义更清晰。

async function getJSON() {let url = 'https://api.github.com/users/ruanyf';try {let response = await fetch(url);return await response.json();} catch (error) {console.log('Request Failed', error);}
}

上面示例中,await语句必须放在try…catch里面,这样才能捕捉异步操作中可能发生的错误。

2.1 Response

fetch()请求成功以后,得到的是一个 Response 对象

const response = await fetch(url);

Response 包含的数据通过 Stream 接口异步读取,但是它还包含一些同步属性,对应 HTTP 回应的标头信息,可以立即读取。

标头信息:

  1. Response.ok:属性返回一个布尔值,表示请求是否成功,true对应 HTTP 请求的状态码 200 到 299,false对应其他的状态码;
  2. Response.status:属性返回一个数字,表示 HTTP 回应的状态码(例如200,表示成功请求);
  3. Response.statusText:属性返回一个字符串,表示 HTTP 回应的状态信息(例如请求成功以后,服务器返回"OK");
  4. Response.url:属性返回请求的 URL。如果 URL 存在跳转,该属性返回的是最终 URL;
  5. Response.type:属性返回请求的类型。可能的值如下:
    • basic:普通请求,即同源请求;
    • cors:跨域请求;
    • error:网络错误,主要用于 Service Worker;
    • opaque:如果fetch()请求的type属性设为no-cors,就会返回这个值。表示发出的是简单的跨域请求,类似表单的那种跨域请求;
    • opaqueredirect:如果fetch()请求的redirect属性设为manual,就会返回这个值;
  6. Response.redirected:属性返回一个布尔值,表示请求是否发生过跳转。

判断请求是否成功

fetch()发出请求以后,有一个很重要的注意点:只有网络错误,或者无法连接时,fetch()才会报错,其他情况都不会报错,而是认为请求成功。
这就是说,即使服务器返回的状态码是 4xx 或 5xx,fetch()也不会报错(即 Promise 不会变为 rejected状态)。

那怎么判断请求是否成功呢?

1、Response.status属性,得到 HTTP 回应的真实状态码
response.status属性只有等于 2xx (200~299),才能认定请求成功。这里不用考虑网址跳转(状态码为 3xx),因为fetch()会将跳转的状态码自动转为 200。

  1. response.ok是否为true

Response.headers

Response 对象还有一个Response.headers属性,指向一个 Headers 对象,对应 HTTP 回应的所有标头。
Headers 对象提供了以下方法,用来操作标头。

Headers.get():根据指定的键名,返回键值。
Headers.has():返回一个布尔值,表示是否包含某个标头。
Headers.set():将指定的键名设置为新的键值,如果该键名不存在则会添加。
Headers.append():添加标头。 Headers.delete():删除标头。
Headers.keys():返回一个遍历器,可以依次遍历所有键名。
Headers.values():返回一个遍历器,可以依次遍历所有键值。
Headers.entries():返回一个遍历器,可以依次遍历所有键值对([key, value])。
Headers.forEach():依次遍历标头,每个标头都会执行一次参数函数。

如:

let response =  await  fetch(url);  
response.headers.get('Content-Type')
// application/json; charset=utf-8

读取内容的方法

Response对象根据服务器返回的不同类型的数据,提供了不同的读取方法。

  • response.text():得到文本字符串;比如 HTML 文件。
  • response.json():得到 JSON 对象;
  • response.blob():得到二进制 Blob 对象;读取图片文件。
  • response.formData():得到 FormData 表单对象;主要用在 Service Worker 里面,拦截用户提交的表单,修改某些数据以后,再提交给服务器。
  • response.arrayBuffer():得到二进制 ArrayBuffer 对象;主要用于获取流媒体文件。

这5个读取方法都是异步的,返回的都是 Promise 对象。必须等到异步操作结束,才能得到服务器返回的完整数据。
如:

const response = await fetch('/users.html');
const body = await response.text();
document.body.innerHTML = body

Response.clone
Stream 对象只能读取一次,读取完就没了。这意味着,前一节的五个读取方法,只能使用一个,否则会报错。

let text =  await response.text();
let json =  await response.json();  // 报错

Response 对象提供Response.clone()方法,创建Response对象的副本,实现多次读取。

const response1 = await fetch('flowers.jpg');
const response2 = response1.clone();const myBlob1 = await response1.blob();
const myBlob2 = await response2.blob();image1.src = URL.createObjectURL(myBlob1);
image2.src = URL.createObjectURL(myBlob2);

上面示例中,response.clone()复制了一份 Response 对象,然后将同一张图片读取了两次。
Response.body
Response.body属性是 Response 对象暴露出的底层接口,返回一个 ReadableStream 对象,供用户操作。
它可以用来分块读取内容,应用之一就是显示下载的进度。

const response = await fetch('flower.jpg');
const reader = response.body.getReader();while(true) {const {done, value} = await reader.read();if (done) {break;}console.log(`Received ${value.length} bytes`)
}

response.body.getReader()方法返回一个遍历器。这个遍历器的read()方法每次返回一个对象,表示本次读取的内容块。
这个对象的done属性是一个布尔值,用来判断有没有读完;value属性是一个 arrayBuffer 数组,表示内容块的内容,而value.length属性是当前块的大小

2.2 定制 HTTP 请求

fetch()的第一个参数是 URL,还可以接受第二个参数,作为配置对象,定制发出的 HTTP 请求

fetch(url, optionObj)

HTTP 请求的方法、标头、数据体都在这个对象里面设置。
如:


const response = await fetch(url, {method: 'POST',headers: {"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",},body: 'foo=bar&lorem=ipsum',
});const json = await response.json();

2.3 option API

fetch()第二个参数的完整 API 如下:

const response = fetch(url, {method: "GET",headers: {"Content-Type": "text/plain;charset=UTF-8"},body: undefined,referrer: "about:client",//用于设定fetch()请求的referer标头referrerPolicy: "no-referrer-when-downgrade",//用于设定Referer标头的规则mode: "cors", //mode属性指定请求的模式 跨域/同源credentials: "same-origin",//是否发送 Cookiecache: "default",//指定如何处理缓存 请求远程服务器与更不更新缓存redirect: "follow",//指定 HTTP 跳转的处理方法 如果页面发生跳转,fetch()跟随/报错/指向新urlintegrity: "",//指定一个哈希值,用于检查 HTTP 回应传回的数据是否等于这个预先设定的哈希值,比如,下载文件时,检查文件的 SHA-256 哈希值是否相符,确保没有被篡改keepalive: false,//为true时 表示页面卸载时,告诉浏览器在后台保持连接,继续发送数据,使用场景用户离开网页时,脚本向服务器提交一些用户行为的统计信息signal: undefined//指定一个 AbortSignal 实例,用于取消fetch()请求
});

可以去 Fetch_API查看详细api

2.4 fetch cancel

fetch()请求发送以后,如果中途想要取消,需要使用AbortController对象。
controller.abort()方法用于发出取消信号
下面是一个1秒后自动取消请求的例子。

let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);try {let response = await fetch('/long-operation', {signal: controller.signal});
} catch(err) {if (err.name == 'AbortError') {console.log('Aborted!');} else {throw err;}
}

3.axios

axios是一个用于网络请求的第三方库,是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:

  • 从浏览器中创建 XMLHttpRequest;
  • 从 node.js 发出 http 请求;
  • 支持 Promise API;
  • 拦截请求和响应;
  • 转换请求和响应数据;
  • 取消请求;
  • 自动转换JSON数据;
  • 客户端支持防止CSRF/XSRF;

3.1 基本使用

最基本的配置项应该包括:

  1. method 请求的方法(可选值: get , post 等);
  2. url 请求的地址 (必须项);
  3. data 请求发送的数据(post等请求需要);
axios({method: 'post',url: '/user/12345',data: {firstName: 'Fred',lastName: 'Flintstone'}
});

请求响应的处理在 then 和 catch 回调中,请求正常会进入 then ,请求异常则会进 catch

// 发送 POST 请求
axios({method: 'post',url: '/user/12345',data: {firstName: 'Fred',lastName: 'Flintstone'}
}).then(res => {consloe.log(res)
}).catch(err => {console.log(err)
})// 发送 GET 请求(默认的方法)
axios('/user/12345');

也可以使用axios.get()axios.post()

// 发送GET请求
axios.get('/user?ID=12345').then(function (response) {console.log(response);
}).catch(function (error) {console.log(error);
});// 发送POST请求
axios.post('/user', {firstName: 'Fred',lastName: 'Flintstone'
}).then(function (response) {console.log(response);
}).catch(function (error) {console.log(error);
});

3.2 响应数据

其中的 data 是后端返回的数据,一般只需要关注 response 中的 data 字段就行

{// `data` 由服务器提供的响应data: {},// `status` 来自服务器响应的 HTTP 状态码status: 200,// `statusText` 来自服务器响应的 HTTP 状态信息statusText: 'OK',// `headers` 服务器响应的头headers: {},// `config` 是为请求提供的配置信息config: {},request: {}
}

3.3 创建实例(封装)

可以使用自定义配置新建一个 axios 实例 axios.create([config]):

const instance = axios.create({baseURL: 'https://some-domain.com/api/',timeout: 1000,headers: {'X-Custom-Header': 'foobar'}
});

以下是实例所拥有的方法

  • request(config);
  • get(url[, config]);
  • delete(url[, config]);
  • head(url[, config]);
  • options(url[, config]);
  • post(url[, data[, config]]);
  • put(url[, data[, config]]);
  • patch(url[, data[, config]]);

axios会把这些 方法中的config 会和创建实例时指定的 config 合并到一起使用

3.4 拦截器

  • axios.interceptors.request 请求拦截器
  • axios.interceptors.response 响应拦截器
// 添加请求拦截器
axios.interceptors.request.use(function (config) {// 在发送请求之前做些什么config.header["Token"] = "xxxx"return config;
}, function (error) {// 对请求错误做些什么return Promise.reject(error);
});// 添加响应拦截器
axios.interceptors.response.use(function (response) {// 对响应数据做点什么if (response.status === 200){return response.data} else {return Promise.reject(new Error('error'))}
}, function (error) {// 对响应错误做点什么return Promise.reject(error);
});

如果想要取消拦截器,可以通过使用一个变量来接收设置拦截器时返回的实例,然后使用 eject 来取消拦截器

const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

3.5 完整的请求配置

{// `url` 是用于请求的服务器 URLurl: '/user',// `method` 是创建请求时使用的方法method: 'get', // default// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URLbaseURL: 'https://some-domain.com/api/',// `transformRequest` 允许在向服务器发送前,修改请求数据// 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法// 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 StreamtransformRequest: [function (data, headers) {// 对 data 进行任意转换处理return data;}],// `transformResponse` 在传递给 then/catch 前,允许修改响应数据transformResponse: [function (data) {// 对 data 进行任意转换处理return data;}],// `headers` 是即将被发送的自定义请求头headers: {'X-Requested-With': 'XMLHttpRequest'},// `params` 是即将与请求一起发送的 URL 参数// 必须是一个无格式对象(plain object)或 URLSearchParams 对象params: {ID: 12345},// `paramsSerializer` 是一个负责 `params` 序列化的函数// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)paramsSerializer: function(params) {return Qs.stringify(params, {arrayFormat: 'brackets'})},// `data` 是作为请求主体被发送的数据// 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'// 在没有设置 `transformRequest` 时,必须是以下类型之一:// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams// - 浏览器专属:FormData, File, Blob// - Node 专属: Streamdata: {firstName: 'Fred'},// `timeout` 指定请求超时的毫秒数(0 表示无超时时间)// 如果请求话费了超过 `timeout` 的时间,请求将被中断timeout: 1000,// `withCredentials` 表示跨域请求时是否需要使用凭证withCredentials: false, // default// `adapter` 允许自定义处理请求,以使测试更轻松// 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)).adapter: function (config) {/* ... */},// `auth` 表示应该使用 HTTP 基础验证,并提供凭据// 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头auth: {username: 'janedoe',password: 's00pers3cret'},// `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'responseType: 'json', // default// `responseEncoding` indicates encoding to use for decoding responses// Note: Ignored for `responseType` of 'stream' or client-side requestsresponseEncoding: 'utf8', // default// `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称xsrfCookieName: 'XSRF-TOKEN', // default// `xsrfHeaderName` is the name of the http header that carries the xsrf token valuexsrfHeaderName: 'X-XSRF-TOKEN', // default// `onUploadProgress` 允许为上传处理进度事件onUploadProgress: function (progressEvent) {// Do whatever you want with the native progress event},// `onDownloadProgress` 允许为下载处理进度事件onDownloadProgress: function (progressEvent) {// 对原生进度事件的处理},// `maxContentLength` 定义允许的响应内容的最大尺寸maxContentLength: 2000,// `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject  promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejectevalidateStatus: function (status) {return status >= 200 && status < 300; // default},// `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目// 如果设置为0,将不会 follow 任何重定向maxRedirects: 5, // default// `socketPath` defines a UNIX Socket to be used in node.js.// e.g. '/var/run/docker.sock' to send requests to the docker daemon.// Only either `socketPath` or `proxy` can be specified.// If both are specified, `socketPath` is used.socketPath: null, // default// `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。允许像这样配置选项:// `keepAlive` 默认没有启用httpAgent: new http.Agent({ keepAlive: true }),httpsAgent: new https.Agent({ keepAlive: true }),// 'proxy' 定义代理服务器的主机名称和端口// `auth` 表示 HTTP 基础验证应当用于连接代理,并提供凭据// 这将会设置一个 `Proxy-Authorization` 头,覆写掉已有的通过使用 `header` 设置的自定义 `Proxy-Authorization` 头。proxy: {host: '127.0.0.1',port: 9000,auth: {username: 'mikeymike',password: 'rapunz3l'}},// `cancelToken` 指定用于取消请求的 cancel token// (查看后面的 Cancellation 这节了解更多)cancelToken: new CancelToken(function (cancel) {})
}

总结

  1. Ajax 是Async Javascript And Xml的简称,它是原生JavaScript的一种请求方案,利用 XMLHttpRequest 进行异步请求数据,实现无感刷新数据;
  2. Fetch 是 ES6 新推出的一套异步请求方案,它天生自带 Promise,同时也是原生的,如果在较小项目中为了项目大小着想和兼容性不是那么高的前提下不妨可以用它来进行异步请求也是不错的;
  3. Axios 是基于 Ajax 和 Promise 封装的一个库,可以利用Promise来更好的管控请求回调嵌套造成的回调地狱;

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【CTS】android CTS测试
  • Nginx和Tomcat负载均衡、动静分离集群1
  • 【C++】#20,#21
  • Maven下载安装、环境配置(超详细)(包括Windows、IDEA)
  • 解决帝国cms栏目管理拼音乱码的问题
  • 数学建模基础:线性模型
  • MySQL-DML-约束
  • 为何云原生是未来?企业IT架构的颠覆与重构(上)
  • LabVIEW开发中的常见通讯协议详解
  • 【调试笔记-20240618-Windows- Tauri 调试中关闭自动重构的功能】
  • Linux中FTP安装
  • 11、反转链表(含源码)
  • sprintboot依赖管理和自动配置
  • 在PHP项目中使用阿里云消息队列MQ集成RabbitMQ的完整指南与问题解决
  • 两种单例模式(保证线程安全)
  • php的引用
  • 2017年终总结、随想
  • Brief introduction of how to 'Call, Apply and Bind'
  • Cookie 在前端中的实践
  • es6要点
  • mac修复ab及siege安装
  • PHP 小技巧
  • 记录一下第一次使用npm
  • 盘点那些不知名却常用的 Git 操作
  • 前端之Sass/Scss实战笔记
  • 驱动程序原理
  • 收藏好这篇,别再只说“数据劫持”了
  • 学习JavaScript数据结构与算法 — 树
  • 源码安装memcached和php memcache扩展
  • 远离DoS攻击 Windows Server 2016发布DNS政策
  • 找一份好的前端工作,起点很重要
  • 如何在招聘中考核.NET架构师
  • ​iOS安全加固方法及实现
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • # 移动硬盘误操作制作为启动盘数据恢复问题
  • (13):Silverlight 2 数据与通信之WebRequest
  • (ZT)薛涌:谈贫说富
  • (二)pulsar安装在独立的docker中,python测试
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (三)elasticsearch 源码之启动流程分析
  • (太强大了) - Linux 性能监控、测试、优化工具
  • (推荐)叮当——中文语音对话机器人
  • (学习日记)2024.01.09
  • (一)Linux+Windows下安装ffmpeg
  • (原创)攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
  • (转)linux 命令大全
  • .form文件_一篇文章学会文件上传
  • .net 4.0 A potentially dangerous Request.Form value was detected from the client 的解决方案
  • .NET CF命令行调试器MDbg入门(四) Attaching to Processes
  • .net core 3.0 linux,.NET Core 3.0 的新增功能
  • .Net 代码性能 - (1)
  • .NET 命令行参数包含应用程序路径吗?
  • .net 前台table如何加一列下拉框_如何用Word编辑参考文献
  • .NET 应用架构指导 V2 学习笔记(一) 软件架构的关键原则
  • .net访问oracle数据库性能问题