实习生面试题:手写原生ajax和jsonp

4,275 阅读3分钟

前言:

平时业务中总会用到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、方法验证

请求成功,最后在控制台打印出下列的数据:

6、该实现过程完整代码仓库

ajax与jsonp 前端后端实现代码仓库,小白请先看readme操作运行项目