探索 Jsonp 的实现原理

332 阅读3分钟

1:什么是 Jsonp

Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)获取资料,即跨域读取数据。

2: Jsonp 的实现原理

Jsonp 的实现原理是通过动态创建一个 script 标签来实现的。

3:什么是跨域

跨域是因为同源策略引起的,同源指的是:协议、域名、端口号。

3-1:不允许跨域的情况

example 描述 是否允许跨域
h ttp://www.a.com/a.js
h ttps://www.a.com/b.js
协议不一样
h ttp://www.a.com/a.js
h ttp://www.b.com/b.js
域名不一样
h ttp://www.a.com/a.js
h ttp://www.a.com:8080/b.js
端口不一样
h ttp://www.a.com/a.js
h ttp://www.192.1.1.168.com/b.js
域名跟域名对应的IP一样
h ttp://www.a.com/a.js
h ttp://bb.a.com/b.js
主域名跟二级域名

3-2:跨域不能访问一下操作

  • Cookie、Storage、IndexDB 无法读取
  • Ajzx 不能接收响应
  • 无法拿到 Dom 对象 (如 iframe中的dom)

同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,\color{red}{服务器响应了,但是无法被浏览器接收}

4: 借助 node.js 实现 Jsonp

4-1:基于 XMLHttpRequest 实现的请求

客户端:index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <button id="xhr">xhr</button>
  <button id="jsonp">jsonp</button>
  <script>
    const XHR_BUTTON = document.querySelector('#xhr') 
    
    XHR_BUTTON.onclick = () => {
      myXhr('GET', 'http://localhost:3000/test?type=1', (body) => {
        console.log(body)
      })
    }
    
    function myXhr(method, url, callback) {
      const XHR = new XMLHttpRequest()
      
      XHR.onreadystatechange = () => {
        console.log(XHR.readyState, XHR.status)
        if (XHR.status === 200
        && XHR.readyState === 4) {
          console.log(XHR.responseText)
        }
      }
      
      XHR.open(method, url, true)
      
      XHR.send()
    }
  </script>
</body>
</html>

服务端: server.js

const http = require('http')
const url = require('url')

let source = [
  {
    type: 1,
    value: '标题一'
  },
  {
    type: 2,
    value: '标题二'
  }
]

// 开启一个服务
const app = http.createServer((req, res) => {
  let pathName = url.parse(req.url).pathname;

  if (pathName === '/favicon.ico') {  // 排除 favicon.ico 的请求
    res.end()
    return
  }

  let queryArr = url.parse(req.url).query.split('&')  // 获取路由参数
  let query = {}

  queryArr.forEach(item => {
    let itemArr = item.split('=')
    query[itemArr[0]] = itemArr[1]
  })

  let body = source.filter(item => item.type === +query.type)

  // 响应数据
  setTimeout(() => {
    const BODY = JSON.stringify(body)
    const JSONP_NAME = query.jsonpName
    
    res.setHeader("Content-type","application/json"); // 解决乱码问题
    if (JSONP_NAME) {
      res.write(`${ JSONP_NAME }(${ BODY })`)
    } else {
      res.write(BODY)
    }
    
    res.end()
  }, 1000)
})

// 监听 3000 端口
app.listen(3000, () => {
  console.log('3000端口已被开启')
})

  • 启动服务端 node server.js

  • 访问客服端,发起请求

服务端成功返回:

客户端识别到不同源,进行拦截:

4-1:Jsonp 的实现

客户端:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <button id="jsonp">jsonp</button>
  <script>
    const JSON_BUTTON = document.querySelector('#jsonp')
    
    JSON_BUTTON.onclick = () => {
      myJsonp('http://localhost:3000/test?type=1', 'jsonp', (body) => {
        console.log(body)
      })
    }
    
    function myJsonp(url, jsonpName, callback) {
      if (url.includes('?')) {
        url += `&jsonpName=${jsonpName}`
      } else {
        url += `?jsonpName=${jsonpName}`
      }
      
      const script = document.createElement('script')
      script.src = url
      document.body.appendChild(script)
      document.body.removeChild(script)
      
      window[jsonpName] = (body) => {
        callback && callback(body)
      }
    }
  </script>
</body>
</html>

服务端: 同上

客户端成功拿到服务端的返回结果

注意:\color{red}{Jsonp 回调函数的名字不需要前后端约定,前端传什么,后端就返回什么}

5:Jsonp 的缺点

只支持 get 方式,不支持其它的,不够灵活

6:解决跨域的其它办法

  • 1:服务端代理(客户端请求 A 服务器, A 服务器再去请求 B 服务器,A 服务器得到结果之后在返回给客户端)
  • 2:服务端使用第三方包(node 可以使用 Cors)
  • 3: window.postMessage
  • 4: document.domain
  • 5: window.name

【学习笔记,有错误之处敬请指教,谢谢阅读!😊】