美文网首页
说说JSONP

说说JSONP

作者: Jason_Shu | 来源:发表于2018-10-11 15:17 被阅读0次

本文是基于之前的一个server小任务的延伸,以历史的一些进程,来模拟当时前端与数据库“沟通”的过程,最后引入JSONP。

首先我们有一个server.js的脚本。

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 temp = url.parse(request.url, true);
    var path = temp.pathname;
    var query = temp.query;
    var method = request.method;

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

  if(path === "/") {
    var string = fs.readFileSync('./index.html', 'utf-8');
    var amount = fs.readFileSync('./db', "utf-8");
    console.log("amount", amount)
    string = string.replace('&&amount&&', amount);
    response.setHeader('Content-Type', 'text/html; charset=utf-8');
    response.write(string);
    response.end();
  } else if(path === "/style.css") {
    var string = fs.readFileSync('./style.css', 'utf-8');
    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', 'utf-8');
    response.setHeader('Content-type', 'text/javascript; 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代码:

    <p>您的余额是<span id="amount">100</span></p>
    <button id="click">Click!</button>
    <script src="./main.js"></script>

main.js代码:

  click.addEventListener("click", function(e) {
      amount.innerText = amount.innerText - 1;
  });
image.png

当我们点击按钮后,虽然会在页面上显示数字变化(从100变为99),但是随着我们刷新页面,数字又会变为100. 这时应该把这个数字记录存在一个“容器”里,每次点击后我们应该将“容器”中把这个数字记录修改掉,然后显示修改后的数值,就算页面刷新,再次从“容器”中获取这个数值,也是修改后的最新数值。这个容器就是数据库。

我们首先创建一个名为“db”的文件,然后里面写上数字“100”。把上面写死的100换成“db”文件里面的100.修改server.js文件。

if(path === "/") {
    var string = fs.readFileSync('./index.html', 'utf-8');
    var amount = fs.readFileSync('./db', "utf-8");  // 读取db文件中的内容赋值给amount
    string = string.replace('&&amount&&', amount);  // 然后将html中&&amount&&用amount值替换
    response.setHeader('Content-Type', 'text/html; charset=utf-8');
    response.write(string);
    response.end();
  }
<p>您的余额是<span id="amount">&&amount&&</span></p>

我们想想怎么从页面中发出一个请求?因为不需要获取什么数据,应该是POST请求最好。

首先我们想到的就是使用form标签。

    <p>您的余额是<span id="amount">&&amount&&</span></p>
    <form action="/pay" method="POST">
        <input id="pay" type="submit" name="pay" value="付款">
    </form>
image.png

因为路径跳转到了/pay,在server.js没有响应的地址。我们来加这个响应地址。

if(path === "/pay" && method.toUpperCase() === "POST") {
      var amount = fs.readFileSync("./db", "utf-8");    //   读取db文件中的数值
      var newAmount = amount - 1;  //  因为付款了,减去1
      fs.writeFileSync("./db", newAmount);  //  再把新数字放入db文件
      response.write("success");  响应success
      response.end();
  }
image.png

然后返回之前的页面刷新,余额变成99。但是这样的用户体验很差,每次需要用户自己去手动返回?在这个基础上,我们用iframe来承载response。

    <p>您的余额是<span id="amount">&&amount&&</span></p>
    <form action="/pay" method="POST" target="result">
        <input id="pay" type="submit" name="pay" value="付款">
    </form>
    <iframe src="about:blank" name="result" frameborder="0"></iframe>
image.png

这样虽然不用跳转,但是还是要刷新。那我们又想:能不能不用iframe?那又可以使用什么方式发送请求呢?可以有CSS的link,a标签,img标签和script标签。(我们使用后面两种)

那我们能不能动态创建一个img来发请求呢?

pay.addEventListener("click", function(e) {
    var img = document.createElement('img');
    img.src = "/pay";
});
image.png

我们可以看到img可以发请求,虽然是GET请求。这就给我们的需求创造了可能。

然后我们修改server.js中的pay路径脚本,同时运用Math.random来增加一种“打钱失败”情况。我们通过什么方式来通知用户操作成功或者失败呢?可以在js里面增加img.onload或者img.onerror来达到效果。

if(path === "/pay") {
      var amount = fs.readFileSync("./db", "utf-8");
      var newAmount = amount - 1;
      if(Math.random() > 0.5) {
          fs.writeFileSync("./db", newAmount);
          response.setHeader("Content-type", "image/jpg");
          response.statusCode = 200;
          response.write("success");
      } else {
          response.statusCode = 400;
          response.write("fail");
      }
      response.end();
  }
pay.addEventListener("click", function(e) {
    var img = document.createElement('img');
    img.src = "/pay";
    img.onload = function() {
        alert("打钱成功");
    };
    img.onerror = function() {
        alert("打钱失败");
    }
});
image.png

我们看到虽然状态码是200,但是还是显示“打钱失败”,说明img没有顺利加载。后来发现其实是需要在成功状况时,在response中写入一个img。

if(path === "/pay") {
      var amount = fs.readFileSync("./db", "utf-8");
      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("./dog.jpg"));  //  加上这句。
          response.write("success");
      } else {
          response.statusCode = 400;
          response.write("fail");
      }
      response.end();
  } 
image.png

确认后页面数字还是必须刷新后才有变化,再次优化,我们就在js里的img.onload得知成功后,直接在前端页面上改变。

pay.addEventListener("click", function(e) {
    var img = document.createElement('img');
    img.src = "/pay";
    img.onload = function() {
        alert("打钱成功");
        amount.innerText = amount.innerText - 1;
    };
    img.onerror = function() {
        alert("打钱失败");
    }
});

然后我们再用script标签来发请求。

pay.addEventListener("click", function(e) {
    var script = document.createElement('script');
    script.src = "/pay";
    document.body.appendChild(script);
    script.onload = function() {
        alert("打钱成功!");
    };
    script.onerror = function() {
        alert("打钱失败!");
    }
});
if(path === "/pay") {
      var amount = fs.readFileSync("./db", "utf-8");
      var newAmount = amount - 1;
      fs.writeFileSync("./db", newAmount);
      response.setHeader("Content-type", "text/javascript");
      response.statusCode = 200;
      response.write('alert("success")');
      response.end();
  }

此时我们发现一个问题,就是alert的success和打钱成功都执行了。原来script标签会执行response里面的内容。这么说我们就可以把script.onload里面内容都写到response里面。

if(path === "/pay") {
      var amount = fs.readFileSync("./db", "utf-8");
      var newAmount = amount - 1;
      fs.writeFileSync("./db", newAmount);
      response.setHeader("Content-type", "text/javascript");
      response.statusCode = 200;
      response.write(`
            amount.innerText = amount.innerText - 1;
      `);
      response.end();
  }

但是我们又发现一个问题,多次点击付款后,会在html代码那里插入多个script。


image.png

于是我们在js中加上删除script语句。

pay.addEventListener("click", function(e) {
    var script = document.createElement('script');
    script.src = "/pay";
    document.body.appendChild(script);
    script.onload = function(e) {
        e.currentTarget.remove();
    };
    script.onerror = function(e) {
        alert("打钱失败!");
        e.currentTarget.remove();
    }
});

然后说一个重要概念,script的src可以请求不同域名。那我们做两个网站,让这两个网站交流。
frank.com:8001 和 jack.com:8002 之间。

pay.addEventListener("click", function(e) {
    var 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("打钱失败!");
        e.currentTarget.remove();
    };
});
image.png

如图,当前域名在frank.com:8001访问了jack.com:8002/pay。但是这有个问题,这里的响应需要frank.com的程序员要清楚jack.com网站细节,有很大的耦合性。

于是我们想到把执行的callbackName放入src域名中,然后把执行的函数放在js中。


window.xxx = function(result) {
    if(result === "success") {
        amount.innerText = amount.innerText - 1;
    }
};

pay.addEventListener("click", function(e) {
    var script = document.createElement('script');
    script.src = "http://jack.com:8002/pay?callbackName=xxx";
    document.body.appendChild(script);
    script.onload = function(e) {
        e.currentTarget.remove();
    };
    script.onerror = function(e) {
        alert("打钱失败!");
        e.currentTarget.remove();
    };
});
if(path === "/pay") {
      var amount = fs.readFileSync("./db", "utf-8");
      var newAmount = amount - 1;
      fs.writeFileSync("./db", newAmount);
      response.setHeader("Content-type", "text/javascript");
      response.statusCode = 200;
      response.write(`
            ${query.callbackName}.call(undefined, 'success');  //  通过query获取函数名
      `);
      response.end();
  }

然后我们把「${query.callbackName}.call(undefined, 'success'); 」中的“success”换做JSON, 并且在JS里更新在window.callbackName的使用。

if(path === "/pay") {
      var amount = fs.readFileSync("./db", "utf-8");
      var newAmount = amount - 1;
      fs.writeFileSync("./db", newAmount);
      response.setHeader("Content-type", "text/javascript");
      response.statusCode = 200;
      response.write(`
            ${query.callbackName}.call(undefined, {
                "success": true,
                "amount": ${newAmount}
            });
      `);
      response.end();
  }
pay.addEventListener("click", function(e) {
    var script = document.createElement('script');
    var functionName = "jason" + Math.floor(Math.random() * 100000, 10);  // 随机构建一个函数名
    window[functionName] = function(result) {
        if(result.success) {
            amount.innerText = result.amount;
        }
    }
    script.src = `http://jack.com:8002/pay?callbackName=${functionName}`;
    document.body.appendChild(script);
    script.onload = function(e) {
        e.currentTarget.remove();
        delete window[functionName];  //  script结束后删除这个函数
    };
    script.onerror = function(e) {
        alert("打钱失败!");
        e.currentTarget.remove();
        delete window[functionName];  //  script结束后删除这个函数
    };
});

但是其实jQuery已经在js那里封装好了。

pay.addEventListener("click", function(e) {

    // 使用jQuery
    $.ajax({
        url: "http://jack.com:8002/pay",
        jsonp: "callback",
        dataType: "jsonp",
        success: function( response ) {
            if(response.success) {
                amount.innerText = response.amount;
            }
        }
    });

    // 不使用jQuery

    // var script = document.createElement('script');
    // var functionName = "jason" + Math.floor(Math.random() * 100000, 10);
    // window[functionName] = function(result) {
    //     if(result.success) {
    //         amount.innerText = result.amount;
    //     }
    // }
    // script.src = `http://jack.com:8002/pay?callbackName=${functionName}`;
    // document.body.appendChild(script);
    // script.onload = function(e) {
    //     e.currentTarget.remove();
    //     delete window[functionName];
    // };
    // script.onerror = function(e) {
    //     alert("打钱失败!");
    //     e.currentTarget.remove();
    //     delete window[functionName];
    // };
});

大体总结下:

JSONP
请求方:frank.com 前端工程师(浏览器)
响应方:jack.com 后端工程师(服务器)
1. 请求方创建script,src指向响应方,同时加上查询条件:?callback=yyy
2. 响应方根据查询条件,构建形如:
    yyy.call(undefined, "你要的数据");
    yyy.("你要的数据")
    这样的响应
3. 浏览器收到响应就会执行yyy.call(undefined, "你要的数据");
4. 那么请求方就知道他要的数据了。

约定:
1. callbackName是一个随机数。
2. callbackName 一般约定俗成是“callback”。

相关文章

  • 说说JSONP

    本文是基于之前的一个server小任务的延伸,以历史的一些进程,来模拟当时前端与数据库“沟通”的过程,最后引入JS...

  • 说说JSONP

    定义 JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据...

  • 交互那些事(二)

    说完ajax我想必须说说jsonp了,谈到jsonp就必须先说说跨域,首先ajax是不能跨域的,除非后台允许跨域或...

  • jsonp原理

    什么是JSONP? 先说说JSONP是怎么产生的: 其实网上关于JSONP的讲解有很多,但却千篇一律,而且云里雾里...

  • JSONP 总结

    什么是JSONP? 先说说JSONP是怎么产生的:1、一个众所周知的问题,Ajax直接请求普通文件存在跨域无权限访...

  • 说说 JSONP 和 XSS

    JSONP先说 JSONP。通过 JavaScript 调用,被调用域名和当前页面域名不一致,就需要用到 JSON...

  • 说说JSON和JSONP区别

    前言 由于Sencha Touch 2这种开发模式的特性,基本决定了它原生的数据交互行为几乎只能通过AJAX来实现...

  • JSONP、JQuery发送AJAX、JSONP请求

    1.JSONP 2.JQuery发送AJAX、JSONP请求 1.JSONP JSONP利用JavaScript...

  • JS-18day

    1、jsonp公开接口 2、jQuery-jsonp 3、jsonp

  • 2018-12-10

    节点操作 ajax jsonp jQuery-jsonp jsonp公开接口

网友评论

      本文标题:说说JSONP

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