一.JavaScript概述

147 阅读14分钟

1.JavaScript历史

在上个世纪的1995年,当时的网景公司正凭借其Navigator浏览器成为Web时代开启时最著名的第一代互联网公司。

由于网景公司希望能在静态HTML页面上添加一些动态效果,于是叫Brendan Eich这哥们在两周之内设计出了JavaScript语言。你没看错,这哥们只用了10天时间。

为什么起名叫JavaScript?原因是当时Java语言非常红火,所以网景公司希望借Java的名气来推广,但事实上JavaScript除了语法上有点像Java,其他部分基本上没啥关系。

2. ECMAScript

为了让JavaScript成为全球标准,几个公司联合ECMA(European Computer Manufacturers Association)组织定制了JavaScript语言的标准,被称为ECMAScript标准。

不过大多数时候,我们还是用JavaScript这个词。如果你遇到ECMAScript这个词,简单把它替换为JavaScript就行了。

3. JavaScript 版本

此外,由于JavaScript的标准——ECMAScript在不断发展,最新版ECMAScript 6标准(简称ES6)已经在2015年6月正式发布了,所以,讲到JavaScript的版本,实际上就是说它实现了ECMAScript标准的哪个版本。

4. JavaScript简介

JavaScript是世界上最流行的脚本语言,因为你在电脑、手机、平板上浏览的所有的网页,以及无数基于HTML5的手机App,交互逻辑都是由JavaScript驱动的。

JavaScript是面向Web的编程语言。绝大多数现代网站都使用了JavaScript,并且所有的现代Web浏览器——基于桌面系统、游戏机、平板电脑和智能手机的浏览器——均包含了JavaScript解释器。这使得JavaScript能够称得上史上使用最广泛的编程语言。

简单地说,JavaScript是一种运行在浏览器中的解释型的编程语言。

为什么我们要学JavaScript?尤其是当你已经掌握了某些其他编程语言如Java、C++的情况下。

简单粗暴的回答就是:因为你没有选择。在Web世界里,只有JavaScript能跨平台、跨浏览器驱动网页,与用户交互。

随着HTML5在PC和移动端越来越流行,JavaScript变得更加重要了。并且,新兴的Node.js把JavaScript引入到了服务器端,JavaScript已经变成了全能型选手。

5.JavaScript解释器

JavaScript解释器或者“引擎”(engine)也有版本号,比如,Google将它的JavaScript解释器叫做V8引擎

6.JavaScript API

JavaScript语言核心针对文本、数组、日期和正则表达式的操作定义了很少的API,但是这些API不包括输入输出功能。输入和输出功能(类似网络、存储和图形相关的复杂特性)是由JavaScript所属的“宿主环境”(host enviroment)提供的。这里所说的宿主环境通常是Web浏览器

基于浏览器的API——这部分也称做“客户端JavaScript”。

7.JavaScript语言核心

//所有在双斜线之后的内容都属于注释
//仔细阅读这里的注释:它们对JavaScript代码做了解释
//变量是表示值的一个符号名字
//变量是通过var关键字声明的

var x; //声明一个变量x

//值可以通过等号赋值给变量

x=0; //现在变量x的值为0

x //=>0:通过变量获取其值

//JavaScript支持多种数据类型

x=1;//数字

x=0.01; //整数和实数共用一种数据类型

x="hello world"; //由双引号内的文本构成的字符串

x='JavaScript'; //单引号内的文本同样构成字符串

x=true; //布尔值

x=false; //另一个布尔值

x=null; //null是一个特殊的值,意思是"空"

x=undefined; //undefined和null非常类似

//JavaScript中的最重要的类型就是对象

//对象是名/值对的集合,或字符串到值映射的集合

var book={ //对象是由花括号括起来的

    topic:"JavaScript", //属性"topic"的值是"JavaScript"

    fat:true //属性"fat"的值是true

}; //右花括号标记了对象的结束

//通过"."或"[]"来访问对象属性

book.topic //=>"JavaScript"

book["fat"]//=>true:另外一种获取属性的方式

book.author="Flanagan"; //通过赋值创建一个新属性

book.contents={}; //{}是一个空对象,它没有属性

//JavaScript同样支持数组(以数字为索引的列表)

var primes=[2,3,5,7]; //拥有4个值的数组,由"["和"]"划定边界

primes[0]  //=>2:数组中的第一个元素(索引为0)

primes.length  //=>4:数组中的元素个数

primes[primes.length-1]  //=>7:数组的最后一个元素

primes[4]=9;  //通过赋值来添加新元素

primes[4]=11;  //或通过赋值来改变已有的元素

var empty=[];  //[]是空数组,它具有0个元素

empty.length  //=>0

//数组和对象中都可以包含另一个数组或对象:

var points=[  //具有两个元素的数组

    {x:0,y:0},  //每个元素都是一个对象
    {x:1,y:1}

];

var data={  //一个包含两个属性的对象

    trial1:[[1,2],[3,4]],  //每一个属性都是数组

    trial2:[[2,3],[4,5]]  //数组的元素也是数组

};

  • 上段代码中通过方括号定义数组元素和通过花括号定义对象属性名和属性值之间的映射关系的语法称为初始化表达式(initializer expression)
  • 表达式是JavaScript中的一个短语,这个短语可以通过运算得出一个值。通过“.”和“[]”来引用对象属性或数组元素的值就构成一个表达式。比如,请看一下上述代码中独占一行的表达式,其后的注释中箭头(=>)后的值就是表达式的运算结果。 JavaScript中最常见的表达式写法是像下面代码这样使用运算符(operator):
//运算符作用于操作数,生成一个新的值

//最常见的是算术运算符

3+2  //=>5:加法

3-2  //=>1:减法

3*2  //=>6:乘法

3/2  //=>1.5:除法
points[1].x-points[0].x  //=>1:更复杂的操作数也能照常工作

"3"+"2"  //=>"32":+可以完成加法运算也可以作字符串连接

//JavaScript定义了一些算术运算符的简写形式

var count=0;  //定义一个变量

count++;  //自增1

count--;  //自减1

count+=2;  //自增2:和"count=count+2;"写法一样

count*=3;  //自乘3:和"count=count*3;"写法一样

count  //=>6:变量名本身也是一个表达式

//相等关系运算符用来判断两值是否相等

//不等、大于、小于运算符的运算结果是true或false

var x=2,y=3;  //这里的=等号是赋值的意思,不是比较相等

x==y  //=>false:相等

x!=y  //=>true:不等

x<y  //=>true:小于

x<=y  //=>true:小于等于

x>y  //=>false:大于等于

x>=y//=>false:大于等于

"two"=="three"  //=>false:两个字符串不相等

"two""three"  //=>true:"tw"在字母表中的索引大于"th"

false==(x>y)   //=>true:false和false相等

//逻辑运算符是对布尔值的合并或求反
(x==2)&&(y==3)  //=>true:两个比较都是true,&&表示"与"

(x>3)||(y<3)   //=>false:两个比较不都是true,||表示"或"

!(x==y)     //=>true:!求反

如果JavaScript中的“短语”是表达式的话,那么整个句子就称做语句(statement)

语句和表达式之间有很多共同之处,粗略地讲,表达式仅仅计算出一个值但并不作任何操作,它并不改变程序的运行状态。而语句并不包含一个值(或者说它包含的值我们并不关心),但它们改变程序的运行状态。

函数是带有名称(named)和参数的JavaScript代码段,可以一次定义多次调用。

//函数是一段带有参数的JavaScript代码端,可以多次调用

function plus1(x){  //定义了名为plus1的一个函数,带有参数x

   return x+1;//返回一个比传入的参数大的值

}
//函数的代码块是由花括号包裹起来的部分

plus1(y)  //=>4:y为3,调用函数的结果为3+1

var square=function(x){  //函数是一种值,可以赋值给变量

    return x*x;  //计算函数的值

};  //分号标识了赋值语句的结束

square(plus1(y))  //=>16:在一个表达式中调用两个函数

当将函数和对象合写在一起时,函数就变成了“方法”(method):

//当函数赋值给对象的属性,我们称为"方法",所有的JavaScript对象都含有方法

var a=[];  //创建一个空数组

a.push(1,2,3);  //push()方法向数组中添加元素

a.reverse();  //另一个方法:将数组元素的次序反转

//我们也可以定义自己的方法,"this"关键字是对定义方法的对象的引用:这里的例子是上文中提到的包含两个点位置信息的数组

points.dist=function(){  //定义一个方法用来计算两点之间的距离

    var p1=this[0];  //通过this获得对当前数组的引用

    var p2=this[1];  //并取得调用的数组前两个元素

    var a=p2.x-p1.x;  //X坐标轴上的距离

    var b=p2.y-p1.y;  //Y坐标轴上的距离
    return Math.sqrt(a*a+b*b); //用Math.sqrt()来计算平方根//勾股定理

};

points.dist() //=>1.414:求得两个点之间的距离

现在,给出一些控制语句的例子,这里的示例函数体内包含了最常见的JavaScript控制语句:

//这些JavaScript语句使用该语法包含条件判断和循环

//使用了类似C、C++、Java和其他语言的语法

function abs(x){  //求绝对值的函数
    if(x>=0){ //if语句...
        return x;//如果比较结果为true则执行这里的代码.
    }
    //子句的结束.
    else{  //当if条件不满足时执行else子句
        return-x;
    }
    //如果分支中只有一条语句,花括号是可以省略的

}

//注意if/else中嵌套的return语句

function factorial(n){  //计算阶乘的函数
    var product=1;  //给product赋值为1
    while(n>1){  //当()内的表达式为true时循环执行{}内的代码
        product*=n;  //"product=product*n;"的简写形式
        n--;  //"n=n-1;"的简写形式
    }
//循环结束
    return product;//返回product

}

factorial(4)  //=>24:1*4*3*2

function factorial2(n){  //实现循环的另一种写法
    var i,product=1;  //给product赋值为1
    for(i=2;i<=n;i++)  //将i从2自增至n
        product*=i;  //循环体,当循环体中只有一句代码,可以省略{}
    return product; //返回计算好的阶乘

}

factorial2(5) //=>120:1*2*3*4*5

JavaScript是一种面向对象的编程语言,但和传统的面向对象又有很大区别。 这里有一个简单的示例,这段代码展示了如何在JavaScript中定义一个类来表示2D平面几何中的点。这个类实例化的对象拥有一个名为r()的方法,用来计算该点到原点的距离:

//定义一个构造函数以初始化一个新的Point对象

function Point(x,y){ //按照惯例,构造函数均以大写字母开始
    this.x=x;  //关键字this指代初始化的实例
    this.y=y;  //将函数参数存储为对象的属性
}

//不需要return

//使用new关键字和构造函数来创建一个实例

var p=new Point(1,1); //平面几何中的点(1,1)

//通过给构造函数的prototye对象赋值

//来给Point对象定义方法

Point.prototype.r=function(){
    //返回x²+y2的平方根
    //this指代调用这个方法的对象
    return Math.sqrt(this.x*this.x + this.y*this.y);

};  //Point的实例对象p(以及所有的Point实例对象)继承了方法r()

p.r()  //=>1.414...

8.JavaScript在Web浏览器中运行

JavaScript代码可以通过<scirpt>标签来嵌入到HTML文件中:

<html>
  <head>
    <script src="library.js"></script>
    <!--引入一个JavaScript库-->
  </head>
  <body>
    <p>This is a paragraph of HTML</p>
    <script>
      //在这里编写嵌入到HTML文件中的JavaScript代码
    </script>
    <p>Here is more HTML</p>
  </body>
</html>

这里的示例代码并不是用来在F12(或者其他调试工具)控制台窗口中直接输入的,而是作为一个单独的HTML文件,并在Web浏览器中直接打开运行的。

JavaScript中的一些重要全局函数:

<script>
      function moveon() {
        //通过弹出一个对话框来询问用户一个问题
        var answer = confirm("准备好了吗?"); //单击"确定"按钮,浏览器会加载一个新页面
        if (answer) window.location = "http://taobao.com";
      }
      //在1分钟(6万毫秒)后执行定义的这个函数
      setTimeout(moveon, 60000);
</script>

如何选取特定的HTML元素、如何给HTML元素设置属性、如何修改元素内容,以及如何给文档添加新节点。这里的示例函数展示了如何查找和修改基本文档的内容:

function debug(msg) {
        //通过查看HTML元素id属性来查找文档的调试部分
        var log = document.getElementById("debuglog"); //如果这个元素不存在,则创建一个

        if (!log) {
          log = document.createElement("div"); //创建一个新的<div>元素
          log.id = "debuglog"; //给这个元素的HTML id赋值
          log.innerHTML = "<h1>Debug Log</h1>"; //定义初始内容
          document.body.appendChild(log); //将其添加到文档的末尾
        }
        //将消息包装在<pre>中,并添加至log中

        var pre = document.createElement("pre"); //创建<pre>标签
        var text = document.createTextNode(msg); //将msg包装在一个文本节点中
        pre.appendChild(text); //将文本添加至<pre>
        log.appendChild(pre); //将<pre>添加至log
}

CSS样式定义了内容的展示方式。可以通过JavaScript来操控Web浏览器中的HTML内容和文档的CSS样式。这通常会使用到HTML元素的style和class属性:

 function hide(e, reflow) {
    //通过JavaScript操纵样式来隐藏元素e
    if (reflow) {
      //如果第二个参数是true
      e.style.display = "none"; //隐藏这个元素,其所占的空间也随之消失
    } else {
      //否则
      e.style.visibility = "hidden"; //将e隐藏,但是保留其所占的空间
    }
  }
  function highlight(e) {
    //通过设置CSS类来高亮显示e
    //简单地定义或追加HTML类属性
    //这里假设CSS样式表中已经有"hilite"类的定义
    if (!e.className) e.className = "hilite";
    else e.className += "hilite";
  }
  • 也可以通过事件处理程序(event handler)来定义文档的行为。
  • 事件处理程序是一个在浏览器中注册的JavaScript函数,当特定类型的事件发生时浏览器便调用这个函数。
  • 通常我们关心的事件类型是鼠标点击事件和键盘按键事件(在智能手机中则是各种触碰事件)。或者说,当浏览器完成了文档的加载,当用户改变窗口大小或当用户向HTML表单元素中输入数据时便会触发一个事件。

定义事件处理程序最简单的方法是,给HTML的以"on"为前缀的属性绑定一个回调。当写一些简单的测试程序时,最实用的方法就是给"onclick"处理程序绑定回调。

假定已经将上文中的debug()和hide()两个函数保存至名为debug.js和hide.js的文件中,那么就可以写一个简单的HTML测试文件,来给<button>元素的onclick属性指定一个事件处理程序:

    <script src="debug.js"></script>
    <script src="hide.js"></script>
    <button onclick="hide(this,true);debug('hide button 1');">Hide1</button>
    <button onclick="hide(this);debug('hide button 2');">Hide2</button>

给一个很重要的事件——"load"事件注册一个事件处理程序。同时,也展示了注册"click"事件处理函数更高级的一种方法addEventListener

  //"load"事件只有在文档加载完成后才会触发
  //通常需要等待load事件发生后才开始执行JavaScript代码
  window.onload=function(){
  //当文档加载完成时执行这里的代码
  //找到文档中所有的<img>标签

     var images=document.getElementsByTagName("img"); //遍历images,给每个节点的"click"事件添加事件处理程序
     //在点击图片的时候将图片隐藏
     for(var i=0;i<images.length;i++){
       var image=images[i];          
       if(image.addEventListener)//注册事件处理程序的另一种方法
          image.addEventListener("click",hide,false);
       else//兼容IE8及以前的版本

       image.attachEvent("onclick",hide);

     }

  //这便是上面注册的事件处理函数

     function hide(event){event.target.style.visibility="hidden";}

  };

9. 示例:一个JavaScript贷款计算器

image.png 这个例子集中使用了诸多技术,展示了真实环境下的客户端JavaScript(包括HTML和CSS)编程。 如何在文档中查找元素

  • 如何通过表单input元素来获取用户的输入数据
  • 如何通过文档元素来设置HTML内容
  • 如何将数据存储在浏览器中
  • 如何使用脚本发起HTTP请求
  • 如何利用<canvas>元素绘图
<!DOCTYPE html>
<html>
  <head>
    <title>JavaScript Loan Calculator</title>
    <style>
      /*这是一个CSS样式表:定义了程序输出的样式*/
      .output {
        font-weight: bold;
      } /*计算结果定义为粗体*/
      #payment {
        text-decoration: underline;
      } /*定义id="payment"的元素样式*/
      #graph {
        border: solid black 1px;
      } /*图表有一个1像素的边框*/
      th,
      td {
        vertical-align: top;
      } /*表格单元格对其方式为顶端对齐*/
    </style>
  </head>

  <body>
    <!--这是一个HTML表格,其中包含<input>元素可以用来输入数据。
     程序将在<span>元素中显示计算结果,这些元素都具有类似"interset"和"years"的id
     这些id将在表格下面的JavaScript代码中用到。我们注意到,有一些input元素定义了"onchange"或"onclick"的事件处理程序,
     以便用户在输入数据或者点击inputs时执行指定的JavaScript代码段
    -->
    <table>
      <tr>
        <th>Enter Loan Data(输入贷款数据):</th>
        <td></td>
        <th>
          Loan Balance,Cumulative Equity,and Interest
          Payments(贷款余额,累积权益,和利息支付)
        </th>
      </tr>
      <tr>
        <td>Amount of the loan(贷款金额)($):</td>
        <td><input id="amount" onchange="calculate();" /></td>
        <td rowspan="8">
          <canvas id="graph" width="400" height="250"></canvas>
        </td>
      </tr>
      <tr>
        <td>Annual interest(年利率)(%):</td>
        <td><input id="apr" onchange="calculate();" /></td>
      </tr>
      <tr>
        <td>Repayment period(偿还期限)(years):</td>
        <td><input id="years" onchange="calculate();" /></td>
      </tr>
      <tr>
        <td>Zipcode(邮政编码)(to find lenders):</td>
        <td><input id="zipcode" onchange="calculate();" /></td>
      </tr>
      <tr>
        <th>Approximate Payments(大致支付):</th>
        <td><button onclick="calculate();">Calculate</button></td>
      </tr>
      <tr>
        <td>Monthly payment(每月支付):</td>
        <td>$<span class="output" id="payment"></span></td>
      </tr>
      <tr>
        <td>Total payment(总计支付):</td>
        <td>$<span class="output" id="total"></span></td>
      </tr>
      <tr>
        <td>Total interest(总利息):</td>
        <td>$<span class="output" id="totalinterest"></span></td>
      </tr>
      <tr>
        <th>Sponsors(赞助商):</th>
        <td colspan="2">
          Apply for your loan with one of these fine
          lenders(向这些优秀的贷款人之一申请贷款):
          <div id="lenders"></div>
        </td>
      </tr>
    </table>

    <!--随后是JavaScirpt代码,这些代码内嵌在了一个<script>标签里-->
    <!--通常情况下,这些脚本代码应当放在<head>标签中-->
    <!--将JavaScript代码放在HTML代码之后仅仅是为了便于理解-->
    <script>
      "use strict"; //如果浏览器支持的话,则开启ECMAScript 5的严格模式
      /**这里的脚本定义了caculate()函数,在HTML代码中绑定事件处理程序时会调用它
       *这个函数从<input>元素中读取数据,计算贷款赔付信息,并将结果显示在<span>元素中
       *同样,这里还保存了用户数据、展示了放贷人链接并绘制出了图表
       */
      function calculate() {
        //查找文档中用于输入输出的元素
        var amount = document.getElementById("amount");
        var apr = document.getElementById("apr");
        var years = 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(years.value) * 12; //现在计算月度赔付的数据
        var x = Math.pow(1 + interest, payments); //Math.pow()进行幂次运算
        var monthly = (principal * x * interest) / (x - 1); //如果结果没有超过JavaScript能表示的数字范围,且用户的输入也正确
        //这里所展示的结果就是合法的
        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 url =
          "getLenders.php" + //处理数据的URL地址
          "?amt=" +
          encodeURIComponent(amount) + //使用查询串中的数据
          "&apr=" +
          encodeURIComponent(apr) +
          "&yrs=" +
          encodeURIComponent(years) +
          "&zip=" +
          encodeURIComponent(zipcode); //通过XMLHttpRequest对象来提取返回数据
        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 sans-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
          );
        }
      }
    </script>
  </body>
</html>