ECMA 参考文档 es6.ruanyifeng.com/
1 ECMAScript 和 JavaScript
浏览器端JavaScript: ECMAScript、BOM、DOM
node端的JavaScript:ECMAScript、nodeAPI
ECMA: 欧洲计算机标准协会
ECMAScript: 一套语法标准
ECMAScript是JavaScript的规格
JavaScript是ECMAScript的实现
ECMAScript的实现: JavaScript、JScript、ActionScript(flash)
复制代码
1.1 ECMAScript的重要版本
ECMAScript3.0 简称ES3
ES5.0 / ES5.1 新增一些扩展
ES6 2015年6月 又叫ES2015 (之后每年6月份出一个新的版本)
ES7 ES2016
ES8 ES2017
ES9 ES2018
ES10 ES2019
ES11 ES2020
复制代码
2 关键字扩展
2.1 let 关键字
用于声明变量,类似于var
复制代码
特点:
① 不能重复声明
② let声明的变量不会提升
③ let声明的变量可以具有块级作用域
④ let声明的全局变量不再是顶层对象的属性
复制代码
2.2 const 关键字
常量不可改变的量,用途:程序的配置信息习惯用常量定义
复制代码
特点:
① 值不能修改,更不能重复声明
② 不会变量提升
③ 具有全局、局部、块级作用域
④ 全局的常量不是window的属性
复制代码
3 对象的简写
var name = '小明'
var age = '18'
// 属性同名可简写
let obj = {
name
age
say() { //方法简写
},
}
console.log(obj) // { name: '小明', age: '18' }
复制代码
4 解构赋值
4.1 数组解构赋值
// 1. 解构声明
let [a, b, c] = [value1, value2, value3]
// 2. 修改变量的值
[a, b, c] = [value1, value2, value3]
// 3 复杂数组 (保证两边的数组形式一致)
let [a, [b], [c, [d,e]]] = [100, [200], [300, [400, 500]]]
// 4. 声明变量的时候可以有默认值 (与函数传参类似)
let [a, b, c, d = 5] = arr
console.log(a, b, c, d) // 1 2 3 5
复制代码
4.2 对象解构赋值
// 简写形式
{name, age} = {name: 100, age: 200}
// 对象形式
let obj = {
name: '小明',
age: 18,
sex: '男',
love: {
eat: '吃饭',
sleep: '睡觉',
peas: '豆豆',
}
}
let { name, age, sex } = obj
console.log(name, age, sex) // 小明 18 男
// 解构重名
let { name: myname } = obj
console.log(myname) // 小明
// 嵌套解构
let { love: { sleep } } = obj
console.log(sleep) // 睡觉
复制代码
4.3 解构赋值应用
1. 交换两个变量的值 //[a,b]=[b,a]
2. 函数参数的默认值 //这个自己脑补一下,这里写不到,格式乱了就不好看咯 ^-^
3. 函数返回多个参数 //let { name, age } = getInfo();
4. 获取模块的方法 //import {say,eat} from './myModule.js';
复制代码
5 字符串扩展
5.1 模板字符串
两个反引号 ``
特点:
${变量} 直接解析
${表达式} 得到表达式的结果
使用场景:
多行字符串,直接在里面回车
拼接多个变量
复制代码
5.2 字符串对象新增的方法
① ES3
indexOf()
lastIndexOf()
substring()
substr()
slice()截取
split()分割
fromCharCode()
toUpperCase()
toLowerCase()
replace()
match()
search()
复制代码
② ES5
trim() 去除两边的空格
复制代码
③ ES6+
startsWith() 返回布尔值
endsWith() 返回布尔值
includes() 返回布尔值
repeat()
padStart() ES8 补全 //message.padStart(100, '#')
padEnd() ES8 补全 //message.padEnd(100, '#')
matchAll() ES10 message.matchAll(/\w/) //返回迭代器(是所有匹配到的结果)
trimStart() ES10
trimEnd() ES10
复制代码
6 数值的扩展
6.1 指数运算符 (**) ES7
2 ** 4; // 计算2的4次方
2 ** 2 ** 3; //运算顺序,先算右边的
复制代码
6.2 二进制和八进制的表示
// 二进制
0b101010
// 八进制
0o17
// 比以前的八进制表示方式语法更合理,在严格模式可用(0开头表示八进制,严格模式不可用)
复制代码
6.3 Math对象新增的方法
Math.trunc() 去掉小数部分
Math.sign() 判断一个数字是正数、负数、还是0
Math.cbrt() 求立方根
Math.hypot() 求所有参数的平方和的平方根(用于勾股定理计算斜边长度)
复制代码
7 函数扩展
7.1 参数默认值
// ES6的写法
function fn(a,b=默认值) {
}
// ES6之前的写法
function fn(a,b) {
if (b === undefined) {
b = 默认值
}
}
复制代码
7.2 rest参数
function sum(...numbers) {
numbers; //得到一个数组,里面是所有的实参; numbers的名字只要符合标识名命名规范即可
}
与arguments类似
rest参数得到是数组, arguments得到是伪数组
复制代码
7.3 箭头函数
① 语法
// 完整写法
(参数1, 参数2) => {
语句1;
语句2;
}
// 如果参数只有一个 省略()
参数 => {
语句1;
语句2;
}
// 如果只有一条 返回语句
参数 => 语句;
// 相当于
(参数) => {
return 语句;
}
复制代码
② 特点
1. this指向。 与谁调用了箭头函数无关,与声明箭头函数位置有关
看声明箭头函数的地方,是不是嵌套在函数内,如果嵌套在了函数内,看外层函数的this指向;如果没有被函数嵌套,指向window
2. 箭头函数内无法获取aruguments,可以使用rest参数
3. 箭头函数不能作为构造函数
4. 箭头函数不能作为生成器
复制代码
③ 适用场景
使用场景:
作为回调函数
不适合的场景:
给对象添加方法;(this指向)
构造函数
生成器函数
复制代码
7.4 函数对象新增属性
name 返回函数名(声明函数时给的名字)
复制代码
8 数组扩展
8.1 扩展运算符
① 定义
rest参数的逆运算 把数组转为用逗号分隔的参数序列
复制代码
② 应用
var nums = [100, 200, 300, 250];
console.log(...nums); //100 200 300 250
// 1. call 数组传参
fn.call({}, ...nums);
fn.apply({}, nums);
// 2. 计算数组中最大的元素
console.log(Math.max(...nums));
// 3. 把一个数组的成员 追加到另一个数组的尾部
var names = ['曹操', '张仁', '刘备'];
nums.push(...names); // nums, push('曹操', '张仁', '刘备')
console.log(nums); //[100, 200, 300, 250, "曹操", "张仁", "刘备"]
// 4. 克隆数组
// let nums1 = nums; //引用类型
let nums1 = [...nums]; //克隆版
nums1[2]= '啦啦啦';
console.log(nums1); //[100, 200, "啦啦啦", 250, "曹操", "张仁", "刘备"]
console.log(nums); //[100, 200, 300, 250, "曹操", "张仁", "刘备"]
// 5. 合并数组
var newArr = [...nums, ...nums1, ...names];
console.log(newArr);
// 6. 把字符串和类数组对象转为数组
// 字符串转换为数组
var newArr1 = [...'hello'];
console.log(newArr1); //["h", "e", "l", "l", "o"]
// 类数组对象转为数组
var btns = document.querySelectorAll('button');
console.log(btns);
console.log([...btns]);
复制代码
③ 解构赋值版的rest参数
// 与解构赋值一起使用 (解构赋值版的rest参数)
let [a, ...b] = [10,20,30,40,50,50,60];
console.log(a, b); // a是10, b是数组[20, 30, 40, 50, 50, 60]
复制代码
8.2 Array函数新增方法
Array.from() 把类数组/字符串转为纯数组
Array.of() 创建数组,参数是数组的成员(任意个数的参数)
复制代码
8.3 Array实例新增的方法
find() 参数是回调函数,返回第一个满足条件的元素
findIndex() 参数是回调函数,返回第一个满足条件的元素的索引
fill() 填充数组,覆盖数组中所有的元素;适合填充 new Array(20)创建的数组
includes() 判断数组中是否包含某个元素,返回布尔值; ES7新增的
flat() 把数组拉平(多维数组变为一维数组),参数默认是1(只拉1层), 可设置Infinity,不论维位数租 ES10新增
flatMap() 参数是回调函数,相当于map和flat的结合 ES10新增
复制代码
9 对象的扩展
9.1 属性名表达式
{
[表达式]:值,
属性名: 值
}
复制代码
9.2 super 关键字
1. super指向调用该函数的实例的原型
2. 只有对象的方法中才有super关键字,且简写形式定义的方法
{
foo(){
super; //得到super
}
}
复制代码
9.3 对象的扩展运算符 … (ES9新增)
定义:把对象转为用逗号分隔的键值对序列
作用:对象的解构赋值,对象操作(对象合并、对象克隆)
复制代码
// 定义对象
var obj = {
name: '曹操',
age: 100,
say() {
console.log('My Name is ' + this.name);
},
};
console.log({ ...obj });
// 相当于
console.log(name:'曹操', age:100, say:fn);
// 对象克隆 (浅克隆)
let obj2 = { ...obj };
obj2.name = '刘备';
console.log(obj2);// {name: "刘备", age: 100, say: ƒ}
console.log(obj); // {name: "曹操", age: 100, say: ƒ}
// 对象合并 如果有重名属性,后面覆盖前面的
var obj3 = { width: 100, height: 200, name: '孙权' };
var obj4 = { ...obj, ...obj3 };
console.log(obj4); //{name: "孙权", age: 100, width: 100, height: 200, say: ƒ}
// 用于对象的解构赋值
let { age, ...b } = obj;
console.log(age); //100
console.log(b); //{name: "曹操", say: ƒ}
复制代码
9.4 Object函数新增的方法
Object.is() 用于比较两个数据是否相等,返回布尔值; 类似于全等,不同点NaN和Nan相等、+0和-0不相等
Object.assign() 合并对象
Object.getOwnPropertyDescriptor() 获取某个自身属性的描述信息
Object.getOwnProppertyDescriptors() 获取对象所有自身属性的描述信息 ES8新增
Object.getPrototypeOf() 获取对象的原型
Object.setPrototypeOf() 给对象设置原型
Object.keys() 返回数组,由对象的属性名组成。
Object.values() 返回数组,由对象的属性值组成。 ES8新增
Object.entries() 返回二维数组,由属性名和属性值 组成 ES8新增
Object.getOwnPropertyNames() 返回数组,有对象的属性名组成
Object.formEntries() Object.entries()的逆运算 ES8新增
复制代码
10 Class语法
10.1 定义类(构造函数)
//定义类
class Person {
//属性,会添加到实例上
//把所有的属性在这里声明
name = null;
age = null;
//定义构造方法 实例化的时候自动执行
constructor(name, age = 10) {
this.name = name;
this.age = age;
}
// 方法,添加到原型上
say() {
console.log('MY Name is ' + this.name);
}
eat() {
console.log('My age is ' + this.age);
}
// 静态方法 没有添加到实例上,构造函数本身的方法
// static getClassName() {
// console.log('类名是 Person 构造函数本身的方法');
// }
}
// Person.getClassName(); // 相当于 Person.getClassName = function(){}
console.log(Person); //输出整个函数
console.log(typeof Person); // Function
console.log(Person.name); // Person ===> name指的是这个对象的名字
// 不能调用
// Person();
// 实例化
var p = new Person('曹操', 19);
console.log(p); // Person {name: "曹操", age: 19}
p.say(); // MY Name is 曹操
var p1 = new Person('吕布', 21);
console.log(p1); // Person {name: "吕布", age: 21}
p1.say(); // MY Name is 吕布
var p2 = new Person();
console.log(p2); // Person {name: undefined, age: 10}
复制代码
注意:
class定义的类 本质还是个函数,但是不能被调用,只能被实例化
typeof 类名 === ‘funciton’
10.2 实例化
new 类名;
new 类名(构造方法的参数);
复制代码
注意:
① 类中定义的属性会添加到实例上
② 类中定义的方法会添加到原型上
10.3 静态方法
class Person{
static 方法名() {
}
}
Person.方法名()
// 静态方法没有添加给实例,添加给构造函数(类)本身
复制代码
10.4 getter 和 setter
class Person {
firstName = '东方';
lastName = '不败';
get fullName() {
return this.firstName + '_' + this.lastName;
}
set fullName(value) {
this.firstName = value.split('·')[0];
this.lastName = value.split('·')[1];
}
}
let p = new Person();
console.log(p); //Person {firstName: "东方", lastName: "不败"}
console.log(p.fullName); //可读可写 东方_不败
复制代码
10.5 继承
1. 使用 extends 来继承
2. 继承之后:
子类的实例的原型指向父类的一个实例
子类自己的原型指向父类 (静态方法也可以继承)
3. 可以在子类上添加属性和方法
4. 在子类上重写父类的方法,子类重写的方法必须调用super()
复制代码
class 子类 extends 父类 {
constructor() {
super();
}
}
复制代码
11 新增的数据类型(原始类型)
11.1 Symbol
1. 创建一Symbol数据(只能调用,不能实例化)
Symbol()
2. symbol数据特点:
① 每创建一个symbol类型的数据,都是唯一的。
② symbol类型的数据可以作为属性名,同字符串一样
3. 应用:
给对象添加属性(不会被覆盖)
4. 注意:(可以作为对象的属性名,(字符串)symbol创建的都是唯一的)
for...in、Object.keys()、Object.values()、Object.entries()、Object.getOwnPropertyNames() 这些方法都取不到属性名时Symbol类型的属性
Object.getOwnPropertySymbols() 获取类型是symbol的属性名
Reflect.ownKeys(obj) 获取对象自身所有的属性(不论属性名时什么类型)
复制代码
11.2 BigInt (ES10)
① 安全数
通过 Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER 两个属性获得
如果整数超过了这个范围,无法按照整型的方式存储,计算会造成不精确
复制代码
② BigInt类型
// 1. 字面量
var a = 100n
console.log(typeof a); //bigint
// 2 转换函数
var b = BigInt(1000)
console.log(b); //1000n
复制代码
bigInt类型的数据适合比较大的数字运算
bigInt类型的数据只能和BigInt类型的数据运算,不能和number类型数据 相互运算
复制代码
12 Set 和 Map
12.1 Set
① 描述
无序且不重复的多个值的 集合
复制代码
② 创建一个Set类型的数据
new Set(); //创建的是空的Set
new Set(数组); //把数组变为Set,去掉重复的值
new Set(类数组)
复制代码
③ Set实例的方法
add(value) 添加一个值
delete(value) 删除一个值
has(value) 判断是否存在某个值
clear() 删除所有的值
keys() 返回遍历器
values() 返回遍历器
entries() 返回遍历器
forEach() 用于遍历
size 属性 获取Set成员的个数
复制代码
④ Set应用
1. 实现数组去重
var arr = new Set([100, 200, 300, 400, 300, 500, 500, 500]);
console.log(arr); //Set(5) {100, 200, 300, 400, 500}
复制代码
12.2 Map
① 描述
Object对象是一种键值对(key-value)的集合,属性名就是键(key),属性值就是值(value)
Map类似于对象,也是键值对的集合,对象的Key只能是字符串和Symbol类型,Map的Key可以是任意类型
复制代码
② 创建Map
// 创建空的
new Map();
// 创建的时候,指定初始值
new Map([
[key,value],
[key,value]
]);
复制代码
③ Map实例的方法
get(key) 获取指定key的值
set(key,value) 设置或添加某个key的值
delete(key) 删除指定的某个key和他值
clear() 清空所有
has(key) 判断某个key是否存在
keys() 返回遍历器,所有key的集合
values() 返回遍历器,所有值的集合
entries() 返回遍历器,所有key-value的集合(二维)
forEach() 用于遍历
复制代码
13.2 iterable 可遍历对象
① 什么是 iterable 可遍历对象
把部署了 iterator 接口的数据结构,称之为'iterable'(可遍历对象)
可以使用 for...of 遍历 iterable对象
iterator接口部署在了数据结构的Symbol.iterator属性上
一个对象,只有具有Symbol.iterator属性,且该属性指向一个返回遍历器的函数,该对象就是
复制代码
② 原生实现了iterator接口的数据结构 (可遍历对象)
Array
Set
Map
String
Arguments
NodeList
HTMLcollection
复制代码
14 generator 生成器
14.1 什么是生成器
生成器就是生成遍历器的函数
复制代码
14.2 定义生成器
function* 生成器名() {
yield 值;
yield 值;
yield 值;
yield 值;
}
复制代码
14.3 yield 关键字
yeild关键返回一个值, 遍历的时候每次得到就是yield的值
调用next(),执行到yield就会停止; 下一次调用next(),执行到下一个yield停止
复制代码
调用生成器函数的时候,函数内不会执行
当调用next()的时候,才开始执行生成器函数内的代码; 执行到yield停止
14.4 使用生成器函数给对象部署iterator接口
let obj = {
name: '曹操',
age: 18,
score: 80,
height: 170
};
// 把obj变为一个 iterable
// 部署iterator接口
obj[Symbol.iterator] = function* (){
for (let i in obj) {
yield [i, obj[i]];
}
};
for (let i of obj) {
console.log(i);
}
复制代码
15 模块
15.1 模块中导出数据
// 模块内部
function say() {}
function eat() {}
export {
say,
eat
}
复制代码
15.2 导入模块
import {say, eat} from '模块文件路径';
复制代码
16 总结
16.1 ECMAScript中数据类型
原始类型(值类型): string、number、boolean、null、undefined、symbol、bigint
对象类型(引用类型): array、object、regexp、set、map......
复制代码
16.2 ECMAScript中声明变量的方式
1、 var
2、 function
3、 let
4、 const (常量)
5、 class
6、 import
复制代码
16.3 实现数组扁平化的方式
var arr = [['a', 'b'],[10, [100, 200]],[['A', 'B'],['一', '二'],],1000,];
//方式一
//console.log(arr.flat(Infinity)); //["a", "b", 10, 100, 200, "A", "B", "一", "二", 1000]
//方式二 利用字符串的join方法 缺点:数组的元素都会变为字符串类型
//let arr2 = arr.join().split(',');
//console.log(arr2); //["a", "b", "10", "100", "200", "A", "B", "一", "二", "1000"]
//方式三: 递归
console.log(flatArray(arr));
function flatArray(array) {
//创建一个空数组
let res = [];
// 遍历传进来的数组
for (var i = 0; i < array.length; i++) {
//判断数组的元素还是不是数组
if (array[i] instanceof Array) {
res = res.concat(flatArray(array[i])); //继续调用把返回值拼起来,然后再赋值给res
// res = [...res, ...flatArray(arr[i])];
// res = res.concat(arguments.callee(arr[i]));
} else {
res.push(array[i]); //添加一个数组
}
}
//返回新数组
return res;
}
复制代码
16.4 实现数组对象拷贝(克隆)的方式 (浅拷贝)
数组Array:
1. [...arr]
2. arr.concat() 不写参数 数组合并方式
3. arr.splice(0) / arr.substring(0) / arr.substr(0) 数组截取方式
对象Object:
1. {...obj}
2. Object.assign(obj) 对象合并方式
复制代码
16.5 实现对象的深度克隆(深拷贝)
let sons = ['曹丕', '曹植', '曹冲'];
let sister = { name: '曹芹', age: 18 };
var obj = {
name: '曹操',
age: 100,
sons,
sister,
say() {},
eat() {},
};
// 1. 借助于JSON,无法拷贝方法,适合于纯数据对象
// JSON.parse(JSON.stringify(obj));
// 2. 使用递归函数 实现深度克隆
let obj1 = deepClone(obj);
obj1.sister.name = '曹雪芹';
console.log(obj);
console.log(obj1);
//定义函数 获取对象的构造函数(类)名
function getObjectClass(obj) {
return Object.prototype.toString.call(obj).slice(8, -1);
}
//深拷贝的函数
function deepClone(obj) {
//判断obj是对象是数组还是其他
if (getObjectClass(obj) === 'Object') {
var res = {}; //创建空的对象
} else if (getObjectClass(obj) === 'Array') {
var res = []; //创建空数组
} else {
return obj;
}
//对传入的对象(遍历)进行遍历
for (let i in obj) {
res[i] = deepClone(obj[i]);
}
//返回新数组或对象
return res;
}
复制代码
16.6 遍历对象属性的方式
1. for ... in 遍历自身以及原型上可以被遍历的属性,属性名是symbol类型不可以
2. Object.keys()、Object.value()、Object.entries() 自身的属性,属性名是symbol类型不可以
3. Object.getOwnPropertyNames() 自身属性名的集合, 属性名是symbol类型不可以
4. Object.getOwnPropertySymbols() 自身属性名时symbol类型的属性名的集合
5. Reflec.ownKeys() 自身所有的属性名的集合 (字符串和symbol都可以)
复制代码
16.7 Promise 对象
// promise对象状态:成功 -> 触发then的第一个回调
// promise对象状态:失败 -> 触发then的第二个回调
// then(onFulfilled, onRejected)
// 传了两个函数,这两个仅会执行一个
// then方法默认返回值 成功状态promise对象
// 什么时候会返回失败状态promise对象呢?
// 1. 方法中函数的返回值是一个失败状态的promise对象
// 2. 方法报错
// 除了以上两个方式,默认就是成功promise
const promise = new Promise((resolve, reject) => {
// 同步调用
resolve();
// reject();
console.log(111);
});
promise
.then(
() => {
console.log(222);
},
() => {
console.log(333);
// 返回一个失败状态promise对象
// return Promise.reject();
return new Promise((resolve, reject) => {
reject();
});
// 报错
// 抛异常(立即产生一个错误)
// throw 'error';
} // 默认返回成功状态promise对象
)
// 下一个then触发哪个?看上一个then的返回值promise状态
.then(
() => {
console.log(444);
},
() => {
console.log(555);
}
);
console.log(666);
复制代码
16.8 Promise 对象其它状态
// Promise 一个异步编程的解决方案
// 作用:用来解决异步回调地狱问题(消除回调函数,以同步方式表达异步代码)
// 特点:
// 状态:
// 1. pending 初始化状态
// 2. resolved / fulfilled 成功状态
// 3. rejected 失败状态
// 注意:
// 同一时间,只能是一个状态
// 只能由初始化状态变成成功/失败状态
// 不能由成功变成失败,也不能由失败变成成功
// (状态初始化为pending,只能改变一次,要么成功,要么失败)
// 怎么判断promise对象的状态?
// then() / catch()
// 结果值:内部的结果值(value/reason)
// resolve(value)
// reject(reason)
// 怎么得到promise对象内部的结果值?
// then((value) => {}) / await
// catch((reason) => {})
// 怎么创建promise对象?
// new Promise() 默认是pending
// Promise.resolve() 默认是resolved
// Promise.reject() 默认是rejected
// 其他方法:
// Promise.all([promise1, promise2...])
// 返回值是一个新的promise对象,新promise对象状态看传入的promise
// 如果传入的promise状态都是成功的状态,新promise也成功
// 如果传入的promise状态有一个失败,新promise立即失败
// Promise.race([promise1, promise2...])
// 返回值是一个新的promise对象,新promise对象状态看传入的promise
// 只看传入的n个promise,哪一个传入promise状态先发生变化
// 新promise和先发生变化promise的状态一致
// Promise.allSettled([promise1, promise2...]) 源自ES11/ES2020
// 返回值是一个新的promise对象,一定是成功状态
// promise对象内部状态值,包含传入的n个promise对象的状态值
const promise11 = Promise.resolve();
const promise22 = Promise.reject();
console.log('成功状态', promise11); //成功状态 Promise {<fulfilled>: undefined}
console.log('失败状态', promise22); //失败状态 Promise {<rejected>: undefined}
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(111);
}, 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(222);
}, 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(333);
}, 3000);
});
// 只有全部成功才成功,只要有一个失败即失败
// Promise.all([promise1, promise2, promise3])
// .then(value => {
// console.log("成功了", value);
// })
// .catch(reason => {
// console.log("失败了", reason);
// });
// 只看执行最快那个,结果和先发生变化promise的状态一致,无论成功或失败
// Promise.race([promise2, promise1, promise3])
// .then(value => {
// console.log("成功了", value);
// })
// .catch(reason => {
// console.log("失败了", reason);
// });
// 等所有promise都执行完,返回成功状态
Promise.allSettled([promise1, promise2, promise3])
.then((value) => {
console.log('成功了', value);
})
.catch((reason) => {
console.log('失败了', reason);
});
复制代码
牛人不是培养出来的,都是自己努力拼搏出来的,靠谁都不如靠自己,自己都不想主动多学习,只期望用一把锤子,就能搞定所有钉子,那你还不如想想怎么买彩票中500万吧,还更实际些。喜欢就请收藏,不喜还请勿喷!谢谢!加油吧!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END