javaweb-javaScript(二)

本文主句介绍 JavaScript进阶内容,函数的定义,时间的触发,常见对象以及JSON(轻量级的数据传输格式),不过并没有提到HTML DOMjs HTML DOM相关的内容(除了innerHTML);这一块并非主要,但其中又有一些比较有意思的方法,挺符合面向对象的思想:createElement,appendChild,parenNode之类的,有兴趣的话可以根据W3School中的帮助文档学习(老板的帮助文档还是很全的).

其实,从这一点看来,JAVA的最大缺点也出来了,(原声,无插件的)无法与系统资源发生直接的关系,需要借助native关键字(引入其它语言操作)才能直接发生关系,JVM能跨平台,但是对应的native关键字引入的其它语言不一定是跨平台的,这一点有时间,我再研究一下native关键字.

1js的函数

1)在java里面定义方法

public void/int 方法名称(参数列表) {

方法体和返回值;

}

2)在js里面定义函数有三种方式

第一种:使用关键字 function 方法名称(参数列表) { 方法体和返回值 }

* 注意一:参数列表,不需要写类型(var),直接写参数名称

* 注意二:返回值,根据实际需要可以有也可以没有

* 代码

//这一种用得比较多

function test1() {

alert("123456");

}

//test1();

//实现两个数的相加

function add1(a,b) {

var sum = a+b;

return sum;

}

alert(add1(2,3));

第二种:匿名函数,使用关键字function(参数列表) { 方法体和返回值; }

* 代码

//也比较多

//第二种定义方式

var test1 = function(a,b) {

return a+b;

}

//调用函数

alert(test1(3,4));

第三种:动态函数(方法体和返回值,包含参数列表都是通过参数传递进来的)

* 使用js里面的内置对象 new Function("参数列表","方法体和返回值")

* 代码

//第三种定义方式,啥玩意,写起来太乱了

var param = "a,b";

var method = "var sum;sum=a+b;return sum;";

var test2 = new Function(param,method);

//调用函数

alert(test2(5,6));

2js的函数的重载(不存在重载)

1)在java里面存在重载,方法名称相同,参数列表不同

2js里面是否存在函数的重载?

第一,在js不存在重载

第二,可以使用js函数里面arguments数组模拟重载的效果

3)模拟重载的效果

* js里面有一个数组arguments,保存传递进来的参数,使用这个数组模拟重载的效果

* 代码

//模拟重载的效果

//js函数里面有一个数组 arguments,保存传递进来的参数的

function add1() {

//alert(arguments.length);

//遍历数组

/*for(var i=0;i<arguments.length;i++) {

alert(arguments[i]);

}*/

//模拟重载的效果(有几个参数,实现这几个参数的相加)

var sum = 0;

for(var i=0;i<arguments.length;i++) {

sum += arguments[i];

}

return sum;

}

//调用

alert(add1(1,2));      

alert(add1(1,2,3));    

alert(add1(1,2,3,4));

3js的事件(详细事件参考帮助文档,但是要注意的是,我并没有看到有右击事件的存在,所以,可能要研究浏览器源代码之类的,劫持一下右键操作?)

1)什么是事件:在html的元素里面可以触发事件调用js里面的函数

2)在html的标签上面如何使用事件

* 有三种方式

* 第一种:使用事件属性调用js方法

** 代码

<input type="button" value="第一种方式" onclick="add1();"/>

* 第二种:首先得到要绑定的标签,在使用事件的属性

** 代码

//第二种方式绑定事件

document.getElementById("buttonid").onclick = add1;

* 第三种:首先得到要绑定的标签,写js的代码

** 代码

document.getElementById("buttonid1").onclick = function() {

alert("aaaaa");

};

4js的常用的事件

1onload事件和onclick事件(载入,和单击事件)

* onloadhtml页面在加载时候触发事件,调用响应的js方法

**  <body onload="test1();">

* onclick:鼠标点击事件

** <input type="text" onclick="test2();"/>

2onfocus事件和onblur事件

* onfocus:获取焦点

* onblur:失去焦点

3onmouseover 鼠标被移到某元素之上

4onmouseout 鼠标从某元素移开

5onkeypress:点击键盘上面的某个键,调用方法

* <input type="text" id="textid1" onkeypress="key1(event);"/>

* function key1(obj) {

//alert(obj.keyCode);

//如果点击键盘上面的回车键 ,调用方法  13

if(obj.keyCode==13) {

alert("key1");

}

}

5jsdom对象

1)什么domdocument  object  model:文档对象模型

* 文档:指的是标记型文档(htmlxml

* 对象:在对象里面有属性和方法

** 使用dom里面提供的对象里面的属性和方法,对标记型文档进行操作

* 要想使用dom对象标记型文档进行操作,首先需要解析标记型文档(html为例)

** html中包含 标签、属性、文本内容

2)使用dom解析html

* 解析过程:根据html的层级结构在内存中分配一个树形结构

* document对象,代表整个文档

* element对象,代表标签

* 属性对象

* 文本对象

* Node节点对象,是这些对象的父对象,在找不到想要使用的方法,到Node里面找

3DHTML的简介

* 不是一种技术,是很多技术的简称。

* 包含的技术:

** html:封装数据

** css:使用属性和属性值修改数据的样式

** ECMAScript:语句和语法

** DOM:对标记型文档进行操作

6document对象(很有用的一种对象)

1document对象代表整个文档

2)方法

第一个:write(),向页面输出内容,可以输出html代码

* document.write("aa");

document.write("<hr/>");

第二个:getElementById(): 获取标签对象,通过标签的id值进行获取

* var input1 = document.getElementById("textid");

document.write(input1.value);

第三个:getElementsByName(): 根据标签里面name属性的值得到标签对象,返回数组

* //getElementsByName()

var inputs1 = document.getElementsByName("name1");

//alert(inputs1.length);

//获取每个input里面的value

for(var i=0;i<inputs1.length;i++) {

var input1 = inputs1[i];

//得到value

alert(input1.value);

}

* 如果只有一个标签,使用getElementsByName返回的也是一个数组,不需要遍历,直接使用数组的下标获取值

* var inputs2 = document.getElementsByName("name2");

//alert(inputs2.length);

alert(inputs2[0].value);

第四个:getElementsByTagName():根据标签的名称获取标签对象,返回数组

* var inputs3 = document.getElementsByTagName("input");

//alert(inputs3.length);

//遍历数组

for(var i=0;i<inputs3.length;i++) {

var input3 = inputs3[i];

//得到标签的value

alert(input3.value);

}

* 如果只有一个标签,使用getElementsByTagName返回的也是一个数组,不需要遍历,直接使用数组的下标获取值

var arr = document.getElementsByTagName("input");

alert(arr[0].value);

7innerHTML属性

1innerHTML属性不是dom里面属性(不是jsDOM,HTML DOM 里面的内容)

2)实现什么功能

第一,获取标签里面的文本内容

** var span1 = document.getElementById("spanid");

alert(span1.innerHTML);

第二,向标签里面设置内容(可以写html代码)

** var div1 = document.getElementById("div1");

div1.innerHTML = "<table border='1'><tr><td>aaa</td><td>bbb</td></tr></table>";

8、练习:动态生成表格(这种方式生成的表格,在输出简单的表格之类很方便,但是缺点是,不能直接使用css样式表,如果是要输出较多的文本,还是推荐用HTML DOM方法,更符合面向对象的思想,同时也更加强大)

1)实现的步骤

* 首先创建页面,在页面中有两个普通输入项和按钮(有事件)

* 点击生成按钮,生成对应的表格

** 首先得到输入的行和列

** 根据行和列生成表格

*** 循环行 <tr>

*** 在行里面循环单元格 <td>

2)代码

//实现生成表格

function add1() {

//得到输入的行和列

var hang = document.getElementById("hid").value;

var lie = document.getElementById("lid").value;

//alert(hang+" :: "+lie);

var tab = "<table border='1' cellpadding='10'>";

//生成行

for(var i=1;i<=hang;i++) {

tab += "<tr>";

//生成单元格

for(var j=1;j<=lie;j++) {

tab += "<td>aaaaa</td>";

}

tab += "</tr>";

}

tab += "</table>";

//alert(tab);

//获取div标签

var div1 = document.getElementById("div1");

//div里面写html代码

div1.innerHTML = tab;

}

9、表单的提交

1)在html中写form标签,提交方式

提交表单有三种方式

第一种方式:在form标签里面,写提交按钮 <input type="submit"/>

= 代码

<form method="get">

username: <input type="text" name="username"/>

<br/>

password: <input type="password" name="password"/>

<br/>

<input type="submit" value="提交"/>

</form>

第二种方式:在form标签里面,写普通按钮 <input type="button"/>

= 代码

//使用button进行表单的提交

function form01() {

//得到form标签

var form01 = document.getElementById("form01");

//提交form表单

form01.submit();

}

第三种方式:使用超链接提交数据(类似于post提交,利用浏览器的地址栏属性提交)

* <a href="要链接的地址?参数名称1=参数的值1&参数名称2=参数的值2">超链接</a>

* <a href="15-表单的提交方式二.html?username=ccc&password=123456">超链接提交数据</a>

10、表单校验

1)规范数据的输入的格式

2)如何进行表单的校验

第一,使用submit进行表单提交,进行表单校验

* 使用事件 onsubmit事件,写在form标签里面

<form method="get" onsubmit="return checkForm();">

* 如何return返回的值true可以提交表单,如果返回false不会提交表单

= 代码

//submit表单的校验

function checkForm() {

//判断用户名不能为空

var username = document.getElementById("usernameid").value;

var password = document.getElementById("passwordid").value;

if(username == "") {

alert("用户名不能为空");

return false;

}

if(password == "") {

alert("密码不能为空");

return false;

}

return true;

}

第二,使用button进行表单校验

= 代码

//使用button提交表单,进行校验

function form01() {

//得到输入项里面的值,判断值是否为空,如果为空不进行提交

var username = document.getElementById("usernameid").value;

var password = document.getElementById("passwordid").value;

//如果值为空,不进行提交

if(username == "") {

alert("用户名不能为空");

} else if(password == "") {

alert("密码不能为空");

} else {

//得到form标签

var form01 = document.getElementById("form01");

form01.submit();

}

}

拓展:使用button实现超链接,通过属性onclick实现

 onclick="window.location='${pageContext.request.contextPath }/update.jsp

?name=${dog.name}'"(值得注意的是windowjs的对象中的而一个${}是属于el表达式,后面的文章会讲到)

11json的简介

1JavaScript Object  NotationJavaScript 对象表示法。json是数据的交换格式,比xml更加轻巧。

jsonjs的原生的格式,通过js操作json不需要依赖其他东西,直接对json格式进行操作。

2json数据格式

* json有两种数据格式

第一种:json的对象的格式

* 写法 {json数据的名称1:json数据的值1,json数据的名称2:json数据的值2.....}

** 类似于key-value形式

** 名称和值之间使用冒号隔开,多个值之间使用逗号隔开

** json数据的名称是字符串的类型,json数据的值 string, number, object, array, true, false, null

** 具体数据的格式 {"name":"zhangsan","age":20,"addr":"nanjing"}

第二种:json的数组的格式

* 写法 [json对象1,json对象2........]

** 在数组里面有多个json对象,多个json对象之间使用逗号进行隔开

** 具体数据的格式 [{"name":"lucy","age":20},{"name":"mary","age":30}]

3)可以使用json的这两种格式组成更加复杂json的格式(Json数组的嵌套)

复杂的格式 {"name":[{"name":"zhangsan","addr":"beijing"},{"name":"lisi","addr":"tianjin"}]}

12js解析json

1js解析json的对象的数据格式

 提示:这里可以用for.....in获得json数组对应的key,再通过key拿到value

* 通过json对象数据格式里面的name的名称得到name对应的值

* 代码

//js解析json的对象格式的数据

var json1 = {"username":"lucy","age":20,"addr":"nanjing"};

//json的对象格式数据进行操作

document.write(json1.username);

document.write("<br/>");

document.write(json1.age);

document.write("<br/>");

document.write(json1.addr);

2js解析json的数组的数据格式

* 根据数组的下标得到json对象,解析json对象,根据数据的名称得到值

* 遍历json数组,得到json数组里面每个json对象,解析每个json对象,根据json对象的数据

的名称得到值

* 代码

//js解析json数组格式的数据

var json2 = [{"username":"zhangsan","age":20,"addr":"beijing"},

{"username":"lisi","age":30,"addr":"tianjin"},

{"username":"wangnwu","age":40,"addr":"nanjing"}];

//js操作数组,遍历数组,根据数组的下标得到值

//遍历json数组格式,得到的是一个json对象,解析json对象(根据名称得到值)

//得到第二个json对象里面的age的值  数组下标从0开始的

document.write(json2[1].age);

//得到第一个json对象里面的addr的值

document.write("<br/>");

document.write(json2[0].addr);

//遍历json数组的格式

document.write("<hr/>");

for(var i=0;i<json2.length;i++) {

//得到数组里面的每一个json对象

var person = json2[i];

//得到每个对象里面的值

var username = person.username;

var age = person.age;

var addr = person.addr;

document.write("username:"+username+" ; age:"+age+" ; addr:"+addr);

document.write("<br/>");

}

13json练习:人员信息的显示

1)把多个人员的信息存到json的数据格式里面,通过js解析json的数据格式,把所有的人员显示到页面的表格里面。

2[{"name":"zhangsan","age":20,"addr":"beijing"},

{"name":"lisi","age":30,"addr":"tinajin"},

{"name":"wangwu","age":40,"addr":"nanjing"}]

3)代码

//创建json的数据的格式,用于存储人员的信息

var persons = [{"name":"zhangsan","age":20,"addr":"beijing"},

{"name":"lisi","age":30,"addr":"tinajin"},

{"name":"wangwu","age":40,"addr":"nanjing"}];

//使用js解析persons格式,把这些人员信息显示到页面上

//遍历json的数组,得到每个人员的信息

//生成表格 ,把数据放到表格里面,把表格显示到页面上

function showData() {

var tab = "<table border='1' cellpadding='10'>";

//添加表头

tab += "<tr><th>姓名</th><th>年龄</th><th>地址</th></tr>";

for(var i=0;i<persons.length;i++) {

//得到数组里面的每个json对象

var person = persons[i];

//得到每个json对象里面值

var name = person.name;

var age = person.age;

var addr = person.addr;

//生成表格

tab += "<tr><td>"+name+"</td><td>"+age+"</td><td>"+addr+"</td></tr>";

}

tab += "</table>";

//alert(tab);

//table表格的代码显示到页面上,使用innerHTML属性

//得到div标签

var div1 = document.getElementById("div1");

//div里面写table代码

div1.innerHTML = tab;

}

 附上 博主写的一个简单的扫雷

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
 <head>
  <title> 扫雷2.0 </title>
  <meta name="Generator" content="EditPlus">
  <meta name="Author" content="">
  <meta name="Keywords" content="">
  <meta name="Description" content="">
 </head>
    <style type="text/css">
        .two{
            background-color:#669966;
            
        }
        table{
            width:500px;
            height:500px;
        }
        .one{
            background-color:white;
        }
        .three{
            background-color:red;
        }
        .four{
            background-color:#009900;
        }
        .bf{
            background-color:#ffff00;
        }
        .b11{
            width:200px;
            height:50px;
        }
    </style>    
 <body class="four">
  <h1>欢迎来到简易扫雷页面.当前版本4.0</h1>
    <h3>总地雷数量为10,点击旗帜可以插旗,点击排雷可以重新排雷</h3>
    <input type="button" onclick="newTable()" value="开始游戏" class="b11"/>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    <input type="button" onclick="booMflg()" value="插旗" class="b11"/>
    <input type="button" onclick="pailei()" value="排雷" class="b11"/><hr/>
    <script type="text/javascript">
    <!--
        //4.0,修正游戏结束后还可以继续点击的情况
        //个别浏览器会出现错位的情况,如果出现这种情况请使用EditPlus打开
        var flag=false;
        var flag2=false;
        var sum=0;
        function newTable(){
            if(flag2){
                alert("游戏已经开始,请不要再次点击");
                return;
            }
            flag2=true;
            //拿到body对象
            var body=document.getElementsByTagName("body")[0];
            //创建表格
            var table=document.createElement("table");        
            table.border=1;
            table.align="center";
            table.calssName="tcss";
            table.id="t1";
            //创建十行十列
            for(var a=0;a<10;a++){
            //创建10行
                var tr=document.createElement("tr");
                //创建10列
                for(var b=0;b<10;b++){
                    var td=document.createElement("td");
                    //添加对应的属性
                    td.id=a+""+b;
                    td.className="two";
                    td.align="center";
                    td.innerHTML="!";
                    td.onclick=scan;
                    td.ooo=true;
                    //挂接单元格到行
                    tr.appendChild(td);
                }
                //挂接到列
                table.appendChild(tr);
                body.appendChild(table);    
            }    
        }        
        var scan=function(){
                var oflg=otherMethod();
                if(!oflg){
                    return
                }
                if(this.innerHTML!="!"){
                    return;
                }
                sum++;
                //this代表当前按下的按钮
                var id=this.id;
                //拿到八个方向的节点ID
                //拿到当前节点组中下一个节点
                var right=this.nextSibling;
                var rid=null;
                var rup=null;
                var rui=null;
                var rdo=null;
                var rdi=null;
                //拿到前一个节点
                var left=this.previousSibling;;
                var lid=null;
                var lup=null;
                var lui=null;
                var ldo=null;
                var ldi=null;
                //拿到父节点组中的前一个节点
                var up=this.parentNode.previousSibling;
                var uid=null;
                //拿到父节点组中的后一个节点
                var down=this.parentNode.nextSibling;
                var did=null;    
                //因为可能会点击到边界,会拿不到附近的节点,所以需要判断是否为null
                if(right!=null){
                rid=right.id;
                rup=right.parentNode.previousSibling;
                rdo=right.parentNode.nextSibling;
                    if(rup!=null){
                        rui=rup.childNodes[rid%10].id;
                    }
                    if(rdo!=null){
                        rdi=rdo.childNodes[rid%10].id;
                    }
                }
                if(left!=null){
                lid=left.id;
                lup=left.parentNode.previousSibling;
                ldo=left.parentNode.nextSibling;
                    if(lup!=null){
                    lui=lup.childNodes[lid%10].id;
                    }
                    if(ldo!=null){
                    ldi=ldo.childNodes[lid%10].id;
                    }
                }
                if(up!=null){
                up=up.childNodes[id%10];
                uid=up.id;
                }
                if(down!=null){
                down=down.childNodes[id%10];
                did=down.id;
                }
                //储存八个方向的对象
                var arr9=[right,left,down,up,rup,lup,ldo,rdo];
                //将八个方向的id存储到数组之中
                var arr8=[rid,rui,rdi,lid,lui,ldi,uid,did];
                var count=0;                        
                for(var z=0;z<arr.length;z++){
                    if(arr[z]==id&&!flag){
                    this.className="three";
                    flag=true;    
                    alert("游戏结束");
                    return;
                    }
                    //遍历判断是否存在对应的雷区,存在,计数器+1
                    for(var x=0;x<arr8.length;x++){
                        if(arr[z]==arr8[x]){
                            count++;
                        }
                    }
                    
                }
                this.innerHTML=count;
                this.className="one";    
                /*
                //优化失败,有时间再排错
                if(count==0){
                    for(var x=0;x<arr9.length;x++){
                        if(arr9[x]!=null){
                        yh1(arr9[x]);
                        }
                    }
                }            
                */
            }
            //切换点击效果按钮
            var booMflg=function(){
                if(!otherMethod()){
                return
                }
                //拿到所有表格
                var table =document.getElementById("t1");
                //循环赋值
                for(var i=0;i<table.childNodes.length;i++){
                    var j=table.childNodes[i];
                    //alert(j.childNodes.length);
                    for(var k=0;k<j.childNodes.length;k++){
                    var nd=j.childNodes[k];
                    nd.onclick=yellowflg;
                    }
                }    
            }            
            //旗帜效果点击选项
            function yellowflg(){    
                if(this.innerHTML!="@"&&this.innerHTML!="!"){
                    return;
                }
            //修改样式
                if(this.ooo){
                    this.className="bf";
                    this.innerHTML="@";
                    this.ooo=false;
                }else{
                    this.className="two";
                    this.innerHTML="!";
                    this.ooo=true;
                }
            }

            function pailei(){
                if(!otherMethod()){
                return
                }
                //拿到所有表格
                var table =document.getElementById("t1");
                //循环赋值
                for(var i=0;i<table.childNodes.length;i++){
                    var j=table.childNodes[i];
                    //alert(j.childNodes.length);
                    for(var k=0;k<j.childNodes.length;k++){
                    var nd=j.childNodes[k];
                    nd.onclick=scan;;
                    }
                }    
            }
            function otherMethod(){
                //判断游戏是否结束与通关
                if(flag){
                alert("游戏结束");            
                return false;
                }else if(sum>=90){
                alert("恭喜,游戏通关");
                return false;
                }else{
                return true;
                }
            }
            //优化无效
            function yh1(obj){
                var id =obj.id;
                //拿到八个方向的节点ID
                //拿到当前节点组中下一个节点
                var right=obj.nextSibling;
                var rid=null;
                var rup=null;
                var rui=null;
                var rdo=null;
                var rdi=null;
                //拿到前一个节点
                var left=obj.previousSibling;;
                var lid=null;
                var lup=null;
                var lui=null;
                var ldo=null;
                var ldi=null;
                //拿到父节点组中的前一个节点
                var up=obj.parentNode;
                if(up!=null){
                    up=up.previousSibling;
                }
                var uid=null;
                //拿到父节点组中的后一个节点
                var down=this.parentNode;
                if(down!=null){
                    down=down.nextSibling;
                }
                var did=null;    
                //因为可能会点击到边界,会拿不到附近的节点,所以需要判断是否为null
                if(right!=null){
                rid=right.id;
                rup=right.parentNode;
                rdo=right.parentNode;
                    if(rup!=null){
                        rup=rup.previousSibling;
                        if(rup!=null){
                            rui=rup.childNodes[rid%10].id;
                        }
                    }
                    if(rdo!=null){
                        rdo=rdo.nextSibling;
                        if(rdo!=null){
                            rdi=rdo.childNodes[rid%10].id;
                        }
                    }
                }
                if(left!=null){
                lid=left.id;
                lup=left.parentNode;
                ldo=left.parentNode;
                    if(lup!=null){
                        lup=lup.previousSigling;
                        if(lup!=null){
                        lui=lup.childNodes[lid%10].id;
                        }
                    }
                    if(ldo!=null){
                        ldo=ldo.nextSibling;
                        if(ldo!=null){
                        alert(ldo.nodeName);
                            ldi=ldo.childNodes[lid%10].id;
                        }
                    }
                }
                if(up!=null){
                up=up.childNodes[id%10];
                uid=up.id;
                }
                if(down!=null){
                down=down.childNodes[id%10];
                did=down.id;
                }
                count=0;
                //储存八个方向的对象
                var arr9=[right,rup,rdo,left,lup,ldo,up,down];
                //将八个方向的id存储到数组之中
                var arr8=[rid,rui,rdi,lid,lui,ldi,uid,did];
                //遍历判断是否存在对应的雷区,存在,计数器+1
                for(var z=0;z<arr.length;z++){
                    for(var x=0;x<arr8.length;x++){
                        if(arr[z]==arr8[x]){
                            return;
                        }
                    }
                }
                //alert(count);
                //当前项==0的时候判断周围是否也等于0
                    obj.className="one";
                    obj.innerHTML=0;
                    for(var x=0;x<arr9.length;x++){
                        if(arr9[x]!=null){
                        yh1(arr9[x]);
                        }
                    }
                            
            }
    //-->
    </script>
    <script type="text/javascript">
    <!--
        //创建10个雷区
        var arr=new Array(10);        
        for(var a=0;a<arr.length;a++){            
          //创建随机数
            var flg=false;
            var b=(parseInt(Math.random()*100));
            //去除重复
            for(var c=0;c<arr.length;c++){
                if(arr[c]==b){
                    flg=true;
                    break;
                }
            }
            if(flg){
                a--;
            continue;
            }
            if(b<10){
            //如果小于10就在前面加上0
            arr[a]="0"+b;
            }else{
            arr[a]=b+"";
            }        
        }
        //-->
    </script>
    
 </body>
</html>
原文地址:https://www.cnblogs.com/adventurer/p/5503437.html