前言:
平時業務中總會用到ajax請求,例如jQuery的ajax以及基於promise的axios請求,但是這是都是封裝好的了,不知道其原生實現是如何的。
所以在面試的時候,被問到了,並不能手寫出來,只能回答出XMLHttpRequest這個api,跟大概的流程,故此在這裡重新手寫ajax跟jsonp請求,熟悉下原生,並且附帶node後端demo
1、XMLHttpRequest實現ajax請求
//對請求data進行格式化處理
function formateData(data) {
let arr = [];
for (let key in data) {
//避免有&,=,?字元,對這些字元進行序列化
arr.push(encodeURIComponent(key) + '=' + data[key])
}
return arr.join('&');
}
function ajax(params) {
//先對params進行處理,防止為空
params = params || {};
params.data = params.data || {};
//普通GET,POST請求
params.type = (params.type || 'GET').toUpperCase();
params.data = formateData(params.data);
//如果是在ie6瀏覽器,那麼XMLHttoRequest是不存在的,應該呼叫ActiveXObject;
let xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
if (params.type === 'GET') {
xhr.open(params.type, params.url + '?' + params.data, true);
xhr.send();
} else {
xhr.open(params.type, params.url, true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
xhr.send(params.data);
}
// 這裡有兩種寫法,第一種寫法:當xhr.readyState===4的時候,會觸發onload事件,直接通過onload事件 進行回撥函式處理
xhr.onload = function () {
if (xhr.status === 200 || xhr.status === 304 || xhr.status === 206) {
var res;
if (params.success && params.success instanceof Function) {
res = JSON.parse(xhr.responseText);
params.success.call(xhr, res);
}
} else {
if (params.error && params.error instanceof Function) {
res = xhr.responseText;
params.error.call(xhr, res);
}
}
}
//第二種寫法,當xhr.readyState===4時候,說明請求成功返回了,進行成功回撥
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 進行onload裡面的處理函式
}
}
}
複製程式碼
2、jsonp實現ajax請求
//對請求data進行格式化處理
function formateData(data) {
let arr = [];
for (let key in data) {
//避免有&,=,?字元,對這些字元進行序列化
arr.push(encodeURIComponent(key) + '=' + data[key])
}
return arr.join('&');
}
//跨域jsonp請求
function jsonp(params) {
//先對params進行處理,防止為空
params = params || {};
params.data = params.data || {};
//後臺傳遞資料時呼叫的函式名
var callbackName = params.jsonp;
// 拿到dom元素head,先不進行操作
var head = document.querySelector('head');
//建立script元素,先不進行操作
var script = document.createElement('script');
//傳遞給後臺的data資料中,需要包含回撥引數callback。
//callback的值是 一個回撥函式的函式名,後臺通過該回撥函式名呼叫傳遞資料,這個引數名的key由雙方約定,預設為callback
params.data['callback'] = callbackName;
//對data資料進行格式化
var data = formateData(params.data);
//設定script請求的url跟資料
script.src = `${params.url}?${data}`;
//全域性函式 由script請求後臺,被呼叫的函式,只有後臺成功響應才會呼叫該函式
window[callbackName] = function (jsonData) {
//請求移除scipt標籤
head.removeChild(script);
clearTimeout(script.timer);
window[callbackName] = null;
params.success && params.success(jsonData)
}
//請求超時的處理函式
if (params.time) {
script.timer = setTimeout(() => {
//請求超時對window下的[callbackName]函式進行清除,由於有可能下次callbackName發生改變了
window[callbackName] = null;
//移除script元素,無論請求成不成功
head.removeChild(script)
//這裡不需要清除定時器了,clearTimeout(script.timer); 因為定時器呼叫之後就被清除了
//呼叫失敗回撥
params.error && params.error({
message: '超時'
})
}, time);
}
//往head元素插入script元素,這個時候,script就插入文件中了,請求並載入src
head.appendChild(script);
//無論是請求超時,還是請求成功,都要移除script元素,script元素只有在第一次插入頁面文件的時候,才會請求src
//無論請求失敗還是成功,都還是要移除window[callbackName]避免增加沒用的全域性方法,因為每次請求的callbackName可能是不同的
//之前有個無聊的問題:為啥jsonp只能是get請求呢?看了實現過程,知道其實是因為script的載入就是get方式的~
}
複製程式碼
3、node後端提供對應的ajax跟jsonp請求介面:
const Koa = require('koa');
const Router = require('koa-router');
const cors = require('koa2-cors');
const koaBody = require('koa-body');
const app = new Koa;
let home = Router();
app.use(cors());
app.use(koaBody())
home.get('/', async (ctx) => {
return ctx.body = {
code: 200,
message: '這個是首頁'
}
})
home.get('/ajax', async (ctx) => {
return ctx.body = {
code: 200,
data: ctx.request.query
}
})
home.post('/ajax', async (ctx) => {
return ctx.body = {
code: 200,
data: ctx.request.body
}
})
home.get('/jsonp', async (ctx) => {
let callbackName = ctx.request.query.callback;
let data = {
code: 200,
data: ctx.request.query
}
//返回體直接是函式呼叫,呼叫的實參是要後臺要傳遞的資料~由於data是物件,需要先進行json格式化
return ctx.body = `${callbackName}(${JSON.stringify(data)})`
})
app.use(home.routes());
app.use(home.allowedMethods())
app.listen(3000, () => {
console.log('start');
})
複製程式碼
4、網頁測試demo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ajax</title>
</head>
<body>
<script src="./ajax.js"></script>
<script>
//這個是ajax的get跟post請求demo
ajax({
type: 'post',
url: 'http://127.0.0.1:3000/ajax',
data: {
name: 'jgchen',
stuNo: 2016130201,
method: 'post'
},
success(res) {
console.log('POST success:',res);
},
error(err) {
console.log(err);
}
})
ajax({
type: 'GET',
url: 'http://127.0.0.1:3000/ajax',
data: {
name: 'jgchen',
stuNo: 2016130201,
method: 'get'
},
success(res) {
console.log('GET success:',res);
},
error(err) {
console.log(err);
}
})
//這個是jsonp的請求demo
jsonp({
url: 'http://127.0.0.1:3000/jsonp',
jsonp: 'callback',
data: {
name: 'jgchen',
stuNo: 2016130201,
method: 'jsonp'
},
success(res) {
console.log('jsonp success:',res);
},
error(err) {
console.log(err);
}
})
</script>
</body>
</html>
複製程式碼
5、方法驗證
請求成功,最後在控制檯列印出下列的資料: