# 数据获取

# Ajax 远程数据获取

浏览器环境提供了 XMLHttpRequestfetch 两个接口用于实现 Ajax。有一些库在此基础上做了二次封装,如:jQuery、Axios。

# 跨域问题

# 同源策略 (SOP)

同源的定义:协议、域名、端口,三者一致。如果不一致,就叫做跨域。

Web 是开放的,站点之间可以相互引用,所以我们能贴其它站点的图片、引用其它站点的 CSS/JS。所以我们说这几个是能跨域的:

  • <img> 标签跨域引用图片
  • <link> 标签跨域引用 CSS
  • <script> 标签跨域引用 JS

但一些敏感操作 (如:Ajax 请求、POST 请求) 就会受到同源策略的约束,无法进行跨域。这是浏览器的一种安全机制,可以有效保护用户的安全。

# CORS 实现跨域通信

CORS 可以实现跨域 Ajax,但需要服务端支持。

CORS 代码示例

客户端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="userInfo"></div>
    <script>
        function renderUser(user) {
            const div = document.getElementById('userInfo')
            const nameElm = document.createElement('p')
            nameElm.innerText = user.name
            const ageElm = document.createElement('p')
            ageElm.innerText = user.age
            div.appendChild(nameElm)
            div.appendChild(ageElm)
        }
        window.addEventListener('load', () => {
            const xhr = new XMLHttpRequest()
            xhr.open('get', 'http://127.0.0.1:3000/api/user', false)
            xhr.onreadystatechange = () => {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    const user = JSON.parse(xhr.response)
                    renderUser(user)
                }
            }
            xhr.send()
        })
    </script>
</body>
</html>

服务器端代码:

const express = require('express')

const app = express()

app.get('/api/user', (req, resp, next) => {
  const user = {
    name: '张三',
    age: 18
  }
  resp.setHeader('Access-Control-Allow-Origin', '*')
  resp.send(JSON.stringify(user))
})

app.listen(3000, () => {
  console.log('server running at http://localhost:3000')
})

# 其它技术实现跨域通信 (JSONP)

在 CORS 出现之前实现跨域 Ajax 比较麻烦,主要有两种技术:

  • 使用 <img src="..."><link rel="stylesheet" href="..."> 发起 GET 请求
  • 使用 JSONP,原理是浏览器的同源策略不会管 <script src="...">

使用 <img><link> 的缺点是:

  • 只能发送 GET 请求
  • 无法收到后端返回的数据
  • 无法确定请求是否报错

使用 JSONP 的缺点是:

  • 只能发送 GET 请求
  • 无法确定请求是否报错
JSONP 代码示例

客户端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="userInfo"></div>
    <script>
        function renderUser(user) {
            const div = document.getElementById('userInfo')
            const nameElm = document.createElement('p')
            nameElm.innerText = user.name
            const ageElm = document.createElement('p')
            ageElm.innerText = user.age
            div.appendChild(nameElm)
            div.appendChild(ageElm)
        }
        // renderUser({name: 'zhang', age: 18})
        window.addEventListener('load', () => {
            const script = document.createElement('script')
            script.src = 'http://127.0.0.1:3000/api/jsonp/user?callback=renderUser'
            document.body.appendChild(script)
        })
    </script>
</body>
</html>

服务器端代码:

const express = require('express')

const app = express()

app.get('/api/jsonp/user', (req, resp, next) => {
  const user = {
    name: '张三',
    age: 18
  }
  const callback = req.query.callback
  resp.send(`
    ${callback}(${JSON.stringify(user)})
  `)
})

app.listen(3000, () => {
  console.log('server running at http://localhost:3000')
})