一个JavaScript贷款计算器

一个JavaScript贷款计算器

一向不喜读书的我最近打算重修JavaScript基础,不得已只能拿起死厚死厚的JavaScript权威指南第六版,走向一条不归路~中途总得留下点印记吧,从今天开始为了让自己坚持不懈的读下这本大师之作,打算每读完一节儿写点总结,唠点收获。OK,走起!

代码展示了许多JS语言和新特性,同样展示了重要的客户端JS技术:

  • 如何在文档中查找元素,如
    var amount=document.getElementById("amount");
    

  • 如何通过表单input元素来获取用户的输入数据,如
    document.getElementById("amount").value
    

  • 如何通过文档元素来设置HTML内容,如
    document.getElementById("amount").value=localStorage.loan_amount;
    

  • 如何将数据存储在浏览器中
    localStorage.loan_amount=amount.value
    

  • 如何使用脚本发起HTTP请求,如
    var req=new XMLHttpRequest();
    

  • 如何利用<canvas>元素绘图
  • g.moveTo(paymentToX(0),amountToY(0));
    g.lineTo(paymentToX(payments),amountToY(monthly*payments));
    

    粘贴一下我的成果


<!DOCTYPE html>
		<meta charset="UTF-8">
		<title>JavaScript贷款计算器</title>
		<style>/*这是一个CSS样式表:定义了程序输出的样式*/
			.output{font-weight: bold;}	/*计算结果定义为粗体*/
			#payment{text-decoration: underline;}/*定义id="payment"的元素样式*/
			#graph{border: solid black 1px;}/*图标有一像素的边框*/
			th,td{vertical-align: top;}/*表格单元格对齐方式为顶端对齐*/
		</style>
	</head>
        	作者:2538300535@qq.com
        	时间:2017-03-09
        	描述:这是一个HTML表格,其中包含<input>元素允许用户输入数据,程序将在<span>元素中显示其计算结果。这些元素有其id名,例如“利益”、“年”,在接下来的JavaScript代码中使用这些id。注意的一些输入元素定义“onchange”或“onclick事件处理程序。当用户输入数据或点击,这些指定的字符串执行的JavaScript代码。
        <table>
        		<th>输入贷款数据:</th>
		      	<td></td>
		      	<th>贷款余额、累计股票和利息支付</th>
				<td>贷款的金额 ($):</td>
					<input id="amount" onchange="calculate();">
				<td rowspan=8>
					<canvas id="graph" width="400" height="250"></canvas>
				<td>年息 (%):</td>
					<input id="apr" onchange="calculate()">
				<td>还款期(年):</td>
					<input id="years" onchange="calculate();">
				<td>邮编 (找到银行):</td>
					<input id="zipcode" onchange="calculate();">
				<th>近似的支付:</th>
					<button onclick="calculate();">计算</button>
				<td>月付款数:</td>
				<td>$<span class="output" id="payment"></span></td>
				<td>付款总额:</td>
				<td>$<span class="output" id="total"></span></td>
				<td>总利息:</td>
				<td>$<span class="output" id="totalinterest"></span></td>
				<th>赞助商:</th>
				<td colspan=2>
					申请贷款,这些优良的银行之一:
					<div id="lenders"></div>
        </table>
        	作者:2538300535@qq.com
        	时间:2017-03-09
        	描述:1.随后是JavaScript代码,这些代码内嵌在一个<script>标签里
        		2.通常情况下,这些脚本代码应当放在<head>标签中
        		3.将JavaScript代码放在HTML代码之后仅仅是为了便于理解
        <script>
        	"user strict";	//如果浏览器支持的话,则开启ECMAScript5的严格模式
        	 * 这里的脚本定义了calculate()函数,在HTML代码中绑定事件处理程序时会调用它
        	 * 这个函数从<input>元素中读取数据,计算贷款赔付信息,并将结果显示在<span>元素中
        	 * 同样,这里还保存了用户数据,展示了放贷人链接并绘制出了图标
        	function calculate(){
        		//查找文档中用于输入输出的元素
        		var amount=document.getElementById("amount");
        		var apr=document.getElementById("apr");
        		var year=document.getElementById("years");
        		var zipcode=document.getElementById("zipcode");
        		var payment=document.getElementById("payment");
        		var total=document.getElementById("total");
        		var totalinterest=document.getElementById("totalinterest");
        		//假设所有的输入都是合法的,将从input元素中获取输入数据
        		//将百分比格式转换为小数格式,并从年利率转换为月利率
        		//将年赔付转换为月赔付
        		var principal=parseFloat(amount.value);
        		var interest=parseFloat(apr.value) / 100 / 12;
        		var payments=parseFloat(year.value)*12;
				//现在计算月度赔付的数据
				var x=Math.pow(1+interest,payments);//Math.pow()进行幂次运算
				var monthly=(principal*x*interest)/(x-1)
				//如果结果没有通过JavaScipt能表示的数字范围,且用户的输入也正确
				//这里所展示的结果就是合法的
				if(isFinite(monthly)){
					//将数据填充至输出字段的位置,四舍五入到小数点后两位数字
					payment.innerHTML=monthly.toFixed(2);
					total.innerHTML=(monthly*payments).toFixed(2);
					totalinterest.innerHTML=((monthly*payments)-principal).toFixed(2);
					//将用户的输入数据保存下来,这样在下次访问时也能取到数据
					save(amount.value,apr.value,years.value,zipcode.value);
					//找到并展示本地放贷人,但忽略网络错误
					try{//捕获这段代码抛出的所有异常
						getLenders(amount.value,apr.value,years.value,zipcode.value);
					catch(e){/*忽略这些异常*/}
					//最后用图表展示贷款余额,利息和资产收益
					chart(principal,interest,monthly,payments);
				}else{
					//计算结果不是数字或者是无穷大,意味着输入数据是非法或者不完整的
					//清空之前的输出数据
					payment.innerHTML="";	//清空元素的文本内容
					total.innerHTML="";
					totalinterest.innerHTML="";
					chart();	//不传参数的话就是清除图表
        	//将用户的输入保存至localStorage对象的属性中
        	//这些属性在再次访问时还会继续保持在原位置
        	//如果你在浏览器中按照file://url的方式直接打开本地文件,
        	//则无法在某些浏览器中使用储存功能(比如FireFox)
        	//而通过HTTP打开文件是可行的
        	function save(amount,apr,years,zipcode){
        		if(window.localStorage){	//只有在浏览器支持的时候才运行这里的代码
        			localStorage.loan_amount=amount;
        			localStorage.loan_apr=apr;
        			localStorage.loan_years=years;
        			localStorage.loan_zipcode=zipcode;
        	//在文档首次加载时将会尝试还原输入字段
        	window.onload=function(){
        		//如果浏览器支持本地存储并且上次保存的值是存在的
        		if(window.localStorage&&localStorage.loan_amount){
        			document.getElementById("amount").value=localStorage.loan_amount;
        			document.getElementById("apr").value=localStorage.loan_apr;
        			document.getElementById("years").value=localStorage.loan_years;
        			document.getElementById('zipcode').value=localStorage.loan_zipcode;
        	//将用户的输入发送至服务器端脚本(理论上)
        	//返回一个本地放贷人的链接列表,在这个例子中并没有实现这种查找放贷人的服务
        	//但是如果该服务存在,该函数会使用它
        	function getLenders(amount,apr,years,zipcode){
        		//如果浏览器不支持XMLHttpRequest对象,则退出
        		if(!window.XMLHttpRequest) return;
        		//找到要显示放贷人列表的元素
        		var ad=document.getElementById("lenders");
        		if(!ad) return		//如果返回为空,则退出
        		//将用户的输入数据进行url编码,并作为查询参数附加在URL里
        		var req=new XMLHttpRequest();	//发起一个新请求
        		req.open("GET",url);			//通过url发起一个HTTP GET请求
        		req.send(null);					//不带任何正文发送这个请求
        		//在返回数据之前,注册了一个事件处理函数,这个函数
        		//将会在服务器的响应返回至客户端的时候调用
        		//这种异步编程模型在客户端JavaScript中是非常常见的
        		req.onreadystatechange=function(){
        			if(req.readyState==4&&req.status==200){
        				//如果代码运行到这里,说明我们得到了一个合法且完整的HTTP响应
        				var response=req.responseText;	//HTTP响应是以字符串的形式呈现的
        				var lenders=JSON.parse(response);	//将其解析为JS数组
        				//将数组中的放贷人对象转换为HTML字符串形式
        				var list="";
        				for(var i=0;i<lenders.length;i++){
        					list+="<li><a href='"+lenders[i].url+"'"+lenders[i].name+"</a>"
        				//将数据在html元素中呈现出来
        				ad.innerHTML="<ul>"+list+"</ul>"
        	//在HTML<canvas>元素中用图标展示月度贷款余额,利息和资产收益
        	//如果不传入参数的话,则清空之前的图标数据
        	function chart(principal,interest,monthly,payments){
        		var graph=document.getElementById("graph");//得到<canvas>标签
        		graph.width=graph.width;	//用一种巧妙的手法消除并重置画布
        		//如果不传入参数,或者浏览器不支持画布,则直接返回
        		if(arguments.length==0 || !graph.getContext) return;
        		//获得画布元素的context对象,这个对象定义了一组回话API
        		var g=graph.getContext("2d");	//所有的回话操作都将基于这个对象
        		var width=graph.width,
        		height=graph.height;	//获得画布的大小
        		//这里的函数作用是将付款数字和美元数据转换为像素
        		function paymentToX(n){
        			return n*width/payments;
        		function amountToY(a){
        			return height-(a*height/(monthly*payments*1.05)) ;       		}
        		//付款数据是一条从(0,0)到(payments,monthly*payments)的直线
        		g.moveTo(paymentToX(0),amountToY(0));	//从左下方开始
        		g.lineTo(paymentToX(payments),	//绘至右上方
        			amountToY(monthly*payments));
        		g.lineTo(paymentToX(payments),amountToY(0))	//再至右下方
        		g.closePath();	//将结尾连接至开头
        		g.fillStyle="#f88";	//亮红色
        		g.fill();    //填充矩形
        		g.font="bold 12px san-serif";	//定义一种字体
        		g.fillText("Total Interest Payments",20,20)   //将文字绘制到图例中
        		//很多资产数据并不是线性的,很难将其反映至图表中
        		var equity=0;
        		g.beginPath();	//开始绘制新图形
        		g.moveTo(paymentToX(0),amountToY(0));	//从左下方开始
        		for(var p=1;p<=payments;p++){
        			//计算出每一笔赔付的利息
        			var thisMonthsInterest=(principal-equity)*interest;
        			equity+=(monthly-thisMonthsInterest);//得到资产
        			g.lineTo(paymentToX(p),amountToY(equity));//将数据绘制到画布上
        		g.lineTo(paymentToX(payments),amountToY(0));	//将数据线绘制至X轴
        		g.closePath();//将线条结尾连接至线条开头
        		g.fillStyle="green";//使用绿色绘制图形
        		g.fill();//曲线之下的部分均填充
        		g.fillText("Total Equity",20,35);//文本颜色设置为绿色
        		//再次循环,余额数据显示为黑色粗线条
        		var bal=principal;
        		g.beginPath();
        		g.moveTo(paymentToX(0),amountToY(bal));
        		for(var p=1;p<=payments;p++){
        			var thisMonthsInterest=bal*interest;
        			bal-=(monthly-thisMonthsInterest);//得到资产总额
        			g.lineTo(paymentToX(p),amountToY(bal));//将直线连接至某点
        		g.lineWidth=3;//将直线宽度加粗
        		g.stroke();//绘制余额的曲线
        		g.fillStyle="black";//使用黑色字体
        		g.fillText("Loan Balance", 20, 50)//图例文字
        		//将年度数据在X轴做标记
        		g.textAlign="center";	//文字居中对齐
        		var y=amountToY(0);   //Y坐标设为0
        		for(var year=1;year*12<=payments;year++){//遍历每年
        			var x=paymentToX(year*12);//计算标记位置
        			g.fillRect(x-0.5,y-3,1,3);//开始绘制标记
        			if(year==1)g.fillText("Year",x,y-5);//在坐标轴做标记
        			if(year%5==0&&year*12!==payments)//每5年的数据
        				g.fillText(String(year),x,y-5);
        		//将赔付数额标记在右边界
        		g.textAlign="right";	//文字右对齐
        		g.textBaseline="middle";	//文字垂直居中
        		var ticks=[monthly*payments,principal];	//我们将要用到的两个点
        		var rightEdge=paymentToX(payments);//设置X坐标
        		for(var i=0;i<ticks.length;i++){//对每两个点做循环
        			var y=amountToY(ticks[i]);//计算每个标记的Y坐标
        			g.fillRect(rightEdge-3,y-0.5,3,1);//绘制标记
        			g.fillText(String(ticks[i].toFixed(0)),rightEdge-5,y)//绘制文本