一道面試題,手寫原生ajax和jsonp

前言:html

平時業務中總會用到ajax請求,例如jQuery的ajax以及基於promise的axios請求,可是這是都是封裝好的了,不知道其原生實現是如何的。前端

因此在面試的時候,被問到了,並不能手寫出來,只能回答出XMLHttpRequest這個api,跟大概的流程,故此在這裏從新手寫ajax跟jsonp請求,熟悉下原生,而且附帶node後端demonode

一、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裏面的處理函數
        }
    }

}

複製代碼

二、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方式的~
}
複製代碼

三、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');
})
複製代碼

四、網頁測試demoios

<!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>
複製代碼

五、方法驗證

請求成功,最後在控制檯打印出下列的數據: git

六、該實現過程完整代碼倉庫

ajax與jsonp 前端後端實現代碼倉庫,小白請先看readme操做運行項目github

相關文章
相關標籤/搜索