JavaScript权威指南(个人笔记):(一)基础

语句结尾最好都打分号

区分大小写

变量是一个值的符号名称,可以通过名称来获得对值的引用。

 

数据类型
1.原始类型5个:数字,字符串,布尔值,null,undefined(后面是两个特殊的原始值)
2.对象类型3个:普通对象,数组,函数( 后面是两个特殊的对象)

 

数字

和其他编程语言不同,JavaScript不区分整数值和浮点数值。JavaScript中的所有数字均用浮点数值表示

二进制浮点数表示法并不能精确表示类似0.1这样简单的数字 

例子:

0.3-0.2 = 0.999999999998  (并不等于0.1)

 

可以使用大整数进行重要的的金融计算,例如,要使用整数“分”而不要使用小数“元”进行基于货币单位的计算。

 

字符串

字符串可以当作只读数组(可以使用方括号来访问字符串中的单个字符)

str.indexOf('a',index)  字符串a在某个位置后,首次出现的位置(如果不加第二个参数,即在整个字符串中首次出现的位置)

str.lastIndexOf('a')  字符串a在整个字符串中最后一次出现的位置

str.replace('a','b')  全文替换,把a换成b(注意:返回新的字符串,原字符串本身并没有发生改变) 

 

布尔值

这些值都会被转为false:

undefined

null

0

-0

NaN

" "  // 空字符串

 

其他的所有值,包括所有对象(数组)都会转为true。

false和上面6个可以转换成false的值有时称作“假值”(falsy value),其他值称作“真值”(truthy value)。

JavaScript期望使用一个布尔值的时候,假值会被当做false,真值会被当做true。

 

null和undefined

null

null是JavaScript语言的关键字,他表示一个特殊值,常用来描述“空值”。

对null执行typeof运算,结果返回字符串“object”,也就是说,可以将null认为是一个特殊的对象值,含义是“非对象”。

但实际上,通常认为null是它自由类型的唯一一个成员,它可以表示数字,字符串和对象是“无值”的。

 

undefined

JavaScript还有第二个值来表示值的空缺。用未定义的值表示更深层次的“空值”。

他是变量的一种取值,表明变量没有初始化,如果要查询对象属性或数组元素的值时返回undefined则说明这个属性或元素不存在。

如果函数没有返回值,则返回undefined。

引用没有提供实参的函数形参的值,也会得到undefined。

undefined是预定义的全局变量(他和null不一样,他不是关键字),他的值就是“未定义”。

如果使用typeof运算符得到undefined的类型,则返回“undefined”,表明这个值是这个类型的唯一成员

 

尽管null和undefined是不同的,但他们都表示“空缺的值”,两者往往可以互换。

判断相等运算符“==”认为两者是相等的(要使用严格相等运算符“===”)来区分他们

null和undefined都不包含任何属性和方法。实际上,使用“.”和“[]”来存取这两个值的成员或方法都会产生一个类型错误。

你或许认为undefined是表示系统级的,出乎意料的或类似错误的值的空缺,而null是表示程序级的,正常的或在意料之中的值的空缺。

如果你想将null或者undefined赋值给变量或者属性,或者将他们作为参数传入函数,最佳选择是使用null

 

全局对象(global object)

全局对象只是一个对象,而不是类。不存在Global()构造函数也就无法实例化一个新的全局对象。

全局对象(global object)在JavaScript中有着重要的用途:全局对象的属性是全局定义的符号,JavaScript程序可以直接使用。

当JavaScript解释器启动时(或者任何Web浏览器加载页面的时候),他将创建一个新的全局对象,并给他一组定义的初始属性:

全局属性,比如undefined, NaN

全局函数,比如isNaN(), parseInt()

构造函数,比如Date(), String()

全局对象,比如Math和JSON

全局对象的初始属性并不是保留字,但他们应该当做保留字来对待。

在代码的最顶级——不在任何函数内的JavaScript代码——可以使用JavaScript关键字this来引用全局对象

在客户端JavaScript中,在其表示的浏览器窗口中的所有JavaScript代码中,Window对象充当了全局对象。这个全局Window对象有一个属性window引用其自身,他可以代替this来引用全局对象。

 

不可变的原始值 和 可变的对象引用

原始值是不可以更改的:任何方法都无法更改(或“突变”)一个原始值。

 

原始值——比较:值的比较。

对象——比较:因为对象是引用类型(值都是引用的),所以对象的比较均是引用的比较。(当且仅当它们引用同一个基对象的时候,它们才相等)

 

例子1:

let obj1 = {name: 'joy'};

let obj2 = {name: 'joy'};

虽然看起来两个对象的属性完全一样,但是这两个对象并不相等,因为他们引用的内存是不一样的(这两个对象是分别开了两个内存)

 

例子2:

let obj = {name: 'joy'};

let obj1 = obj ;

let obj2 = obj ;

现在obj1和obj2就相等了,因为他们引用的是同一个基对象(等于引用的是同一个内存)

 

包装对象(原始值转换成对象)

let s = 'joy';

s.length;

字符串s既然不是对象,为什么它会有属性呢?因为只要引用了字符串s的属性,JavaScript就会将字符串值通过调用new String(s)的方式转化成对象,这个对象继承了字符串的方法,并用来处理属性的引用。

null和undefined没有包装对象:访问他们的属性会造成一个类型错误。

存取字符串,数字或布尔值的属性时,自动创建的临时对象称作包装对象,他只是偶尔用来区分:字符串值和字符串对象,数字和数值对象,布尔值和布尔对象。

可通过new String(),new Number(),new Boolean()来显式创建包装对象(即原始值转换成他们各自的包装对象)。(当不用new运算符调用这些函数时,它们会作为类型转换函数,看显式类型转换部分)

例子1:

let num1 = 1;  // 数字

let num2 = new Number(num1);  // 数字对象

num1 == num2  // =>true  值相等

num1 === num2  // =>false  类型不相等

 

例子2:

new String(s)  // =>  String {"joy"}(创建一个新的字符串对象)

new Number(3)  // =>  Number {3}(创建一个新的数值对象)

 

对象转为原始值

对象转布尔值:所有的对象(包括数组和函数,包装对象)都转换为true。对于包装对象是如此:new Boolean(false)是一个对象而不是原始值,他将转换为true

对象转为字符串和数字:字符串是toString( )  ,数字是valueOf( )

例子:

({x:1, y:2}).toString()  // => "[object object]"

 

数据类型转换

 

显式类型转换

做显示类型转换最简单的方法就是使用Boolean(),Number(),String()或Object()函数(类型转换函数)。

例子:

Number( "3" )  // => 3  把字符串类型转换成数字类型

Object(3)  // => new Number(3)  把数字类型转换成对象

 

需要注意的是,除了null或undefined之外的任何值都具有toString()方法,这个方法的执行结果通常和String()方法放回结果一致。

同样需要注意的是,如果试图把null或undefined转换为对象,则会抛出一个类型错误(TypeError)。但Object()函数在这种情况下不会抛出异常:它仅简单地返回一个新创建的空对象

 

在JavaScript中的某些运算符会做隐式的类型转换。

如果“+”运算符中的一个操作数是一个字符串,它将会把另一个操作数转换为字符串。

一元“+”运算符将其操作数转为数字。

一元“!”运算符将其操作数转换为布尔值并取反。在代码中会经常见到这种类型转换的惯用法:

x + " "  // 等价于String(x)

+x  // 等价于Number(x) 也可以写成 x-0

!!x  // 等价于Boolean(x) 注意是双感叹号

 

转换和相等性

由于JavaScript可以做灵活的类型转换,因此其“==”相等运算符也随相等的含义灵活多变。

例如,如下这些比较结果均是true:

null == undefined  // 这两值被认为相等

"0" == 0  // 在比较之前,字符串转换成数字

0 == false  // 在比较之前,布尔值转换成数字

"0" == false  // 在比较之前,字符串和布尔值都转换成数字

 

需要特别注意的是,一个值转换为另一个值并不意味着两个值相等。

比如,如果在期望使用布尔值的地方使用了undefined,他将会转换为false,但这并不表明undefined == false

if语句将undefined转换为false,但“==”运算符从不试图将其操作数转换为布尔值!!!

 

变量作用域

一个变量的作用域(scope)是程序源代码中定义这个变量的区域。

全局变量拥有全局作用域,在JavaScript代码中的任何地方都是有定义的。

然而在函数内声明的变量只在函数体内有定义。他们是局部变量,作用域是局部性的。函数参数也是局部变量,他们只在函数体内有有定义。

在函数体内,局部变量的优先级高于同名的全局变量。如果在函数内声明一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就被局部变量所遮盖!!!

 

函数作用域和声明提前

函数作用域(function scope):变量在声明他们的函数体以及这个函数体内嵌套的任意函数体内都是有定义的。(注意:这个只对var声明来说,let声明是支持块级作用域的)

function test(x) {

  var i = 0;    // i 在整个函数体内均是有定义的

  if (x) {

    var j = 1;    // j 在整个函数体内均是有定义的,不仅仅是在这个代码段内

    return x + j;

  }

  console.log(j);    // j 已经定义了,但可能没有初始化

}

 

作为属性的变量

当声明一个JavaScript全局变量时,实际上是定义了一个全局对象的一个属性。(注意:var声明的时候才是这样,并且要在浏览器运行)

JavaScript可以允许使用this关键字来引用全局对象,却没有方法可以引用局部变量中存放的对象。这种存放局部变量的对象的特有性质,是一种对我们不可见的内部实现。然而,这些局部变量对象存在的观念是非常重要的,看下一节。

 

作用域链

全局变量在程序中始终都是有定义的。局部变量在声明他的函数体内以及其所嵌套的函数内始终是有定义的。

在JavaScript的最顶层代码中(也就是不包含在任何函数定义内的代码),作用域链由一个全局对象组成。在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。在一个嵌套的函数体内,作用域链上至少有三个对象。理解对象链的创建规则是非常重要的。当定义一个函数时,实际上保存一个作用域链。当调用这个函数时,他创建一个新的对象来存储他的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”。

对于嵌套函数来讲,事情变得更加有趣,每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的。内部函数在每次定义的时候都会有微妙的差别——在每次调用外部函数时,内部函数的代码都是相同的,而且关联这段代码的作用域链也不相同。

 

表达式

表达式(expression)JavaScript中的一个短语,JavaScript解释器会将其计算(evaluate)出一个结果。

 

原始表达式

原始表达式时表达式的最小单位——他们不再包含其他表达式。

JavaScript中的原始表达式包含常量或直接量,关键字和变量

 

对象和数组的初始化表达式

对象和数组初始化表达式实际上是一个新创建的对象和数组。

这些初始化表达式有时称作“对象直接量”和“数组直接量”。

和直接量不同,他们不是原始表达式,因为他们所包含的成员或元素都是子表达式。

 

函数定义表达式

函数定义表达式定义一个JavaScript函数。表达式的值是这个新定义的函数。

从某种意义上讲,函数定义表达式可称为“函数直接量”

一个典型的函数定义表达式包括关键字function,跟随其后的是一对圆括号,括号内是一个以逗号分隔的列表,列表含有0个或多个标识符(参数名),然后再跟随一个由花括号包裹的JavaScript代码段(函数体)

函数定义表达式同样可以包含函数的名字。

 

属性访问表达式

属性访问表达式运算得到一个对象属性或一个数组元素的值

不管使用哪种形式的属性访问表达式,在“.”和“[”之前的表达式总是会先计算。如果计算结果是null或undefined,表达式会抛出一个类型错误异常,因为这两个值都不能包含任何属性。

不论哪种情况,如果命名的属性不存在,那么整个属性访问表达式的值就是undefined

 

调用表达式

JavaScript中的调用表达式(invocation expression)是一种调用(或执行)函数或方法的语法表示。

例子:

f(0)  // f是一个函数表达式,0是一个参数表达式

a.sort()  // a.sort是一个函数,他没有参数

 

任何一个调用表达式都包含一对圆括号和左圆括号之前的表达式。如果这个表达式是一个属性访问表达式,那么这个调用称作“方法调用”。

在方法调用中,执行函数体的时候,作为属性访问主题的对象和数组便是其调用方法内this的指向。

这种特性使得在面向对象编程范例中,函数(其OO名称为“方法”)可以调用其宿主对象。

 

对象创建表达式

对象创建表达式(object creation expression)创建一个对象并调用一个函数(这个函数称作构造函数)初始化新对象的属性。

65页 

 

运算符

运算符用于算术表达式,比较表达式,逻辑表达式,赋值表达式等。

表4-1是按照运算符的优先级排序的,标题为A的列,表示运算符的结合性,L(从左至右)或R(从右至左)。标题为N的列,表示操作数的个数。

 

操作数类型和结果类型

JavaScript运算符通常会根据需要对操作数进行类型转换

乘法运算符“*”希望操作数为数字,但表达式"3" * "5"却是合法的,因为JavaScript会将操作数转换为数字。

 

运算顺序

例子:

假设a=1
b=(a++)+a;  // b等于3

  1  +  2 

 

算术表达式

基本的算术运算符*(乘法),/(除法),%(求余),+(加法)-(减法)。我们会在随后有专门一节讲述“+”运算符。剩下的4个运算符非常简单,只是在必要的时候将操作数转换为数字,然后求积,商,余数和差。所有那些无法转换为数字的操作数都转换为NaN值。如果操作数(或者转换结果)是NaN值,算术运算的结果也是NaN。

 

“+”运算符
二元加法运算符“+”可以对两个数字做加法,也可以做字符串连接操作
加号的转换规则优先考虑字符串连接,如果其中一个操作数是字符串或者转换为字符串的对象,另一个操作数将会转换为字符串,加法将进行字符串的连接操作。如果两个操作数都不是类字符串(string-like)的,那么都将进行算术加法运算。

 

从技术上讲,加法操作符的行为表现为:
1.如果其中一个操作数是对象,则对象会遵循对象到原始值的转换规则转换为原始类值:日期对象通过toString()方法执行转换,其他对象则通过valueOf()方法执行转换(如果valueOf()方法返回一个原始值的话)。由于多数对象都不具备可用的valueOf()方法,因此他们会通过toString()方法执行转换。
2.在进行了对象到原始值的转换后,如果其中一个操作数是字符串的话,另一个操作数也会转换为字符串,然后进行字符串连接。
3.否则,两个操作数都将转换为数字(或者NaN),然后进行加法操作。

 

例子:
1+ {} // => "1[object object]":对象转换为字符串后进行字符串连接
true + true // => 2:布尔值转换为数字后做加法
2+ null // => 2:null转换为0后做加法
2 + undefined // => NaN:undefined转换为NaN后做加法

 

一元算术运算符
一元运算符作用于一个单独的操作数,并产生一个新值。一元算术运算符(+,-,++和--),必要时,他们会将操作数转换为数字。需要注意的是,“+”和“-”是一元运算符,也是二元运算符。

 

一元加法(+)
一元加法运算符把操作数转换为数字(或者NaN),并返回这个转换后的数字。如果操作数本身就是数字,则直接返回这个数字。

 

递增(++)

(1)前增量运算符:当运算符在操作数之前,对操作数进行增量计算,并返回计算后的值。

  let i = 1 ,  j = ++i;  // i 和 j 的都是2 

(2)后增量运算符:当运算符在操作数之后,对操作数进行增量计算,但返回未做增量计算的值

  let i = 1 ,  j = i++;  // i => 2 ,  j => 1

注意:++x并不总和x=x+1完全一样。比如在x是字符串“1”的情况下。

 

关系表达式

关系运算符用于测试两个值之间的关系(比如“相等”,“小于”,或“是...的属性”),根据关系是否存在而返回true或false。

关系表达式总是返回一个布尔值,通常在if,while或者for语句中使用关系表达式,用以控制程序的执行流程。

 

相等和不等运算符

两个运算符允许任意类型的操作数,如果操作数相等则返回true,否则返回false

为了减少概念混淆,应该把“=”称作“得到和赋值”,把“==”称作“相等”,把“===”称作“严格相等”。

 

“!=”和“!==”运算符的检测规则是“==”和“===”运算符的求反。

如果两个值通过“==”的比较结果为true,那么通过“!=”的比较结果则为false

如果两个值通过“===”的比较结果为true,那么通过“!==”的比较结果则为false

(我们只要记住“!=”称作“不相等”,“!==”称作“不严格相等”就可以了。)

 

如果两个操作数类型不同,相等运算符“==”也可能会认为他们相等。检测相等将会遵守如下规则和类型转换:

1.如果一个值是null,另一个是undefined,则他们相等

2.如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值进行比较

3.如果其中一个值是true,则将其转换为1再进行比较。false为0。

4.如果一个值是对象,另一个值是数字或字符串。则将对象转换为原始值,然后再进行比较

5.其他不同类型之间的比较均不相等

 

in运算符

in运算符希望他的左操作数是一个字符串或可以转换为字符串,希望他的右操作数是一个对象。如果右侧的对象拥有一个名为左操作数值的属性名,那么表达式返回true

例子:

"toString" in {} // => true:对象继承了toString()方法

"0" in [2] // => true:数组包含元素“0”

 

逻辑表达式

逻辑与(&&)
“&&”运算符可以从三个不同的层次进行理解。
最简单的第一层理解是,当操作数都是布尔值的时候,“&&”对两个值进行于(AND)操作,只有在第一个操作数和第二个操作数的都是true的时候,他才返回true。如果其中一个操作数是false,他返回false。

“&&”常用来链接两个关系表达式:

x == 0 && y == 0  // 只有在x和y都是0的时候,才返回true

关系表达式的运算结果总是为true和false,因此这样使用的时候,“&&”运算符本身也返回true或false。

 

但是“&&”操作数并不一定是布尔值,有些值可以当作“真值”和“假值”。对“&&”的第二层理解是,“&&”可以对真值和假值进行布尔与(AND)操作。如果两个操作数都是真值,那么返回一个真值;否则,至少一个操作数是假值的话,则会返回一个假值。在JavaScript中任何希望使用布尔值的地方,表达式和语句都会将其当作真值或假值来对待,因此实际上“&&”并不总是返回true和false,但也并无大碍。

 

需要注意的是,上文提到了运算符返回一个“真值”或者“假值”,但并没有说明这个“真值”或“假值”到底是什么值。为此,我们深入讨论“&&”的第三层理解。当左操作数是假值,那么整个表达式的结果一定也是假值,因此“&&”这时简单地返回左操作数的值,而并不会对右操作数进行计算。当左操作数是真值,“&&”运算符将计算右操作数的值并将其返回作为整个表达式的计算结果。

例子:

let o = {x:1};

let p = null;

o && o.x  // => 1  o是真值,因此返回值为o.x

p && p.x  // =>null  p是假值,因此将其返回,而并不去计算p.x

 

“&&”的行为有时候称作“短路”,我们也会经常看到很多代码利用了这一特性来有条件地执行代码。

例子:

if(a == b) stop();  // 只有在a==b的时候才调用stop()

(a == b) && stop();  // 同上

这两行代码是完全等价的 

 

逻辑或(||)

如果左操作数是真值,那么返回这个真值。

如果左操作数是假值,那么计算右侧的表达式,并返回这个表达式的计算结果。

例子1:

let val = a || b || 500;  // 如果变量a已经定义了,直接使用它,否则在使用b,如果b也没有定义,则使用一个默认写死的常量。

 

例子2:

function foo(p) {

  p = p || {};  // 如果参数p没有传入任何对象,则使用一个新创建的对象

}

 

其他运算符 

typeof    检测操作数类型(也可以写成这种形式:typeof())

instanceof  判断其左边对象是否为其右边类的实例,也包含对“父类”的检测(左边一定是对象,右边一定是函数)

in  测试对象的属性是否存在,也可以检测数组中的索引是否存在(可以用这个办法判断“键”是否存在)(关联数组也可以)

delete  删除对象属性或者数组元素(如果是数组,即使删除了其中的元素,但是长度也不会改变)(只能删除自有属性,不能删除继承属性。要删除继承属性,必须从原型对象上删除它) 

eval(str) 执行字符串其中的的 JavaScript 代码(只能传递原始值字符串,不能传递String对象) (是一个函数也是一个运算符) (小程序用不了)

 

语句

声明语句:

var和function都是声明语句,他们声明或定义变量或函数。这些语句定义标识符(变量名和函数名)并给其赋值,这些标识符可以在程序中任意地方使用。

声明语句本身什么也不做,但它有一个重要的意义,通过创建变量和函数,可以更好的组织代码的语义。

 

关键字function用来定义函数,我们已经见过函数定义表达式。函数定义也可以写成语句的形式。函数两种定义写法:

function f(x) { return x+1; }  // 含有变量名的语句(函数声明语句(函数名一定有))

var f = function(x) { return x+1; }  // 将函数表达式赋值给一个变量(函数定义表达式(函数名可有可无))

 

尽管函数声明语句和函数定义表达式包含相同的函数名,但二者仍然不同。两种方法都创建了新的函数对象,但函数声明语句中的函数名是一个变量名,变量指向函数对象。和通过var声明变量一样,语句中的函数被“提前”到了脚本或函数的顶部。因此他们在整个脚本和函数内都是可见的。使用var的话,只有变量声明提前了——变量的初始化代码仍然在原来的位置。然而使用函数声明语句的话,函数名和函数体均提前。

 

条件语句(condition):

if

else if(并不是真正的JavaScript语句,它只不过是多条if/else语句连在一起时的一种惯用写法)

switch(case的判断是用 “===” 判断)(在函数中使用switch语句,可以使用return来代替break)

 

循环语句(loop):

while

do/while

for

for/in(遍历对象中所有可枚举的属性(包括自由属性和继承属性))

 

跳转语句(jump):

break

continue(跳过本次循环)

return(如果没有return,调用表达式的结果是undefined)(可以单独使用,这样的话函数也会向调用程序返回undefined)(由于JavaScript可以自动插入分号,所以return关键字和后面的表达式之间不能有换行)

 

throw语句  (110页)

所谓异常(exception)是当发生了某种异常情况或错误时产生的一个信号。

抛出异常,就是用信号通知发生了错误或异常状况。

捕获异常是指处理这个信号,即采取必要的手段从异常中恢复。

在JavaScript,当产生运行时错误或者程序使用throw语句时就会显示的抛出异常。使用try/catch/finally语句可以捕获异常。

 

throw语句的语法如下:

  throw expression;

expression的值可以是任意类型的。可以抛出一个代表错误码的数字,或者包含可读的错误消息的字符串。

当JavaScript解释器抛出异常的时候通常采用Error类型和其子类型,当然也可以使用他们。

一个Error对象有一个name属性表示错误类型,一个message属性用来存放传递给构造函数的字符串。

例子:当使用非法参数调用函数时就抛出一个Error对象

function factorial(x) {

  if ( x<0 ) throw new Error("x不能是负数");  // 如果输入参数是非法的,则抛出一个异常

  for (var f=1; x>1; f*=x, x--) /* empty */ ;

  return f;

}

 

当抛出异常的时,JavaScript解释器会立即停止当前正在执行的逻辑,并跳转至就近的异常处理程序。

异常处理程序就是用try/catch/finally语句的catch从句编写的,如果抛出异常代码块没有一条相关联的catch从句,解释器会检查更高层的闭合代码块,看他是否有相关联的异常处理程序。以此类推,直到找到一个异常处理程序为止。

如果抛出异常的函数没有处理他的try/catch/finally语句,异常将向上传播到调用该函数的代码。这样的话,异常就会沿着JavaScript方法的词法结构和调用栈向上传播。如果没有找到任何异常处理程序,JavaScript将把异常当成程序错误来处理,并报告给用户。

 

try/catch/finally语句

try/catch/finally语句是JavaScript的异常处理机制。

其中try从句定义了需要处理的异常所在的代码块。catch从句跟随在try从句之后,当try块内某处发生了异常,调用catch内的代码逻辑。

关键词catch后跟随了一对圆括号,圆括号内是一个标识符。这个标识符和函数参数很像。当捕获一个异常时,把和这个异常相关的值(比如Error对象)赋值给这个参数。

例子:

try {

  // 要求用户输入一个数字

  let n = Number(prompt("请输入一个正整数", ""));

  // 假设输入是合法的,计算这个数的阶乘

  let f = factorial(n);  // 调用上面一个例子的factorial函数

  // 显示结果

  alert(f);

}

catch (ex) {

  // 如果输入不合法,将执行这里的逻辑。

  alert(ex);  // 告诉用户发生了什么错误

}

 

posted @ 2020-07-16 10:48  或许从前  阅读(181)  评论(0)    收藏  举报