面试题之Jsonp的理解及手写代码

吐槽君 分类:javascript

前言:

在前端面试过程中经常会被问到关于跨域的问题,跨域的解决方案。本篇文章就带大家从基础开始先理解,再到手写解决方案的代码,防止一看就会,一写就废。

什么是Jsonp?

  • Jsonp(JSON with Padding) 是json的一种‘使用模式’,可以跨域的获取到数据
  • 了解同源机制协议、域名、端口都相同,是一种安全策略,所有支持JavaScript的浏览器都使用这个策略。
  • 跨域:当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。

如何拿到网页中的json数据?

我们准备好一个网址photo.sina.cn/aj/index?pa… ,里面有json数据。现在要实现跨域拿到数据。

  • xhr/fetch 因运行在 沙箱(Sandbox) 中的js同源机制,无法请求跨域sina 资源。在使用script 标签中有跨域访问资源能力有例如:src link img a href.

沙箱:Sandbox是一种虚拟的程序运行环境,用以隔离可疑软件中的病毒或者对计算机有害的行为。比如浏览器就是一个Sandbox环境,它加载并执行远程的代码,但对其加以诸多限制,比如禁止跨域请求、不允许读写本地文件等等。

具体代码实现

<body>
    <!-- 写一个callback函数 来得到数据 -->
    <script>
        function callback(data){
            console.log(data);
        }
    </script>
    <!-- 通过src 可以访问到外源网址 -->
    <!-- 需要在网址后面加上 &callback=函数名 ;用一个函数来得到外源数据-->
    <script src="https://photo.sina.cn/aj/index?page=1&cate=recommend&callback=callback"></script>
</body>
 

这样我们就可以通过callback()函数来处理数据了
查看结果:

image.png

手写Jsonp函数

接下来我们希望使用Json原理封装一个Jsonp函数,不用再为了远程的资源。

代码思路:

  1. 准备带有padding的请求url,得到一个字符串dataStr
  2. 构造script
  3. js代码使用document.createElement创建一个script标签
  4. 在函数作用域中外界如何访问到?使用window

初步版本:

<script>
        let jsonp=(url,data={},callback='callback')=>{
            //准备好带有padding的请求url
        let dataStr=url.indexOf('?')=== -1?'?':'&'
        // console.log(dataStr);
        for(let key in data){
            dataStr +=`${key}=${data[key]}&`
        }
        dataStr +=`callback=`+callback

        //构造 script
        let oScript=document.createElement('script')
        oScript.src=url+dataStr
        //appendChild () 方法可向节点的子节点列表的末尾添加新的子节点
        document.body.appendChild(oScript)

        window[callback]=(data)=>{
            console.log(data);
        }
    }

    jsonp('https://photo.sina.cn/aj/index?a=1',{
            page:1,
            cate:'recommend'
        })       
    </script>
 

可以看一下实现效果,浏览器中是否以key value 的形式打印出了数据

image.png
虽然实现了效果,但还可以在 window[callback]这部分进行优化。

优化版本:

    <script>
        let jsonp=(url,data={},callback='callback')=>{
            //准备好带有padding的请求url
        let dataStr=url.indexOf('?')=== -1?'?':'&'
        // console.log(dataStr);
        for(let key in data){
            dataStr +=`${key}=${data[key]}&`
        }
        dataStr +=`callback=`+callback

        //构造 script
        let oScript=document.createElement('script')
        oScript.src=url+dataStr
        //appendChild () 方法可向节点的子节点列表的末尾添加新的子节点
        document.body.appendChild(oScript)

        // window[callback]=(data)=>{
        //     console.log(data);
        // }
        return new Promise((resolve,reject)=>{
            window[callback]=(data)=>{
                try{
                    resolve(data)
                }catch(e){
                    reject(e)
                }finally{
                    oScript.parentNode.removeChild(oScript)// 注意这句代码,OScript移除,细节
                }
            }
        })
    }

    jsonp('https://photo.sina.cn/aj/index?a=1',{
            page:1,
            cate:'recommend'
        }).then(response=>{
            console.log(response,'-------then');
        })       
    </script>
 

服务器端的跨域访问

  • 首先建立一个api-server文件夹,进行初始化:npm init -y
  • 新建index.js文件
var http = require('http');
http.createServer(function(req, res){
// req url  callback=?
console.log(req.url); //结果: /?a=1&callback=callback
let data = {a: 1};
res.writeHead(200, {'Content-type' : 'text/json'})
  const reg = /callback=([\w]+)/
  if (reg.test(req.url)) {
    let padding = RegExp.$1
    res.end(`${padding}(${JSON.stringify(data)})`)
  } else {
    res.end(JSON.stringify(data));
  }

 
//  res.end('<p>Hello World</p>');
 res.end(JSON.stringify(data));
}).listen(3000);
 

在之前手写的main.html的文件<script>中添加以下代码:

jsonp('http://localhost:3000',{
            a:1
        })
        .then(response =>{
            console.log(response,'---------');
        })
 

我们可以看一下结果:

  • 我们通过服务器端跨域拿到了数据。

image.png

使用cors中间件实现跨域

首先安装cors:使用命令 yarn add cors

  1. server文件夹中新建一个js文件
var express = require('express');
var cors = require('cors');//后端cors 中间件
const app = express();
app.use(cors());
app.get('/product',(req,res)=>{
    res.json({
        a:1,
        b:2
    })
})
app.listen(8000,()=>{
    console.log('server is ok')
})

 
  1. server文件夹外部新建一个html文件
<script>
        fetch('http://localhost:8000/product')
        .then(data=>data.json())
        .then(data=>{
            console.log(data)
        })
</script>
 

实现结果:

image.png

总结:

我们知道了解决跨域的方案有:1. 借助src访问外网,使用callback=fun。2.封装Jsonp函数。3. 服务器端使用cors中间件。

本人是在校大三学生,也是在边学习边总结,如果有写的不妥的地方,请大家评论指出。
image.png

回复

我来回复
  • 暂无回复内容