javascript基础--跨域

来源:互联网 发布:使命召唤12cpu优化 编辑:程序博客网 时间:2024/05/29 19:28

引言

为什么会有跨域,什么是跨域,这些疑问在没有接触它之前都是正常的。简单的来讲,是因为某些人利用加载外部域的外部脚本代码来搜集原网站信息,以致各种信息泄露,财产损失等现象发生。所以javascript出于安全的角度,提出同源策略,禁止加载不同域下的资源。但是我们开发人员有时的确也有需要使用外部资源,怎么办呢?这就是跨域。

1.什么是跨域

域名的组成:
这里写图片描述
判定方法:

  • 1.当协议,子域名,主域名,端口号四个之间任意一个不同时,都算不同域
  • 2.不同域名之间请求资源就算跨域

示例:

//请求不同的资源http://www.baidu.com/index.html  http://www.baidu.com/1.png  非跨域//协议不同http://www.baidu.com/index.html  https://www.baidu.com/index.html 跨域 //子域名不同http://www.baidu.com/index.html  http://bbs.baidu.com/index.html 跨域 //主域名不同http://www.baidu.com/index.html  http://www.sina.com/index.html 跨域 //端口号不通http://www.baidu.com:80/index.html  http://www.baidu.com:81/index.htm 跨域

2.跨域方法一: document.domain

首选得知道document.domain是什么?
释义:document.domain返回当前文档的域名,可以试试,我用的bing搜索,没有广告哦。

这里写图片描述

可以打印那么可以赋值修改domain吗?试试就知道了

这里写图片描述

可见document.domain修改是有原则的,具体有两条

  • 可以修改为本身,相当于没做修改(感觉被耍了)
  • 可以修改为本身的主域名(跨域就从这里做文章)

这么讲,假设你们班上没有同名的人。你叫李小明,别人可以叫你李小明,也可以叫你小明,不可能叫你李大明,周小明。老师提问,叫到李小明,你肯定一下子就站起来了。如果恰好你们班上有个人叫王小明,那老师叫小明,谁又该站起来呢。所以跨域也是这个道理,利用这种歧义,让“名字”相同,“姓氏”不同的人,实现共享信息

举个例子:www.baidu.com下面www是子域名,baidu.com是主域名。所以要修改,只有两种可能,其它情况都不行

  • 第一,修改为www.baidu.com
  • 第二,修改为baidu.com

我们实现跨域主要是依靠第二个结果来做文章。所以document.domain只对主域名相同,子域名不同的情况实现跨域。为什么这样讲?看看例子。
准备好两个页面:www.a.com cn.a.com —> 两个域名的主域名都是a.com,子域名不同
实现方式:借助Frame对象

www.a.com a.html页面

document.domain = 'a.com';var ifr = document.createElement('iframe');ifr.src = 'http://cn.a.com/b.html';ifr.style.display = 'none';document.body.appendChild(ifr);ifr.onload = function(){    //获取Frame对象文档内容    var doc = ifr.contentDocument || ifr.contentWindow.document;    // 在这里操纵b.html    alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeVaue);};

cn.a.com b.html页面

document.domain = 'a.com';

2.跨域方法二:jsonp

这里就不讨论jsonp和json的区别了,就像java和javascript名字相似,其实质是两种东西。
jsonp其实就是利用script标签不受同源策略限制为出发点的。像下面这样

//jquery CDN服务,引用不同域文件完全不受同源策略限制<script src="//cdn.bootcss.com/jquery/3.1.1/core.js"></script>

我们先构建jsonp的雏形
A域下有个a.html,B域下有个b.js
A域下a.html页面内容为:

<script type="text/script">    function info(my){        console.log(my.info);    }</script><script type="text/script" src="B/b.js"></script>

B域下b.js的内容为:

info({info:"kingboss"});

结果为:kingboss
所以jsonp的实质是,另一个域下的文件传递一个callback执行函数,函数名与原域所定义的函数名相同。

当然世界是多变的,代码是活的,所以我们可以动态的创建script标签,引入可变的src地址
A域下a.html页面内容改为:

<script type="text/script">    function info(my){        console.log(my.info);    }    function addScript(src){        var script = document.createElement('script');        script.setAttribute("type","text/javascript");        script.src = src;        document.body.appendChild(script);    }    window.onload = function(){        addScript("B/b.js");    }</script>

B域下b.js的内容不变:

info({info:"kingboss"});

有人就会问了,加入B域下的文件不知道A域下的函数名怎么办?简单,那就让A域下的文件告诉B就ok了,具体的告知方式就是在url部添加callback参数
A域下a.html页面内容改为:

<script type="text/script">    function info(my){        console.log(my.info);    }    function addScript(src){        var script = document.createElement('script');        script.setAttribute("type","text/javascript");        script.src = src;        document.body.appendChild(script);    }    window.onload = function(){        addScript("B/b.js?callback=info");    }</script>

B域下的文件根据callback参数动态生成执行函数
jquery的jsonp实现上面的例子

<script src="//cdn.bootcss.com/jquery/3.1.1/core.js"></script><script type="text/javascript">    $(function(){        $.ajax({             type: "get",             async: false,             url: "B/b.js",             dataType: "jsonp",             jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)             jsonpCallback:"flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据             success: function(my){                 console.log(my.info)             },             error: function(){                 alert('fail');             }         });    })</script>

注意:jsonp只支持get请求,原因不是很清楚

3.跨域方法三:location.hash

前面我们讲过document.domain只适用于主域名相同,子域名不同的情况,那么主域名也不同呢?又该怎么办。所以就有了进阶版,location.hash传值方法。
什么是location.hash,其实它是js用来管理内部地址的对象,内容为:

属性 描述 hash 从井号 (#) 开始的 URL(锚) host 主机名和当前 URL 的端口号 hostname 当前 URL 的主机名 href 完整的 URL pathname 当前 URL 的路径部分 port 当前 URL 的端口号 protocol 当前 URL 的协议 search 从问号 (?) 开始的 URL(查询部分)

方法:利用location.hash进行传值,选择其传值的理由是改变hash指不会刷新页面。A域下a.html通过创建iframe指向B域下b.html,由于不同域,所以B域下会创建A域的a1.html(代理页面),代理页面a1.html和a.html同属一个域,因此可以修改。缺点:数据完全暴露在url中,容量也有限制
逻辑图
这里写图片描述

A域下 a.html

<script type="text/javascript">    function startRequest(){        var ifr = document.createElement('iframe');        ifr.style.display = 'none';        ifr.src = 'B/b.html#data';        document.body.appendChild(ifr);    }    function checkHash() {        try {            //获取hash传入的data值            var data = location.hash ? location.hash.substring(1) : '';            if (console.log) {                console.log('Now the data is '+data);            }        } catch(e) {};    }    setInterval(checkHash, 2000);</script>

B域下 b.html

<script type="text/script">    switch(location.hash){        case '#paramdo':            callBack();            break;        case '#paramset':            //do something……            break;    }    function callBack(){        try {            parent.location.hash = 'somedata';        } catch (e) {            // ie、chrome的安全机制无法修改parent.location.hash,            // 所以要利用一个中间的B域下的代理iframe            var ifrproxy = document.createElement('iframe');            ifrproxy.style.display = 'none';            ifrproxy.src = 'A/a1.html#somedata';    // 注意该文件在A域下            document.body.appendChild(ifrproxy);        }    }</script>

A域下 a1.html

<script type="text/javascript">    //因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值    parent.parent.location.hash = self.location.hash.substring(1);</script>

4.跨域方法四:window.name

先了解一下什么是window.name,以及它有什么特性。window.name方法传值其实和location.hash类似,也依靠代理文件实现。共需要三份文件,A域下的a.html;B域下的b.html;A域下的a1.html(空代理文件)

B域下的b.html

<script type="text/javascript">    window.name = 'data';    // 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右</script>

A域下的a.html

<script type="text/javascript">    var state = 0,     //动态地创建iframe    iframe = document.createElement('iframe'),    iframe.src = 'B/b.html';    document.body.appendChild(iframe);    loadfn = function() {        if (state === 1) {            var data = iframe.contentWindow.name;    // 读取数据            alert(data);    //弹出'data'        } else if (state === 0) {            //改变iframe的location指向代理文件            iframe.contentWindow.location = "A/a1.html";    // 设置的代理文件        }      };    //兼容,能力检测    if (iframe.attachEvent) {        iframe.attachEvent('onload', loadfn);    } else {        iframe.onload  = loadfn;    }    //销毁iframe    iframe.contentWindow.document.write('');//文档内容清空    iframe.contentWindow.close();//关闭子窗口    document.body.removeChild(iframe);//去除tag</script>

疑问:
1.什么是window.name?
解释:name 属性可设置或得到窗口的名称,其值为字符串;该名称是在 open() 方法创建窗口时指定的或者使用一个 frame 标记的 name 属性指定的,默认情况下 name 属性值是为空的。窗口的 name 属性可以用于a或form标签的 target 属性值,这样表示超链接文档或表单提交结果应该显示于指定 name 的窗口或框架中。
特点是:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。—->这也解释了为什么A域的代理文件可以访问到B域的window.name

2.什么是contentWindow属性?
contentWindow : 兼容各个浏览器,可取得子窗口的 window 对象。
contentDocument : Firefox 支持,> ie8 的ie支持。能够以 HTML 对象来返回 iframe 中的文档。可以通过所有标准的 DOM 方法来处理被返回的document对象

解决了上面的疑惑,用图来呈现此逻辑
这里写图片描述

结束语

虽然跨域有很多种方法,但是要折中,比如引入iframe本身就对网页优化有一定的影响,还有location.hash的安全漏洞等问题。所以不要滥用。

0 0