数据库
有时候,我们需要长久的存储数据,不随着外界因素的改变而改变,这样的数据栖息地可以称之为数据库。
version1.0-表面工作
建立一个扣款功能做例子
<!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>
表单自带特性:每一次提交,都会刷新页面。
最好是,付款之后,页面只有数字那部分局部刷新。
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>
存在的问题
过时。虽然不需要再按返回了,但是还是需要刷新页面才能看到真正的余额。
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();
}
优点
- 不用像`<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();
}
优化-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>
优化-2
只有一个问题了(๑•̀ㅂ•́)و✧,使用<script>
发送请求会在页面中实际的添加<script></script>
标签才能生效,所以,点多少次按钮就会添加多少个<script></script>
标签。
修改后的前端代码:
- 在
<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
构造
看下后端代码:
if(path === './'){
//your code
}else if(path === '/pay'){
//your code
response.write(`
${query.callbackName}.call(undefined,'success'); //??注意!!!
`);
response.end();
}
看到??注意!!!的那一行,我们回调时传入的参数是'success'
,是个字符串,字符串前面的内容我们称之为左Padding,后面的内容我们称之为右Padding。当传入的参数是个JSON的时候,我们就称之为JSON+Padding,JSONP。
if(path === './'){
//your code
}else if(path === '/pay'){
//your code
response.write(`
${query.callbackName}.call(undefined,{ //??注意!!!
"success":true;
"left":${newAmount};
});
`);
response.end();
}
引申义,那么像这样前端和后端交互,后端又传入一个参数回调一个函数的技术,叫做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帮我们发送请求时传入符合行业约定的参数:
jQuery帮我们生成随机函数名的回调函数:
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了
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。