《前端竹節》(1)【跨域通訊】

1、同源策略

用戶瀏覽網站時不免須要將一些常常用到的信息,緩存在本地以提高交互體驗,避免一些多餘的操做。那麼這些信息中不免有些就會涉及用戶的隱私,怎麼保證用戶的信息不在多個站點之間共享,以達到可用的最小範圍,這邊引出了瀏覽器的同源策略。那麼...javascript

什麼是同源策略?
同時知足如下三個條件的網頁稱爲同源:html

  1. 協議相同
  2. 域名相同
  3. 端口相同

非同源的網頁將受到如下限制:前端

  1. Cookie、LocalStorage 和 IndexDB 沒法讀取。
  2. DOM 沒法得到
  3. AJAX 請求不能發送

同源策略是必要的,但這些限制有時也會對一些合理的使用帶來不便,這便引出了跨域通訊的需求。java

2、跨域通訊

常見的跨域通訊方式有以下集中:jquery

  1. JSONP
  2. Hash
  3. Postmessage
  4. WebSocket
  5. CORS

2.1 JSONP

JSONP的原理是,首先客戶端動態添加一個<script>元素,向服務器請求JSON數據,在請求的URL中添加search字段,指定回調函數名;服務器在收到請求後,會將數據放在指定名字的回調中傳回來。實現源碼:ios

/**
  * jsonp
  * @param  {[type]} url      [description]
  * @param  {[type]} onsucess [description]
  * @param  {[type]} onerror  [description]
  * @param  {[type]} charset  [description]
  * @return {[type]}          [description]
  */
const jsonp = function(url, onsucess, onerror, chartset){
    let callbackName = getName('tmp_jsonp')
    window[callbackName] = function(){
        if(onsucess && onsucess instanceof Function){
            onsucess(arguments[0])
        }
    }
    let script = createScript(url + '&callback=' + callbackName, charset)
    script.onload = script.onreadystatechange = function () {
        // script標籤加載完成(即jsonp請求完成)後移除建立的臨時標籤
        if(!script.readyState || /loaded|complete/.test(script.readyState)){
            script.onload = script.onreadystatechange = null
            // 移除該script的 DOM 對象
            if (script.parentNode) {
                script.parentNode.removeChild(script)
            }
            window[callbackName] = null
        }
    }
    script.onerror = function () {
        if (onerror && util.isFunction(onerror)) {
            onerror();
        }
    }
    document.getElementsByTagName('head')[0].appendChild(script)
}
 /**
  * 獲取一個隨機的5位字符串
  * @param  {string} prefix [字符前綴]
  * @return {string}        [字符集]
  */
const getName = function (prefix) {
    return prefix + Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5)
}
 /**
  * 在頁面中建立script
  * @param  {string} url     [url]
  * @param  {string} charset [字符集]
  * @return {string}         [建立完成的script標籤]
  */
const createScript = function (url, charset) {
    let script = document.createElement('script')
    script.setAttribute('type', 'text/javascript')
    charset && script.setAttribute('charset', charset)
    script.setAttribute('src', url)
    script.async = true
    return script
}

2.2 hash

hash方式主要用在內嵌iframe的父子窗口間的通訊,原理是URL的#後部分的改變不會使頁面刷新。
(1)父窗口向子窗口發送數據web

// 父窗口向子窗口發送數據
let src = orignURL + '#' + data
document.getElementById('myIframe').src = src
// 子窗口接收父窗口發送的數據
window.onhashchange = function(){
    let message = window.location.hash
    ...
}

(2)子窗口向父窗口發送數據,也同理ajax

parent.location.herf = target + '#' + hash

2.3 postmessage

首先建立目標窗口的window對象,而後經過該對象的postmessage方法傳遞數據,最後目標窗口經過監聽message事件來接受數據。json

// 發送數據
let bWindow = window.open('http://b.com', 'title')
bWindow.postmessage('hello message', 'http://b.com')
// 接受數據
window.addEventListener('message', function(e){
    // e.source 發送消息窗口的引用
    // e.origin 發送消息的網址
    // e.data 發送消息的內容
})

使用postmessage時須要注意的問題:axios

  1. 接受方打開了對message事件的響應,接收時須要對發送方進行驗證
  2. 發送消息時須要指定肯定的接收方,不可指定爲*進行羣發

2.4 websocket

// ws和wss的區別就是加不加密
let ws = new WebSocket('ws://xx.com')
ws.onopen = function(e){}

ws.onmessage = function(e){}

ws.onclose = function(e){}

2.5 CORS

能夠簡單理解爲跨域通訊的ajax,須要瀏覽器和服務器同時支持,主要關鍵仍是須在瀏覽器端進行配置。可參考CORS配置說明

3、先後端如何通訊

除了跨域通訊,前端最常接觸的即是先後端通訊,常見的先後端通訊方式有如下三種:

  1. ajax/fetch
  2. websocket
  3. cors

4、如何建立ajax

4.1 Vue框架實現ajax

Vue是個沒什麼入侵性的框架,因此在ajax方面能夠有不少選擇:

  1. 使用原生js的XHR實現
  2. 引入jquery
  3. Vue2.0官方推薦axios.js
  4. fetch API

4.2 原生js實現ajax

/**
 * 原生JS實現ajax
 * @param  {object} options [傳入參數]
 * @return {[type]}         [description]
 */
const ajax = function(options){
    var opt = {
        url: '',
        type: 'get',
        data: {},
        success: function () {},
        error: function () {},
    }
    Object.assign(opt, options)
    if(opt.url){
        let xhr = XMLHttpRequest
            ? new XMLHttpRequest()
            : new ActiveXObject('Microsoft.XMLHTTP')
        let data = opt.data,
            url = opt.url,
            type = opt.type.toUpperCase(),
            dataArr = []
        for(let k in data){
            dataArr.push(k + '=' + data[k])
        }
        if(type == 'GET'){
            url = url + '?' + dataArr.join('&')
            xhr.open(type, url.replace(/\?$/.g, ''), true)
            xhr.send()
        }
        if(type == 'POST'){
            xhr.open(type, url, true)
            xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
            xhr.send(dataArr.join('&'))
        }
        xhr.onload = () => {
            if(xhr.status === 200 || xhr.status === 304 || xhr.status === 206){
                let res
                if(opt.success && opt.success instanceof Function){
                    res = xhr.responseText
                    if(typeof res === 'string'){
                        res = JSON.parse(res)
                        opt.success.call(xhr, res)
                    }
                }
            }else{
                if(opt.error && opt.error instanceof Function){
                    opt.error.call(xhr, res)
                }
            }
        }
    }
}
相關文章
相關標籤/搜索