因为学习了腾讯出的前端进阶课程,所以打算把课程内容都总结一遍。有些都是很普通很常见的知识,但是为了巩固自己的知识面,梳理自己的知识树,所以每个知识点都会写成文章,所有文章都会放在公众号右下角的前端进阶课程总结中~~~也希望能够帮助到需要的人
跨域的东西, 简直不要接触太多,网上相关内容一抓一大把,但是突然学习到一个关于前端解决跨域的方式
就是 利用 iframe
不管你有没有了解过,反正我没有
我觉得很有用并且容易忘,所以我记录下来哈哈哈
下面会分三块内容进行描述
1、基本原理
2、简单模拟
2、封装的函数
3、封装函数实战
如果觉得排版不好看,可以看原版 【JS应用】Iframe 解决跨域 ,或者拉到最后关注公众号吧
解决场景
现在我们在 http://a.com 的域名下有一个页面
我们要请求 http://b.com 下的一个接口,很明显是会跨域的,无法直接请求
今天我们使用 iframe 来解决这个问题
基本原理
1、需要三个页面
两个同域(http://a.com)的 页面,一个和接口同域的 页面(http://b.com)
其中一个页面是父页面,也就是真正的内容页,展示数据的
另外两个作为子页面,是辅助父页面请求跨域数据用的,不会显示在页面中
2、利用全局变量 window.name 存储数据
父子页面利用 window.name 进行通信,但是前提是同域
当 父子页面不同域的时候,父子无法访问对方 window.name
但是,如果是同级页面切换,就算是不同域,window.name 也是可以访问的
3、数据请求
现在请求 http://b.com 的接口,所以只能在 http://b.com 下的页面(这个是子页面)进行请求
请求成功后,保存在当前的 window.name 中
4、页面跳转
上一步保存完数据之后,由 http://b.com 页面 跳到 http://a.com 页面
此时 http://a.com 页面就能通过 window.name 拿到 http://b.com 保存过的数据啦
5、给父页面传递数据
上面那个 http://a.com 页面是辅助页,拿到数据之后,需要传递给真正需要数据的父页面(同样是 http://a.com 下)
大概了解之后,我们来说一下简单流程
现在有 http://a.com 下的内容页 A,需要请求接口 http://b.com/xxxx,但是跨域
1、内容页 A 嵌入一个隐藏 iframe,iframe 加载 http://b.com 下的辅助页面 B
2、辅助页面B 开始请求接口 http://b.com/xxx,请求成功,存放到 window.name
3、隐藏 iframe 由页面B 跳转到 http://a.com 下另一个辅助页(页面C)
4、页面C 读取到 B存放的 window.name,然后传递给 父页面A
5、父页面A 拿到 数据,用于展示在页面上
简单模拟
现在我启动了两个服务
1、localhost:3001 下有 a.html 和 c.html
a.html 是内容页,需要使用数据的终端页(以下简称A)
c.html 是辅助页(以下简称C)
2、localhost:3002 下有 b.html
b.html 也是辅助页,用于请求数据(以下简称B)
内容页 A
在 A 中,使用 iframe 嵌入了 B,并且全局设置了一个函数 getData
这个函数的作用是,为了给 C页面调用,传入接口的数据的
<body>
我是A页面
<script>
window.getData=function(data){
console.log("获取到数据",data)
}
</script>
<iframe src="http://localhost:3002/b.html" ></iframe>
</body>
辅助页 B
B 页面当然是用于请求接口了,这里使用 定时器模拟接口,请求成功后跳转到 C
<body>
我是B页面
<script>
console.log("B页面开始请求接口")
setTimeout(function(){
window.name="我是B页面保存的数据"
location.href="http://localhost:3001/c.html"
},2000)
</script>
</body>
辅助页 C
B 请求完,跳到C 之后,C 拿到 window.name,然后调用 A 的方法 getData,并且把数据传过去
<body>
我是C页面
<script>// 调用页面A 的方法,并把 name 传过去
parent.getData(window.name)
</script>
</body>
没错,这就完成了 iframe 解决跨域的问题,但是实际上并不会这么做,肯定是封装得更加适用一些
详情就看下面吧
封装函数
经过上面的说明,我们首先要明确我们的目的
1、iframe
2、两个辅助页
3、数据回调
所以我们封装的函数必须要满足这几个东西
首先,封装一个函数创建 iframe 插入 body 中,并且转到传入的 url
function createIframe (url) {
var doc = document
var iframe = doc.createElement('iframe')
iframe.src = url
iframe.frameborder = '0'
iframe.style.display = 'none'
doc.body.appendChild(iframe)
}
很简单,都能看懂
之后,我们需要在 url 上拼接参数,所以我们需要在封装一个 url 相关的函数
为什么需要拼接参数?
比如请求的接口需要某些参数,这些参数是父页面提供的,所以就只能把这些参数放到 iframe 的 url 上以便通信
拼接 url 函数
function parseUrl (url, param) {
return url + (url.indexOf('?') === -1 ? '?' : '&') + serialize(param)
}
当时放在 url 上的参数,是要经过转义的,比如不能出现中文,所以需要多一个函数用于格式化参数
function serialize (data) {
var ret = []
Object.keys(data).forEach(item => {
ret.push(encodeURIComponent(item) + '=' + encodeURIComponent(data[item]))
})
return ret.join('&')
}
那么现在就万事俱备,只欠主菜了,马上来看
function cross (option, callback) {
var funcName = getFunctionName()
var data = option.data || {}
window[funcName] = function (response) {
callback(response)
window[funcName] = null
}
var url = parseUrl(option.targetUrl, {
data:option.data,
url:option.url,
parentFuc:funcName,
skipUrl:option.skipUrl
})
createIframe(url)
}
在这个函数中,解决了我们一开始提到的两个问题
1、数据回调
2、两个辅助页
很明显,这个函数是给我们的父页面调用的啦
首先,cross 函数接收两个参数,options 和 callback,一个个解释
options
是一个对象,包含下面四个字段
targetUrl, 用于请求接口,跟接口同域那个辅助子页面
skipUrl ,请求接口成功后,跳转到的那个子页面
data, 请求接口时需要传递的参数
url ,需要进行请求的接口名
callback
很明显,这个回调,也就是接口请求完成,跳转完成之后会触发的
但是并不是直接触发,而是包装了一层,你看其中这段
var funcName = getFunctionName()
window[funcName] = function (response) {
callback(response)
}
主要作用还是像我们模拟中的一样,在父页面中注册一个函数,给子页面传递数据用
但是这个函数注册就有点意思了,因为是全局的,非常害怕全局污染和重名,所以这里弄了一个随机生成函数名的函数 getFunctionName,如下
function getFunctionName () {
return ('iframe_' + Math.random())
.replace('.', '')
}
但是,函数名是随机的,子页面怎么知道我要调用那个函数?
子页面:我不知道函数名字啊!!!!
所以需要把函数名字也传递
怎么传递?
放到 url 上,然后子页面就可以直接从 url 上拿参数,并取其中的字段 parentFunc ,就可以知道函数名啦
优化
随着请求越来越多,生成的全局随机函数肯定会越来越多,并且是一次性的,不会再被使用,留着根本没用,还占用资源
所以我们最好手动清除他,所以你看到在设置全局函数时,回调执行完之后就执行设置为 null
window[funcName] = function (response) {
callback(response)
window[funcName] = null
}
没错,封装的函数我们已经看完了,我们还是有必要来看下怎么使用的啊
封装函数的实践
在这里我先出一个大王函数,用于获取链接参数的,直接得到对象的
作用如下
然后就到我们的正文了
首先,我们的内容页率先登场,并且调用 cross 表示调用接口
<body>
<script>
cross({
// 请求数据的页面
targetUrl: 'http://127.0.0.1:3002/b.html',
// 当区域跳转的页面
skipUrl: 'http://127.0.0.1:3001/c.html',
// 需要请求的接口
url:"http://localhost:3002/getUserInfo"
data: {} // 参数
}, data= >{
console.log("接收到数据",data)
});
});
</script>
</body>
接着,到了我们要请求接口的B 页面
拿到 url 上的请求接口和请求参数,调用 ajax
调用成功后,拿到 url 上的 跳转链接 skipUrl,直接跳过去,并且带上参数 parsetFunc
因为C 页面中,需要知道函数名,所以这里必须带上
<body>
<script>
var param = parseQueryString(location.href)
ajax({
url:${param.url},
data:${param.data},
success(data){
// 数据保存在 window.name
window.name = data
// 跳转到父页面配置的子页面,并且把配置的函数名带上
location.href = `${param.skipUrl}?parentFunc = ${param.parentFunc}`;
}
})
</script>
</body>
那么现在就只剩下我们的C 页面了
从url 上获取到函数名,然后拿到 父页面的 window(也就是 parent)
直接调用,并且传入 window.name
这样,整个流程就走完了
<body>
<script>
var param = parseQueryString(location.href)
parent[param.parentFuc](window.name);
</script>
</body>
不过说真的,这种方法也就作为一个备胎方法,大家熟悉熟悉知道一下就好了,毕竟项目中也不太可能用到
不过面试可能会问哈哈,多掌握点总是没错的
最后
鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵,如果有任何描述不当的地方,欢迎后台联系本人,领取红包