Skip to content

面试知识归纳:2.5通信类

通信类

什么是同源策略及限制

前后端如何通信

如何创建Ajax

跨域通信的几种方式

什么是同源策略及限制

同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制

http://www.a.com:80

协议不同 跨域 域名不同 跨域 端口不同 跨域

  • Cookie、localStorage、indexDB无法读取
  • DOM无法获得
  • Ajax请求无法发送

前后端通信方式

  • Ajax
  • WebSocket
  • CORS 跨域资源共享

如何创建Ajax

XMLHttpRequest 对象的工作流程 兼容性处理 事件的触发条件 事件的触发顺序

跨域通信的几种方式

  • Jsonp
  • Hash
  • postMessage
  • WebSocket
  • CORS 可暂理解为支持跨域通信的Ajax

注:改变hash不会刷新页面所以可做跨域,而search会刷新页面

Jsonp

利用script可以原合成连接远程url来实现跨域请求的:如 <script src="http://jsonp.js?callback=xxx"></script>

callback定义了一个函数名,而远程服务端通过调用制定的函数并传入参数来实现传递参数。 下面,我们来简单实现一个可扩展的JSONP对象。

js
const JSONP = {
  // 获取事件戳
  now() {
    return (new Date()).getTime()
  },

  // 获取16位随机数
  rand() {
    return Math.random().toString().substring(2, 18)
  },

  // 删除节点元素
  removeElem(elem) {
    let parent = elem.parentNode
    if (parent && parent.nodeType !== 11) {
      parent.removeChild(elem)
    }
  },

  // url组装
  parseData(data) {
    let ret = ''
    if (typeof data === 'string') {
      ret = data
    } else if (typeof data === 'object') {
      for (let key in data) {
        ret += `&${key}=${encodeURIComponent(data[key])}`
      }
    }
    // 加个时间戳,防止缓存
    ret += `&_time=${this.now()}`
    ret = ret.substr(1)
  },

  getJSON(url, data, func) {
    // 函数名
    let name
    // 拼接url
    url = url + (url.indexOf('?') === -1 ? '?' : '&') + this.parseData(data)
    // 检查callback的函数名是否已经定义
    let match = /callback=(\w+)/.exec(url)
    if (match && match[1]) {
      name = match[1]
    } else {
      // 如果未定义函数名,就随机生成函数名
      // 随机生成的函数名通过时间戳拼接16位随机数的方式,重名的概率低
      name = `jsonp_${this.now()}_${this.rand()}`
      // 把callback中的?替换成函数名
      url = url.replace('callback=?', `callback=${name}`)
    }

    let script = document.creatElement('script')
    script.src = url
    script.id = `id_${name}`

    // 把传进来的函数重新组装,并把它设置为全局函数,远程调用的就是这个函数
    window[name] = funtion(json) {
      // 该函数执行后,要销毁这个函数
      window[name] = undefined
      // 获取这个script元素
      const elem = document.getElementById(`id_${name}`)
      // 删除head里面插入的script,这三步是位了不污染整个dom
      JSONP.removeElem(elem)
      // 执行传入的函数
      func(json)
    }

    // 在head里面插入script元素
    const head = document.getElementsTagName('head')[0]
    head.appendChild(script)
  }
}

hash跨域

利用hash,场景是当前页面A通过iframe嵌入了跨域的页面B,在A中的伪代码如下:

js
let B = document.getElementsByTagName('iframe')
B.src = B.src + '#' + 'data'

在B中的伪代码如下:

js
window.onhashchange = () => {
  let data = window.location.hash
}
// postMessage
// 窗口A (http://A.com) 向跨域窗口的B (http://B.com)发送消息
[A]window.postMessage('data', 'http://B.com')
// 在窗口B中监听
[B]window.addEventListener('message', event => {
  console.log(event.origin) // http://A.com
  console.log(event.source) // A window
  console.log(event.data)   // data
}, false)

webSocket

js
const ws = new WebSocket('wss://echo.websocket.org')
ws.onopen = event => {
  console.log('Connection open...')
  ws.send('Hello WebSocket')
}
ws.onmessage = event => {
  console.log('Received message: ' + event.data)
  ws.close()
}
ws.onclose = event => {
  console.log('Connection closed ...')
}

CORS

关于CORS 可以参考阮一峰老师的 www.ruanyifeng.com/blog/2016/04/cors.html

需要浏览器和服务端都支持。

对于GET、POST、HEAD简单请求方式,通信时在头信息增加一个 Origin 字段。该字段用于说明本次请求来自哪个源(协议、域名、端口),服务器根据该值,决定是否同意请求。

  • Access-Control-Allow-Origin 必须添加 * 表示接收任意域名请求
  • Access-Control-Allow-Credentials 可选,布尔值。表示是否允许发送Cookie
  • Access-Control-Expose-Headers 可选 CORS请求时,XML对象的getReponseHeader()方法只能拿到6个基本字段
js
fetch(url, {
  method: 'get'
}).then(res => {
  // console.log(res.data)
}).catch(err => {
  // 出错了,等价于 then 的第二个参数,但这样更好用更直观
})

为什么CORS能支持跨域通信呢? 浏览器会拦截Ajax请求,若该请求跨域,会在http头中添加一个Access-Control-Allow-Origin头,所以就能实现跨域。

共 20 个模块,1301 篇 Markdown 文档。