文章目录
一、产生原因
- 浏览器的同源策略会出于安全考虑,限制JavaScript跨域请求资源;
- 同源:浏览器的协议、域名、端口号都相同就叫同源;
- 客户端请求服务器文件时,协议、域名、端口号只要有一个不同就会产生跨域问题;
二、几种主流跨域解决方案
这里补充一个内容
- json:轻量级数据格式
- 优点:
- 轻量级,体积小,节省流量,提高加载速度
- 解析成原生js对象,解析速度比XML快
- 查找数据无需查找标签,效率更高
- 注意:js不能直接引入json文件,可以通过ajax请求获得
- 在数据传输流程中,json是以文本即字符串的形式进行传递的,而js操作的是json对象,所以json对象和json字符串之间的转换是关键
- JSON字符串转JSON对象 JSON.parse ( JSON字符串 )
- JSON对象转JSON字符串 JSON.stringify(JSON对象 )
json文件如:
{
"name": "haha",
"age": 18,
"brother": [{
"name": "哥哥",
"age": 19
}, {
"name": "哥哥",
"age": 19
}]
}
1、通过服务端代理请求——要服务器
比如说:服务端php,php是没有跨域限制的,我们可以让服务端去别的网站获取内容然后返回页面,按照我的理解可以理解为在html页面通过ajax向php发出请求,让php代替去别的网站获取内容返回给我们(返回的为json:轻量级数据格式)
如:通过服务端代理请求百度的数据
html页面的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
list-style: none;
}
.wrap{
width: 500px;
margin: 100px auto 0;
}
.ipt{
padding: 6px 8px;
width: 480px;
}
.list{
background-color: #ccc;
}
.list li{
line-height: 30px;
padding: 4px 10px;
}
.list li:hover{
background-color: #eee;
}
</style>
</head>
<body>
<div class="wrap">
<input type="text" class="ipt">
<ul class="list">
<!-- <li>12306</li>
<li>12315</li>
<li>12345</li> -->
</ul>
</div>
<script src="./utils.js"></script>
<script>
var ipt = $1('.ipt')
var list = $1('.list')
// oninput 输入一次改变一次
// onchange 失去焦点时改变
// onkeyup 键盘按下时改变
ipt.onkeyup = function (){
var keyword = ipt.value
// 空值判断
if (!keyword) {
return
}
ajax({
url: './data/baidu.php',
type: 'get',
data: 'wd='+keyword,
dataType: 'json',
success: function (json){
// console.log(json)
var domStr = ''
json.s.forEach(function (item){
domStr += '<li>'+item+'</li>'
})
list.innerHTML = domStr
},
error: function (){
}
})
}
</script>
</body>
</html>
baidu.php
<?php
/*
****************************************************
请求方式: get
url: ./data/baidu.php
参数: wd = 搜索关键字
return: '{s:['数据1','数据2'.。。。]}'
json字符串
****************************************************
*/
// header("Access-Control-Allow-Origin:*");
// 服务端代理请求
$url = 'http://suggestion.baidu.com/su?wd=';
function getJSONStr($str){
return substr($str,17);
}
function crul($key){
global $url;
$data = file_get_contents($url.$key);
$data = getJSONStr($data);
$data = str_replace("{q:\"","",$data);
$data = str_replace("\",p:","{%aaa%}",$data);
$data = str_replace(",s:[","{%aaa%}",$data);
$data = str_replace("]});","",$data);
$arr = explode("{%aaa%}",$data);
$res = array();
$res['q'] = iconv("GB2312","UTF-8",$arr[0]);
if ($arr[1] == 'true'){
$arr[1] = true;
}else{
$arr[1] = false;
}
$res['p'] = $arr[1];
if (strlen($arr[2])>0){
$arr[2] = substr($arr[2],1,-1);
$arr[2] = str_replace("\",\"",",",$arr[2]);
$arr[2] = iconv("GB2312","UTF-8",$arr[2]);
}
$res['s'] = explode(',',$arr[2]);
echo json_encode($res);//json_encode()转换成json字符串
}
$key = $_REQUEST['wd'];
crul($key);
?>
utils.js内用到的代码
function $1(selector){
return document.querySelector(selector)
}
function ajax(options){
// data -> 'key=value&key=value'
// 1.创建数据交互对象
if (window.XMLHttpRequest) {
var xhr = new XMLHttpRequest() // 非IE5 6
} else {
var xhr = new ActiveXObject('Microsoft.XMLHTTP') // IE5 6
}
// 判断并格式化参数data
var data = ''
// if (typeof options.data === 'object' && options.data !== null && options.data.constructor === 'Object') {
if (isObject(options.data)) {
// 把对象格式化成 -> 'k1=v1&k2=v2&k3=v3'
for (var key in options.data) {
data += key+'='+options.data[key]+'&'
}
// data = 'k1=v1&k2=v2&k3=v3&'
data = data.substring(0,data.length-1)
}
if (typeof options.data === 'string') {
data = options.data
}
// 判断请求方式
if (options.type.toLowerCase() === 'get') {
var time = ''
time = options.cache ? '' : Date.now()
// 2.打开连接
xhr.open(options.type,options.url+'?'+data+'&_='+time,true) // 默认true,异步
// 3.发送请求
xhr.send(null) // get请求传null
}
if (options.type.toLowerCase() === 'post') {
// 2.打开连接
xhr.open(options.type,options.url,true) // 默认true,异步
// post 请不会有缓存问题
// 设置请求头,作用 模拟表单 post 请求提交数据,在send方法之前设置
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded")
// 3.发送请求
xhr.send(data) // post请求 要传递的参数在此传
}
// 4.等待请求/响应状态
// xhr.readyState 请求状态,0-4状态改变会触发一个readystatechange事件
xhr.onreadystatechange = function (){
// console.log( xhr.readyState );// 2 3 4
if (xhr.readyState === 4) {// 请求完成
// xhr.status 响应状态
if (xhr.status === 200) {// OK 响应就绪
// xhr.responseText 响应的数据
// options.success(xhr.responseText)
// 支持dataType配置
if (options.dataType === 'json') {
var json = JSON.parse(xhr.responseText)
options.success(json)
} else if (options.dataType === 'xml') {
options.success(xhr.responseXML)
} else {
options.success(xhr.responseText)
}
} else {
// console.log(xhr.status)
options.error(xhr.status)
}
}
}
}
页面效果
注意:我这里开了Nginx 且端口号为85 要在本地服务器打开才行即在浏览器查看需要打开本地进行查看 http://localhost:85
对了,我这里百度的api是
百度关键词:
url地址:http://suggestion.baidu.com/su
-----请求参数-----
cb 回调函数
wd 关键词
-----返回数据-----
JSON返回示例:
{
q: "123",
p: false,
s: [
0: "12306"
1: "12306铁路客户服务中心"
2: "12306火车票网上订票官网"
3: "12333"
4: "12333社保查询网"
5: "12306验证码识别"
6: "123网址之家"
7: "12345"
8: "123456hd"
9: "12308"
]
}
2、jsonp跨域(针对get请求)——无需服务器
原理:jsonp跨域就是利用script标签的跨域能力请求资源
- 浏览器出于安全考虑限制了JavaScript的跨域能力,但是没有限制标签的跨域
注意:
- jsonp拿到的数据是json对象,而不是json字符串,不需要解析可以直接用
- script标签可以跨域,类似的还有link、img、iframe
<img src="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3807427310,283445384&fm=26&gp=0.jpg" alt="">
<!-- 页面引入子窗口 子窗口可以引入外部的一些资源 -->
<iframe width="600" height="400" src="https://www.baidu.com/" frameborder="1"></iframe>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
- jsonp跨域就是利用script标签的特征:请求回来的数据当成js文件执行
既然叫jsonp,显然目的还是json,而且是跨域获取
即利用js创建一个script标签,把json的url赋给script的src属性,把这个script插入页面,让浏览器去跨域获取资源 - callback是页面存在的回调方法,参数就是想得到的json
- 回调方法要遵从服务端的约定一般是用 callback 或者 cb
- jsonp针对get形式的请求
jsonp实现跨域具体怎么做
- 注意:在html页面请求了php文件,必须打开服务器才不会报错,因为php是基于服务端的脚本语言,必须借助一些服务器才能打开
- 如果后端返回的是函数调用,那么,前端必须有这个全局函数即
js function test(json){ console.log(json.wd) }
不 然在在php直接echo ‘test()’
会报错,告诉我们test未定义- 原因:请求回来的数据当成js文件执行 在php调用了函数 但是在js文件未定义函数
- 可以在函数接收一个形参(json),这个形参就是从php返回的我们需要的数据
- 需要注意的是,我们不能一进入页面就调用函数,因为调用函数就直接执行了,我们并不知道需要执行的内容 一般会在地址栏传一个数据给后端,告诉后端我们需要拿什么数据 后端需要接收一下前端传的数据 如 :
function test(json){ console.log(json.wd) } <script src="./data/jsonp.php?cb=test"></script> //后端 jsonp.php文件中 $wd = $_GET['wd']; echo 'test({a:123,b:456,wd:'.$wd.'})';
- 如果后端返回的是函数调用,那么,前端必须有这个全局函数即
jsonp实现思路: 这样依旧会一进入页面就直接执行了,无法根据用户需要进行获取内容,比如说搜索,用户输入内容后我们再去向后端请求数据,因此我们需要动态创建script标签,即在需要的时候才创建script标签请求资源
jsonp的实现
动态创建script标签步骤如下:
- 创建script标签
var oScript = document.createElement(‘script’) - 给创建的script标签的src属性赋值(数据地址)
oScript.src = ‘./data/jsonp.php?wd=888’ - 将创建的script标签插入body末尾
document.body.appendChild(oScript) - 这时点击btn会一直创建script 因此我们要在script标签加载完成后,可以删除掉
oScript.onload = function (){
document.body.removeChild(oScript)
}
<button class="btn">请求数据</button>
<script>
var btn = document.querySelector('.btn')
btn.onclick = function (){
// 需要是动态创建script标签
var oScript = document.createElement('script')
// 给创建的script标签的src属性赋值(数据地址)
oScript.src = './data/jsonp.php?wd=888'
// 将创建的script标签插入body末尾
document.body.appendChild(oScript)
// 这时点击btn会一直创建script
// 因此我们要在script标签加载完成后,可以删除掉
oScript.onload = function (){
document.body.removeChild(oScript)
}
}
// 全局函数
function test(json){
console.log(json.wd)
}
</script>
jsonp请求百度的数据例子:(不需要打开服务器,因为请求的不是php文件)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
list-style: none;
}
.wrap{
width: 500px;
margin: 100px auto 0;
}
.ipt{
padding: 6px 8px;
width: 480px;
}
.list{
background-color: #ccc;
}
.list li{
line-height: 30px;
padding: 4px 10px;
}
.list li:hover{
background-color: #eee;
}
</style>
</head>
<body>
<div class="wrap">
<input type="text" class="ipt">
<ul class="list">
<!-- <li>12306</li>
<li>12315</li>
<li>12345</li> -->
</ul>
</div>
<script>
var ipt = document.querySelector('.ipt')
var list = document.querySelector('.list')
// 不需要在服务器端打开
ipt.onkeyup = function (){
var oScript = document.createElement('script')
oScript.src = 'http://suggestion.baidu.com/su?cb=mycallback&wd='+ipt.value
document.body.appendChild(oScript)
oScript.onload = function (){
document.body.removeChild(oScript)
}
}
// 请求成功执行的函数
function mycallback(json){
var domStr = ''
json.s.forEach(function (item){
domStr += '<li>'+item+'</li>'
})
list.innerHTML = domStr
}
//mycallback在后端请求到的数据: mycallback({q:"1",p:false,s:["1688黄页网","192.168.1.1 登陆入口","1公顷等于多少平方米","1升等于多少毫升","192.168.0.1 登陆页面","163","163 邮箱","123","11pro max","192.168.1.1手机登录"]});
</script>
</body>
</html>
jsonp的封装及使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
list-style: none;
}
.wrap{
width: 500px;
margin: 100px auto 0;
}
.ipt{
padding: 6px 8px;
width: 480px;
}
.list{
background-color: #ccc;
}
.list li{
line-height: 30px;
padding: 4px 10px;
}
.list li:hover{
background-color: #eee;
}
</style>
</head>
<body>
<div class="wrap">
<input type="text" class="ipt">
<ul class="list">
<!-- <li>12306</li>
<li>12315</li>
<li>12345</li> -->
</ul>
</div>
<script>
var ipt = document.querySelector('.ipt')
var list = document.querySelector('.list')
ipt.onkeyup = function (){
jsonp({
url: 'http://suggestion.baidu.com/su',
data: 'wd='+ipt.value,
jsonp: 'cb',
jsonpCallback: 'hehe',//自定义函数名
success: function mycb(json){
var domStr = ''
json.s.forEach(function (item){
domStr += '<li>'+item+'</li>'
})
list.innerHTML = domStr
}
})
}
// mycallback({q:"1",p:false,s:["1688黄页网","192.168.1.1 登陆入口","1公顷等于多少平方米","1升等于多少毫升","192.168.0.1 登陆页面","163","163 邮箱","123","11pro max","192.168.1.1手机登录"]});
function isObject(obj){
if (Object.prototype.toString.call(obj) === '[object Object]') {
return true
}
return false
}
function jsonp(options){
// options.success 变成全局函数
// options.jsonpCallback = 'hehe'
window[options.jsonpCallback] = options.success
// 判断 options.data的数据类型
// 如果字符串,直接赋值data变量
// 如果是对象,转成参数序列的字符串
var data = ''
if (typeof options.data === 'string') {
data = options.data
}
if (isObject(options.data)) {
for (var key in options.data){
data += key+'='+options.data[key]+'&'
}
data = data.substring(0,data.length-1)
}
// 创建 script标签
var oScript = document.createElement('script')
// 给src属性赋值(url+接口参数)
oScript.src = options.url+'?'+options.jsonp+'='+options.jsonpCallback+'&'+data
// 把script插入文档中
document.body.appendChild(oScript)
// script标签加载完成时,删除此标签
oScript.onload = function (){
document.body.removeChild(oScript)
}
}
// window.mycallback()
</script>
</body>
</html>
例题:查询手机号归属地及运营商
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text" class="ipt">
<button class="btn">查询</button>
<div class="show"></div>
<script src="./utils.js"></script>
<script>
var ipt = $1('.ipt')
var btn = $1('.btn')
var show = $1('.show')
btn.onclick = function (){
jsonp({
url: 'http://tcc.taobao.com/cc/json/mobile_tel_segment.htm',
data: 'tel='+ipt.value,
jsonp: 'callback',
jsonpCallback: 'hehe',
success: function (json){
show.innerText = '归属地:'+json.province+',运营商:'+json.catName
}
})
}
</script>
</body>
</html>
3、CORS 跨域资源共享(xhr2)——要打开服务器
- CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)
- 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制
- 整个CORS通信过程,都是浏览器自动完成,不需要用户参与
- 对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样
- 实现CORS通信的关键是服务端,只要服务端实现了CORS接口,就可以跨源通信
实现CORS并不难,只需服务端即后端做一些设置即可:如在php文件中设置
<?php
header("Access-Control-Allow-Origin:*"); // 允许任何来源
?>
注意:IE10以下不支持CORS
4、Nginx代理跨域——要打开服务器
找到以下文件如下图进行配置
D:\phpstudy_pro\Extensions\Nginx1.15.11\conf\vhosts\localhost_80.conf
配置以上图片内容后重启Nginx