前后端交互的方式

有很多种方式可以发送HTTP请求,比如以下(及存在的局限):

  • 用 form 可以发请求,但是会刷新页面或新开页面;
  • 用 a 可以发 get 请求,但是也会刷新页面或新开页面;
  • 用 img 可以发 get 请求,但是只能以图片的形式展示;
  • 用 link 可以发 get 请求,但是只能以 CSS、favicon 的形式展示;
  • 用 script 可以发 get 请求,但是只能以脚本的形式运行。

JSONP

JSONP即“JSON Padding”,当两个网站(如x.com访问y.com,不同域)之间需要访问,可以通过script作为交互方式,具体过程为:

  • 请求方(x.com前端)定义一个发送请求成功/失败后执行的函数f(回调函数,即使用方提供函数给对方调用);
  • 请求方动态创建script(添加到body),其src指向响应方url(y.com后端),同时将回调函数名作为参数传递,即http://y.com?callback=f
  • 响应方接收请求,根据查询参数f和返回的数据、构造调用这个函数的JavaScript代码字符串,形如f.call(undefined, data)f(data)作为响应结果返回给请求方;
  • 请求方浏览器接收响应(一段JS代码),被添加到body就会执行f.call(undefined, data),从而获得需要的数据data。

示例:JSONP请求

请求方html

<script>
    button.addEventListener(
        'click', (e) => {
            let functionName = 'x' + parseInt(Math.random() * 10000, 10)    // 随机生成回调函数名称
            window[functionName] = function(result) {
                if (result === 'success') {
                    // ...
                }
            }
            let script = document.createElement('script')
            script.src = 'http://y.com/?callback=' + functionName     // 发送请求获取script
            document.body.appendChild(script)    // 把script加入body中,自动执行
            script.onload = function(e) {
                e.currentTarget.remove()
                delete window[functionName]
            }
            script.onerror = function(e) {
                e.currentTarget.remove()
                delete window[functionName]
            }
        }
    )
</script>

也可以使用jQuery:

$.ajax({
    url: 'http://y.com/?callback=' + functionName,
    dataType: 'jsonp',
    success: function(response) {
       if(response === 'success'){
           // ...
       }
    }
})

响应方node.js

if (path === '/' && method === 'GET') {
    response.setHeader('Content-Type', 'application/javascript')
    response.write(`
        ${query.callback}.call(undefined, 'success')
    `)
    response.end()
}

一些细节:

  • 除了<a>标签外,<script>(只能以脚本形式运行)、<img>(只能展示为图片)都可以用作发送请求(Http Headers Content-Type中的image/jpgtext/javascript);
  • 发送请求、接收响应然后可以把数据填充到页面上,而不需要刷新整个页面;
  • 不同网站之间script访问不受限制(防盗链除外),因此可以在页面上<script src='xxx'></script>引入script并自动执行,JSONP也常被用作前后端数据交互的方式;
  • 请求方动态创建回调函数,名称一般使用随机数、执行后销毁,避免污染命名空间;而且执行成功/失败后会从页面上把script删除;
  • 由于页面上执行的逻辑完全由请求方前端实现,响应方后端只需要写好执行回调函数的字符串(函数名称为请求参数)、填充返回的数据即可,实现了前后端解耦;
  • 由于JSONP是通过动态创建script实现的,所以只支持GET请求,且只能以脚本形式运行。

AJAX

同源策略

  • 只有协议+端口+域名完全一样,浏览器才允许发送XMLHttpRequest请求(可以发送请求,但不能获取响应);
  • CORS(Cross-Origin Resource Sharing)跨域:要发送不同源(即协议、端口、域名中一或多个不同)请求,需要服务端配合,在响应头中加入Access-Control-Allow-Origin字段、内容为请求方域名即可放行。

AJAX即“Asynchronous Javascript And XML”,以XML和JSON格式作前后端交互,支持发送各种HTTP请求及任何形式展示响应,这个过程:

  • 使用XMLHttpRequest发送请求;
  • 服务器返回XML/JSON格式字符串;
  • 前端JavaScript解析XML,并更新局部页面。

示例(发送XMLHttpRequest请求):

let request = new XMLHttpRequest()
request.open('get', 'http://x.com')
request.onreadystatechange = () => {
    if (request.readyState === 4 && request.status >= 200 && request.status < 300 ) {
        let string = request.responseText
        let object = window.JSON.parse(string)
    }
}
request.send()

XML

目前已很少用作前后端交互,前端JS解析XML字符串:

let parser = new DOMParser()
let xmlDoc = parser.parseFromString(xmlString)

// 然后可以使用DOM API操作XML,很麻烦
xmlDoc.getElementsByTagName('heading')[0].textContent

JSON

JSON是一种类似JavaScript的数据格式化语言:

类型 JavaScript JSON
未定义 undefined -
null null
数组 ['a', ['b'] ["a", "b"]
函数 function(){} -
对象 {name: 'ywh'} {"name": "ywh"}
字符串 'ywh' "ywh"
变量 var a = {}; a.self = a -
原型链 {__proto__} -

注意JSON字符串的表示必须用双引号,前端JS解析JSON字符串:

let jsonObj = window.JSON.parse(jsonString)    // 返回JS对应类型的变量

实现AJAX

HTTP请求设置

request line

request.open('post', '/xxx')

request headers

request.setRequestHeader('Content-Type', 'x-www-form-urlencoded')

request playload(GET请求默认不显示)

request.send('ywh 18')

HTTP响应读取

response line(注意状态码不代表返回信息,即使是404也有可能带响应)

let status = request.status
let statusText = request.statusText

response headers

let headers = request.getAllResponseHeaders()
let contentType = request.getResponseHeader('Content-Type')

response body

let body = request.responseText

模拟jQuery发送HTTP请求

前面提到使用原生JS自行实现jQuery:

window.jQuery = function(nodesOrSelector) {
    // 判断传入的是节点还是选择器字符串,转换成统一的对象(伪数组)
    let nodes = {}
    if (typeof nodesOrSelector === 'string') {
        let temp = document.querySelectorAll(nodesOrSelector)
        for (let i = 0; i < temp.length; i++) {
            nodes[i] = temp[i]
        }
        nodes.length = temp.length
    }
    else if (nodesOrSelector instanceof Node) {
        nodes = {
            0: nodesOrSelector,
            length: 1
        }
    }
    return nodes
}

window.$ = jQuery    // 起别名
var node = $(item)    // 返回一个对象,内部封装了多个函数

把AJAX封装为jQuery一个函数来调用:

window.$.ajax = function(options) {
    let url    // 接受两种形式的参数:(url, options)或(options)(options中包含url)
    if (arguments.length === 1) {
        url = options.url
    }
    else {
        url = arguments[0]
        options = arguments[1]
    }
    let method = options.method
    let headers = options.headers
    let body = options.body
    let success = options.success
    let fail = options.fail 

    let reqeust = new XMLHttpRequest()
    for (let key in headers) {
        request.setRequestHeader(key, headers[key])
    }
    request.onreadystatechange = () => {
        if (request.readyState === 4) {
            if (request.status >= 200 && request.status < 300) {
                success.call(undefined, request.responseText)
            }
            else {
                fail.call(undefined, request)    
            }
        }
    }
    request.send(body)
}

btn.addEventListener('click', (e) => {
    window.$.ajax({
        url: '/xxx', 
        method: 'get', 
        // headers: '', 
        // body: '', 
        success: (x) => {
            console.log(x)
        }, 
        fail: () => {}    // 注意箭头函数没有arguments
    })
})

// 使用结构化参数,如果改由逐个参数传入存在问题:
// 封装后无法获取函数参数名称(应该传入什么?);
// 没有默认参数,只能传入undefined/null占位(很难看);

依然存在问题:调用函数依然依然需要通过文档获悉参数的名称,调用不方便

使用Promise优化:then可以连续根据每次成功/失败处理后的结果,调用指定的函数做多次处理,而不需要把所有函数都封装在success/fail的函数中。

window.$.ajax = function(options) {
    return new Promise(    // 返回Promise对象
        function (resolve, reject) {
            if (arguments.length === 1) {
                url = options.url
            }
            else {
                url = arguments[0]
                options = arguments[1]
            }
            let request = new XMLHttpRequest()
            request.open(options.method, url)
            request.onreadystatechange = () => {
                if (request.readyState === 4) {    
                    if (request.status >= 200 && request.status < 300) {
                        resolve.call(undefined, request.responseText)   // 成功:对应Promise对象的第一个函数参数
                    }
                    else {
                        reject.call(undefined, request)    // 失败:对应Promise对象的第二个函数参数
                    }
                }
            }
            request.send(options.body)
        }
    )
}

let promise = window.$.ajax({
    url: '/xxx',
    method: 'get'
})

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

推荐阅读更多精彩内容