33-高级:JSONP入门

请先了解:《阮一峰:浏览器同源政策及其规避方法

注意:请首先更新之前用过的 nodejs-test 代码,否则会报错

nodejs-test 代码(饥人谷)
三种声明:window.x,var x,let x的区别:

image.png

一、数据库是什么鬼


  1. 文件系统是一种数据库;
  2. MySQL 是一种数据库;

只要能长久地存数据,就是数据库

  1. 111
  2. 222

①. 111
②. 222
③. 333

二、用文件当数据库


例:用数据库做加法:

var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]

if(!port){
  console.log('请指定端口号好不啦?\nnode server.js 8888 这样不会吗?')
  process.exit(1)
}

var server = http.createServer(function(request, response){
  var parsedUrl = url.parse(request.url, true)
  var path = request.url 
  var query = ''
  if(path.indexOf('?') >= 0){ query = path.substring(path.indexOf('?')) }
  var pathNoQuery = parsedUrl.pathname
  var queryObject = parsedUrl.query
  var method = request.method

  /******** 从这里开始看,上面不要看 ************/



  console.log('HTTP 路径为\n' + path)
    if(path == '/style.css'){
      var string = fs.readFileSync('./style.css','utf8')
      response.setHeader('Content-Type', 'text/css; charset=utf-8')
      response.write(string)
      response.end()
    }else if(path == '/main.js'){
      var string = fs.readFileSync('./main.js','utf8')
      response.setHeader('Content-Type', 'text/javascript; charset=utf-8')
      response.write(string)
      response.end()
    }else if(path == '/'){
      var string = fs.readFileSync('./index.html','utf8')   
      response.setHeader('Content-Type', 'text/html; charset=utf-8')
      response.write(string)
      response.end()
    }else{
      response.statusCode = 404
      response.setHeader('Content-Type','text/html;charset=utf-8')
      response.write('找不到对应的路径')
      response.end()
    }





  /******** 代码结束,下面不要看 ************/
})

server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度然后用电饭煲打开 http://localhost:' + port)

对应路径匹配到的html:

<body>
    <h5>你的账户余额是:<span id="amount">100</span></h5>
    <button id="button">付款1块钱</button>
    <script>
        button.addEventListener('click',(e)=>{
            let n = amount.innerText
            let number = n - 0 //将字符串转换为数字
            let newNumber = n - 1
            amount.innerText = newNumber
        })
    </script>
</body>

以上代码改变的余额只是 html 里的数据,而不是后台的数据库里 的数据,也就是刷新网页后数据又还原了;

第二步:接下来,尝试创建一个极简的数据库,然后通过前台的点击改变数据库的数据:明显改变后台数据库需要用到 post 请求,而不是get 请求,因此首先尝试用 form 表单;(如果是【post】,那么表单数据将放在请求体中被发送出去。如果是【get】,那么表单数据将会追加到查询字符串中,以查询字符串的形式提交到服务端。)
变更后增加代码如下:

     else if(path === '/pay' && method.toUpperCase() === 'POST'){
      var amount = fs.readFileSync('./db','utf8')
      var newAmount = amount - 1
      if(Math.random() > 0.5){
        fs.writeFileSync('./db', newAmount)
        response.write('success')
      }else{
        response.write('fail')
      }
      response.end()
    }
    <h5>你的账户余额是:<span id="amount">&&&amount&&&</span></h5>
    <form action="/pay" method="post" target="result">
        <input type="submit" value="付款">
    </form>

    <iframe name="result" src="about:blank" frameborder="1" height="200px"></iframe>

每次提交 form 表单后页面都会刷新,怎么解决???
如上:引入 iframe 标签,让提交 form 表单 后在iframe 里刷新。response.write('success') 与 response.write('fail') 也会显示在 iframe 里;

第三步:能不能不用刷新且不用引入 ifame 标签???
解决方法:不用 form 标签单发请求,用其他标签如:a 标签,img 标签,link标签,script 标签 等.......

三、用<img src="">发请求


  1. <img src="">尝试:
  console.log('HTTP 路径为\n' + path)
    if(path === '/style.css'){
      var string = fs.readFileSync('./style.css','utf8')
      response.setHeader('Content-Type', 'text/css; charset=utf-8')
      response.write(string)
      response.end()
    }else if(path === '/main.js'){
      var string = fs.readFileSync('./main.js','utf8')
      response.setHeader('Content-Type', 'text/javascript; charset=utf-8')
      response.write(string)
      response.end()
    }else if(path === '/'){
      var string = fs.readFileSync('./index.html','utf8')   
      var amount = fs.readFileSync('./db','utf8') // 100,那么db的类型是啥???
      string = string.replace('&&&amount&&&',amount)
      response.setHeader('Content-Type', 'text/html; charset=utf-8')
      response.write(string)
      response.end()
    }else if(path === '/pay'){
      var amount = fs.readFileSync('./db','utf8')
      var newAmount = amount - 1
      if(Math.random() > 0.5){
        fs.writeFileSync('./db', newAmount)
        response.setHeader('Content-Type','image/png') //由于实际并没有响应一个图片文件,导致statusCode = 200 依然 alert('fail')
        response.statusCode = 200
        response.write('success')
      }else{
        response.statusCode = 400
        response.write('fail')
      }
      response.end()
    }else{
      response.statusCode = 404
      response.setHeader('Content-Type','text/html;charset=utf-8')
      response.write('找不到对应的路径')
      response.end()
    }

    <h5>你的账户余额是:<span id="amount">&&&amount&&&</span></h5>
    <!-- <form action="/pay" method="post" target="result">
        <input type="submit" value="付款">
    </form> -->

    <!-- <iframe name="result" src="about:blank" frameborder="1" height="200px"></iframe> -->
    <button id="button">付款1块钱</button>
    <script>
        button.addEventListener('click',(e)=>{
            let image = document.createElement('img')
            image.src = '/pay'
            image.onload = function(){
                alert('success')
            }
            image.onerror = function(){
                alert('fail')
            }
        })
    </script>

<img src="">的方式发请求时,只要 createElement 就会 get ,不需要添加到 html 里。实际测试时发现可以修改后台数据,但是只会执行alert('fail'),原因可能是没有实际 响应一个图片文件。修改成功后,依然需要刷新页面才能显示新数据,alert('fail')alert('success)可以提示用户付款结果。

接下来,实际在响应中返回一个图片测试,修改如下:

    }else if(path === '/pay'){
      var amount = fs.readFileSync('./db','utf8')
      var newAmount = amount - 1
      if(Math.random() > 0.5){
        fs.writeFileSync('./db', newAmount)
        response.setHeader('Content-Type','image/jpg')
        response.statusCode = 200
        response.write(fs.readFileSync('./000.jpg')) //传一个真实的图片
      }else{
        response.statusCode = 400
        response.write('fail')
      }
      response.end()

在如实响应一个图片文件后,alert 结果就正常了;
目前还有一个问题: 用户需要手动刷新页面才能看到余额的变化,怎么解决????

下一步:不用手动刷新,如下:
index.html

            image.onload = function(){
                alert('success')
                window.location.reload() // 加这句,自动刷新
            }

不用手动刷新的另一种思路,在 image.onload 里面直接修改#amount里面的内容,让页面显示的值与后台一样。

三、用<script></script>发请求

如下:
index.html

<body>
    <h5>你的账户余额是:<span id="amount">&&&amount&&&</span></h5>
    <button id="button">付款1块钱</button>
    <script>
        button.addEventListener('click',(e)=>{
            let script = document.createElement('script')
            script.src = '/pay'
            document.body.appendChild(script)
            script.onload = function(){
                alert('success')
                window.location.reload()
            }
            script.onerror = function(){
                alert('fail')
            }
        })
    </script>
    <script src="./main.js"></script>
</body>

<script></script>发请求与 img 不同,script 仅仅只是创建的话不会发送请求,需要添加到body里面才行;

server.js变更:

    }else if(path === '/pay'){
      var amount = fs.readFileSync('./db','utf8')
      var newAmount = amount - 1
      if(Math.random() > 0.5){
        fs.writeFileSync('./db', newAmount)
        response.setHeader('Content-Type','application/javascript')
        response.statusCode = 200
        response.write('') // 响应一个空的js文件
      }else{
        response.statusCode = 400
        response.write('fail')
      }
      response.end()
    }

测试中发现,每success一次,都会在body里添加一个src='/pay'的script的标签;
然后将response.write('') // 响应一个空的js文件变更为response.write('alert("success")') // 响应一个空的js文件,后发现响应的js文件会被执行...............因此可以将点击后的监听放到响应里;
如下:

    }else if(path === '/pay'){
      var amount = fs.readFileSync('./db','utf8')
      var newAmount = amount - 1

      fs.writeFileSync('./db', newAmount)
      response.setHeader('Content-Type','application/javascript')
      response.statusCode = 200
      response.write(`
        alert("付款成功")
        window.location.reload()
      `) //此处使用了ES6的书写规则..

      response.end()

如果不想刷新整个页面可以改成:

      response.write(`
      alert("付款成功")
      //window.location.reload()
      amount.innerText = amount.innerText - 1
      `)

最后还有个问题:没点击一次,就会在body里面增加一个script标签.........................怎么解决?????

四、删除script标签(请求完成后)


在script里的内容执行完后删除script标签:
在前端监听script即可:

            script.onload = function(e){
                // alert('success')
                // window.location.reload()
                e.currentTarget.remove()
            }
            script.onerror = function(e){
                alert('fail')
                e.currentTarget.remove()
            }

以上都是请求的同一域名下的文件,接下来尝试请求不同域名下的js文件。

五、请求另一个网站的script


注意:请首先更新你的 nodejs-test 代码(server.js),否则会报错
环境配置:在hosts文件里配置:

127.0.0.1  frank.com
127.0.0.1  jack.com

这样就有两个域名了,虽然ip地址一样;
然后在 git bash开两个node server:

Administrator@PC-20160402SHGE MINGW64 /j/web-project/node-demo (master)
$ PORT=8001 node server.js
监听 8001 成功
请用在空中转体720度然后用电饭煲打开 http://localhost:8001


Administrator@PC-20160402SHGE MINGW64 /j/web-project/node-demo (master)
$ PORT=8002 node server.js
监听 8001 成功
请用在空中转体720度然后用电饭煲打开 http://localhost:8002

在浏览器可以分别打开http://jack.com:8002/http://frank.com:8001/,只是这两个网址的源代码一样;
到此,环境就准备好了。

测试:让frank.com的前端访问jack.com的后端。
变更代码如下:
html

<body>
    <h5>你的账户余额是:<span id="amount">&&&amount&&&</span></h5>
    <button id="button">付款1块钱</button>
    <script>
        button.addEventListener('click',(e)=>{
            let script = document.createElement('script')
            script.src = 'http://jack.com:8002/pay'
            document.body.appendChild(script)
            script.onload = function(e){
                e.currentTarget.remove()
            }
            script.onerror = function(e){
                alert('fail')
                e.currentTarget.remove()
            }
        })
    </script>
</body>

server

else if(path === '/pay'){
    var amount = fs.readFileSync('./db','utf8')
    var newAmount = amount - 1

    fs.writeFileSync('./db', newAmount)
    response.setHeader('Content-Type','application/javascript')
    response.statusCode = 200
    response.write(`
    alert("付款成功")
    // window.location.reload()
    amount.innerText = amount.innerText - 1
    `)
    response.end()
}

测试结果,在frank.com的页面点击时能请求到jack.com的后端响应的script文件。
以上代码还存在一个问题:jack.com的后端需要对frank.com的页面细节了解很清楚,并且需要在后端写一段js代码,这样 耦合 成都太紧,可操作性太差;

怎么解除这种耦合的紧密性?????

方法:
frank.com的前端事先在click里面定义一个方法:

window.xxx = function(result){
    if(result === 'success'){
        alert("付款成功")
        // window.location.reload()
        amount.innerText = amount.innerText - 1
    }else{
        alert("付款失败")
    }
}

然后,jack.com的后端的响应中调用这一个方法:

response.write(`         //这个不是单引号....
xxx.call(undefined,'success')
`)

以上还有点小问题:jack.com的后端得找frank.com的前端询问事先定义的方法名,怎么解决???
优化:可以通过在请求中加入查询参数将函数名传过去;
如下:
html

script.src = 'http://jack.com:8002/pay?callbackName=xxx'

server

response.write(`         //这个不是单引号....
  ${query.callbackName}.call(undefined,'success')
`)

这样,解耦完成。。。。

总结:需要解决两个网站之间的交流问题时,由于 script 标签可以请求其他网站的文件,因此可以使用 script 来解决;基本思路就是:如果A站要请求B站的一个数据,那么A站首先通过 script 标签向B站发一个请求,B站收到请求后准备把数据传给A站时,只要调用A站传过来的一个函数,并将数据作为被调用函数的参数,最后B站将这个调用作为响应内容响应给A站,这样交流就完成了。------------------如果被调用函数的参数是JSON,那么,这就是JSONP了。

六、什么是JSONP


JSONP出现的目的:为了要解决两个网站之间的交流。
JSONP的工作过程:
假定现在有两个网站frank.com和jack.com,并且frank.com 的前端要向jack.com 的后端请求一个数据。
请求方:frank.com 的前端程序员(浏览器)
响应方:jack.com 的后端程序员(服务器)
其工作过程如下:

  1. 请求方创建script,script的src 指向响应方,同时传递一个查询参数?callbackName=yyy;
  2. 响应方根据查询参数callbackName=yyy,构建形如:
    1. yyy.call(undefined,'请求方要的数据')
    2. yyy('请求方要的数据')
      这种格式的响应;
  3. 由于响应的内容是script类型的,请求方(浏览器)收到响应后,会直接执行响应的内容,也就是执行yyy.call(undefined,'请求方要的数据');
  4. 这样请求方就得到了不同网址的后端的数据。

在书写JSONP时有些默认的约定:

  1. callbackName -> callback
  2. yyy -> 随机数,比如abc48489312668()

上面整个过程就是 JSONP。

一个符合约定的JSONP:
html

<body>
    <h5>你的账户余额是:<span id="amount">&&&amount&&&</span></h5>
    <button id="button">付款1块钱</button>
    <script>
        button.addEventListener('click',(e)=>{
            let functionName = 'xxx' + parseInt(Math.random() * 1000000,10)
            window[functionName] = function(result){
                if(result === 'success'){
                    alert("付款成功")
                    amount.innerText = amount.innerText - 1
                }else{
                    alert("付款失败")
                }
            }

            let script = document.createElement('script')
            script.src = 'http://jack.com:8002/pay?callback=' + functionName
            document.body.appendChild(script)
            script.onload = function(e){
                e.currentTarget.remove()
            }
            script.onerror = function(e){
                alert('fail')
                e.currentTarget.remove()
            }
        })
    </script>
    <script src="./main.js"></script>
</body>

server

var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.env.PORT || 8888;


var server = http.createServer(function(request, response){

  var temp = url.parse(request.url,true)
  var path = temp.pathname
  var query = temp.query
  var method = request.method

  /******** 从这里开始看,上面不要看 ************/


  console.log('HTTP 路径为\n' + path)
    if(path === '/style.css'){
      var string = fs.readFileSync('./style.css','utf8')
      response.setHeader('Content-Type', 'text/css; charset=utf-8')
      response.write(string)
      response.end()
    }else if(path === '/main.js'){
      var string = fs.readFileSync('./main.js','utf8')
      response.setHeader('Content-Type', 'text/javascript; charset=utf-8')
      response.write(string)
      response.end()
    }else if(path === '/'){
      var string = fs.readFileSync('./index.html','utf8')   
      var amount = fs.readFileSync('./db','utf8') // 100,那么db的类型是啥???
      string = string.replace('&&&amount&&&',amount)
      response.setHeader('Content-Type', 'text/html; charset=utf-8')
      response.write(string)
      response.end()
    }else if(path === '/pay'){
      var amount = fs.readFileSync('./db','utf8')
      var newAmount = amount - 1
      fs.writeFileSync('./db', newAmount)
      response.setHeader('Content-Type','application/javascript')
      response.statusCode = 200
      response.write(`
        ${query.callback}.call(undefined,'success')
      `)
      response.end()
    }else{
      response.statusCode = 404
      response.setHeader('Content-Type','text/html;charset=utf-8')
      response.write('找不到对应的路径')
      response.end()
    }
    
  /******** 代码结束,下面不要看 ************/
})

server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度然后用电饭煲打开 http://localhost:' + port)

用jQuery重新写,如下:
html

<body>
    <h5>你的账户余额是:<span id="amount">&&&amount&&&</span></h5>
    <button id="button">付款1块钱</button>
    <script>
        button.addEventListener('click',(e)=>{
            $.ajax({
                url:"http://jack.com:8002/pay",
                dataType:"jsonp",
                success:function(response){
                    if(response === 'success'){
                        amount.innerText = amount.innerText - 1
                    }
                }
            })
        })

    </script>
    
  1. 111
  2. 222

①. 111
②. 222
③. 333

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

推荐阅读更多精彩内容

  • 1. JSONP的实现原理 JSONP是通过动态创建script实现的。请求方:frank.com 的前端程序员(...
    王童孟阅读 470评论 0 2
  • 前端操作数据库 首先,我们试着通过前端来操作一个数据库。数据库是什么?只要能长久的存数据,就是数据库。文件系统是一...
    是刘快啊阅读 374评论 0 2
  • 什么是数据库?//只要一个东西可以永久存放数据就是数据库,不管是硬盘啊,光盘啊~~~ MYSQL 即是软件,也是一...
    joker731阅读 370评论 0 1
  • 局部刷新怎么做? 有没有想过,不返回 HTML,返回 JS 方案一:用图片造 get 请求 方案二:用 scrip...
    加油吧_阅读 259评论 0 0
  • html实现打钱效果 接下来用node来实现后台打钱效果创建index.js文件,下面代码第二部分写在里面,并且通...
    小志1阅读 501评论 1 1