简单讲讲JSONP

195 阅读7分钟

先看百度解析如下:

JSONP(JSON with Padding)是数据格式JSON的一种“使用模式”,可以让网页从别的网域要数据,利用script元素开放策略,网页可以从其他来源动态产生的JSON数据,而这种使用模式就是所谓的 JSONP。


了解什么是数据库?
1、文件系统是一个数据库
2、MySQL是一个数据库
只要能长久的存数据,就是数据库。


下面要讲一段故事:

最早开始前后端交互是从form开始:

    <h5>您的账户余额是:<span id="amount">&&&amount&&&</span></h5>
    <form action="/pay" method="POST">
        <input type="submit" value="付款">
    </form>
    <script src="/server.js"></script>

action="/pay" method="post"前端写这些参数,让后端知道如何操作。
创建一个db(随便打的)的文件,作为数据库,里面就写100。

后台关联起来:

if(path === '/'){          //如果用户请求的路径是根目录
    var string = fs.readFileSync('./index.html','utf8')  
    var amount = fs.readFileSync('./db','utf8')   //读取这两个文件里的数据
    string = string .replace('&&&amount&&&',amount) 
    //把db数据库文件里的内容替换进文中&&&amount&&&处
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(string)
    response.end()
  }else if(path === '/server.js'){
    var string = fs.readFileSync('./server.js','utf8')
    response.setHeader('Content-Type', 'application/javascrip')
    response.write(string)
    response.end()
  }else if(path === '/pay' && method.toUpperCase() === 'POST'){ 
  //如果路径是pay,且发送的是post
    var amount = fs.readFileSync('./db','utf8') //100
    var newAmount = amount - 1
    fs.writeFileSync('./db',newAmount)
    response.write('success')
    response.end()
  }else{
    response.statusCode = 400
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write()
    response.end()
  }

至此这就是旧时代的前后端配合交互的方法。
以下说说问题,使用form发请求,每次点击付款,成功-1后要返回并刷新页面,才能更新数据。虽然我们可以加个<iframe name="result" src="about:blank" frameborder="0" height="200"></iframe>,但还是需要刷新,用户体验不良好。

后来人们发现不止是form,a,img,link,script标签,都可以发送请求。

那么试试用创建img来发送请求:

<h5>您的账户余额是:<span id="amount">&&&amount&&&</span></h5>
<button id="button">打钱</button>
<script>
    button.addEventListener('click',(e)=>{
        let image = document.createElement('img')
        image.src = '/pay'
        image.onload = function(){
            alert('打钱成功')
            window.location.reload()
        }
        image.onerror = function(){
            alert('打钱失败')
        }
    })
</script>

发现img有个缺陷,无法发送post请求,因为除了能写image.src = '/pay'外,浏览器没有给选项能选post,所以他只能get。虽然无论成功或失败,都会弹出提示,但是最后还是需要用户手动刷新页面更新数据,用户体验不良好。

所以为了用户体验,加个代码window.location.reload()成功后自动帮用户刷新页面,这时用户体验就非常良好了。但是又有个问题,简单的页面或许可以,但是若页面内容多了,这样每次都刷新页面就会重新渲染整个页面,当然是不太好吧?那还有什么更好的呢?

height=

既然如此,我们来帮用户-1,会怎么样呢:

<h5>您的账户余额是:<span id="amount">&&&amount&&&</span></h5>
<button id="button">打钱</button>
<script>
    button.addEventListener('click',(e)=>{
        let image = document.createElement('img')
        image.src = '/pay'
        image.onload = function(){
            alert('打钱成功')
            amount.innerText=amount.innerText - 1
        }
        image.onerror = function(){
            alert('打钱失败')
        }
    })
</script>

后台:

if(path === '/'){      //如果用户请求的路径是根目录
    var string = fs.readFileSync('./index.html','utf8')  
    var amount = fs.readFileSync('./db','utf8')    //读取这两个文件里的内容
    string = string .replace('&&&amount&&&',amount)  //把db数据库文件里的内容替换进文中&&&amount&&&处
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(string)
    response.end()
  }else if(path === '/server.js'){
    var string = fs.readFileSync('./server.js','utf8')
    response.setHeader('Content-Type', 'application/javascrip')
    response.write(string)
    response.end()
  }else if(path === '/pay'){  //如果路径是pay
    var amount = fs.readFileSync('./db','utf8') //100
    var newAmount = amount - 1
    fs.writeFileSync('./db',newAmount)
    response.setHeader('Content-Type', 'image/png')
    response.write(fs.readFileSync('./a.png'))
    response.end()
  }else{
    response.statusCode = 400
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write('找不到对应路径,你需要自行修改index.js')
    response.end()
  }

运行,点击打钱,弹出提示成功,数字自动-1,不用刷新页面。只在数字那里-1,这叫局部刷新。

height=

现在看看script方法:

<script>
        button.addEventListener('click',(e)=>{
        let script = document.createElement('script') 
        script.src = '/pay'
        document.body.appendChild(script)
        script.onload = function(){
            alert('打钱成功')
        }
        script.onerror = function(){
            alert('打钱失败')
        }
    })
</script>
//以上省略。。。
}else if(path === '/pay'){  
    var amount = fs.readFileSync('./db','utf8') //100
    var newAmount = amount - 1
    fs.writeFileSync('./db',newAmount)
    response.setHeader('Content-Type', 'application/javascript')
    response.write('alert("success")')
    response.end()
  }

注意:script也没有post接口

首先好处是不用返回图片,返回字符串速度会更快。但是这个script是会执行的呀!每点击一次,会不自觉多出一个script,这个有pay的script也是会执行的,先执行完后台弹出提示,再执行onload事件,既如此,干嘛要多执行一个。删掉删掉~

height=

顺便把多出来的script解决一下:

<script>
        button.addEventListener('click',(e)=>{
        let script = document.createElement('script') 
        script.src = '/pay'
        document.body.appendChild(script)
        script.onload = function(e){
            e.currentTarget.remove()
        }
        script.onerror = function(){
            alert('打钱失败')
            e.currentTarget.remove()
        }
    })
</script>
......
}else if(path === '/pay'){  
    var amount = fs.readFileSync('./db','utf8') //100
    var newAmount = amount - 1
    fs.writeFileSync('./db',newAmount)
    response.setHeader('Content-Type', 'application/javascript')
    response.write(`
      amount.innerText = amount.innerText - 1`)  
      //这段代码为什么会被当做js执行,是因为它基于http协议,上面就声明了是js代码,而且我们以script引入
    response.end()
  }

删掉提示框,删掉会增多的script(虽然删掉了但是变量还在内存里的,只不过在页面看不见了而已~),页面数据其实还是需要用手动刷新来更新的,但我们选择帮用户自动-1。局部刷新~纵享丝滑

以上方法也叫SRJ(Server randered Javascript),直译:服务器返回的js
是一种无刷新局部更新页面内容方案

script可以不受域名限制访问任何网页,除非对方有防盗链。
这里有一件重大提示:get极其容易伪造,所以重要操作如打钱取钱pay付只会用post来做。



好了,现在我们来回想一下细节:

后台为啥写了前端代码?这不符合常理,而且如果你要调用别的网页内容,那别人又怎么会知道你写的是什么代码呢,所以后台一般就会写callback.call(),无论你发来什么,我一律给你返回去就行了。

也许后端给你返json语言呢?

看这个结构,箭头左边的叫左padding,右边的叫右padding
这就是JSON + Padding = JSONP  它在某些情况下可以是JSONP
但大部分情况下其实就是String + Padding = StringP 利用动态标签进行跨域请求。

看一眼完整代码:

<script>
    button.addEventListener('click',(e)=>{
        let script = document.createElement('script')
        let functionName = 'abcds'+parseInt(Math.random()*100000,10)
        //十进制随机正整数,形如:abcds647379779734327   的随机函数名
        window[functionName] = function(result){
            if(result === 'success'){
                amount.innerText = amount.innerText - 1
            }else{}
        }
        script.src = '/pay?callback='+functionName
        document.body.appendChild(script)
        script.onload = function(e){
            e.currentTarget.remove()
            delete window[functionName]   //每次用完函数名,删掉,每一次就会是生成新的名字,不会重复,这样也不会污染全局变量
        }
        script.onerror = function(){
        alert('打钱失败')
        e.currentTarget.remove()
        delete window[functionName]
        }
    })
</script>

这里添加了随机函数名,每次执行,都会生成一个随机正整数函数名,callback传参给服务器,服务器执行完后返回对应函数,并且把生成的随机名删掉,确保每次执行函数名都不一样(可以理解为一次性名字)。


jQuery版:

<script>
        button.addEventListener('click', (e) => {
            $.ajax({
                url: "/pay",
                dataType: "jsonp",
                success: function (response) {
                    if (response === 'success') {
                        amount.innerText = amount.innerText - 1
                    }
                }
            })
        })
</script>

jquery只需要提供url,使用什么类型,成功之后要做什么就行了



总结:

用form可以发送get,post请求,但需要刷新页面或新开一个页面
用a可以发送get请求,但需要刷新页面或新开一个页面
用img可以发送get请求,但只能以图片形式展示
用link可以发get请求,但只能以css,favicon的形式展示
用script可以发get请求,但只能以脚本形式运行


JSONP
1、请求方创建script,src指向响应方,同时传一个查询参数 ?callback=xxx
2、响应方根据查询参数callbackname,构造形如:
  1、xxx.call(undefined,'你要的参数')
  2、xxx('你要的数据')
3、浏览器接受到响应就会执行xxx.call(undefined,'你要的参数')
4、那么请求方就知道了他要的数据

这就是JSONP


题外话:为什么JSONP不支持POST请求?

因为JSONP是通过动态创建script实现的,动态创建script时只能用get,无法用post。
form表单会刷新页面,img只能知成功or失败,不能知更多数据,用script是为了让后台知道我用的是代码,callback传参告诉他。