美文网首页Java面试
前后端交互的方式

前后端交互的方式

作者: 千反田爱瑠爱好者 | 来源:发表于2018-08-13 22:35 被阅读788次

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

    相关文章

      网友评论

        本文标题:前后端交互的方式

        本文链接:https://www.haomeiwen.com/subject/glqpbftx.html