{"action":"created","issue":{"url":"https://api.github.com/repos/rccoder/blog/issues/5","repository_url":"https://api.github.com/repos/rccoder/blog","labels_url":"https://api.github.com/repos/rccoder/blog/issues/5/labels{/name}","comments_url":"https://api.github.com/repos/rccoder/blog/issues/5/comments","events_url":"https://api.github.com/repos/rccoder/blog/issues/5/events","html_url":"https://github.com/rccoder/blog/issues/5","id":137128811,"number":5,"title":"浅谈浏览器端JavaScript跨域解决方法","user":{"login":"rccoder","id":7554325,"avatar_url":"https://avatars3.githubusercontent.com/u/7554325?v=4","gravatar_id":"","url":"https://api.github.com/users/rccoder","html_url":"https://github.com/rccoder","followers_url":"https://api.github.com/users/rccoder/followers","following_url":"https://api.github.com/users/rccoder/following{/other_user}","gists_url":"https://api.github.com/users/rccoder/gists{/gist_id}","starred_url":"https://api.github.com/users/rccoder/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rccoder/subscriptions","organizations_url":"https://api.github.com/users/rccoder/orgs","repos_url":"https://api.github.com/users/rccoder/repos","events_url":"https://api.github.com/users/rccoder/events{/privacy}","received_events_url":"https://api.github.com/users/rccoder/received_events","type":"User","site_admin":false},"labels":[{"id":333154790,"url":"https://api.github.com/repos/rccoder/blog/labels/JavaScript","name":"JavaScript","color":"fbca04","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":28,"created_at":"2016-02-29T02:15:29Z","updated_at":"2018-02-04T13:26:51Z","closed_at":null,"author_association":"OWNER","body":"[![](https://badge.juejin.im/entry/577636e42e958a005579c3b0/likes.svg?style=flat)](https://juejin.im/entry/577636e42e958a005579c3b0/detail)\r\n\r\n由于安全的原因，浏览器做了很多方面的工作，由此也就引入了一系列的跨域问题，需要注意的是：\r\n\r\n**跨域并非浏览器限制了发起跨站请求，而是跨站请求可以正常发起，但是返回结果被浏览器拦截了。最好的例子是 `CSRF` 跨站攻击原理，请求是发送到了后端服务器无论是否跨域！注意：有些浏览器不允许从HTTPS的域跨域访问HTTP，比如Chrome和Firefox，这些浏览器在请求还未发出的时候就会拦截请求，这是一个特例**\r\n## 1. JSONP\r\n\r\n`JSONP`的全称是 \"JSON With Padding\"， 词面意思上理解就是 \"填充式的JSON\"。它不是一个新鲜的东西，隶属于 `JSON` 的一种使用方法，或者说是一种使用模式，可以解决一些常见的浏览器端网页跨域问题。\r\n\r\n正如他的名称一样，它是指被包含在调用函数中的JSON，比如这样:\r\n\r\n``` javascript\r\ncallback({\"Name\": \"小明\", \"Id\" : 1823, \"Rank\": 7})\r\n```\r\n\r\n由于 `jQuery` 的一些原因，使得 `JSONP` 常常与 `Ajax` 混淆。实际上，他们没有任何关系。\r\n\r\n由于浏览器的同源策略，使得在网页端出现了这个“跨域”的问题，然而我们发现，所有的 `src` 属性并没有受到相关的限制，比如 `img` / `script` 等。\r\n\r\n`JSONP` 的原理就要从 `script` 说起。`script` 可以执行其他域的`js` 函数，比如这样：\r\n\r\n``` javascript\r\na.html\r\n...\r\n<script>\r\n  function callback(data) {\r\n    console.log(data.url)\r\n  }\r\n</script>\r\n\r\n<script src='b.js'></script>\r\n...\r\n\r\n\r\nb.js\r\ncallback({url: 'http://www.rccoder.net'})\r\n```\r\n\r\n显然，上面的代码是可以执行的，并且可以在console里面输出http://www.rccoder.net\r\n\r\n利用这一点，假如b.js里面的内容不是固定的，而是根据一些东西自动生成的， 嗯，这就是JSONP的主要原理了。回调函数+数据就是 `JSON With Padding` 了，回调函数用来响应应该在页面中调用的函数，数据则用来传入要执行的回调函数。\r\n\r\n至于这个数据是怎么产生的，说粗鲁点无非就是字符串拼接了。\r\n\r\n**简单总结一下:** Ajax 是利用 XMLHTTPRequest 来请求数据的，而它是不能请求不同域上的数据的。但是，在页面上引用不同域的 js 文件却是没有任何问题的，这样，利用异步的加载，请求一个 js 文件，而这个文件的内容是动态生成的（后台语言字符串拼接出来的），里面包含的是 JSON With Padding（回调函数+数据），之前写的那个函数就因为新加载进来的这段动态生成的 js 而执行，也就是获取到了他要获取的数据。\r\n\r\n重复一下，在一个页面中，a.html这样写，得到 UserId 为 1823 的信息:\r\n\r\n``` javascript\r\na.html\r\n\r\n...\r\nsrc=\"http://server2.example.com/RetrieveUser?UserId=1823&callback=parseResponse\">\r\n...\r\n```\r\n\r\n请求这个地址会得到一个可以执行的 JavaScript。比如会得到：\r\n\r\n``` javascript\r\n  parseResponse({\"Name\": \"小明\", \"Id\" : 1823, \"Rank\": 7})\r\n```\r\n\r\n这样，a.html里面的 `parseResponse()` 这个函数就能执行并且得到数据了。\r\n\r\n**等等，jQuery到底做了什么:**\r\n\r\njQuery 让 JSONP 的使用API和Ajax的一模一样：\r\n\r\n``` javascript\r\n$.ajax({\r\n  method: 'jsonp',\r\n  url: 'http://server2.example.com/RetrieveUser?UserId=1823',\r\n  success: function(data) {\r\n    console.log(data)\r\n  } \r\n})\r\n```\r\n\r\n之所以可以这样是因为 jQuery 在背后倾注了心血，它会在执行的时候生成函数替换`callback=dosomthing` ，然后获取到数据之后销毁掉这个函数，起到一个临时的代理器作用，这样就拿到了数据。\r\n\r\n**JSONP 的后话**：\r\n\r\nJSONP的这种实现方式不受同源策略的影响，兼容性也很好；但是它之支持 GET 方式的清楚，只支持 HTTP 请求这种特殊的情况，对于两个不同域之间两个页面的互相调用也是无能为力。\r\n## 2. CORS\r\n\r\n`XMLHttpRequest` 的同源策略看起来是如此的变态，即使是同一个公司的产品，也不可能完全在同一个域上面。还好，网络设计者在设计的时候考略到了这一点，可以在服务器端进行一些定义，允许部分网络访问。\r\n\r\nCORS 的全称是 Cross-Origin Resource Sharing，即跨域资源共享。他的原理就是使用自定义的 HTTP 头部，让服务器与浏览器进行沟通，主要是通过设置响应头的 `Access-Control-Allow-Origin` 来达到目的的。这样，XMLHttpRequest 就能跨域了。\r\n\r\n值得注意的是，正常情况下的 XMLHttpRequest 是只发送一次请求的，但是跨域问题下很可能是会发送两次的请求（预发送）。\r\n\r\n更加详细的内容可以参见：\r\n\r\n> https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS\r\n\r\n**CORS 的后话：**\r\n\r\n相比之下，CORS 就支持所有类型的 HTTP 请求了，但是在兼容上面，往往一些老的浏览器并不支持 CORS。\r\n\r\nDesktop:\r\n\r\n| 浏览器 | 版本 |\r\n| --- | --- |\r\n| Chrome | 4 |\r\n| Firefox (Gecko) | 3.5 |\r\n| Internet Explorer | 8 (via XDomainReques)  10 |\r\n| Opera | 12 |\r\n| Safari | 4 |\r\n\r\nMobile:\r\n\r\n| 设备 | 版本 |\r\n| --- | --- |\r\n| Android | 2.1 |\r\n| Chrome for Android | yes |\r\n| Firefox Mobile (Gecko) | yes |\r\n| IE Mobile | ? |\r\n| Opera Mobile | 12 |\r\n| Safari Mobile | 3.2 |\r\n## 3. window.name\r\n\r\nwindow.name 在一个窗口（标签）的生命周期之内是共享的，利用这点就可以传输一些数据。\r\n\r\n除此之外，结合 iframe 还能实现更加强大的功能：\r\n\r\n需要3个文件： a/proxy/b\r\n\r\n``` javascript\r\na.html\r\n\r\n<script type=\"text/javascript\">\r\n    var state = 0, \r\n    iframe = document.createElement('iframe'),\r\n    loadfn = function() {\r\n        if (state === 1) {\r\n            var data = iframe.contentWindow.name;    // 读取数据\r\n            alert(data);    //弹出'I was there!'\r\n        } else if (state === 0) {\r\n            state = 1;\r\n            iframe.contentWindow.location = \"http://a.com/proxy.html\";    // 设置的代理文件\r\n        }  \r\n    };\r\n    iframe.src = 'http://b.com/b.html';\r\n    if (iframe.attachEvent) {\r\n        iframe.attachEvent('onload', loadfn);\r\n    } else {\r\n        iframe.onload  = loadfn;\r\n    }\r\n    document.body.appendChild(iframe);\r\n</script>\r\n```\r\n\r\n``` javascript\r\nb.html\r\n\r\n<script type=\"text/javascript\">\r\n    window.name = 'I was there!';    // 这里是要传输的数据，大小一般为2M，IE和firefox下可以大至32M左右\r\n                                     // 数据格式可以自定义，如json、字符串\r\n</script>\r\n```\r\n\r\nproxy 是一个代理文件，空的就可以，需要和 a 在同一域下\r\n## 4. document.domain\r\n\r\n在不同的**子域** + iframe交互的时候，获取到另外一个 iframe 的 window对象是没有问题的，但是获取到的这个window的方法和属性大多数都是不能使用的。\r\n\r\n这种现象可以借助`document.domain` 来解决。\r\n\r\n```\r\nexample.com\r\n\r\n<iframe id='i' src=\"1.example.com\" onload=\"do()\"></iframe>\r\n<script>\r\n  document.domain = 'example.com';\r\n  document.getElementById(\"i\").contentWindow;\r\n</script>\r\n```\r\n\r\n``` javascript\r\n1.example.com\r\n\r\n<script>\r\n  document.domain = 'example.com';  \r\n</script>\r\n```\r\n\r\n这样，就可以解决问题了。值得注意的是：**`document.domain` 的设置是有限制的，只能设置为页面本身或者更高一级的域名。**\r\n\r\n**document.domain的后话：**\r\n\r\n利用这种方法是极其方便的，但是如果一个网站被攻击之后另外一个网站很可能会引起安全漏洞。\r\n## 5.location.hash\r\n\r\n这种方法可以把数据的变化显示在 url 的 hash 里面。但是由于 chrome 和 IE 不允许修改parent.location.hash 的值，所以需要再加一层。\r\n\r\na.html 和 b.html 进行数据交换。\r\n\r\n``` html\r\na.html\r\n\r\nfunction startRequest(){\r\n    var ifr = document.createElement('iframe');\r\n    ifr.style.display = 'none';\r\n    ifr.src = 'http://2.com/b.html#paramdo';\r\n    document.body.appendChild(ifr);\r\n}\r\n\r\nfunction checkHash() {\r\n    try {\r\n        var data = location.hash ? location.hash.substring(1) : '';\r\n        if (console.log) {\r\n            console.log('Now the data is '+data);\r\n        }\r\n    } catch(e) {};\r\n}\r\nsetInterval(checkHash, 2000);\r\n```\r\n\r\n``` html\r\nb.html\r\n\r\n//模拟一个简单的参数处理操作\r\nswitch(location.hash){\r\n    case '#paramdo':\r\n        callBack();\r\n        break;\r\n    case '#paramset':\r\n        //do something……\r\n        break;\r\n}\r\n\r\nfunction callBack(){\r\n    try {\r\n        parent.location.hash = 'somedata';\r\n    } catch (e) {\r\n        // ie、chrome的安全机制无法修改parent.location.hash，\r\n        // 所以要利用一个中间域下的代理iframe\r\n        var ifrproxy = document.createElement('iframe');\r\n        ifrproxy.style.display = 'none';\r\n        ifrproxy.src = 'http://3.com/c.html#somedata';    // 注意该文件在\"a.com\"域下\r\n        document.body.appendChild(ifrproxy);\r\n    }\r\n}\r\n```\r\n\r\n``` html\r\nc.html\r\n\r\n//因为parent.parent和自身属于同一个域，所以可以改变其location.hash的值\r\nparent.parent.location.hash = self.location.hash.substring(1);\r\n```\r\n\r\n这样，利用中间的 c 层就可以用 hash 达到 a 与 b 的交互了。\r\n## 6.window.postMessage()\r\n\r\n这个方法是 HTML5 的一个新特性，可以用来向其他所有的window对象发送消息。需要注意的是我们必须要保证所有的脚本执行完才发送MessageEvent，如果在函数执行的过程中调用了他，就会让后面的函数超时无法执行。\r\n\r\n> https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage\r\n## 参考资料\r\n\r\nhttp://www.cnblogs.com/rainman/archive/2011/02/20/1959325.html\r\n\r\nhttp://www.cnblogs.com/rainman/archive/2011/02/21/1960044.html\r\n\r\n---\r\n## 捐赠\r\n\r\n写文不易，赠我一杯咖啡增强一下感情可好？\r\n\r\n![alipay](https://cloud.githubusercontent.com/assets/7554325/13387514/4e6c3b42-def2-11e5-849c-90c5f1109fe7.png)\r\n"},"comment":{"url":"https://api.github.com/repos/rccoder/blog/issues/comments/362906715","html_url":"https://github.com/rccoder/blog/issues/5#issuecomment-362906715","issue_url":"https://api.github.com/repos/rccoder/blog/issues/5","id":362906715,"user":{"login":"lscho","id":11583677,"avatar_url":"https://avatars2.githubusercontent.com/u/11583677?v=4","gravatar_id":"","url":"https://api.github.com/users/lscho","html_url":"https://github.com/lscho","followers_url":"https://api.github.com/users/lscho/followers","following_url":"https://api.github.com/users/lscho/following{/other_user}","gists_url":"https://api.github.com/users/lscho/gists{/gist_id}","starred_url":"https://api.github.com/users/lscho/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lscho/subscriptions","organizations_url":"https://api.github.com/users/lscho/orgs","repos_url":"https://api.github.com/users/lscho/repos","events_url":"https://api.github.com/users/lscho/events{/privacy}","received_events_url":"https://api.github.com/users/lscho/received_events","type":"User","site_admin":false},"created_at":"2018-02-04T13:26:51Z","updated_at":"2018-02-04T13:26:51Z","author_association":"NONE","body":"不要被 JSONP 这个名字误导啊，他只是一个名字而已。script 标签是开放性的，没有域名的限制，也就是说通过 script 标签引入的任何脚本，都能被执行。所以可以利用这个特性来绕过异步请求同源的限制。\r\n\r\n思路很简单，就是你先在本地创建一个回调函数，姑且称为 callback ，然后利用 script 标签不管引入来自哪个域名的脚本，都可以调用 callback。而数据就通过给 callback 传递参数（一般都是json，当然字符串也可以），来达到跨域数据交换。所以 jsonp 要求后端返回的数据是 callback(Object) 这种类型的。"}}
{"actor":{"display_login":"lscho"}}