《JavaScript高等程序设计》(第3版)读书笔记 第3章

  • 函数名不能运用关键字(typeof不可但typeOf可以,辨别大小写)
  • 标识符就是指变量、函数、属性的名字,或许函数的参数。

    1. 第一个字符必须是一个字母、下划线(_)或许一个美圆标记($)
    2. 其他字符可以是字母、下划线、美圆标记或许数字
  • 严厉形式 在严厉形式下ECMAScript3中的一些不肯定的行动将获得处置惩罚,而且某些不安全的操纵也会抛出毛病。
// 在全部剧本顶部增加
"use strict";

// 在函数内部上方运用
function deSmoething() {
    "use strict";
    // 函数体
}
  • 关键字和保存字

break,do,instanceof,typeof,case,else,new,var,catch,finally,return,viod,continue,for,switch,while,debugger*,function,this,with,default,if,throw,delete,in,try

  • 在函数中不必var声明变量会变成全局变量,有用但不引荐
function test() {
    message = 'hi'; //全局变量
}
test()
alert(message);     // 'hi'
  • 数据范例 ECMAScript中有5种简朴数据范例(也称为基础数据范例):Undefined,Null,Boolean,Number,String 另有1种庞杂数据范例Object。本质上Object是由一组无序名值对构成的。
  • typeof返回值”undefined”,”boolean”,”string”,”number”,”object”,”function” (typeof null 返回效果是”object”) (typeof 是操纵符不是函数因而最好不要用括号)

Undefined

// 在运用bar声明变量但未对其加以初始化,这个变量的值就是undefined
var message;
alert(message == undefined); // true
alert(message === undefined); // true

// 下面这个变量并没有声明
// var age
alert(age); // 报错

// 关于还没有声明过的变量,只能实行一项操纵,即运用typeof操纵符检测其数据范例
// 对未经声明的变量运用delete不会致使毛病,但如许没什么意义,而且在严厉形式下确切会致使毛病
var message;
// var age
alert(typeof message);     // "undefined"
alert(typeof age);        // "undefined"

// 二者都返回了undefined这个效果有其逻辑上的合理性。
// 因为虽然这两种变量从手艺角度看有本质区分,但实际上不管对那种变量也不可能实行真正的操纵

Null 只需一个值的数据范例(null)

// 从逻辑角度来看,null值示意一个空对象指针,而这也恰是运用typeof 操纵符检测null值时会返回"object"的缘由
var car = null;
alert(typeof car);         //    "object"

//  如果定义变量预备在未来用于保存对象,那末最好将该变量初始化为null而不是其他值
if (car != null) {
   ...
}

// 实际上 undefined值时派生自null值得,因而ECMA-262划定他们的相称性测试要返回true
alert(null == undefined)     // true
  • Boolean 范例
// 虽然Boolean范例的字面值只需2个,但ECMAScript中一切范例的值都有与这两个Boolean值等价的值。
// 任何非零的数字值(包括无穷大)返回true   0 和 NaN 返回false
var message = "Hello world!";
Boolean(message);  // true
Boolean(0);        // false
Boolean(NaN);      // false
Boolean(Infinity)  // true
Boolean({})        // true
Boolean(null)      // false

Number 范例

// 八进制如果字面值中的数值超出了局限,那末前导零将被疏忽,背面的数值将被当作十进制数值剖析
var num1 = 070;         // 八进制的56
var num2 = 079;         // 无效的八进制数值 剖析为79
var num3 = 08;          // 无效的八进制数值 剖析为8

// 八进制字面量在严厉形式下是无效的,会致使支撑该形式的JavaScript引擎抛出毛病

// 十六进制字面值的前两位必须是0x 后跟任何十六进制数字 (0~9 和 A~F)个中字母不辨别大小写
// 在举行算术盘算时,一切八进制和十六进制示意的数值终究都将被转换成十进制数值

// 鉴于JavaScript中数值的保存体式格局,可以保存正零和负零,二者被认为相称
0 === -0  // true

浮点数。

  • 因为保存浮点数值须要的内存空间是保存证书值得两倍,因而ECMAScript会不失时机的将浮点数值转换为整数值。
  • 浮点数值的最高精度是17位小数,但在举行盘算时其精度远远不如证书。比方 0.1 + 0.2 = 0.30000000000000004
  • 这是运用基于IEEE754数值的浮点盘算的通病,ECMASscript并不是独此一家

数值局限。

  • 因为内存限定,ECMAScript可以示意的最小值保存在Number.MIN_VALUE 在大多数浏览器中这个值时 5e-324
  • 最大值在Number.MAX_VALUE中在大多数浏览器中,这个值是1.7976931348623157e+308;
  • 如果盘算中超过了局限将会自动转换成Infinity或-Infinity
  • Infinity没法列入盘算
  • 想要肯定某个值是不是是有穷的(在局限内)运用isFinite()函数
  • 接见Number.NEGATIVE_INFINITY 和 Number.POSITIVE_INFINITY 也可以获得负和正Infinity的值

Infinity + -Infinity // NaN

var res = Number.MAX_VALUE + Number.MAX_VALUE;
alert(isFinite(res));     //    false

NaN 即非数值(Not a Number)

  • 是一个特别的数值,这个数值用于示意一个原本要返回数值的操纵数值未返回数值的状况
  • 任何触及NaN的操纵都邑返回NaN
  • NaN与任何值都不相称,包括NaN自身
  • isNaN() 在接收到一个值以后,会尝试将其转换为数值,而任何不能转换为数值的值都邑致使全部函数返回true
isNaN(NaN);     // true
isNaN(10);     // false
isNaN('10');     // false
isNaN('blue'); // ture
isNaN(true);     // false 可以被转换成数值1

// isNaN()也适用于对象,在基于对象挪用isNaN()函数时,会起首挪用对象的valueOf()要领,然后肯定该要领返回的值是不是可以转换为数值
// 如果不能,则基于全部返回值再挪用toString()要领,再测试返回值
// 全部历程也是ECMAScript中内置函数和操纵符的平常实行流程
var o = {
    valueOf: function() {
        return '10';
    }
}
isNaN(o)    // false 先返回'10' 再转换成数值10

var o2 = {
    valueOf: function() {
        return 'blue';
    }
}
isNaN(o2)    // ture 先返回'blue' 不能转换成数值
  • 数值转换。Number() parseInt() parseFloat()
Number(true) // 1
Number(false) // 0
Number(null) // 0
Number(undefined) // NaN
Number('') // 0

// 如果字符串中包括有用的十六进制花样,比方"0xf" 则将其转换为雷同大小的十进制数值
// Number()也适用于对象,在基于对象挪用isNaN()函数时,会起首挪用对象的valueOf()要领,然后肯定该要领返回的值是不是可以转换为数值
// 如果不能,则基于全部返回值再挪用toString()要领,再测试返回值

// 因为Number()函数在转换字符串时比较庞杂而且不够合理,因而在处置惩罚整数和浮点数的时刻更一般的做法是用parseInt() 和 parseFloat()函数
Number('    0.2') // 0.2
Number('   02blue') // NaN
parseInt('   02blue') // 2
Number('    0.2blue;') // NaN
parseFloat('    0.2blue;') // 0.2

// 在运用parseInt() 剖析八进制字面量的字符串时 ECMAScript 3 和 5 存在不合
// ECMAScript 3 认为56(八进制), ECMAScript 5 认为是70(十进制)
var num = parseInt("070")
// ECMAScript 5 中 parseInt()已不具有剖析八进制值得才能 纵然在非严厉形式下也是云云
// 可认为这个函数供应第二个参数防止歧义
var num = parseInt("0xAF", 16);  // 175
// 实际上,如果有第二个参数,字符串可以不带“0x”
var num1 = parseInt("AF", 16);   // 175
var num2 = parseInt("AF");   // NaN

// parseFloat() 从第一个字符最先剖析,直到末端,除非剖析到碰见一个无效的浮点数为止
// 第一个小数点有用以后就无效
// parseFloat() 只能剖析十进制值,因而它没有用的哥参数指定基数的用法,非十进制数值都返回0
// 如果字符串包括的是一个可剖析为整数的数,会返回一个整数
parseFloat("1234blue"); // 1234
parseFloat("0xA"); // 0
parseFloat("22.24.5")  // 22.24
parseFloat("0980.5"); // 980.5
parseFloat("3.125e"); // 31250000

String 范例

  • 单引号和双引号都可以用,单首尾必须婚配

字符字面量

  • \n 换行
  • \t 制表
  • \b 退格
  • \r 回车
  • \f 进纸
  • \ 斜杠
  • \’ 单引号
  • \” 双引号
  • \xnn 以十六进制代码nn示意的一个字符(个中n为0~F) 比方 \x41示意”A”
  • \unnnn 以十六进制代码nnnn示意一个Unicode字符 (个中n为0~F) 比方 \u03a3 示意希腊字符 sigma 字符串length为1

字符串的特性

  • ECMAScript 中的字符串时不可变的,字符串一旦建立,它们的值就不能转变
  • 要转变某个变量保存的字符串,起首要烧毁本来的字符串,然后再用另一个包括新值得字符串添补该变量
var lang = "Java";
lang = lang + "Script";
/*
 * 完成这个操纵的历程
 * 起首建立一个能包容10个字符的新字符串,然后再这个字符串中添补"Java" 和 "Script"
 * 末了烧毁本来的字符串"Java"和字符串"Script",因为这个两个字符串都没用了
 * 这个历程是在背景发作的,而这也是某些旧版本的浏览器 (1.0以下的Firefox IE6等)拼接字符串速率很慢的缘由地点
 */

转换为字符串

  • 运用险些每一个值都有的toString()要领 数值、布尔值、对象和字符串值(没错,字符串也有)
  • 在不知道要转换的值是不是是null或undefined的状况下,还可以运用转型函数String()
// toSring()可以传一个参数:输出数值的基数 默许十进制
var num = 10;
num.toString();   // "10"
num.toString(2);  // "1010"
num.toString(8);  // "12"
num.toString(10); // "10"
num.toSring(16);  // "a" 

null.toString();  // 报错
undefined.toString() // 报错 

String(null); // "null"
String(undefined); // "undefined"

Object 范例

// 三种写法等价 但不发起省略小括号
var o = {};
var o = new Object();
var o = new Object;
o.constructor // ƒ Object() { [native code] }  就是Object() 
  • 从手艺角度讲,ECMA-262中对象的行动不一定适用于JavaScript中的其他对象。浏览器环境中的对象,比方BOM DOM中的对象都属于宿主中的对象

Object 的每一个实例都具有以下属性和要领

  1. constructor: 保存着用于建立当前对象的函数
  2. hasOwnProperty(propertyName): 用于搜检给定的属性在当前对象实例中(而不是在实例的原型中)是不是存在。个中propertyName必须为字符串
  3. isPrototypeOf(object): 用于搜检传入的对象是不是是当前对象的原型
  4. propertyIsEnumerable(propertyName): 用于搜检给定的属性是不是能运用for-in语句来罗列
  5. toLocaleString(): 返回对象的字符串示意,该字符串与实行环境的区域对应
  6. toString(): 返回对象的字符串
  7. valueOf(): 返回对象的字符串、数值或布尔值示意。一般与toString()要领的返回值雷同
// Baz.prototype, Bar.prototype, Foo.prototype 和 Object.prototype 在 baz 对象的原型链上:
function Foo() {}
function Bar() {}
function Baz() {}

Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);

var baz = new Baz();

console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true

操纵符

  • 包括算数操纵符、位操纵符、关联操纵符和相称操纵符

递增和递减操纵符

  • 前置型 在赋值前转变
  • 后置型 在赋值后转变
var num1 = 2;
var num2 = 20;
var num3 = --num1 + num2;         // 21
var num4 = num1 + num2;           // 21

var num1 = 2;
var num2 = 20;
var num3 = num1-- + num2;         // 22
var num4 = num1 + num2;           // 21
  • 在运用于一个包括有用数字字符串时,先将其转换为数值
  • 在运用于一个包括有用数字的字符串时,将变量的值设置为NaN,字符串变量变成数值变量
  • false,先将其转换为0
  • true,先将其转换为1
  • 浮点数,一般加减1
  • 对象,先挪用vlueOf()要领,如果效果是NaN则挪用toString()要领后再运用前述划定规矩
var s1 = "2";
var s2 = "z";
var b = false;
bar f = 1.1;
var o = {
    varluOf: function() {
        return -1;
    }
};

s1++;        // 3
s2++;         // NaN
b++;        // 1
f--;         // 0.10000000000000009 浮点运算舍入毛病致使
o--;        // -2

一元加减操纵符

  • +号会对值自动转换成数值
  • -号会变成负数

位操纵符

  • ECMAscript中的一切数值都以IEEE-754 64位花样存储,但位操纵符并不直接操纵64位值。而是先将64位的值转成32位的整数,然后实行操纵 ,末了再将效果转回64位。
  • 32位中的前31位示意整数的值,第32位示意数值的标记,0为正数 1为负数,即为标记位
  • 正数以纯二进制花样存储,没有用到的位用0添补
  • 负数一样二进制,但运用的花样是二进制补码

    • 求数值绝对值的二进制码
    • 求二进制反码 将0换成1 1换成0
    • 获得的二进制反码加1
  • NaN 和 Infinity 运用位操纵符时,会被当作0处置惩罚,因为64位转存32位致使
  • 按位非(NOT) ~
  • 按位与(AND) &
  • 按位或(OR) |
  • 按位异或(XOR) ^ 两个数值对应位上只需一个1时才返回1,其他状况返回0
  • 左移 << 右边以0添补
  • 有标记的右移 >> 数值向右挪动,保存标记位,左边以标记位数值添补
  • 无标记的右移 >>> 数值向右挪动,不保存标记位 ,左边以0来添补
var oldvalue = 64;
var newvalue = oldvalue >> 5;        // 二进制10

var oldvalue = 64;
var newvalue = oldvalue >>> 5;      // 二进制 10

var oldvalue = -64;
var newvalue = oldvalue >>> 5;         // 即是十进制的134217726

布尔操纵符

  • 逻辑非 !
!"blue"       // false
!0            // true
!NaN          // true
!null         // true
!undefined    // true
!""           // true
!12345        // false
  • 逻辑与 && 逻辑与是短路操纵,如果第一个操纵数可以决议效果,就不会对第二个操纵数求值

    var found = true;
    var result = (found && someUndefinedVariable);     // 抛出毛病
    alert(result);         //    这里不会实行
    
    var found = false;
    var result = (found && someUndefindedVariable);     // 不会发作毛病
    alert(result);         // false
    • 如果第一个操纵数是对象,则返回第二个操纵数
    • 如果第二个操纵数是对象,则只需在第一个求值效果为true的时刻才会返回该对象
    • 如果两个都是对象,则返回第二个操纵数
    • 如果有一个操纵数是null 则返回null
    • 如果有一个操纵数是NaN 则返回NaN
    • 如果有一个操纵数是undefined 则返回undefined
    • 如果求值的操纵数没有声明会抛出毛病
  • 逻辑或 || 短路操纵

    var found = true;
    var result = (found || someUndefinedVariable);     // 不会抛出毛病
    alert(result);         // true   
    
    var found = false;
    var result = (found || someUndefindedVariable);     // 发作毛病
    alert(result);         // 这里不会实行
    • 如果第一个操纵数是对象,则返回第一个操纵数
    • 如果第一个操纵数求值为false,则返回第二个
    • 如果两个都是对象,则返回第一个操纵数
    • 如果两个操纵数都是null 则返回null
    • 如果两个操纵数都是NaN 则返回NaN
    • 如果两个操纵数都是undefined 则返回undefined
    • 如果求值的操纵数没有声明会抛出毛病

乘性操纵符

  • 如果介入盘算的某个操纵数不是数值,背景会先试用Number()转型函数
  • 如果有一个操纵数是NaN 则效果是NaN
  • 如果Infinity 与 0 相乘 则效果是NaN
  • 如果Infinity 与 非0相乘 则效果是Infinity 或 -Infinity
  • Infinity * Infinity = Infinity
  • -Infinity * Infinity = -Infinity
  • Infinity / Infinity = NaN
  • 0 / 0 = NaN
  • 非0的有限数 / 0 = Infinity 或 – Infinity
  • 0 / 非0的有限数 = Infinity 或 – Infinity
  • 有限数值 % 0 = NaN
  • Infinity % Infinity = NaN
  • Infinity % 有限数值 = NaN
  • Infinity + -Infinity = NaN
  • +0 + -0 = +0
  • 两个操纵数都是字符串 效果是拼接字符串
  • 如果只需一个操纵数是字符串,则将另一个操纵数转换为字符串
  • +0 – -0 = +0
var num1 = 5;
var num2 = 10;
var message = "the sum of 5 and 10 is " + num1 + num2;
alert(message);    // "the sum of 5 and 10 is 510"
var message = "the sum of 5 and 10 is " + (num1 + num2);
alert(message);    // "the sum of 5 and 10 is 15"

var res6 = 5 - null;  // 5 null转为0

关联操纵符

  • 如果两个操纵数都是字符串,则比较两个字符串对应的字符编码值
  • 如果一个操纵数是数值,则将另一个操纵数转换为一个数值举行比较
  • 如果一个操纵数是对象,挪用valueOf()要领,如果没有则挪用toString()
  • 如果一个操纵数是布尔值,则先将其转换为数值
  • 任何操纵数与NaN举行关联比较,效果都是false
// 字母B的字符编码为66 而字母a的字符编码是97
// 如果要真正依据字母递次比较字符串,就要雷同大小
var result = "Brick" < "alphabet";         // true
var result = "Brick".toLowerCase() < "alphabet".toLowerCase();     // false

// "2" 字符编码是50,而"3"的字符编码是51
var result = "23" < "3";    // true
var result = "23" > 3;    // false

var result = "a" < 3;     // false 因为"a"被转换成了NaN

var result = NaN < 3;    // false
var result = NaN >= 3;    // false

相称操纵符

  • 相称和不相称 == , !=

    • 这两个操纵符都邑先转换操纵数(一般称为强迫转型),然后再比较他们的相称性
    • 如果有一个操纵数是布尔值,先转为1或0再比较
    • 如果一个是字符串另一个是数值,则转换为数值比较
    • 对象先挪用valueOf(),用获得的基础范例值依据前面划定规矩举行比较
    • null 和 undefined 相称
    • 比较之前不能讲null和undefined转换成其他任何值
    • 如果有一个操纵数是NaN,则相称操纵符返回false, NaN也不即是NaN
    • 如果两个操纵数都是对象,则比较他们是不是是同一个对象,如果都指向同一个对象,则相称操纵符返回true
  • 全等和不全等 ===, !==

    • 除了比较之前不转换操纵数范例,全等和不全等与相称和不相称操纵符没有扫码区分。
null == undefined     // true
null === undefined    // false null 和 undefined是两个基础范例

前提操纵符(前提运算符、三元表达式)

variable = boolean_expression ? true_value : false_value;

赋值操纵符

  • = 把右边的值赋给左边的变量
  • *= /= %= += -=
  • <<= 左移赋值
  • ‘>>=’ 有标记右移赋值
  • ‘>>>=’ 无标记右移赋值
  • 主要目标就是简化赋值操纵,运用它们不能带来任何机能的提拔

逗号操纵符

  • 运用逗号操纵符可以在一条语句中实行多个操纵
  • 逗号操纵符总会返回表达式中的末了一项
var num1=1, num2=2, num3=3;
var num = (5,3,2,4,0);     // num的值为0

语句

if语句

  • 引荐代码块写法

do-while 语句

  • 后测试轮回语句,只需在轮回体中的代码实行以后,才会测试出口前提
var i = 2;
do {
    i += 2;
    console.log(i);
} while (i < 2);

// 打印 4

while 语句

  • 前测试轮回语句,在轮回体内的代码被实行之前,就会对出口前提求值
var i = 2;
while (i < 2 ) {
    i += 2;
    console.log(i);
}
// 没有打印

for 语句

  • 前测试轮回语句,在轮回体内的代码被实行之前,就会对出口前提求值
  • 运用while轮回做不到的,运用for轮回一样做不到,也就是说for轮回只是把轮回有关的代码集合在了一个位置
  • 因为ECMAScript中不存在块级作用域,因而在轮回内部定义的变量也可以在外部接见到
// 无穷轮回
for (;;) {
    ...
}

for-in 语句

  • for-in语句是一种精准迭代语句,可以用来罗列对象的属性
  • 轮回输出的属性名的递次是不可展望的,依据浏览器而异
  • 迭代对象为null或undefined会抛出毛病, ECMAScript 5 改正了这一行动,不再抛出毛病,而是不实行语句

label语句

  • 加标签的语句平常都要与for语句等轮回语句合营运用
var i, j;

loop1:
for (i = 0; i < 3; i++) {      //The first for statement is labeled "loop1"
   loop2:
   for (j = 0; j < 3; j++) {   //The second for statement is labeled "loop2"
      if (i == 1 && j == 1) {
         continue loop1;
      }
      console.log("i = " + i + ", j = " + j);
   }
}

// Output is:
//   "i = 0, j = 0"
//   "i = 0, j = 1"
//   "i = 0, j = 2"
//   "i = 1, j = 0"
//   "i = 2, j = 0"
//   "i = 2, j = 1"
//   "i = 2, j = 2"
// Notice how it skips both "i = 1, j = 1" and "i = 1, j = 2"
var itemsPassed = 0;
var i, j;

top:
for (i = 0; i < items.length; i++){
  for (j = 0; j < tests.length; j++){
    if (!tests[j].pass(items[i])){
      continue top;
    }
  }

  itemsPassed++;
}
var i, j;

loop1:
for (i = 0; i < 3; i++) {      //The first for statement is labeled "loop1"
   loop2:
   for (j = 0; j < 3; j++) {   //The second for statement is labeled "loop2"
      if (i == 1 && j == 1) {
         break loop1;
      }
      console.log("i = " + i + ", j = " + j);
   }
}

// Output is:
//   "i = 0, j = 0"
//   "i = 0, j = 1"
//   "i = 0, j = 2"
//   "i = 1, j = 0"
// Notice the difference with the previous continue example

break和continue语句

  • break会马上退出轮回
  • continue虽然也会马上退出轮回,但会从轮回顶部继承实行

with语句 (不引荐运用)

  • 严厉形式下不能运用
  • 定义with语句的目标主要是为了简化屡次编写同一个对象的事情
  • 大批运用with语句会致使机能下落,同时也会给调式代码形成难题
var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href;

with(location) {
    var qs = search.substring(1);
    var hostName = hostname;
    var url = href;
}

switch语句

  • 经由过程为每一个case背面都增加一个break语句,就可以防止同时实行多个case
  • 如果确切须要夹杂集合情况,不要忘了增加解释,申明故意省略了break关键字
  • switch语句在比较值时运用的是全等操纵,因而”10″不即是10
switch (i) {
    case 25:
        // 兼并两种状况
    case 35:
        alert("25 or 35");
        break;
    case 45:
        alert("45");
        break;
    default:
        alert("Other");
}

函数

  • return语句可以不带任何返回值,函数在住手实行后返回undefined
  • 引荐的做法是要么让函数一直都返回一个值要么永久都不要返回值,不然会给调试带来贫苦
  • 严厉形式对函数有一些限定,不然会抛出毛病

    • 不能把函数定名为eval或arguments
    • 不能把参数定名为eval或arguments
    • 不能涌现两个定名参数同名的状况

明白参数

  • ECMAScript函数不介意通报进来多少个参数,也不在乎数据范例,即使定义接收两个参数,挪用时也未必要传两个
  • 缘由是ECMAScript中的参数在内部是用一个数组来示意的,在函数体内可以经由过程arguments对象来接见这个参数数组
  • arguments对象只是与数组相似,它并不是Array的实例,可以用方括号语法接见每一个元素,也有length属性
  • 函数体内部可以arguments[0],arguments[1]…不显式地运用参数,申明ECMAScript函数的一个主要特性:定名的参数只供应便利性,但不是必须的,剖析器不会考证参数名
  • 函数体内可以直接运用arguments[1] = 10 来给参数赋值,但严厉形式下会报错不能运用

没有重载

  • ECMAScript函数不能像传统意义上那样完成重载,而在其他语言中可认为一个函数编写两个定义,只需这两个定义的署名(接收的参数的范例和数目)差别即可
  • 如果在ECMAscript中定义了两个名字雷同的函数,则该名字只属于后定义的函数
    原文作者:陈思达
    原文地址: https://segmentfault.com/a/1190000019059760
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞