24_用js写一个ajax的原生实现方法

一、JS原生Ajax

1、基本概念

  • ajax:一种请求数据的方式,不需要刷新整个页面;
  • 核心:ajax的技术核心是 XMLHttpRequest 对象;
  • ajax 请求过程:创建 XMLHttpRequest 对象、连接服务器、发送请求、接收响应数据;

2、函数封装

前端代码:

  ajax({
        url: "http://localhost:3000/api/userinfo",              //请求地址
        type: "POST",                       //请求方式
        data: { name: "super", age: 20 },        //请求参数
        dataType: "json",
        success: function (response, xml) {
            console.log(response)
            response = JSON.parse(response) || {}
            var temp = [];
            if (response.status === 'success') {
                var data = response.data;
                Object.keys(data).forEach(function(key){
                    temp.push('<span>'+key+':'+data[key]+'</span><br/>');
                })
                document.write(temp.join(''));
            }
        },
        fail: function (status) {
            console.error(status);
        }
});

function ajax(options) {
    options = options || {};
    options.type = (options.type || "GET").toUpperCase();
    options.dataType = options.dataType || "json";
    var params = formatParams(options.data);

    //创建 - 非IE6 - 第一步
    if (window.XMLHttpRequest) {
        var xhr = new XMLHttpRequest();
    } else { //IE6及其以下版本浏览器
        var xhr = new ActiveXObject('Microsoft.XMLHTTP');
    }

    //接收 - 第三步
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
            var status = xhr.status;
            if (status >= 200 && status < 300) {
                options.success && options.success(xhr.responseText, xhr.responseXML);
            } else {
                options.fail && options.fail(status);
            }
        }
    }

    //连接 和 发送 - 第二步
    if (options.type == "GET") {
        xhr.open("GET", options.url + "?" + params, true);
        xhr.send(null);
    } else if (options.type == "POST") {
        xhr.open("POST", options.url, true);
        //设置表单提交时的内容类型
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        xhr.send(params);
    }
}
//格式化参数
function formatParams(data) {
    var arr = [];
    for (var name in data) {
        arr.push(encodeURIComponent(name) + "=" + encodeURIComponent(data[name]));
    }
    arr.push(("v=" + Math.random()).replace(".",""));
    return arr.join("&");
}

后端逻辑:

//server.js
var http = require('http');
var fs = require('fs');
var querystring = require('querystring');

var server = http.createServer(function(req, res) {

    // 前端页面请求路由
    if (req.url == "/index") {
        fs.readFile('index.html', function(err, data) {
            res.writeHead(200, { "Content-type": "text/html;charset=utf-8" });
            res.end(data);
        });
    } else {
        fs.readFile('404.html', function(err, data) {
            res.writeHead(200, { "Content-type": "text/html;charset=utf-8" });
            res.end(data);
        });
    }

    
    // 请求方法
    if (req.method.toLowerCase() === 'post') {
        // 接口路由
        if(req.url == "/api/userinfo") {
            var reqData = '';
            req.on('data', function (chunk) {
                reqData += chunk;
            });

            req.on('end', function () {
                //将字符串转换位一个对象
                console.log(reqData); //name=super&age=20&v=0865843628884519

                var dataString = reqData.toString();
                //将接收到的字符串转换位为json对象
                var dataObj = querystring.parse(dataString);
                dataObj.sex = '男'
                //输出数据
                var data={
                status:'success',
                message:'获取数据成功',
                    data:dataObj
                };
                res.writeHead(200, { "Content-Type": "application/json;charset=UTF-8" });
                res.end(JSON.stringify(data));
            });
        } 
    };
});

server.listen(3000, '127.0.0.1');

console.log('启动服务,监听 127.0.0.1:3000');

3、函数解析

(1)创建

  • IE7及其以上版本中支持原生的 XHR 对象,因此可以直接用: var oAjax = new XMLHttpRequest()
  • IE6及其之前的版本中,XHR对象是通过MSXML库中的一个ActiveX对象实现的。有的书中细化了IE中此类对象的三种不同版本,即MSXML2.XMLHttp、MSXML2.XMLHttp.3.0和MSXML2.XMLHttp.6.0;若感觉太麻烦,可以直接使用下面的语句创建: var oAjax=new ActiveXObject('Microsoft.XMLHTTP')

(2)连接和发送

  • open()函数的三个参数:请求方式、请求地址、是否异步请求(同步请求的情况极少)
  • GET 请求方式是通过URL参数将数据提交到服务器的,POST则是通过将数据作为 send 的参数提交到服务器
  • POST 请求中,在发送数据之前,要设置表单提交的内容类型
  • 提交到服务器的参数必须经过 encodeURIComponent() 方法进行编码,实际上在参数列表”key=value”的形式中,key 和 value 都需要进行编码,因为会包含特殊字符。每次请求的时候都会在参数列表中拼入一个 “v=xx” 的字符串,这样是为了拒绝缓存,每次都直接请求到服务器上

要点:

  • encodeURI() :用于整个 URI 的编码,不会对本身属于 URI 的特殊字符进行编码,如冒号、正斜杠、问号和井号;其对应的解码函数 decodeURI()
  • encodeURIComponent():用于对 URI 中的某一部分进行编码,会对它发现的任何非标准字符进行编码;其对应的解码函数 decodeURIComponent()

(3)接收

  • 接收到响应后,响应的数据会自动填充XHR对象,相关属性如下

    • responseText:响应返回的主体内容,为字符串类型;
    • responseXML:如果响应的内容类型是 "text/xml" 或 "application/xml",这个属性中将保存着相应的xml 数据,是 XML 对应的 document 类型;
    • status:响应的HTTP状态码;
    • statusText:HTTP状态的说明;
  • XHR对象的readyState属性表示请求/响应过程的当前活动阶段,这个属性的值如下

    • 0-未初始化,尚未调用open()方法;
    • 1-启动,调用了open()方法,未调用send()方法;
    • 2-发送,已经调用了send()方法,未接收到响应;
    • 3-接收,已经接收到部分响应数据;
    • 4-完成,已经接收到全部响应数据;
  • 在readystatechange事件中,先判断响应是否接收完成,然后判断服务器是否成功处理请求,xhr.status 是状态码,状态码以2开头的都是成功,304表示从缓存中获取,上面的代码在每次请求的时候都加入了随机数,所以不会从缓存中取值,故该状态不需判断

要点

  • ajax请求是不能跨域的
  • 只要 readyState 的值变化,就会调用 readystatechange 事件,(其实为了逻辑上通顺,可以把readystatechange放到send之后,因为send时请求服务器,会进行网络通信,需要时间,在send之后指定readystatechange事件处理程序也是可以的,但为了规范和跨浏览器兼容性,建议在open之前进行指定)

二、Fetch API

Fetch API提供了一个fetch()方法,它被定义在BOM的window对象中,你可以用它来发起对远程资源的请求。 该方法返回的是一个Promise对象,让你能够对请求的返回结果进行检索。

1、Fetch 基本用法

fetch()方法,包含了需要fetch 的网址和对应的属性设定( 例如method、headers、mode、body...等,最基本的写法属性不一定要填),执行之后会送出Request,如果得到回应就会回传带有Response 的Promise 内容,使用then 将回传值传递下去。

fetch('网址')
    .then(function(response) {
// 处理 response
    }).catch(function(err) {
// 错误处理
});

2、Fetch 的 Request 属性

以下列出Fetch常用的的Request属性。

属性 设定值
url 第一个参数,必填项,代表需要fetch对象的网址
method GET、POST、PUT、DELETE、HEAD ( 默认GET )
headers 设置相关的Headers 内容( 预设{} )
mode cors、no-cors、same-origin、navigate ( 默认cors )
referrer no-referrer、client 或某个网址( 默认client )
credentials omit、same-origin、include ( 默认omit )
redirect follow、error、manual ( 默认manual )
cache default、no-store、reload、no-cache、force-cache ( 默认default )

3、Fetch 的Response 属性

以下列出Fetch常用的Response属性。

属性 设定值
headers 包含与response 相关的Headers 内容
ok 成功返回true,不成功返回false
status 状态代码,成功为200
statusText 状态信息,成功为ok
type response 的类型,例如basic、cors...等
url response 的url

4、Fetch 的Response 方法

以下列出Fetch常用的Response方法。

属性 设定值
json() 返回Promise,resolves 是JSON 对象
text() 返回Promise,resolves 是text string
blob() 返回Promise,resolves 是blob ( 非结构化对象,例如文字或二进制信息)
arrayBuffer() 返回Promise,resolves 是ArrayBuffer ( 有多少bytes )
formData() 返回Promise,resolves 是formData ( 表单资料对应的的Key 或Value )
clone() 创建一个Response对象的克隆。
error() 返回Response 的错误内容

5、Fetch 的Get 用法

Get 是Fetch 最简单的方法,使用Get 必须要将fetch 第二个参数里的method 设定为get,如果遇到跨域问题,就搭配其他属性例如mode、credentials 来进行细部设定(但针对非跨域的就没用了),下方的示例做了一个简单的后端请求,通过fetch 传递姓名和年纪的参数,就会看到后端回应一串文字。

前端代码:

const name = 'ooxx';
const age = 18;
const uri = `http://127.0.0.1:3000/api/fetch/userinfo?name=${name}&age=${age}`;
fetch(uri, {method:'GET'})
    .then(res => {
        return res.text();// 使用 text() 可以得到纯文字 String
    }).then(result => {
      console.log(result); // 得到「你的名字是:ooxx,年纪:18 岁」
});

后端逻辑:

var pathname = url.parse(req.url).pathname;
if (pathname == "/api/fetch/userinfo") {
    var reqData = url.parse(req.url).query;
    var dataStr = querystring.parse(reqData);
    res.writeHead(200, { "Content-Type": "application/json;charset=UTF-8" });
    res.end('你的名字是:'+dataStr.name +',年纪:'+dataStr.age+' 岁');
}

6、Fetch 的Post 用法

使用POST方法可以搭配body属性设定传递参数,比如我的接口地址,可以接收name和age所组成的JSON请求,当网址接收到要求后,就会回应一个json对象,需要注意的是,如果是传递「中文」可能会出现乱码,这时可以使用encodeURI来做转码,且要通过JSON.stringify来转换成string方式传递。

前端代码:

fetch(uri, {
    method:'POST',
    body:encodeURI(JSON.stringify({
        name:'ooxx',
        age:18
    })),
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
    }
})
    .then(res => {
        return res.json(); // 使用 json() 可以得到 json 对象
    }).then(result => {
    console.log(result);
    // 得到 {name: "ooxx", age: 18, text: "你的名字是 ooxx,年纪18岁~"}
});

后端逻辑:

if(req.url == "/api/fetch/userinfo") {
    var reqData = '';
    req.on('data', function (chunk) {
        reqData += chunk;
    });

    req.on('end', function () {
      //将字符串转换位一个对象
      var dataObj = JSON.parse(decodeURI(reqData.toString()));
      var data = {
        name: dataObj.name,
        age: dataObj.age,
        text: '你的名字是 '+dataObj.name+',年纪'+dataObj.age+'岁~'
      }

      res.writeHead(200, { "Content-Type": "application/json;charset=UTF-8" });
      res.end(JSON.stringify(data));
    });
}

7、Fetch 搭配async、await、promise.all

过去在XMLHttpRequest 或jQuery AJAX 的全盛时期,如果要确保每个GET 或POST 的要求,都要按照指定的顺序进行,往往会用上一连串的callback 辅助,但是当callback 越来越多,代码也就越来越难管理,然而fetch 返回的是一个Promise,我们也就能直接利用await 或promise.all 的作法,轻松掌握同步与非同步之间的转换。

下方的例子是一个非同步的示例,因为没有进行任何的同步处理,所以执行之后,会先出现hello的文字,接着才是通过fetch 得到的结果。

const postURL = (name,age) => {
    return fetch(uri, {
        method:'POST',
        body:encodeURI(JSON.stringify({
            name:name,
            age:age
        })),
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
        }
    })
        .then(res => {
            return res.json();
        }).then(result =>{
            console.log(result);
        });
};
postURL('Lily',18);
console.log('hello!!!');
postURL('Tom',18);

因为fetch 的特性,可以改成async 和await 的写法,执行后也就能按照我们要的顺序进行。

async function fetchAll () {
  await Promise.all([postURL('Lily',18), postURL('Tom',18)]);
  console.log('hello!!!');
}
fetchAll()

8、兼容性

关于Fetch API的兼容性,现代浏览器大部分还是支持的,可以放心使用,如下图所示:

Fetch API 的神奇,简化了许多原本较为复杂的用法,也让项目代码写起来更加干净易读好维护。更重要的是 JavaScript ES6 原生支持,你不需要安装任何依赖包,直接可以在项目中使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Ajax和XMLHttpRequest 我们通常将Ajax等同于XMLHttpRequest,但细究起来它们两个是...
    changxiaonan阅读 2,217评论 0 2
  • AJAX 原生js操作ajax 1.创建XMLHttpRequest对象 var xhr = new XMLHtt...
    碧玉含香阅读 3,168评论 0 7
  • 践行人员]肖永辉(父),刘芳(母),肖雯心(女宝) [打卡日期]:2018年7月10日 [累计天数]:5/30 #...
    雯与宸阅读 128评论 0 0
  • 记忆里总有一个镜头,我和母亲站在月台上,向父亲挥手。那时年幼,并不知这五指一晃,就要又半年见不到父亲,只觉得父亲坐...
    白色婚礼阅读 586评论 0 4
  • 今天晚上的沙龙田老师讲到了固定化思维,在我理解的固定化思维是一些负面的不好的需要我们调整和改变的,因为它会影响我们...
    重启人生阅读 314评论 0 3