JavaScript基础1

JavaScript

前端三层

  • HTML 结构层 从语义的角度描述页面结构
  • CSS 样式层 从美观的角度描述页面样式
  • JavaScript 行为层 从交互的角度描述页面行为

JavaScript 描述

  • javascript是一种运行在客户端的脚本语言,最早在HTML的网页上使用,用来给HTML网页增加动态功能。
  • 浏览器就是一种运行JavaScript脚本的客户端,JavaScript的解释器被称为JavaScript引擎,为浏览器的一部分。

JavaScript 组成

  • JavaScript是由ECMAScript,DOM和BOM三部分组成。

JavaScript 书写位置

  • 写在行内

    <input type="button" value="按钮" onclick="alert("你好") />
    
  • 写在html的 <script>的标签中

    <script type="text/javascript">
      alert("你好");
    </script>
    
  • 写在外部文件中

    <script scr="index.js"></script>
    
  • 注意:引用外部文件的 <script> 中不可以书写javascript代码,自定义的javascript的代码,需要再重新写入一个新的<script>标签中。

JavaScript 的注释

  • javascript的注释分为两种:块级注释和单行注释。
  • 块级注释又称作多行注释,作用范围是选中的多行,语法为“/**/”。
  • 单行注释是//符号后面的注释,作用范围是单行,语法为 "// "。

alert() 语句

  • 作用:在浏览器中弹出一个警示框,警示框中的内容可以自定义。
  • alert方法是js中内制好的一个方法,要想实现这个功能必须在alert关键字后面添加小括号执行,自定义的内容需要传递给方法中的参数,输出时参数的内容会出现在页面弹框中。
  • 语法:
    • alert作为一个函数,如果要执行,关键字后面必须要紧跟小括号。
    • 可以传递任意参数,参数的数据类型不同,语法要求不同。
    • 如果要给参数添加一对引号,要么是单引号,要么是双引号,不能一单一双。
    • 所有的有特殊功能的符号都必须是英文符号
    • alert()语句后面必须要添加英文状态下的分号。
    • js对换行,缩进和空格不敏感。
    • 语句的执行顺序是从上往下,从左往右 加载执行的。

prompt() 语句

  • 作用:弹出一个对话框,内部有一个提示语句以及一个输入框,可以在输入框中根据提示任意输入内容。
  • 是js的内置功能,必须添加小括号才能执行,有两个参数可以传递,每个参数一般都是字符串类型,必须添加引号,两个参数中间用逗号分隔。第一个参数表示提示内容,第二个参数表示默认值,可以不传递。

console 控制台

  • 浏览器审查元素中的console部分,可以调试程序中出现的bug。可以提示错误的个数,错误的类型以及错误所在的位置。
  • 可以在控制台中直接书写一些js代码,并且执行。
  • 在控制台打印输出:console.log();方便进行bug定位。

数据类型

字面量

  • 用于表达一个固定值的表示方法,又叫做常量。所见即所得,js程序执行到代码中的字面量,会立即知道它的数据类型以及他的值。可以用于表示固定的值,比如数字,字符串,布尔值和undefined。

数字字面量

  • 这里的数字就是数学意义上的数字。

  • 数字字面量区分:整数字面量,浮点数字面量,特殊值。

  • 书写时直接书写字面量,不需要添加任何辅助符号。

  • 八进制 数值范围是0-7,逢八进一,前缀用0或者0o表示。如012/0o12,表示1*8 + 2*1 = 10

  • 十六进制 数值范围是0-9和a-f, 逢十六进一,前缀用0x或0X表示。如0x1a,表示1*16 + 10*1 = 26

  • 注意:在八进制中,如果超出0-7的范围,并且是使用的0开头的前缀,计算机会自动装换成十进制的数字。如果是使用0o开头的前缀,则会抛出语法错误异常,同理十六进制如果超出范围,只会抛出语法异常。

浮点数字面量

  • 浮点数包含:整数,小数点,小数三部分。
  • 浮点数不区分进制,所有的浮点数都是十进制下的数字。
  • 注意:如果浮点数大于0且小于一,可以省略小数点之前的0不写。
  • 浮点数的最高精度是17位小数,在计算的时候存在精确度问题。例如0.2+0.1,得到的不是0.3,而是0.30000000000...04。

特殊数字字面量

  • NaN:not a number表示不是一个正常的数字,但是类型还是Number类型,这个数字没办法用之前的表示方法表示。NaN与任何一个值都不相等,包括它本身。isNaN()方法,来判断他是不是NaN类型。

字符串字面量

  • 描述:字符串是由任意个数的有序或者无序字符组成的串,在js中有自己特殊的写法。
  • 组成:字母、汉字、特殊字符、空白符等。
  • 写法:用一对单引号或者双引号及引号内的字符构成,引号中间的字符可以是任意对个,也可以是没有字符的空字符串。
  • 注意:字符串中如果字符包含双引号,则外层应该用单引号包裹,反之则用双引号包裹。

转义字符

  • 字符串中有一些特殊的字符不能直接书写,可以使用转义""字符对特殊字符进行转义。
  • 在字符串中可以使用转义字符\加普通字母,代替一下特殊字符。如\n表示换行,
    \t表示table制表。

变量

  • 描述:是计算机内存中存储数据的标识符,根据变量名称可以获取到内存中存储的数据。变量相当于一个容器,内部可以存储任意类型的数据,使用变量时,就是内存存储的数据。
  • 作用:可以方便的获取或者修改内存中的数据。
  • 定义:在使用变量之前,必须先有定义,使用var关键字定义变量,关键字后面跟一个空格,空格后面是变量名称。

变量的命名规则和规范

  • 规则:
    • 由字母、数字、下划线和$组成,不能以数字开头。
    • 字母区分大小写。
    • 不能是关键字和保留字,关键字和保留字指的是js中的特殊词。
  • 规范:
    • 变量名必须有意义。
    • 遵循驼峰命名法。多个单词组合而成的,第一个单词首字母小写,后面的单词首字母需要大写。

变量赋值

  • 变量定义之后,初始时没有进行赋值,内部有一个默认存储的值叫undefined(未定义),表示变量内部未赋值,可以存储数据了。
  • 变量赋值的方式:通过等号=赋值,等号右边的值赋值给左边的变量。
  • 注意:书写时,等号=两侧习惯书写一个空格。

变量赋值的几种情况

  • 变量赋值时:内部可以存储任意类型的数据,甚至是一个变量。赋值过程中,等号右侧的变量使用的是存储的数据。
  • 注意:变量参与赋值过程时,等号 左边右不变。等号左侧会被赋值,将来值发生变化,等号右侧的变量使用内部的值参与运算,自身不会发生变化。
  • 变量的赋初值过程可以与声明过程写在一起。也可以通过多次赋值的方式改变内部的数据。
  • 一个关键字var可以同时定义多个变量,并且都赋初值。多个变量之间用逗号进行分隔,最后一个变量侯后面使用分号进行结尾。

数据类型

简单的数据类型

  • Number 数字类型
  • String 字符串类型
  • undefined undefined类型
  • Boolean 布尔类型
  • null null类型

复杂的数据类型

  • Object 对象类型

Number 类型

  • 数字类型,不区分整数、浮点数、特殊值,都是Number类型。

字符串类型

  • 字符串用于表示文本,通过将其内容括在引号内编写。

Boolean 类型

  • Boolean只有true和flase两个字面量的值,必须是小写字母。true为1,flase为0。

Undefined 类型

  • undefined本身是一个数据,表示未定义。
  • 变量只声明的时候,值默认是undefined。

Null类型

  • null 本身是一个数据。
  • 从逻辑角度,null值表示一个空对象指针。
  • 如果定义的变量准备再将来用于保存对象,最好将该变量的初始化为null。

检测数据类型

  • 使用typeof的方法进行数据检测。
  • 检测方式:在typeof后面加小括号()执行,将要检测的数据放在小括号内部。
  • 例如:console.log(typeof("字符串类型"))
  • 也可以将typeof作为关键字,后面加空格,空格后面添加数据的方式,检测数据。
  • 例如:console.log(typeof "String类型")

变量的数据类型

  • Js语言是一门动态类型的语言,变量并没有一个单独的数据类型,而是会随着内部存储数据的变化,数据类型也会发生变化。
  • 变量的数据类型,与内部存储数据有关。
  • 将来使用变量时,需要知道内部存储的数据是什么类型,避免程序出错。

数据类型转换

转换成字符串类型

  • 数据toString()方法
  • String()方法,有些值没有toString()方法,这时候可以使用String(),比如undefined和null
  • +号拼接字符串方法
    • nun + "",当+两边一个操作符是字符串类型,一个操作符是其他类型的时候,会先把其他类型转成字符串在进行字符串拼接,返回字符串。

转换成数值类型

  • Number()方法

  • 转型函数Number()可以用于任何数据类型,将其他数据类型转为数字。

    • 字符串:纯数字字符串转换为对应数字,空字符串和空白字符串转为0,非空非纯数字字符串转为NaN。
    • 布尔值:true转为1,false转为0
    • undefined:转为NaN。
    • null:转为0。
  • parseInt()方法:字符串转整数方法

  • 作用:对浮点数进行取整操作;将字符串转为整数数字。

    • 对数字取整功能,直接舍弃小数部分,只保留整数。
    • 将字符串转为整数数字,也包含取整功能。
  • 字符串中,必须是纯数字字符串或者数字字符开头的字符串,才能转换为正常数字,且只取整数部分。如果不是数字开头的字符,回转换为NaN。

  • parseFloat(): 字符串转浮点数方法;

  • 作用:将字符串转为浮点数数字。

  • 要求:满足浮点数数字字符必须在字符串开始位置,如果不在开始位置返回值都是NaN。

转换成布尔类型

  • Boolean()方法
  • 转型函数Boolen()可以用于任何数据类型,将其它数据类型转为布尔类型的值。
  • 转为false: NaN、0、空字符串、null、undefined
  • 转为true: 非0非NaN数字、非空字符串。

操作符

  • 操作符也叫运算符,是js中发起运算的最简单的方式。
  • 表达式的组成包含操作数和操作符,表达式会得到一个结果,然后用结果参与程序。

算数运算符

  • +、-、*、/、%、()
  • NaN参与的运算,得到的结果都是NaN。
  • Infinity 参与的运算,视情况而定。
  • 有字符串参与的 + 运算:+ 变为连接字符,将前后连接成整体字符串。
  • 隐士转换:除了字符串参与的 + 运算,其他情况下,所有其他数据类型参与数学运算时,计算机暗中将其他数据类型先自动转化成数字类型,再参与运算。这个过程中不需要使用parseInt()和parseFloat()、Number()等方法,过程是暗中进行的,这就是隐士转换。

比较运算符

  • 也叫做关系运算符,一个比较运算符比较他的操作数,并返回一个布尔类型的值。
    • > 大于
    • < 小于
    • >= 大于等于
    • <= 小于等于
    • == 相等,只判断数值大小是否相等,不判断数据类型。
    • != 不等,与相等完全相反
    • === 全等,不光要判断值相等,还要判断数据类型相等。
    • !== 不全等,与全等完全相反
  • 比较顺序:从前往后进行比较,前面得出的结果再与后面比较。
  • 当NaN参与比较运算的时候,除了不等于和不全等于返回true之外,其他的比较运算均返回false。
  • 其他数据类型与数字类型相比较的时候,也会进行隐士转换。
  • null的判断比较特殊,当null和0比较时,相等判为false,<= 和 >= 判断为true。
  • null == undefined
  • 两个字符串进行比较的时候,不会发生隐士转换,转换成数字,而是比较两个字符串的unicode编码顺序,比较时不关心字符串的长度,从第一个字符开始比较,依次往后比较,直到比较出大小,就不再进行比较。

逻辑运算符

  • 逻辑运算符常用于布尔类型值之间,当操作数都是布尔值时,返回值也都是布尔值。
    • && 逻辑运算符 且
    • || 逻辑运算符 或
    • ! 逻辑运算符 非
  • 除了布尔类型的值之外,其他数据类型的值也可以参与逻辑运算。运算过程中需要将操作数隐士转为布尔类型的值,参与判断计算。最终运算结果还是原来某个位置的数据。
  • 并非所有逻辑元素返回的结果都是布尔值,其他数据参与得到的就是数据本身。
  • 如果是同种运算符的话,按照从前往后的顺序进行执行。如果是综合逻辑运算符的话,是按照非、与、或的顺序进行执行。
  • a && b; 当a为true的时候,值为b,当a为false的时候,选择a。
  • a || b; 当a为true的时候,选择a,当a为false的时候,选择b。

赋值运算符

  • 赋值运算符必须有变量参与运算。主要会做两件事情
    • 将变量中原始的值参与于右侧的值进行数学运算。
    • 将运算结果重新赋值给变量。
  • 赋值运算符符号
    • = 等于
    • += 加等于
    • -= 减等于
    • *= 乘等于
    • /= 除等于
    • %= 取余等于
    • ++ 递加
    • -- 递减

一元运算符

  • ++和--也叫一元运算符,只有一个操作数。
  • ++ 和 -- 可以写在变量的前面和后面,但是位置不同可能会导致程序的运算结果不同。
    • 例如a++: ++在变量符号之后,a++在参与程序运算的过程中使用的原始值,是没有提前进行加1的值。当第二次再使用变量a时,此时的a表示已经加1后的新值。现参与,后自加。
    • ++a: ++符号在变量之前,++a在参与程序运算过程中,先进行加1运算,并将加1后的值赋予a,当第二次用a时,a也是加1后的值。先自加,后参与。

运算优先级

  • 优先级从高到低
    • ()优先级最高
    • 一元运算符 ++ -- !
    • 算术运算符 先 *、/、% 后 +、-
    • 关系运算符 > >= < <=
    • 相等运算符 == != === !===
    • 逻辑运算符 先&& 后||
    • 赋值运算符

表达式

  • 一个表达式可以生成一个值,可能是运算、函数调用、也有可能是字面量。表达式可以放在任何需要值的地方。
  • 特点:表达式会先执行出一个结果,然后再参与其他程序。

if语句

  • if语句是最常用的条件分支语句,作用就是通过某个指定的判断条件,决定走哪个分支的代码。
  • 结构:
    if(表达式1){
      执行分支1
    }else if(表达式2) {
      执行分支2
    }else {
      执行分支3
    }
    
  • 注意事项
    • if语句可以实现选择的功能,两个分支可以选择一个,不会都执行
    • if语句可以不写else分支,表示条件成立就执行后面的结构体,如果条件不成立,就直接跳出if语句不执行。
    • 如果if语句的结构体是单行语句,可以将{}省略。
    • if语句能够控制自己内部的流程,但是不论走到那个分支,结束后都要继续执行if语句后面的程序。

if嵌套语句

  • if语句的结构体部分,可以是任意代码,也可以是另外一组if语句。
  • 如果想执行内部的if语句,必须先满足外面的if语句。同时还要满足内部if语句的某个条件。

三元表达式

  • 又叫三元操作符,必须有三个操作数参与的运算。
  • 操作符号:?:
  • 表达式:在参与js程序时,都必须先计算出表达式的结果,才能参与后面的程序。
  • 由于三元表达式具备了一些选择的效果,所以也是一种条件分支语句。

三元表达式语法

  • 语法: boolean_expression?true_value: false_value
  • 描述:表达是在参与三元运算的时候,必须要得到一个布尔类型的值(true或false),来作为判断依据。如果为true,则选择true_value,如果为false,则选择false_value。
  • 优点:
    • 在二选一的情况下,三元表达式结构更加简单。
    • 三元表达式作为一个表达式参与程序时,必须运算出结果才能参与程序,可以利用这个特点,将二选一的结果赋值给一个变量。
    • 遇到给一个变量进行二选一的情况下,可以使用三元表达式。

switch语句

  • switch允许一个程序求一个表达式的值,并且将这个值跟内部的case标签的值进行匹配,如果匹配成功,则执行相应case里的执行体,在匹配的时候是进行了全等匹配,不止值相等,数据类型也要相等。

  • 语法:

    switch(表达式) {
      case 值1: 
        结构体1;
        break;
      case 值2: 
        结构体2;
        break;
    
      ...
      default: 
        结构体n;
        break;
      
    }
    
  • 运行机制

    • switch首先会将表达式计算出一个结果,用结果去匹配结构体内部的case;
    • 从上往下进行匹配,如果匹配成功,会立即执行这个case后面的语句,直到遇到break跳出整个switch语句。
    • 如果前面的case没有匹配成功,会跳过case之间的语句,去匹配下一个case,直到匹配成功,如果都没有成功就执行default后面的语句。
  • 注意事项:

    • 通过一个变量匹配多个值得时候,优先考虑switch语句。
    • default可以不写,相当于if语句不写else;
    • 根据结构需要有时候必须要在每个case后面写break。如果不写break,对应case语句执行完之后不会跳出switch语句,而是会继续执行其他case后面的语句,直到遇到一个break。

总结

  • if语句:最常用的语句,所有的判断情况都能书写。
  • 三元表达式:多用于给变量赋值根据条件二选一的情况。
  • switch语句:多用于给一个表达式去匹配多种固定值的可能性的情况。

循环语句

for循环

  • 描述:for循环语句是一种前测试的循环语句,在执行循环之前,先要判断入口条件,如果条件为真,可以循环执行,如果条件为假,则必须跳出循环不再执行。

  • 语法:

    for(;;){
    
    }
    呆板的语法
    for(定义循环变量;变量的最大值或最小值;步长){
      循环体;
    }
    
  • 注意事项:

    • 小括号内必须包含两个分号。
    • for循环{}后面可以不写分号。
    • 如果判断条件不写,相当于永远为真,for循环则会一直执行。出现死循环。
    • for循环结构体内可以嵌套任何语句。

do while循环

  • do while 循环是一种后测试循环语句,会先执行一次结构体,执行完后才会去判断入口条件,如果条件为真,能够继续下一次循环,如果条件为假则跳出循环。

do while 循环语法

  • do {
    结构体
    }while(条件表达式);
  • do: 做什么。后面是每次循环的循环体
  • while: 当....时候。
  • 先执行一次循环体,然后当条件表达式为真时,可以继续循环。

do while 循环注意事项

  • 如果循环中需要循环变量参与,循环变量必须定义在循环外面,否则会被重置。
  • 循环变量自加的过程需要写在{}循环体内部。
  • 如果将循环变量写在结构体内,i的初始值每次都会被重置,容易出现死循环。
  • 变量自加过程卸载输出语句前面和后面,结果是不同的。
  • do while 循环即便条件第一次测试就为假,也会执行一次结构体。
  • 至少会执行一次循环体。

while 循环

  • while循环是一种前测试循环语句,在执行循环体之前都要测试入口条件,条件为真继续执行,条件为假直接跳出循环。

while 循环语法

  • while(条件表达式){
    循环体;
    }
  • 当条件表达式为真时,执行循环体,如果为假,跳出循环。

while循环注意事项

  • 如果需要循环变量岑宇,必须定义在循环体外部,避免被重置。
  • 循环变量自加的过程写在循环体内部。

break 语句

  • break语句的作用可以立即停止当前的for、do while、while循环。
  • 根据一些条件设置break位置,直到循环能够执行到break语句立即停止执行,跳出循环。

break 注意事项

  • break如果没有特殊只是,只能停止自己所在的那一层循环,并不能终止外部循环。
  • 如果想停止外层循环,可以给外层循环添加一个标签名label,在内层循环的break关键字后面空格加一个label名。
// 当k满足条件大于等于2时,会终止所有循环。
waiceng : for (var i = 1; i <= 4; i++) {
    for (var k = 1; k <= 4; k++) {
      console.log(i, k)
      if (k >= 2) {
        break waiceng;
      }
    }
}

continue 语句

  • continue表示当前的异常循环数据不是程序想要的,会立即停止当前次的循环,立即进入下一次循环。

continue 注意事项

  • 要根据特殊条件设置continue的位置。
  • 如果没有特殊指示,只能进入自己的下一次循环,不能立即停止外层循环的这一次进入下一次。
  • 控制外层循环的方式与break一样,都是添加外层的标签名。

穷举思想

  • 穷举思想:是一种解决问题的方法,将所有的需要数据所在的范围内所有的数据都意义列举出来,再根据规律的条件对所有这些数据进行筛选,这种方式就是穷举法。

制作方法

  • for循环:外层使用for循环进行意义列举。
  • if语句:内层用if语句进行判断,筛选需要的数据,如果满足条件就操作数据,如果不满足条件则跳过,看下一次循环的数据。

累加器

  • 累加器本质就是变量。实现累加的效果,就是利用循环,每次循环就将新的数据加到原始的变量中去,赋值过程是一个加等于赋值。

累加器注意事项

  • 累加器必须定义在循环外面的前面,如果定义在循环内部,每次循环都会将累加器重置,不能实现累加功能。
  • 累加器的初始值必须设置,而且必须设置为0,不会影响累加结果。
  • 使用最终累加结果时,必须在for循环结束后的外面,如果写在循环内部,循环没有结束,并不是最终需要的值。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,776评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,527评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,361评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,430评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,511评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,544评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,561评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,315评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,763评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,070评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,235评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,911评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,554评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,173评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,424评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,106评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,103评论 2 352

推荐阅读更多精彩内容