论jsonp和用户体验的提高

版权声明:本文为博主原创文章,转载请附上原文地址 https://blog.csdn.net/writing_happy/article/details/80328774

论jsonp和用户体验的提高

  要说jsonp的来源,还是离不开用户体验以及程序员伟大的智慧。事情的开始是这样的……

什么是数据库?

  学过计算机的可能会脱口而出SQL Server, MySQL, Oracle等等,但是我要说的跟这些术语没有关系。数据库,是一个用来存储数据的仓库。没有问题吧?
  如果现在我们有一个需求,操作数据的需求….
  你现在有100块钱,点击页面中的一个按钮你的余额就减去1。如果是纯前端操作的话,那我们可以把“余额”包裹起来,然后获取文本,点击按钮的时候修改文本。
  如果只是这样的话那么我们使用下边的代码就可以纯前端实现了。

<body>
  <p> 我,秦始皇,打钱。你的余额为<span id="amount">100</span> </p> 
  <input type="submit" value="付款一元" id="button">

  <script>
    button.addEventListener('click', function(){
      amount.innerText = amount.innerText -1
    })
  </script>
</body>

  但是你知道的,一旦刷新余额仍然是100。这个数字变化不过是个假象。所以我们需要做的是,点击按钮之后余额减少并且把它写到数据库中。下次进入页面的时候再从数据库中把这个数据取出来。

从数据库中读取数据

  首先需要做的是从数据库中把数据取出来,这样才方便进行下一步操作。(后续代码可能会有点多,所以我会放上GitHub代码链接。)
  读取数据什么的,这就涉及到后端的知识了。我们使用node.js做一个小小的[服务器]
(https://github.com/wenchuyang/Node.js-Server/blob/9a6ac32b6be73e6a1e212d0d74311fd7eae50632/server.js)。同时把我们的html文件稍稍修改一下。
  服务器端要讲起来可能有些费劲,所以只挑主题相关的说。

  var money = +fs.readFileSync('money.db', 'utf8')
  if(path === '/'){
    var string = fs.readFileSync('index.html', 'utf8')
    string = string.replace('&&&amount&&&', money)
    response.write(string)
    response.end()
  }else{
    response.statusCode = 404
    response.setHeader('Content-Type', 'text/html; charset=utf-8')
    response.write('没有对应的网页,滚!')
    response.end()
  }

  我们使用变量money来存储余额,从money.db文件中读取这个“100”,注意这里的100其实是字符串,当然在后边的运算中它会自动转换为数值类型。但是素来有些强迫症的我还是喜欢在一开始就给它转换了。
  后边的是服务器进行的判断,如果你请求的路径是根路径的话,那么会给你返回index.html文件内容。index.html文件中原先的100使用特殊字符串&&&amount&&&代替,然后再在后端将其替换成money.db中的数据100。
  如此我们便可以从数据库中得到这个100了。

修改数据库中的数据

  当然,光是读取肯定是不够的。我们的目的是修改数据。所以第二步,修改数据库中的数据。
server.js
index.html
  在index.html文件中,我们使用一个form表单与服务器建立连接,当path为’/pay’的时候执行修改数据库中数据的操作。所以在server.js文件中,将变量money减一并且写入money.db文件。然后告诉用户操作执行成功。此时数据库中的100已经被修改成了99,当你重新请求根目录的时候你会发现得到的余额成了99。
  到此为止,我们的需求基本上是完成了。现在该是用户体验优化了。首先,我们现在的情况是,点击付款之后会执行页面跳转,我们必须点击返回按钮返回当前页面然后手动刷新当前页面才能看见最终的结果。现在做的第一步是,阻止页面跳转。
  这里可以用到iframe的name属性和form的target属性了。在form表单提交给服务器之后,将服务器返回的结果写在iframe当中,然后再手动刷新当前页面,这样可以避免用户点击返回,稍稍可以提高一点用户体验。

 <form action="/pay" method="POST" target="xxx">
    <p> 我,秦始皇,打钱。你的余额为<span id="amount">&&&amount&&&</span> </p> 
    <input type="submit" value="付款一元" id="button">
  </form>
  <iframe src="about:blank" frameborder="0" name="xxx"></iframe>

  当然,你们知道,如果只是这种程度的话还远远不够程序员出去吹牛逼。所以有了其它的方案……

让我们悄无声息地创建一个请求

  前边用的是form表单创建的一个请求,但是除了form之外,img,a,link,script都可以创建请求。其中a标签必须是在点击的时候才会创建请求,其它的都是在页面加载的时候创建请求。如果说要悄无声息地创建请求的话,这几个好像都可以,比如把那个打款按钮里边写上a标签,或者是在点击按钮的时候动态创建img,link,script等….我们姑且先使用img试一试。
server.js
index.html
  先别忘了把form删掉。然后当然是在index.html里边使用js动态添加img标签,img的src设置为’/pay’。

button.addEventListener('click', function(){
  let img = document.createElement('img')  
  img.src = '/pay'
  img.onload = function(){
    alert('打钱成功,再打一元,一统天下!')
    window.location.reload()
  }
  img.onerror = function(){
    alert('打钱失败,重新打钱')
  }
})

  如果加载成功的话显示成功并且帮用户刷新当前页面。如果失败的话显示失败。
  在服务器端接到路径为’/pay’的请求之后我们设置响应

response.statusCode = 200
response.setHeader('Content-Type', 'image/png; charset=utf-8')
fs.writeFileSync('money.db', --money)
response.write(fs.readFileSync('test.png'))
response.end()

  这里需要注意的是因为我们请求的是图片资源,所以header里边的MIME类型是image/png,或者是image/jpg等,并且由于浏览器的设置我们需要真的传入一个图片,不然的话依然会显示加载失败。其它操作不变。
  现在是不是好多了呢?当然你还可以做一件事,这样平白刷新一下有点浪费资源啊 ~ 所以你可以直接在加载成功之后对innerText进行修改。

img.onload = function(){
  alert('打钱成功,再打一元,一统天下!')
  amount.innerText = amount.innerText -1
}

  这样是不是就结束了呢?你为什么要平白传一张图片呢….很浪费的。所以——用script试一试。

用script创建一个请求

  前边我们用img骗过浏览器创建了一个请求,但是那个方法需要我们传一张图片。所以用script试一试。套路和前边的还是一样。
  把img改成script,在index.html文件当中唯一要注意的是它和img不同,img只要创建这个文件就可以了,但是script需要加一句document.body.appendChild(script)将它放到html文档里去才能生效。
  同时服务器端传入的数据MIME类型需要改成JavaScript对应的application/javascript。此时你可以把那句多余的传入图片的语句删除了。
  另外需要注意的一点是此时我们实际上是利用js给html动态添加了script标签,作为一个强迫症患者我建议你不论加载成功还是失败在最后给它加上一句document.body.removeChild(script),把这个标签移除。不然你按一次就会多出一个script标签…..
  以及,你可以在服务器端使用随机数模拟失败的情况。像这样

if(Math.random() > 0.5){
  response.statusCode = 200
  response.setHeader('Content-Type', 'application/javascript; charset=utf-8')
  fs.writeFileSync('money.db', --money)
}else{
  response.statusCode = 400
}

  所以这是我们得到的index.htmlserver.js
  到现在功能已经实现了,但是代码不够优雅 ~ 嗯….我们用一个函数把需要执行的操作包裹起来。不使用onload而直接在服务器端执行这个函数。
index.html
server.js
  把我们传入的路径’/pay’后边加上参数,变成'/pay?callbackName=xxx'。其中callbackName是需要在后端执行的函数的名字。这个函数将我们在onload里边所做的操作封装了起来。并且在函数执行完之后及时删除这个函数,具体原因后边会提到。

window.xxx = function(e){
  alert('打钱成功,再打一元,一统天下!')
  amount.innerText = amount.innerText -1
  delete window.xxx
}

  服务器端读取到这个函数名字然后执行它。

response.write(`${query.callbackName}.call(undefined, 'success')`)

  好了,这就是jsonp的全过程了。你可能会很奇怪,明明没有提到jsonp啊!让我们回到这句代码:

response.write(`${query.callbackName}.call(undefined, 'success')`)

  我们在调用函数的时候传入的参数是一个字符串“success”,如果这个字符串换成json格式,可以写成这样:

{
  "success": true,
  "money": ${newAmount}
}

  括号到字符串之间的距离,称作padding,括号里边的是json,所以这个技术叫做jsonp。或者说,这一类的技术,统称为jsonp。不论你传入的是string,还是json。

什么是jsonp?

  所以由上边我们可以知道,所谓的jsonp,实际上是利用动态的jacascript创建请求的一种技术。
  这项技术里,有两个主要概念,请求方(浏览器)和响应方(服务器)。
  jsonp的执行流程如下:
1. 请求方利用js创建script标签,并把它的src指向响应方,同时传入参数callbackName。
2. 响应方根据查询参数进行响应,并创建这个函数的执行xxx.call(undefined, 'success')
3. 请求方接收到响应之后执行这段代码xxx.call(undefined, 'success')

  此外有两个约定:
1. 上边使用的callbackName我们一般直接用callback
2. 这个传入的函数是全局变量,函数的名称要避免与原有的全局变量重复,所以可以用随机数得到。let functionName = 'wcy' + parseInt(Math.random()*1000000).toString()
  至此已经结束。最终代码如下:
index.html
server.js>>>>>这里有一处错误,我没有把callbackName改成callback。。。。

jquery帮我们实现的jsonp

index.html代码如下:

button.addEventListener('click', function(){
  $.ajax({
    url: "http://localhost:8080/pay",
    dataType: "jsonp",
    success: function( response ) {
        alert('成功!!')
        amount.innerText = +amount.innerText -1
    }
  }).fail(function(){
    alert('失败,请重新打钱')
  })
})

猜你喜欢

转载自blog.csdn.net/writing_happy/article/details/80328774