全栈开发工程师学习秘籍 — JSONP原理和实战

227 阅读6分钟

1、区分同源和非同源

jsonp和ajax相同,都是客户端向服务器端发送请求:给服务器传递数据或者从服务器端获取数据的方式

ajax属于同源策略 jsonp属于非同源策略(跨域请求)=> 实现跨域请求的方式有很多种,只不过jsonp是最常用的

区分同源和非同源

用当前页面的地址(http://localhost:63342/JavaScript201606/ajax/1.html) && 数据请求的接口地址(http://localhost:63342/JavaScript201606/ajax/data.txt)

1)协议

2)域名或者ip

3)端口号

以上三部分完全相同属于同源策略,我们使用ajax技术获取数据;只要有一个不一样的,就属于非同源,我们一般使用jsonp获取数据

=> 当前页面的url地址(webstorm在预览页面的时候会默认的创建一个本地虚拟的服务,端口号是63342)http://localhost:63342/JavaScript201606/ajax/1.html

=> 我们需要在html中做一件事情

[把本地同一个服务下的data.txt中内容获取到]

我们需要请求数据的地址)http://localhost:63342/JavaScript201606/ajax/data.txt

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<script type="text/javascript">
    var xhr = new XMLHttpRequest;
    xhr.open("get","data.txt",true);
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status == 200) {
            console.log(xhr.responseText);
        }
    };
    xhr.send(null);   
</script>
</body>
</html>

[我想获取的是一天腾讯体育项目中的数据]

我们需要请求数据的地址 matchweb.sports.qq.com/matchUnion/…

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<script charset="utf-8" type="text/javascript">
  function fn(data) {
      console.log(data);
  }
</script>
<script charset="utf-8" type="text/javascript" src="http://matchweb.sports.qq.com/matchUnion/calendar?from=web&columnId=hot&callback=fn"></script>
</body>
</html>

jsonp的原理(JSONP请求一定需要对方的服务器做支持才可以)

在script的世界中,没有同源跨域这一说,只要你给我src属性中的地址是一个合法的地址,script都可以把对应的内容请求回来

JSONP就是利用了script的这个原理

1)我们首先把需要请求数据的,那个跨域的API数据接口的地址,赋值给script的src

2)把当前页面中的某一个函数名当做参数值,传递给需要跨域请求数据(腾讯)的服务器(url问号传参:?callback=fn)

3)腾讯服务器接收到你的请求后,需要进行特殊的处理:把你传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:我们传递进去的函数名是fn,它准备好的数据是"fn([{"name":"hehe"}])" => "我传递的函数名(需要给我们的数据)"

4)最后腾讯的服务器把准备的数据通过HTTP协议返回给我们客户端,客户端发现其实就是让我们的fn执行,而且还给fn传递了一堆的数据,那些数据就是我们想要的

2、JSONP的原理

jsonp的原理就是服务器端把客户端传过去的函数名和数据拼接到一起组中返回给客户端,理论上可以返回任何数据格式,常用的格式有普通格式字符串,json格式字符串,数字,xml,但是一般项目中服务器返回的都是json格式字符串

server.js

var http = require("http"),
    url = require("url"),
    fs = require("fs");
var server1 = http.createServer(function (req, res) {
    var urlObj = url.parse(req.url, true),
        pathname = urlObj.pathname,
        query = urlObj.query;//获取url中所有通过?传递的

    // 静态资源文件请求的处理
    var reg = /\.(HTML|CSS|JS|ICO)/i;
    if (reg.test(pathname)) {
        var suffix = reg.exec(pathname)[1].toUpperCase();
        var suffixMIME = "text/html";
        switch (suffix) {
            case "CSS":
                suffixMIME = "text/css";
                break;
            case "JS":
                suffixMIME = "text/javascript";
                break;
        }
        try {
            var conFile = fs.readFileSync("." + pathname, "utf-8");
            res.writeHead(200, {'content-type': suffixMIME + ';charset=utf-8;'});
            res.end(conFile);
        } catch (e) {
            res.writeHead(404, {'content-type': 'text/plain;charset=utf-8;'});
            res.end("file is not found~");
        }
        return;
    }

    // JSONP的处理
    if(pathname === "/getAll") {
        // 接收客户端传递进来的函数名
        var fnName = query["callback"];////获取通过?传递的callbak对应的值fn

        // 准备数据
        var con = JSON.parse(fs.readFileSync("./temp.json", "utf-8"));

        // 返回给客户端内容
        res.writeHead(200,{'content-type':'text/javascript;charset=utf-8;'});
        res.end(fnName + '(' + con + ')');// 把con当参数传递给fn,让fn执行  fnName是获取到的fn
        

    }
});
server1.listen(81, function () {
    console.log("server is success,listening on 81 port!")
});


开启这个服务

查看本机端口号

根据地址获取数据

通过客户端获取数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<script charset="utf-8" type="text/javascript">
  function ccc(data) {
      console.log(data);
  }
</script>
<script charset="utf-8" type="text/javascript" src="http://192.168.0.100:81/getAll?callback=ccc"></script>
</body>
</html>

3、jQuery的AJAX和JSONP调用

很简单,就不写了

4、模拟百度模糊搜索

sp0.baidu.com/5a1Fazu8AA5…

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>模拟百度的下拉搜索</title>
    <style type="text/css">
        *{
            margin:0;
            padding: 0
        };
        input{
            display: block;
            outline: none;
        }
        ul,li{
            list-style: none;
        }
        html,body{
            font-size: 14px;
            color: #000;
            font-family: "微软雅黑"
        }
        .box{
            margin: 50px auto;
            width: 300px;
        }
        .box input{
            padding: 0 10px;
            width: 278px;
            height: 30px;
            border: 1px solid green;
        }
        .box ul {
            display: none;
            border:  1px solid green;
            border-top:none;
        }
        .box ul li {
            padding: 0 10px;
            height: 30px;
            line-height: 30px;
            cursor: pointer;
        }
        .box ul li:hover {
            background: #eee;
        }

    </style>
</head>
<body>
    <div class="box">
        <input type="text" id="searchInp">
        <ul id="searchBox">
            <!-- <li>哈哈镜食品加盟</li>
            <li>哈哈镜加盟</li>
            <li>哈哈 max</li>
            <li>哈哈镜</li> -->
        </ul>
    </div>
    <script charset="utf-8" type="text/javascript" src="js/jquery-1.11.3.min.js"></script>
    <script charset="utf-8" type="text/javascript">
        var searcModule = (function(){
            var $searchInp = $("#searchInp"),
                $searchBox = $("#searchBox"),
                interval = 100;

            // 向百度的服务器发送jaopn请求,把数据获取到之后绑定到容器中
            function bindHTML() {
                var searchKey = $searchInp.val();
                function callback(data){
                    data = data["s"];
                    var str = '';
                    $.each(data, function(index,item){
                        if(index<=3){
                            str+='<li>' + item + '</li>';
                        }
                    });
                    if (str.length === 0) {
                        $searchBox.html(str);
                        return
                    }
                    $searchBox.html(str).stop().slideDown(300);
                }
                $.ajax({
                    url:"https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=" + searchKey,
                    dataType:"jsonp",
                    jsonp:"cb",
                    success:callback
                });
            }

            // 事件绑定和模块的入口
            function init(){


                // // 给展示框中的li绑定方法,当点击li的时候,把当前li中的内容放入到文本框中,并且隐藏展示框
                // $searchBox.on("click", function(e){
                //     var tar = e.target,
                //         target = tar.tagName.toUpperCase(),
                //         $tar = $(tar);//把获取到的原生对象转换成jq对象
                //     if(target === "LI") {
                //         $searchInp.val($tar.html());
                //         $(this).stop().slideUp(interval);
                //     }

                // });
                
                // // 文本框获取焦点或者输入内容的时候判断当前文本框中是否有内容,有内容绑定数据,没有内容隐藏展示框
                // $searchInp.on("focus keyup", function () {
                //     var val = $(this).val();
                //     if (val.length>0) {
                //         bindHTML();
                //         return;
                //     }
                //     $searchBox.stop().slideUp(interval);
                // }).on("blue", function() {
                //     // 当文本框失去焦点的时候我们只需要收起展示框即可
                //     // 加一个定时器延迟其实就是想让盒子的li点击事件先触发,然后再触发文本框失去焦点事件
                //     window.setTimeout(function(){
                //         $searchBox.stop().slideUp(interval);
                //     },50)
                    
                // });


                // 以上是setTimeout处理   以下事件委托处理失去焦点问题
                //---------------------------------------------------------------------------------------
                //---------------------------------------------------------------------------------------
                // 事件委托处理:点击的是li我们让li中的内容显示在文本框中,展示框消失;点击的是文本框什么事情都不做;否则展示框消失
                $(document).on("click", function(e){
                    var tar = e.target,
                        target = tar.tagName.toUpperCase(),
                        $tar = $(tar);//把获取到的原生对象转换成jq对象
                    if(target === "LI" && tar.parentNode.id === "searchBox") {
                        $searchInp.val($tar.html());
                        $searchBox.stop().slideUp(interval);
                        return;
                    }
                    if(tar.id === "searchInp"){
                        return;
                    }
                    $searchBox.stop().slideUp(interval);
                });
                
                // 文本框获取焦点或者输入内容的时候判断当前文本框中是否有内容,有内容绑定数据,没有内容隐藏展示框
                $searchInp.on("focus keyup", function () {
                    var val = $(this).val();
                    if (val.length>0) {
                        bindHTML();
                        return;
                    }
                    $searchBox.stop().slideUp(interval);
                });


            }

            return {init: init};


        })();

        searcModule.init();

    </script>
</body>
</html>