这样回答jsonp,面试官直接给我发了offer

221 阅读6分钟

今天,兄弟狗焕出去面试。

面试官问了他关于的jsonp的问题,五连击。

狗焕拽进了拳头,憋了半天,敢怒不敢言。

我看穿了他的心事,写下这篇文档,希望我的兄弟狗焕可以如从前那样,快乐的在夕阳下奔跑,开心起来。


凭借记忆,狗焕把面试官的连环问题做了以下汇总,我顺着脉络给他一点点的梳理讲解一下,目录在下面。

问题1:为什么说jsonp不是真正的ajax?
问题2:为什么jsonp在callback后面写了一个函数就能获取到值?
问题3:服务端给前端返回的是json吗?
问题4:客户端怎么接收服务端返回的数据?
问题5:点击按钮的时候,想用jsonp发送请求,用原生js应该怎么处理?
方便大家理解,写个demo
    - 效果展示
    - 客户端代码
    - 服务端代码
总结:

问题1:为什么说jsonp不是真正的ajax?

我们在使用ajax的时候常常会受到浏览器同源策略的限制。所谓同源就是:协议、域名和端口都相同表示同一个源,否则就是不同的源。很多时候客户端和服务端不是同源下的,这样就会存在跨域问题,而jsonp正好能解决跨域。

那jsonp的工作原理是什么呢?

我们在发送ajax请求的时候会受到同源策略的限制,但是html标签中的img、link和script是不受同源策略限制的,而script正好是获取js脚本资源的,所以名正言顺的使用了script进行跨域操作,通过设置src的链接获取到我们想要的数据。

ajax的原理是创建xhrHttpRequest对象加载资源的,且是受到同源策略的限制。而jsonp是是通过script标签本身不受同源策略限制的特性,设置src值为接口路径,当成普通资源来加载的,所以不是真正的ajax。

问题2:为什么jsonp在callback后面写了一个函数就能获取到值?

我们假设我们的jsonp接口是这样的http://127.0.0.1:9002/findUser?callback=showData,这里的callback是在使用jsonp的时候默认约定好的,showData是我们在客户端定义的全局函数,我们在这个全局函数showData中接收到服务端返回的数据,做一些逻辑处理,诸如这样:

function showData(res) {
    // res是服务端返回的数据,我拿到这个数据做一系列的操作
    ...
}

狗焕痛苦的大叫:“对啊,script只是请求一段代码片段,它怎么把返回结果塞进showData入参里的呢?这也太诡异了吧,我当时就是这样卡住了,想不通。”

狗焕兄弟不要着急,服务端到底返回什么呢?我接下来就告诉你。

服务端返回的是什么?

狗焕兄弟你刚刚也说到了,通过script标签请求到资源就是一段js片段,然后js引擎会把这段js片段执行是吧?

返回的js片段跟我们在控制台写一段js代码本质上是一样的。

我们打开自己的思路,跟普通ajax返回的数据格式比较一下,如果服务端给我们返回的是一段类似于下面这样的js片段,一切是不是就能说的通了呢?

// 下面是一段基于node.js语法的服务端代码

// 普通ajax返回的结果,直接返回json数据
const data = {
    name: '狗焕',
    age: 24
}
res.end(data)


// jsonp返回的结果,直接返回带函数名为showData的js片段
res.end(`showData(${data})`)

你看,服务端直接把带返回值的执行函数给你返回了,那前端获取到这段js代码片段之后,是不是就直接会把执行你在全局定义的函数showData,而此时服务端已经把你要的res返回给你了。

你看看,是不是很妙啊。

问题3:服务端给前端返回的是json吗?

不是json。

这是最关键的一步,你就是卡在这里出不去了,总认为返回的数据就应该是一个json,然后拿到这个json做一些逻辑处理。

事实上服务端直接给你返回了带参数的执行函数,script请求到这个资源就会立即执行showData方法,然后进入到你定义的showData函数内,你打一下断点是不是这样的执行顺序。

问题4:客户端怎么接收服务端返回的数据?

就像问题2这里提到的,直接定义一个函数(函数名叫啥都行,比如叫haha),然后在这个函数中的入参就是jsonp返回的数据,拿到这个数据处理对应的逻辑即可。

另外在写jsonp请求的时候,记得callback=${funcName},这里的funcName是一个变量,把这个位置的名称替换成你定义的函数名哈哈即可,于是请求地址就变成了http://127.0.0.1:9002/findUser?callback=haha,是不是超级简单?

问题5:点击按钮的时候,想用jsonp发送请求,用原生js应该怎么处理?

这个也很简单,我们参照ajax的实现方式就可以了。

传统的ajax请求一般是在点击按钮的时候,就会发起一个ajax的请求。

那我们直接把这里发起一个ajax的请求替换成发起一个jsonp的请求就好了。同时考虑到产生的script标签会越来越多,考虑到页面的性能,我们要把创建的script标签删除掉。

所以jsonp请求怎么发出去?执行下面3步即可:

第一步:创建一个script标签,id名为jsonp
第二步:script标签src属性设置为接口的地址
第三步:获取到数据,处理相应的逻辑,并将id为jsonp的script标签删除

好了,以上就是整个jsonp的执行全流程了,回答好了上面5个问题之后,对jsonp的整个流程毫无问题了。

为了方便大家理解整个前后端交互的流程,我写一个demo,把整个交互都展示出来,让大家更好的吸收这个知识点。

写个demo展示出来

效果展示

image.png

每次获取到数据渲染完成之后,就会把当前的script标签删除。再次点击,生成script标签的id就变化了。

客户端代码

<body>
<button id="btn">获取jsonp数据</button>
<p id="user"></p>
<script>
let key = 0;
const $btn = document.getElementById('btn');
$btn.onclick = function(){
  const $script = document.createElement('script');
  const scriptId = `jsonp_${key}`;
  $script.setAttribute('id', scriptId);
  $script.setAttribute('src', `http://127.0.0.1:9002/findUser?callback=showData&id=${scriptId}`);
  document.body.appendChild($script);
  key++;
}

// 接收到jsonp的数据
function showData(res) {
   // 在这里打印一下当前json的id是什么
  console.log(res.id)
  const $dom = document.getElementById('user');
  const $script = res.id;
  user.innerHTML = `用户的名字叫:${res.name},用户的年龄是:${res.age}岁`;
  if ($script) {
    $script.parentNode.removeChild($script);
  }
}
</script>
</body>

这里做了一个优化,把每次产生的script的id也传给后端了,然后以返回值的方式获取到当前是哪一个script发出的请求。如果点击多次的情况下,这样可以准确的删除掉当前的script。以及如果已经获取到数据了,避免页面重复渲染的问题。

服务端代码

const http = require('http');
const url = require('url');

const server = http.createServer(function(req,res){
    let data = '服务器返回的数据'
    const urlParse = url.parse(req.url, true)
    const query = urlParse.query
    const id = query.id
    //设置请求参数的content-type(媒体类型信息)
    res.setHeader('content-type','text/html;charset=utf-8')

    switch (urlParse.pathname) {
      // 返回jsonp请求的数据
      case '/findUser':
        data = `${query.callback}({
          id: ${id  || ''},
          name: '狗焕',
          age: 24
        })`
        break;

      default:
        break;
    }

    res.end(data)
})

server.listen('9002',(err)=>{
  if(!err){
    console.log('服务器开启成功')
  }
})

从上面的代码可以看到,服务端通过获取callback的值,动态的返回了前端可能用到的函数名。如果客户端定义的是showData,那么返回值就是showData({ name: '狗焕', age: 24 })

总结:

其实整个节点最容易卡住的地方就是我们会自认为服务端返回的还是一个json数据格式,其实不然。在jsonp中服务端返回的是带返回值的执行函数showData({ name: '狗焕', age: 24 })这样的代码片段。

突破了这个卡点,后面的问题就迎刃而解了。

看完这篇文档,果然兄弟狗焕开心的回到电脑前开始学习,原来学习真的可以很轻松。

如果这篇文档对你有帮助,欢迎点赞、关注或者在评论区留言,我会第一时间对你的认可进行回应。精彩内容在后面,防止跑丢,友友们可以先关注我,每一篇文章都能及时通知不会遗失。