一二面_通讯类

84 阅读6分钟

一二面_通讯类

什么是同源策略及限制?

(MDN官方解释)

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

大白话:

一个源包含三部分内容:协议、域名、端口。www.baidu.com, https是协议,www.baidu.com是域名,后面默认的端口是80,这三者构成一个源,这三个其中有一个不一样,也就是我们所说的源不一样,也就是跨域了。同源策略一定要记住什么是源。

什么是限制:不是一个源的文档没有权利去操作另一个源的文档,主要限制在这几个方面:

1.Cookie、LocalStorage和IndexDB无法读取

2.DOM无法获取

(无法获取或操作另一个资源的DOM,比如document.body是拿不到的,任何DOM都是拿不到的)

3.Ajax请求不能发送

(Ajax只适合同源的通讯,跨域就不行了)

前后端如何通讯?(考察知识面是否够宽和主动学习的能力)

最常用的三种方式:Ajax、WebSocket、CORS

Ajax(同源下面的通讯方式)

WebSocket(不受同源策略的限制)

CORS(支持跨域通讯也支持同源通讯,是一个新的通讯标准)

如何创建Ajax?(看代码)

(前后端通讯经常用的一个手段,而我们在使用过程中一方面是借助第三方的库,比如说VUE有个插件去做这个事,jQuery有一个封装好的方法让你用,如果抛弃了第三方的工具和框架,如何用原生的JS去实现一个Ajax,考验候选人动手能力以及对框架背后原理的一个掌握)

考察这几方面:

1.XMLHttpRequest对象的工作流程

XMLHttpRequest对象的工作流程你是不是清楚,第一步第二步第三步等都干了啥,看你代码中有没有体现清晰

2.兼容性处理

看你能不能响应兼容性的一个处理(XMLHttpRequest只有高级浏览器才支持,老版IE是不支持的,以及FireFox,做这个题目的时候有没有想兼容性的问题,判断你考虑问题是否周全,重点不是要求你一定要在各个浏览器中跑起来,看的是你的逻辑和思维方式)

3.事件的触发条件

要明确某一个事件是在什么条件下触发的,不要很多事件触发乱糟糟的

4.事件的触发顺序

XMLHttpRequest对象有好多个事件,每个事件都是怎么样依次触发的,这个顺序要掌握,比如说在响应中,把第二步要干的事放在第一步去干了是不可以的,是响应不了的

跨域通讯的几种方式?

(什么是跨域?跨域有几种限制?跨域的几种方式?)

(面试的时候这5种都说出来,不要落下任何一个)

1.JSONP

(这个是面试中一定要说的出来的,包括代码、思路)

(JSONP的原理是什么,是怎么实现的)(看代码)

原理:(在出现postMessage、CORS之前,一直使用JSONP来做跨域通讯的),它是利用script标签的异步加载来实现的,(页面是www.amook.com,script标签的地址的域名不是amook.com,所有的网站的网JS的地址和域名是不一致的,这就是跨域了)

2.Hash

(url地址中#后面的东西,Hash变动页面不会刷新,这就是用Hash做跨域通讯的一个基本原理。seach,url中?后面的东西,search改变是会刷新页面的,所以search不能做跨域通讯)

3.postMessage

(HTML5中新增加的,为什么HTML5中会增加这个那,同源通讯目标就是限制跨域通讯,实际业务中又需要跨域通讯,所以新增这个)

这个就是一个HTML5的标准,利用一些API的实现就可以轻松地完成

4.WebSocket

(不受同源策略的限制的,所以可以用于跨域通讯)

(面试的时候更多的不会让你去写这块的代码,他主要是想知道你了不了解WebSocket这种通讯方式,如果你没在项目中明确强调你对WebSocket有实战经验,面试官一般情况下不会深究让你把代码写一下WebSocket怎么用,了解这块的概念,知道怎么回事就行了)

5.CORS

(也是新出的通讯标准,可以理解为支持跨域通讯的Ajax,浏览器在识别你用Ajax发送了一个跨域请求的时候,它会在HTTP头中加一个origin来允许跨域通讯,这就是CORS,如果不加这个头的话,就是一个普通的Ajax,浏览器就会给你拦截了,说是非法的,不允许请求)

(这部分的代码不是一两分钟就写出来的,面试官基本不会让你去写代码,面试官要问你的点是:第一,你了不了解这块的知识点;第二,你了不了解这块的原理。都说清楚了就行,不会深究这块的代码)

面试官问:CORS为什么就支持了跨域通讯了?

答:浏览器会拦截Ajax请求,如果它觉得这个Ajax请求是跨域的,它会在HTTP的头中加一个origin来允许跨域通讯

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>通讯类</title>
</head>
<body>
    <script>
        // ***动态创建一个script标签
        util.createScript = function (url, chatset) {
            var script = document.createElement('script');
            script.setAttribute('type', 'text/javascript');
            charset && script.setAttribute('charset', charset);
            script.setAttribute('src', url);
            script.async = true;
            return script;
        }
   
        
        // JSONP的实现
        util.jsonp = function (url, onsuccess, onerror, charset) {
            // 第一步给服务端传递一个回调的名,
            // 这个回调的名是,我用加载script标签的方式发出一个请求,你给我返回一块内容,这个内容是一个我JS块,也就是script的一个块,这个块中有我这个回调名+代码,它就能运行了,
            // 比如在html文件中请求了一个这个内容,下面紧挨着注释了的这一行代码就是浏览器发出的请求,并告诉服务端你的callback的名称,
            // 因为它将来是要作为函数名给你返回的,既然是函数名所以你就要创建一个函数,所以它在调jsonp的时候,你本地就要有一个jsonp这个函数,后面的话才能把它给你的数据执行出来,当函数去运行
            // <script src = "http://www.abc.com/?data=name&callback=jsonp" charset = "utf-8"></script>//callback后面是回调名,这里回调名为jsonp,这个名叫什么都行,只要与服务端商量好了;data是参数,这些都可以传
            // 服务器给你下发一个这样的script内容,而且它利用你回调的这个东西就执行了这个代码,
            // <script>
            //     jsonp ({
            //         data: {}
            //     })
            // </script>
            // 所以下面就是你告诉它一个回调的名称
            var callbackName = util.getName('tt_player');
            // 你要在window以这个回调名称注册一个全局函数
            window[callbackName] = function () {
                if (onsuccess && util.isFunction(onsuccess)) {
                    onsuccess(arguments[0]);
                }
            };
            // createScript就是动态创建一个script标签,该函数在上面***
            var script = util.createScript(url + '&callback' + callbackName, charset);
            // script.onload监听这个脚本的加载事件,如果它响应完了以后,你给我响应一个onload
            script.onload = script.onreadystatechange = function () {
                // 判断这个onload是否成功,
                if (!script.readyState || /loaded|complete/.test(script.readyState)) {
                    script.onload = script.onreadystatechange = null;
                    // 成功了以后看能不能拿到script.parentNode1这个数据,再移除该script的DOM对象
                    if (script.parentNode) {
                        script.parentNode.removeChild(script);
                    }
                    // 删除函数或变量
                    window[callbackName] = null;
                }
            };
            script.onerror = function () {
                if (onerror && util.isFunction(onerror)) {
                    onerror();
                }
            };
            // 往html中增加一个script标签,其实目的就是把这个请求发送过去,如果不加这一句它是没法发送过去的
            document.getElementByTagName('head')[0].appendChild(script);
        }
    </script>
        
    <script>
        // 手动创建Ajax
        util.json = function (options) {
            var opt = {
                url: '',
                type: 'get',
                data: {},
                success: function () {},
                error: function () {},
            };
            util.extend(opt, options);
            if (opt.url) {
                // 第一步声明对象
                // 判断XMLHttpRequest这个对象是不是存在,也就是所谓了浏览器特征检查,如果存在就新建一个对象,不存在就新建一个IE下面的一个对象,
                // 而且这个地方还考虑到了兼容
                var xhr = XMLHttpRequest ? new XMLHttpRequest () : new window.ActiveXObject('Microsoft.XMLHTTP'); 
                var data = opt.data,
                    url = opt.url,
                    type = opt.type.toUpperCase(),
                    dataArr = [];
                for (var k in data) {
                    dataArr.push(k + '=' +data[k]);
                }
                // 这里判断了一下对GET和POST两个的支持,GET和POST发布的方式是不一样的
                // 面试过程中不用区分GET和POST
                // 对GET的一个处理方式
                if (type === 'GET') {
                    url = url + '?' +dataArr.join('&');
                    // 第二步是open,确认你这个XMLHttpRequest对象发动的方式,用是HTTP的哪个协议,是PUT还是GET还是DELET还是POST等,这是open需要确定的事情
                    xhr.open(type, url.replace(/\?$/g, ''), true);
                    // 第三步发送,把这个请求发送出去
                    xhr.send();
                }
                // 对POST的一个处理方式
                if (type === 'POST') {
                    xhr.open(type, url, true);
                    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                    xhr.send(dataArr.join('&'));
                }
                // 第四步是响应
                // 这里写的简单,这个事件只做了一个onload,监听成功或失败
                xhr.onload = function () {
                    // 这个响应需要判断HTTP状态码,
                    // (200是普通的正常响应,或者是利用强缓存也会返回一个200)
                    // (304是重定向,利用本地的缓存就可以了,不能忽视这个304,如果忽视了,当服务器出现304的时候,代码就没有响应,程序就不正常了)
                    // 后面加一个206也可以,如果你的这个Ajax请求的是媒体资源,还要加上206,
                    // 因为媒体资源特别大,不是一次性返回过来的,它是资源的一部分,也就是服务端给你下发的状态码是206,你这里不写206,是收不到响应的
                    if (xhr.status === 200 || xhr.status === 304) {
                        var res;
                        if (opt.success && opt.success instanceof Function) {
                            res = xhr.responseText;
                            if (typeof res === 'string') {
                                // 接下来就是把拿过来的东西通过JSON转成JSON格式
                                res = JSON.parse(res);
                                opt.success.call(xhr, res);
                            }
                        }
                    } else {
                        if (opt.error && opt.error instanceof Function) {
                            opt.error.call(xhr, res);
                        }
                    }
                }
            }
        }
    </script>

    <script>
        // hash实现
        // 利用hash,场景是当前页面A,通过iframe或frame嵌入了跨域的页面B,他俩是跨域的,现在想给页面B发送一个消息过去
        var B = document.getElementsByTagName('iframe');
        // 拿到B的窗口的src通过hash的方式发一串字符串,这个字符串可以通过一个完整的JSON,再转成字符串发给B,此时发是发出去了,需要B接收
        B.src = B.src + '#' +'data';
        // B中的伪代码如下
        // B在自己的代码中增加一个window.onhashchange,这个事件是用来监听你当前的hash有没有发生改变(B.src = B.src + '#' +'data';,对B来说url变化了),因为变化了,所以就可以拿到了,
        // 拿到以后通过 window.loaction.hash就可以拿到具体内容,注意:window.loaction.hash拿到的可不止一个,如果出了A发送过来的东西,后面还有别的拼接,就需要特殊处理一下
        window.onhashchange = function () {
            var data = window.loaction.hash;
        }

    </script>

    <script>
        // postMessage
        // 窗口A(http://A.com)向跨域的窗口B(http://B.com)发送信息
        // 发送的时候要选中B窗口(给谁发送就要选中那个窗口),下面这个window是B窗口下的window,
        // 调用postMessage这个API,第一个参数是你要发送的数据部分(推荐使用字符串格式),
        // 第二个参数是接收方的源(有两种方式:一个是加那个源(推荐);一个是加*,*表示可以给任何窗口发送,这个是不安全的)
        window.postMessage('data', 'http://B.com');
        // 在窗口B中监听
        // 在B窗口中监听message事件(和click等事件用法一样),
        window.addEventListener('message', function (event) {
            // 判断发送者的源,在你的响应程序中要选择性的接收,比如说我只接收来自A.com的信息,其他的一律不接收,通过event.origin这个属性来哦判断
            console.log(event.origin); //http://A.com
            // source引用的是A窗口的一个对象,
            console.log(event.source); //window(这个window是A窗口下的window)
            // 它给你发送的消息,你怎么拿到这个数据,通过event.data拿到的
            console.log(event.data); //data!
        }, false);

        // 要是B窗口给A窗口发送,是一样的道理
        // 可以完全用event.source.postMessage发送过去
        // A窗口下的响应要在A窗口下加一个window.addEventListener('message', function () {}, false);
    </script>

    <script>
        // WebSocket
        // 参考资料:http://www.ruanyifeng.com/blog/2017/05/websocket.html
        // 先new一个对象,这里有两种:ws 和 wss,区别是wss被加密了,后面跟一个服务器地址,相当于类似一个对象来管理这个链接
        var ws = new WebSocket('wss://echo.websocket.org');

        // 需要做的有三个事件:onopen、onmessage、onclose
        // onopen时要把这个请求发送出去
        ws.onopen = function (evt) {
            console.log('Connection open ...');
            // 发送消息出去
            ws.send('Hello WebSockets!');
        };

        // onmessage事件,对方给你消息你要接收,通过evt.data来拿到
        ws.onmessage = function (evt) {
            console.log('Received Message: ' + evt.data);
            // 关闭通道
            ws.close();
        };

        // 最后你说你不用了,我这个连接的通道断了,你就监听onclose来确定它是不是关闭了,关闭的方法就是close()
        ws.onclose = function (evt) {
            console.log('Connection closed.');
        };

    </script>

    <script>
        // CORS
        // 参考资料:http://www.ruanyifeng.com/blog/2016/04/cors.html
        // url(必须),options(可选)
        // fetch是一个新的Ajax的API,用来实现CORS通讯的,fetch的第一个参数是地址,第二个参数是配置选项
        // 整个代码看不出来是CORS,因为它没有跨域,这个就是一个普通的通讯,就是一个Ajax,所以同源通讯的情况下除了Ajax还有fetch,这个时候fetch就是Ajax,只不过用法不一样了,
        // 那个时候用XMLHttpRequest对象,这个时候就用fetch这个API就行了
        // 如果说想用它使用在跨域通讯,那么在它里面加一些配置就OK了,具体的做法看参考资料阮一峰老师的资料进行配置
        fetch('/some/url', {
            method: 'gat',
            // then是回调成功
        }).then(function (response) {
        
            // catch是捕获错误
        }).catch(function (err) {
            // 出错了:等价于then的第二个参数,但这样更好用更直观
        });
    </script>
</body>
</html>