我理解JSON,但不理解JSONP。维基百科关于JSON的文档是JSONP的最高搜索结果。上面写着:

JSONP或“带填充的JSON”是一个JSON扩展,其中前缀被指定为调用本身的输入参数。

嗯?什么电话?这对我来说毫无意义。JSON是一种数据格式。没有电话。

第二个搜索结果来自一个叫雷米的人,他写了关于JSONP的文章:

JSONP是脚本标记注入,将服务器的响应传递给用户指定的函数。

我可以理解,但这仍然没有任何意义。


那么什么是JSONP?为什么创建它(它解决了什么问题)?我为什么要用它?


附录:我刚刚在维基百科上为JSONP创建了一个新页面;根据jvenema的回答,它现在对JSONP有了清晰而透彻的描述。


当前回答

其实并不太复杂。。。

假设你在domain example.com上,你想向domain example.net发出请求。要做到这一点,你需要跨越域边界,这在大多数浏览器中是不允许的。

绕过此限制的一项是<script>标记。当您使用脚本标记时,域限制将被忽略,但在正常情况下,您无法对结果执行任何操作,脚本只会得到评估。

输入JSONP。当您向启用了JSONP的服务器发出请求时,您会传递一个特殊参数,该参数会告诉服务器有关您的页面的一些信息。这样,服务器能够以页面可以处理的方式很好地包装其响应。

例如,假设服务器需要一个名为callback的参数来启用其JSONP功能。那么您的请求将如下所示:

http://www.example.net/sample.aspx?callback=mycallback

如果没有JSONP,这可能会返回一些基本的JavaScript对象,如下所示:

{ foo: 'bar' }

然而,使用JSONP,当服务器接收到“回调”参数时,它会稍微不同地包装结果,返回如下内容:

mycallback({ foo: 'bar' });

如您所见,它现在将调用您指定的方法。因此,在页面中,您可以定义回调函数:

mycallback = function(data){
  alert(data.foo);
};

现在,当加载脚本时,将对其求值,并执行您的函数。瞧,跨域请求!

还值得注意的是JSONP的一个主要问题:您失去了对请求的大量控制。例如,没有“好”的方法可以取回正确的故障代码。因此,您最终使用计时器来监视请求等,这总是有点可疑。JSONRequest的提议是允许跨域脚本编写、维护安全性和允许正确控制请求的一个很好的解决方案。

目前(2015年),CORS是与JSONRequest相比的推荐方法。JSONP对于较旧的浏览器支持仍然有用,但考虑到安全问题,除非您别无选择,否则CORS是更好的选择。

其他回答

在理解JSONP之前,您需要了解JSON格式和XML。目前,web上最常用的数据格式是XML,但XML非常复杂。这使得用户不方便处理嵌入在网页中的内容。

为了使JavaScript能够轻松地交换数据,即使作为数据处理程序,我们也根据JavaScript对象使用了措辞,并开发了一种简单的数据交换格式,即JSON。JSON可以用作数据,也可以用作JavaScript程序。

JSON可以直接嵌入JavaScript中,使用它们可以直接执行某些JSON程序,但由于安全限制,浏览器沙盒机制禁用跨域JSON代码执行。

为了使JSON可以在执行后传递,我们开发了一个JSONP。JSONP通过JavaScript回调功能和<script>标记绕过浏览器的安全限制。

简而言之,它解释了JSONP是什么,它解决了什么问题(何时使用它)。

其实并不太复杂。。。

假设你在domain example.com上,你想向domain example.net发出请求。要做到这一点,你需要跨越域边界,这在大多数浏览器中是不允许的。

绕过此限制的一项是<script>标记。当您使用脚本标记时,域限制将被忽略,但在正常情况下,您无法对结果执行任何操作,脚本只会得到评估。

输入JSONP。当您向启用了JSONP的服务器发出请求时,您会传递一个特殊参数,该参数会告诉服务器有关您的页面的一些信息。这样,服务器能够以页面可以处理的方式很好地包装其响应。

例如,假设服务器需要一个名为callback的参数来启用其JSONP功能。那么您的请求将如下所示:

http://www.example.net/sample.aspx?callback=mycallback

如果没有JSONP,这可能会返回一些基本的JavaScript对象,如下所示:

{ foo: 'bar' }

然而,使用JSONP,当服务器接收到“回调”参数时,它会稍微不同地包装结果,返回如下内容:

mycallback({ foo: 'bar' });

如您所见,它现在将调用您指定的方法。因此,在页面中,您可以定义回调函数:

mycallback = function(data){
  alert(data.foo);
};

现在,当加载脚本时,将对其求值,并执行您的函数。瞧,跨域请求!

还值得注意的是JSONP的一个主要问题:您失去了对请求的大量控制。例如,没有“好”的方法可以取回正确的故障代码。因此,您最终使用计时器来监视请求等,这总是有点可疑。JSONRequest的提议是允许跨域脚本编写、维护安全性和允许正确控制请求的一个很好的解决方案。

目前(2015年),CORS是与JSONRequest相比的推荐方法。JSONP对于较旧的浏览器支持仍然有用,但考虑到安全问题,除非您别无选择,否则CORS是更好的选择。

JSONP代表带填充的JSON。

这是一个网站,有很多例子,从最简单的使用这一技术到最先进的平面JavaScript的解释:

w3schools.com/JSONP

上面描述的我最喜欢的技术之一是Dynamic JSON Result,它允许以URL参数将JSON发送到PHP文件,并让PHP文件根据获得的信息返回JSON对象。

jQuery等工具也有使用JSONP的工具:

jQuery.ajax({
  url: "https://data.acgov.org/resource/k9se-aps6.json?city=Berkeley",
  jsonp: "callbackName",
  dataType: "jsonp"
}).done(
  response => console.log(response)
);

出身背景

您应该尽可能使用CORS(即您的服务器或API支持它,浏览器支持足够),因为JSONP具有固有的安全风险。

示例

JSONP(带填充的JSON)是一种常用于绕过web浏览器中的跨域策略。(您不允许对浏览器认为位于不同服务器上的网页发出AJAX请求。)

JSON和JSONP在客户端和服务器上的行为不同。JSONP请求不会使用XMLHTTPRequest和关联的浏览器方法进行调度。而是创建一个<script>标记,其源设置为目标URL。然后将此脚本标记添加到DOM(通常在<head>元素内)。

JSON请求:

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {
  if (xhr.readyState == 4 && xhr.status == 200) {
    // success
  };
};

xhr.open("GET", "somewhere.php", true);
xhr.send();

JSONP请求:

var tag = document.createElement("script");
tag.src = 'somewhere_else.php?callback=foo';
  
document.getElementsByTagName("head")[0].appendChild(tag);

JSON响应和JSONP响应之间的区别在于,JSONP应答对象作为参数传递给回调函数。

JSON:

 { "bar": "baz" }

日本电话:

foo( { "bar": "baz" } );

这就是为什么您看到包含回调参数的JSONP请求,以便服务器知道包装响应的函数的名称。

当浏览器评估<script>标记时(请求完成后),该函数必须存在于全局范围中。

处理JSON响应和JSONP响应之间需要注意的另一个区别是,JSON响应中的任何解析错误都可以通过包装对responseText求值的尝试来捕获在try/catch语句中。由于JSONP响应的性质,响应中的解析错误将导致无法缓存的JavaScript解析错误。

这两种格式都可以通过在启动请求之前设置超时并在响应处理程序中清除超时来实现超时错误。

使用jQuery

使用jQuery发出JSONP请求的有用之处在于,jQuery在后台为您完成所有工作。

默认情况下,jQuery要求您包含&callback=?在AJAX请求的URL中。jQuery将接受您指定的成功函数,为其分配一个唯一的名称,并将其发布到全局范围中。然后它将取代问号?在回调中=?使用它指定的名称。

比较类似的JSON和JSONP实现

以下假定响应对象{“bar”:“baz”}

JSON:

   var xhr = new XMLHttpRequest();
    
    xhr.onreadystatechange = function () {
      if (xhr.readyState == 4 && xhr.status == 200) {
        document.getElementById("output").innerHTML = eval('(' + this.responseText + ')').bar;
      };
    };
    
    xhr.open("GET", "somewhere.php", true);
    xhr.send();

日本电话:

 function foo(response) {
      document.getElementById("output").innerHTML = response.bar;
    };
    
    var tag = document.createElement("script");
    tag.src = 'somewhere_else.php?callback=foo';
    
    document.getElementsByTagName("head")[0].appendChild(tag);

这是我为那些需要的人所做的ELI5(像我一样解释我)尝试。

TL;博士

JSONP是为了绕过浏览器中的安全限制而发明的一个老把戏,该限制禁止我们获取不同于我们自己的网站/服务器(称为不同的源1)中的数据。

该技巧通过使用<script>标签从其他地方加载JSON(例如:{“city”:“Barcelona”})来实现,这将向我们发送封装在函数中的数据,即实际的JSONP(“带填充的JSON”):

tourismJSONP({"city":"Barcelona"})

通过这种方式接收数据,我们可以在旅游JSONP功能中使用数据。JSONP是一种糟糕的做法,不再需要,不要使用它(请在最后阅读)。


问题是

假设我们想在我们的web.com上使用另一个web.com上托管的一些JSON数据(或任何原始数据)。如果我们要使用GET请求(想想XMLHttpRequest,或fetch调用,$.ajax等),我们的浏览器会告诉我们,这是不允许的,并且会出现以下错误:

这是一个内容安全策略限制错误,旨在保护用户免受某些攻击。您应该正确配置它(请参见末尾)。

JSONP技巧对我们有何帮助?好吧,<script>标签不受整个服务器(origin1)的限制!这就是为什么我们可以从任何服务器加载像jQuery或GoogleMaps这样的库。

重要的一点是:如果你仔细想想,这些库是实际的、可运行的JS代码(通常是一个包含所有逻辑的大型函数)。但原始数据不是代码。没有什么可跑的;这只是纯文本。

因此,浏览器将下载<script>标记所指向的数据,在处理时,它会理所当然地抱怨:

这是我们装的垃圾吗?这不是代码。我不会计算!


旧的JSONP黑客

如果我们能让纯文本以某种方式运行,我们就能在运行时获取它。我们需要另一个web.com将其作为代码发送,因此当下载时,浏览器将运行它。我们只需要两件事:1)以可以运行的方式获取数据,2)在客户端中编写一些代码,以便在数据运行时调用我们的函数并使用数据。

对于1)如果外部服务器是JSONP友好的,我们将要求提供如下数据:

<script src="https://anotherweb.com/api/tourism-data.json?myCallback=tourismJSONP"></script>

所以我们会收到这样的信息:

tourismJSONP({"city":"Barcelona"})

这使得我们可以使用JS代码进行交互。

根据2),我们需要在代码中编写一个同名函数,如下所示:

function tourismJSONP(data){
  alert(data.city); // "Barcelona"
}

浏览器将下载JSONP并运行它,它调用我们的函数,其中参数数据将是来自另一个web.com的JSON数据。我们现在可以随心所欲地处理数据。


不要使用JSONP,使用CORS

JSONP是一种跨站点黑客,有一些缺点:

我们只能执行GET请求由于这是一个由简单脚本标记触发的GET请求,因此我们不会得到有用的错误或进度信息还有一些安全问题,比如在客户端JS代码中运行,可能会被更改为恶意负载它只解决了JSON数据的问题,但同源安全策略适用于其他数据(WebFonts、使用drawImage()绘制的图像/视频…)它既不优雅,也不可读。

重要的是现在没有必要使用它。

您应该在这里阅读CORS,但其要点是:

跨源资源共享(CORS)是一种使用额外的HTTP头,告诉浏览器提供web应用程序在一个源上运行,从另一个源访问所选资源起源当web应用程序执行跨源HTTP请求时请求具有不同来源(域、协议或端口)。



起源由三个方面定义:协议、端口和主机。所以https://web.com与http://web.com(不同的协议)https://web.com:8081(不同的港口),显然https://thatotherweb.net(不同主机)