欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

前后端交互的方式

程序员文章站 2024-03-23 17:33:58
...

有很多种方式可以发送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
)