JavaScript笔记(1):JavaScript的基本知识

最近在复习之前学过的JavaScript,现在把笔记整理一下

数据类型

整数和浮点数

不区分整形和浮点数,统一使用Number表示

123
0.456
1.2345e3
-99
NaN
Infinity
字符串

使用单引号或者双引号括起来的文本,比如'abc',"abc"都是字符串
使用转义字符''可以帮我们使用一些功能字符,如下
'I'm "OK"!';//打印结果是I'm "OK"!
ASCII表示:\x##
Unicode表示:\u####

多行字符串(下面这种写法只支持ES6标准)

`多行
字符串
测试`

模板字符串
为了方便进行字符串的拼接

var name = 'Jack';
var age = 20
var message = `hello, ${name},are you ${age} year old!`;
alert(message);

获取字符串长度

var s = 'hello';
s.length
布尔值
true;
false;
2  > 1;//true
2 >= 3 ;//false

比较(注意)

==,自动转换数据类型再进行比较
===,不会转换类型,在数据类型不一致的情况下会返回false\

ps:坚持使用===
NaN === NaN;//false,NaN和任何值都不想等,包括自己
可以通过isNaN()方法来对其进行判断
isNaN(NaN);//true
浮点数的比较不能进行直接比较,而要将结果与一个精确值进行比较
Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001;//true

null表示空,和0或者''都不一样,前者表示数值0,后者表示长度为0的字符串,而null表示空
数组

可以是任意数据的集合
var arr = [1,2,3.14,'hello',true];
创建数组var arr1 = new Array(1,2,3);
获取数组长度
arr.length;
通过索引获取数组的索引位置值
arr[2];//3.14
超出访问会扩充数组
arr1[5] = 2;//数组变为[1,2,3,undefined,undefined,2]
常用的数组函数如下

indexOf:根据值求索引
slice:取子数组
push:把元素添加到末尾
pop:返回并移除末尾元素
unshift:把元素添加到头部
shift:删掉头部元素
sort:把数组进行排序
reverse:翻转数组
splice(start,num,arg1...argn):从start开始删除num个元素,并在后面添加n个元素
concat:拼接数组
join:将数组的每个元素都使用join中的元素进行连接
对象

一组由键-值组成的无序集合

var person = {
  name:'Bob',
  age:20,
  city:'ShangHai',
  hasCar:true,
  zipcode:null
}

可以使用对象名.属性名的方式来获取对象的属性
在属性名包含特殊字符,比如单引号时,可以使用对象名[属性名]来访问对象的属性
访问不存在的属性返回undefined
可以使用如下方式进行删除和新增属性,以及判断属性是不是在对象中

person.wife = 'Linda';//增加wife属性
delete person.zipcode;//删除zipcode属性
person.son;//返回undefined,因为没有son属性
hasCar in person;//true,person包含hasCar属性

属性可能是继承得到的,比如toString属性,于是我们使用hasOwnProperty来判断对象自身是不是包含该属性

变量

使用var进行定义,比如var a = 12;

1、a可以是任意类型
2、a的初始类型为int型,但是可以转化为字符串型
strict模式

变量假如没有进行声明即使用,那么变量就会变成全局变量,这样将会对程序的运行进行严重的影响,我们使用strict模式来强制进行变量声明,使用的方式如下

'use strict';
//code体
...
...
...
判断语句

if(some){
}
some是布尔类型,则根据布尔值进行判断
some不是布尔类型,null,undefined,0,NaN和''表示false,其它一律视为true

循环

for ... in//对对象或者数组中的元素进行遍历
for(var key in o){
}

集合

Map

Map是一个键值对的结构,查询速度很快
创建一个Map对象方式如下

var m = new Map([['Michael',95],['Bob',75],['Tracy',85]]);//创建一个Map
m.get('Michael');//通过键值获取值
m.set('Jack',89);//设置一个键值对
Set

没有值重复的一个无序集合,创建如下

var s = new Set([1,2,3,4,3]);
s;//{1,2,3,4}
s.add(5);//添加值
s.delete(4);//删除值
iterable

Map、Array、Set都属于iterable类型,可以使用for ... of来进行遍历

var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
    alert(x);
}
for (var x of s) { // 遍历Set
    alert(x);
}
for (var x of m) { // 遍历Map
    alert(x[0] + '=' + x[1]);
}

for ... in和for ... of的区别,前者得到的是索引,后者得到的是值

函数
一系列动作的抽象
定义
方法1

function 函数名(){
        函数体
}

方法2

var 函数名 = function(){
        函数体
}

举例如下

function abs(x){
    if(x >= 0)
        return x;
    else
        return -x;
}

调用

abs(10);
abs(-9);

调用函数时,即使调用参数与定义的不一样也没关系,可以多出参数数量,也可以少于参数数量,可以调用,但是结果可能不同,总之,JavaScript允许接收任意个参数

函数参数
arguments

我们调用函数时,函数中会有一个默认的关键字,代表了当前函数的调用参数的集合,举个例子

function foo(x){
    alert(x);
    for(var i = 0;i < arguments.length;i++)
        alert(arguments[i]);
}
foo(10,20,30);
rest参数

表示函数参数列表的剩余项,并且只能放在函数参数项的最后,如下所示

function foo(a,b, ...rest){
      console.log('a = ' + a);
      console.log('b = ' + b);
      console.log(rest);
}
foo(1,2,3,4,5);
foo(1);

以上,即使参数没有达到rest所需的条件,rest也不会是undefined,而是会返回一个空的数组

return

要注意JavaScript引擎有个问题,就是你把一句话写成两行的时候,此时该引擎就会在行的末尾自动添加分号,因此我们在return语句中千万注意将一句话写在一行

函数的作用域

1、如果不同的函数各自声明了同一个变量,那么该变量只在各自的函数体中起作用
2、内部函数可以访问外部变量
3、内部函数中的变量假如和外部函数中的变量重名,那么先访问内部函数的变量

全局作用域

不在函数中定义的变量具有全局作用域,实际上,JavaScript将该变量绑定到window上面去了,如下

var course = 'Learn JavaScript';
alert(course);
alert(window.course);//

上面的运行结果是一样的

let

我们使用var定义变量是不能定义具有局部作用域的变量的

'use strict';

function foo() {
    for (var i=0; i<100; i++) {
        //
    }
    i += 100; // 仍然可以引用变量i
}

为了解决这个问题,我们可以使用let进行定义块级的作用域,如下

'use strict';

function foo() {
    var sum = 0;
    for (let i=0; i<100; i++) {
        sum += i;
    }
    i += 1; // SyntaxError
}
const

const是来定义常量的,并且和let一样具有块级作用域

方法

对象中绑定的函数称为对象的方法,如下

var person = {
    name:'Jack',
    birth:1992,
    age:function(){
        var y = new Date().getFullYear();
        return y - this.birth;
    }
}
person.age;//25
person.age();//25
age();//NaN
//这两种方式都可以调用方法

上面的返回结果与this关键字有关,假如我们通过对象调用age方法,那么this指向对象自身,假如我们直接调用age方法,那么this指向window

apply

apply是一个回调的自带函数,我们看一个例子

function getAge(){
    var y = new Date().getFullYear();
    return y - this.birth;
}

var person = {
    name:'Jack',
    birth:1992,
    age:getAge
};

person.age();//25
getAge.apply(person,[]);//25
高阶函数

JavaScript的函数其实都指向一个变量,所以一个函数可以接收另一个函数作为参数,这称为高阶函数,如下

function add(x,y,f){
    return f(x) + f(y);
}
map/reduce

map作为高阶函数,它抽象了运算规则,把运算的方法当成map函数的参数进行传递,然后对计算的结果进行返回,举例如下

function pow(x){
    return x * x;
}
var arr = [1,2,3,4,5,6,7];
arr.map(pow);//返回结果[1,4,9,16,25,36,49]
//arr计算的结果不会覆盖arr原先的值,因此需要进行定义一个变量来接受计算的值

reduce的用法则是,reduce必须接收两个参数,并且将这两个参数计算的结果和下一个元素进行累积运算,如下

[a,b,c,d].reduce(f) = f(f(f(a,b),c),d)

举个例子,将一个数组变成一个整数,如下

var arr = [1,2,3,4];
arr.reduce(function(x,y){
    return x * 10 + y;
});//结果为1234
filter

用于把数组的某些元素过滤掉,然后返回剩下的元素,如下

var arr = [1,2,3,4,5,6,9,10,15];
var r = arr.filter(function(x){
    return x % 2 !=== 0
});
alert(r);//[1,3,5,9,15]
sort

对数组进行排序,但是默认情况下是将数组元素转化为字符串再进行比较,但是我们依然可以对比较的函数进行定义,如下

var arr = [10,20,1,2];
arr.sort(function(x,y){
  if(x < y)
    return -1;
  else
    return 1;
});
闭包
function lazy_sum(arr){
    var sum = function(){
        return arr.reduce(function(x,y){
          return x + y;
        });
    }
}
var f = lazy_sum([1,2,3,4,5]);
f();

像上面这样,将相关参数和变量都保存在返回的函数中,这种结构被称为闭包
创建一个匿名函数并立即执行的语法

(function (x){
   return x * x;
})(3);//9

闭包可以进行私有变量的封装,如下所示

function create_counter(initial){
  var x = initial || 0;
   return {
     inc:function(){
       x += 1;
       return x;
     }
  }
}
var c1 = create_counter();
c1.inc();
c1.inc();
c1.inc()
箭头函数

箭头函数与匿名函数的意思是差不多的,而且简化了函数定义,箭头函数有两种形式,一种是只包含一个表达式,另一种可以包含多条语句,这时候就不能省略{...}和return了,如下
形式1

var fn = x => x * x;

形式2

var fn = x => {
    if(x > 0)
        return x * x;
    else
        return - x * x;
}
this

箭头函数和匿名函数虽然很像,但是它们的this的作用域是不同的,匿名函数的this指向window,而箭头函数的this指向词法作用域

generator

generator和函数的不同之处在于其可以返回多次,它的写法如下所示

function* foo(x){
    yield x + 1;
    yield x + 2;
    return x + 3;
}

标准对象

JavaScript的一切都是对象

Date对象:用来表示日期和时间,可以获取系统的时间,也可以自行设置
var now = new Date();//获取系统的时间
var d = new Date(2015,5,14,20,15,30,123);//设置指定的时间
RegExp对象:正则表达式对象,用于对字符串进行特定规则的匹配

正则匹配(基本)

\d:表示匹配一个数字
\w:表示匹配一个字符
\s:表示匹配一个空格
.:表示匹配任意一个字符
*:表示匹配任意个字符
+:表示匹配至少1个字符
?:表示匹配0或1个字符
{n}:表示匹配n和字符
{n,m}:表示匹配n-m个字符

正则匹配(进阶)

精确匹配可以使用`[]`来表示范围
[0-9a-zA-Z\_]:表示匹配一个数字、字母或下划线
[0-9a-zA-Z\_]+:表示匹配至少一个数字、字母或下划线
[a-zA-Z\_\$][0-9a-zA-Z\_\$]*:匹配由字母、下划线或$开头,后面跟上任意个数字、字母、下划线或者$的字符串
[a-zA-Z\_\$][0-9a-zA-Z\_\$]{0, 19}:对上面的后面字符串的个数进行了限定,不得超出20个
A|B:表示匹配A或B
^表示行的开头,^\d表示必须以数字开头
$表示行的结束,\d$表示必须以数字结束

有两种方式创建一个正则表达式

var re1 = /ABC\-001/;//第一种方法
var re2 = new RegExp('ABC\\-001');//第二种方法

创建完RegExp对象之后,使用test(参数)方法来进行测试

var re = new RegExp('^\d{3}-\d{3,8}');
re.test('010-12345');//true
re.test('010-1234x');//false
re.test('010 12345');//false

切分字符串
str.split('正则表达式'),如下

'a b c'.split(' ');//['a','b','c']
'a b   c'.split('\s+');//['a','b','c']

分组
对字符串进行提取,提取的规则是正则表达式,它的作用是这样的,使用()对正则表达式进行分组,匹配成功之后,返回的结果是一个数组,包括:1、匹配成功的字符串,2、根据()对匹配字符串进行分组的每一个组元素

var re = new RegExp('^(\d{3})-(\d{3,8})$');
re.exec('010-12345');//返回['010-12345','010','12345']

贪婪匹配
匹配尽可能多的字符,正则表达式默认的是贪婪匹配,如下

var re = /^(\d+)(0*)$/
re.exec('102300');//结果为['102300','102300','']

如何采用非贪婪匹配呢,也就是采用尽可能少的匹配呢,加个?就可以了,如下

var re = /^(\d+?)(0*)$/
re.exec('102300');//结果为['102300','1023','00']

全局搜索
全局匹配可以多次执行exec()方法来搜索一个匹配的字符串,如下

var re = /[a-zA-Z]+Script/g;
var s = 'JavaScript,VBScript, JScript and ECMAScript';

re.exec(s);//['JavaScript']
re.lastIndex;//10

re.exec(s);//['VBScript']
re.lastIndex;//20
JSON

一种用来进行传递数据的格式,它规定了字符集必须是UTF-8,规定了字符串和Object的键都必须使用双引号""
使用stringify可以将对象进行序列化,如下

var person = {
    name: '小明',
    age: 14,
    gender: true,
    height: 1.65,
    grade: null,
    'middle-school': '\"W3C\" Middle School',
    skills: ['JavaScript', 'Java', 'Python', 'Lisp']
};

JSON.stringify(person);//'{"name":"小明","age":14,"gender":true,"height":1.65,"grade":null,"middle-school":"\"W3C\" Middle School","skills":["JavaScript","Java","Python","Lisp"]}'
//也可以使用参数让结果更加美观
JSON.stringify(person,null,' ');//{
  "name": "小明",
  "age": 14,
  "gender": true,
  "height": 1.65,
  "grade": null,
  "middle-school": "\"W3C\" Middle School",
  "skills": [
    "JavaScript",
    "Java",
    "Python",
    "Lisp"
  ]
}

反序列化就是讲JSON格式解析成JavaScript对象,使用的方法是parse,如下

JSON.parse('[1,2,3,true]'); // [1, 2, 3, true]
JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14}
JSON.parse('true'); // true
JSON.parse('123.45'); // 123.45

面向对象编程

JavaScript语言没有类和实例的概念,而是通过原型来进行面向对象编程,看下面

var robot = {
    name:'Robot',
    height:1.6,
    run:function(){
        console.log(this.name + ' is running...');
    }
};

var xiaoming = {
    name:'xiaoming'
};

xiaoming.__proto__ = robot;
xiaoming.name;
xiaoming.run();

JavaScript和Java的区别就在于JavaScript没有‘class’概念,而是通过原型链的方式来进行继承,而这种继承,只是将一个对象的原型指向另一个对象而已
JavaScript进行获取对象属性的操作时,会现在当前的对象中找,没有找到就到其原型对象中去找,没有找到就一直追溯到Object.prototype,如果还没找到就返回undefined

构造函数
function Student(name){
    this.name = name;
    this.hello = function(){
        alert('Hello, ' + this.name + '!');
    }
}
var Jack = new Student('Jack');
Jack.name;
Jack.hello();
//上面的函数,如果就是普通调用,就会返回一个undefined,如果使用new关键字,那么就是一个构造函数,它绑定的this指向新建的对象,

如果不同的对象可以共用一部分方法,那么就可以大大减少内存的使用,比如下面

function Student(name){
  this.name = name;
}

Student.prototype.hello = function(){
  alert('Hello, ' + this.name + '!');
};

上面用到了原型的概念,当我们在函数的原型上定义方法时,那么假如定义了该函数的对象,那么不同的对象就可以使用相同的原型方法,这样做可以增加内存的使用效率

原型继承

由于JavaScript没有类的概念,那么JavaScript也不能像类那样进行继承,但是假如我想创建一个继承自原先实例的类,那么我们可以使用原型继承的方式进行继承

function PrimaryStudent(props){
    Student.call(this,props);
    this.grade = props.grade || 1;
}

function F(){
}

F.prototype = Student.prototype;

PrimaryStudent.prototype = new F();

PrimaryStudent.prototype.constructor = PrimaryStudent;

PrimaryStudent.prototype.getGrade = function(){
    return this.grade;
};

var xiaoming = new PrimaryStudent({
    name:'小明',
    grade:2
});

xiaoming.name;//小明
xiaoming.grade;//2

浏览器

浏览器对象
window:表示当前浏览器窗口
navigator:获取浏览器的信息
screen:表示屏幕的信息
location:表示当前页面的URL信息
document:表示当前页面,由于HTML在浏览器中以DOM形式表示为树结构,因此document表示DOM数的根节点
document.getElementById();//由于ID是唯一的,因此可以通过id唯一定位一个元素
document.getElementsByTagName();//返回一组节点,这组节点包含参数而不是和参数一致
document.getElementsByClassName();//返回一组节点,这组节点包含参数而不是和参数一致
history:保存了浏览器的历史记录(不应该继续使用这个对象了)
操作DOM

更新:更新DOM节点表示的HTML内容
有两种形式可以用来更新HTML内容
1、直接修改innerHTML

//获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
p.innerHTML = 'ABC';//结果为<p id="p-id">ABC</p>
p.innerHTML = 'ABC<span style="clolr:red">RED</span>XYZ'

2、通过修改innerText,这样只会修改元素的内容

var p = document.getElementById('p-id');
p.innerText = '<script>alert</script>';//只是会设置p的内容,而不会修改节点的属性

遍历:遍历DOM节点下的子节点
使用节点的children属性可以对节点的子节点进行遍历

var parent = document.getElementById('parent');
var i = 0;
var arr = [];
for(i = 0;i < parent.children.length;i++)
    arr.push(parent.child[i]);

添加:在该DOM节点下新增一个子节点
有两种方法可以添加新的节点
1、使用appendChild

var js = document.getElementById('js'),
       list = document.getElementById('list');
list.appendChild(js);//会将原先的节点添加到list的末尾

2、使用insertBefore
把子节点插入到指定的位置

var list = document.getElementById('list'),
      ref = document.getElementById('python'),
      haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.insertBefore(haskell,ref);

删除:将该节点从HTML中删除
使用removeChild函数可以将节点进行删除,但是此时节点还在内存中

var self = document.getElementById('to-be-removed');//获取被删除的节点

var parent = self.parentElement;

var removed = parent.removeChild(self);

removed = self;//true,说明被删除的节点还存在于内存中

操作表单
获取值,可以通过拿取input节点的value属性来获取用户对应的输入值

var input = document.getElementById('email');
input.value;//

这对于单选或复选只能获取预设值,假如我们需要获取其是否被选上,那么我们可以用checked来进行获取

设置值
对表单节点的value或者checked进行值的设置

提交表单
有两种方式提交表单
1、使用form中的button的onclick进行提交

<!-- HTML -->
<form id="test-form">
    <input type="text" name="test">
    <button type="button" onclick="doSubmitForm()">Submit</button>
</form>

<script>
function doSubmitForm() {
    var form = document.getElementById('test-form');
    // 可以在此修改form的input...
    // 提交form:
    form.submit();
}
</script>

2、使用表单自身的onsubmit事件

<!-- HTML -->
<form id="test-form" onsubmit="return checkForm()">
    <input type="text" name="test">
    <button type="submit">Submit</button>
</form>

<script>
function checkForm() {
    var form = document.getElementById('test-form');
    // 可以在此修改form的input...
    // 继续下一步:
    return true;//只有return true才会提交,return false将不会提交
}
</script>

第一种方式扰乱了浏览器form的正常提交

操作文件
可以进行上传文件的唯一控件就是<input type="file">
但是浏览器处于安全考虑只允许使用点击的方式来上传文件

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

推荐阅读更多精彩内容

  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,204评论 0 4
  • 阳春三月的柳州,并不是想象中的春日苒苒,而是下起了小雨,这是我对这个城市的第一印象。刚从动车上下来的我,接...
    小宇飒阅读 373评论 2 4
  • 深圳,晴。今天是好友新婚之宴席,有些话想要对你说。 祝福的话,席间已经说了很多很多,更重要的是,从今往后,你就成家...
    Echo可可阅读 122评论 0 0
  • 1当一个公共变量会被多个对象同时修改。或者异步线程在同一时刻对其修改,这样会造成在获取某个值时多个对象对其修改会返...
    andy_tu阅读 281评论 0 0
  • 我们
    oasisjiaojiao阅读 212评论 0 0