数据库

有时候,我们需要长久的存储数据,不随着外界因素的改变而改变,这样的数据栖息地可以称之为数据库。

version1.0-表面工作

建立一个扣款功能做例子

jsonp_扣款

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>pay</title>
</head>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<body>
    <p>您的账户余额: <span id="amount">100</span></p>
    <button id='button'>付款1块</button>
</body>
<script>
    $('#button').on('click',() => {
        let n=document.getElementById('amount').innerText;
        n -= 1;
        document.getElementById('amount').innerText = n;
    })
</script>
</html>

存在的问题

之所以称之为表面,是因为,所有的操作都是表面现象,当页面刷新时,一切都会回到初始状态。而且,别人很容易就能够获取你的信息。最好是,我们去调取数据库里你的信息,然后显示在页面上。

version2.0-将操作在后端进行

以往,我们是在前端的代码中去改用户的余额。现在,不,我们去后端直接改数据库里的余额,然后,把后端数据库的金额显示在前端页面上。而前端用来向后端发起一个请求,请求更改数据库里的余额。

<button>显然不能发起一个请求,所以使用<form>,表单是可以发起一个请求的,而且可以指定请求的方式:get/post

前端代码:

  • 使用<form>发起一个请求
  • 请求路径action='/pay'
  • 请求方法method='post'
<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>pay</title>
</head>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<body>
    <p>您的账户余额: <span id="amount">$$amount$$</span></p>
    <form id='button' action='/pay' method='post'>
        <input type='submit' value='付款1块钱'>
    </form>
</body>
</html>

使用Javascript写的后端代码:

  • 修改数据库金额,返回成功提示response.write('success');
  • 读取数据库金额
  • 显示数据库金额在页面上
//读取数据库金额,并显示在页面上
if(path === './'){
    var index_string = fs.readFileSync('./index.html','utf8');
    var amount = fs.readFileSync('./db','utf8');
    index_string.replace('$$amount$$',amount);
    response.setHeader('Content-type','text/html;charset=utf-8');
    response.write(string);
    response.end();
    
//修改数据库中金额,并返回success
}else if(path === '/pay' && method === 'post'){
    var amount = fs.readFileSync('./db','utf8');
    var newAmount = amount - 1;
    fs.writeFileSync('./db',newAmount);
    response.write('success');
    response.end();
}

效果

点击按钮:
点击付款1块钱,发送如下请求,并且页面中显示success。说明请求成功,数据库中金额修改成功!
form_post请求

返回页面:
jsonp_2

查看数据库:
jsonp_2_数据库

存在问题

有没有发现,我们每一次付款,都需要返回、刷新页面才能看到自己正确的余额。用户体验极差!
并且,<form>表单自带特性:每一次提交,都会刷新页面。
最好是,付款之后,页面只有数字那部分局部刷新

version3.1-<iframe>

<iframe>的功能就是在页面中单开一个空间作为新的页面,我们可以将<form>关联到这个<iframe>上,每次提交都只刷新<iframe>即可。

<body>
    <p>您的账户余额: <span id="amount">$$amount$$</span></p>
    <form id='button' action='/pay' method='post' target='result'>
        <input type='submit' value='付款1块钱'>
    </form>

    <iframe name='result' src='about:blank' frameborder='0' height=200></iframe>
</body>

jsonp_iframe

存在的问题

过时。虽然不需要再按返回了,但是还是需要刷新页面才能看到真正的余额。

version3.2-<img>发送请求

既然,我们不希望页面刷新,那我们就得换一个方式发送请求。铛铛铛铛!这就是<img>!不过<img>只能发送get请求,不过,方法先进,用户体验良好,get也可以接受。

前端代码:

  • 注意!功能是在js里实现的
  • 当点击<button>,创建一个<img>,路径指向后台文件/pay
  • 如果图片加载成功image.onload=function(){},提示成功,并将金额amount.innerText -= 1
  • 如果图片加载失败image.onerror=function(){},提示失败
<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>pay</title>
</head>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<body>
    <p>您的账户余额: <span id="amount">$$amount$$</span></p>
    <button id='button'>付款1块钱</button>>
</body>
<script>
    $('#button').on('click',function(){
        var amount=document.getElementById('amount');
        var image=document.createElement('img');
        image.src='./pay';
        image.onload=function(){
            alert('success');
            amount.innerText -= 1;
        }
        image.onerror=function(){
            alert('fail');
        }
    })
</script>

使用Javascript写的后端代码:

  • 与之前区别在于,这次不需要要用到刷新页面,当然刷新页面功能也没问题
  • 与之前区别在于,这次返回的是一张图片response.write(fs.readFileSync(你的图片路径));
//和之前一样,读取数据库中金额,并显示在页面上,但这里并不一定会用到这些代码,因为我们不需要刷新页面了
if(path === './'){
    var index_string = fs.readFileSync('./index.html','utf8');
    var amount = fs.readFileSync('./db','utf8');
    index_string.replace('$$amount$$',amount);
    response.setHeader('Content-type','text/html;charset=utf-8');
    response.write(string);
    response.end();
}

//修改数据库中金额,成功后返回一张图片
else if(path === '/pay'){
    var amount = fs.readFileSync('./db','utf8');
    var newAmount = amount - 1;
    fs.writeFileSync('./db',newAmount);
    response.setHeader('Content-type','image/jpg');
    response.write(fs.readFileSync(你的图片路径));
    response.end();
}

jsonp_img

优点

  • 不用像`<iframe>一样新开辟一块地盘
  • 之前的思路:修改后端数据库 → 获取数据库金额显示在页面上(需要刷新页面获得显示)
    现在的思路:修改后端数据库 → 修改成功后,直接修改页面显示(不刷新);获得数据库金额(刷新)
  • 成功、失败的提示依赖alert(),不像之前需要新的页面
  • 机智的地方在:虽然建立了<img>标签,也成功获取了一张图片,但是图片并没有显示出来,因为,我们只是在js里创建了<img>标签,并没有把它加到页面里,没有append()到哪里去。所以,我们利用了<img>能够发送请求的特点,而没有真正的显示出图片。

问题

哇!这样已经很好了,可以返回一个大小很小的图片呀,或者什么乱起八糟的图片。但是,图片毕竟是图片,返回一张图片会影响响应速度。

version3.3-<script>发送请求

我们发现,<script>居然也能发送请求,那更好了,<script>请求返回的是空字符串,那可比图片小多了。

前端代码:

  • js创建<script>,指定<script>路径为./pay
  • <img>发送请求不一样在于:<script>需要加载到<body>才能生效
  • <script>加载成功:script.onload=function(){}
  • <script>加载失败:script.onerror=function(){}
<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>pay</title>
</head>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<body>
    <p>您的账户余额: <span id="amount">$$amount$$</span></p>
    <button id='button'>付款1块钱</button>>
</body>
<script>
    $('#button').on('click',function(){
        var amount=document.getElementById('amount');
        var image=document.createElement('script');
        script.src='./pay';
        document.body.appendChild(script);
        script.onload=function(){
            alert('success');
        }
        script.onerror=function(){
            alert('fail');
        }
    })
</script>

使用Javascript写的后端代码:

  • 与之前区别在于,这次不需要要用到刷新页面,当然刷新页面功能也没问题
  • 与之前区别在于,这次返回的是空字符串response.write('');
//和<img>发送请求时的没有区别
if(path === './'){
    var index_string = fs.readFileSync('./index.html','utf8');
    var amount = fs.readFileSync('./db','utf8');
    index_string.replace('$$amount$$',amount);
    response.setHeader('Content-type','text/html;charset=utf-8');
    response.write(string);
    response.end();
    
//修改数据库中金额,成功后返回空字符串
}else if(path === '/pay'){
    var amount = fs.readFileSync('./db','utf8');
    var newAmount = amount - 1;
    fs.writeFileSync('./db',newAmount);
    response.setHeader('Content-type','application/javascript');
    response.write('');
    response.end();
}

jsonp_script

优化-1

在使用<script>发送请求的本例中,我们没有在前端代码中加入,当script.onload=function(){}时,页面中数字减一amount.innerText -= 1;。这是因为,经过测试,

在前端代码中加入:

script.onload=function(){
    //之前的代码
    console.log('我是前端');
}

在后端代码中加入:

else if(path === '/pay'){
    //之前的代码
    response.write('console.log("我是后端")');
    //之后的代码
}

结果发现,首先出现后端的console.log("我是后端"),再出现前端的console.log('我是前端')

之前,我们将amount.innerText -= 1;放在前端代码里是因为,之前使用<img>,返回只能是一张图片;现在我们使用<script>,返回值会被当做Javascript执行。

所以,我们直接在后端代码输入我们想要执行的前端代码就行啦???

改进后的后端代码:

  • 只修改了response.write()部分⭐⭐⭐
<Script>
    if(path === './'){
        var index_string = fs.readFileSync('./index.html','utf8');
        var amount = fs.readFileSync('./db','utf8');
        index_string.replace('$$amount$$',amount);
        response.setHeader('Content-type','text/html;charset=utf-8');
        response.write(string);
        response.end();
    }else if(path === '/pay'){
        var amount = fs.readFileSync('./db','utf8');
        var newAmount = amount - 1;
        fs.writeFileSync('./db',newAmount);
        response.setHeader('Content-type','application/javascript');
        response.write(`
            // console.log("我是后端");
            amount.innerText -= 1;
            // alert("success");
        `);
        response.end();
    }
</Script>

jsonp_script_2

优化-2

只有一个问题了(๑•̀ㅂ•́)و✧,使用<script>发送请求会在页面中实际的添加<script></script>标签才能生效,所以,点多少次按钮就会添加多少个<script></script>标签。
jsonp_script_3

修改后的前端代码:

  • <script>加载成功后,删除这个标签e.currentTarget.remove();
  • 如果<script>加载失败,删除这个标签e.currentTarget.remove();
<script>
    $('#button').on('click',function(){
        var amount=document.getElementById('amount');
        var image=document.createElement('script');
        script.src='./pay';
        document.body.appendChild(script);
        script.onload=function(e){
            // alert('success');
            e.currentTarget.remove();
        }
        script.onerror=function(){
            // alert('fail');
            e.currentTarget.remove();
        }
    })
</script>

不错???

为什么要在前端代码里优化这个问题呢??因为先执行的是后端代码,最后执行的是前端代码,功能是先在后端实现的,所以,在前端里删除这个标签也不会对功能产生什么影响。

小结

前端代码:

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>pay</title>
</head>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<body>
    <p>您的账户余额: <span id="amount">$$amount$$</span></p>
    <button id='button'>付款1块钱</button>>
</body>
<script>
    $('#button').on('click',function(){
        var amount=document.getElementById('amount');
        var image=document.createElement('script');
        script.src='./pay';
        document.body.appendChild(script);
        script.onload=function(e){
            // alert('success');
            e.currentTarget.remove();
        }
        script.onerror=function(){
            // alert('fail');
            e.currentTarget.remove();
        }
    })
</script>

使用Javascript写的后端代码:

if(path === './'){
    var index_string = fs.readFileSync('./index.html','utf8');
    var amount = fs.readFileSync('./db','utf8');
    index_string.replace('$$amount$$',amount);
    response.setHeader('Content-type','text/html;charset=utf-8');
    response.write(string);
    response.end();
}else if(path === '/pay'){
    var amount = fs.readFileSync('./db','utf8');
    var newAmount = amount - 1;
    fs.writeFileSync('./db',newAmount);
    response.setHeader('Content-type','application/javascript');
    response.write(`
        // console.log("我是后端");
        amount.innerText -= 1;
        // alert("success");
    `);
    response.end();
}

该方法称之为SRJ:Server Rendered Javascript,即后端返回Javascript

前后端职能分离

在功能上,上述代码非常好的实现了前端与后端交互,调用数据库数据显示在页面上。但是,在职能上,前后端还是有耦合的现象,在这里,后端里写进了前端代码amount.innerText -= 1;。所以,此时,我们需要解耦

解耦的思路很简单,将服务器上的前端代码都放到前端的一个函数里,然后,后端只需要调用这个函数就OK了。

使用Javascript写的后端代码:
将下面这段:

if(path === './'){
    //your code
}else if(path === '/pay'){
    //your code
    response.write(`
        // console.log("我是后端");
        amount.innerText -= 1;
        // alert("success");
    `);
    response.end();
}

修改为:

if(path === './'){
    //your code
}else if(path === '/pay'){
    //your code
    response.write(`
        ${query.callbackName}.call(undefined,'success');      //??新添加
    `);
    response.end();
}

前端代码:
<script>添加函数,并且,在<script>里添加请求参数?callbackName=xxx

<!DOCTYPE html>
<!--your code-->
<script>
    window.yyy=function(status){                        //??新添加
        if(status === 'success'){
            amount.innerText -= 1;
        }
        else if(){}
    }

    $('#button').on('click',function(){
        var amount=document.getElementById('amount');
        var image=document.createElement('script');
        script.src='./pay?callbackName=yyy';            //??新添加
        document.body.appendChild(script);
        script.onload=function(e){
            // alert('success');
            e.currentTarget.remove();
        }
        script.onerror=function(){
            // alert('fail');
            e.currentTarget.remove();
        }
    })
</script>

解释:
现在,前端里新添加的<script>的地址里传入了参数?callbackName=yyy,并且添加了回调函数window.yyy=function(){}。后端里,使用${query.callbackName}来查找地址里?callbackName的所指,然后使用call()来调用所指的yyy函数。

效果:
jsonp前后端分离

JSONP

构造

看下后端代码:

if(path === './'){
    //your code
}else if(path === '/pay'){
    //your code
    response.write(`
        ${query.callbackName}.call(undefined,'success');      //??注意!!!
    `);
    response.end();
}

看到??注意!!!的那一行,我们回调时传入的参数是'success',是个字符串,字符串前面的内容我们称之为左Padding,后面的内容我们称之为右Padding。当传入的参数是个JSON的时候,我们就称之为JSON+PaddingJSONP

if(path === './'){
    //your code
}else if(path === '/pay'){
    //your code
    response.write(`
        ${query.callbackName}.call(undefined,{     //??注意!!!
            "success":true;
            "left":${newAmount};
        });      
    `);
    response.end();
}

引申义,那么像这样前端和后端交互,后端又传入一个参数回调一个函数的技术,叫做JSONP。

逻辑和约定

JSONP流程和约定

根据约定的修改代码

前端代码:

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>pay</title>
</head>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<body>
    <p>您的账户余额: <span id="amount">$$amount$$</span></p>
    <button id='button'>付款1块钱</button>>
</body>
<script>
    $('#button').on('click',function(){
        var amount=document.getElementById('amount');
        var image=document.createElement('script');
        
        var functionName="philip"+parseInt(Math.random()*100000,10);    ??//随机函数名
        script.src='./pay?callback='+functionName;                 ??//传入随机函数名
        window[functionName]=function(status){                ??//随机函数名的函数
            if(status === 'success'){
                amount.innerText -= 1;
            }else if(){}
        }
        
        document.body.appendChild(script);
        script.onload=function(e){
            // alert('success');
            e.currentTarget.remove();
            delete window[functionName];
        }
        script.onerror=function(){
            // alert('fail');
            e.currentTarget.remove();
            delete window[functionName];
        }
    })
</script>

后端代码:

if(path === './'){
    var index_string = fs.readFileSync('./index.html','utf8');
    var amount = fs.readFileSync('./db','utf8');
    index_string.replace('$$amount$$',amount);
    response.setHeader('Content-type','text/html;charset=utf-8');
    response.write(string);
    response.end();
}else if(path === '/pay'){
    var amount = fs.readFileSync('./db','utf8');
    var newAmount = amount - 1;
    fs.writeFileSync('./db',newAmount);
    response.setHeader('Content-type','application/javascript');
    response.write(`
        ${query.callback}.call(undefined,'success');         ??//修改为${query.callback}
    `);
    response.end();
}

使用jQuery

$.ajax({
    url: "",                          ??//要请求的网址
    dataType: "jsonp",                ??//我要使用jsonp
    success: function(status){        ??//响应成功后,我要执行的函数
        if(status === 'success'){
            amount.innerText -= 1;
        }
    }
})

jQuery帮我们发送请求时传入符合行业约定的参数:
jsonp_jquery_1
jQuery帮我们生成随机函数名的回调函数:
jsonp_jquery_2

Q&A

Q:为什么JSONP不能使用POST请求?
A:1、JSONP是通过动态创建script实现的;2、动态创建script不能发送get请求

总结

1、使用<form>会刷新页面
2、改用<img>发送请求,但只能知道成功与否,没有更多信息
3、改用<script>
4、如何得到更多内容,添加?callback参数
5、后端传入一个参数,调用下callback函数
6、OK,得到内容,最终的方法3、4、5,就是JSONP


BreezingSummer
45 声望0 粉丝

« 上一篇
冒泡事件
下一篇 »
AJAX