你需要知道的30个ES6—ES12开发技巧!,成为一名合格前端架构师

这里最终会打印出2。在函数调用时,参数 x, y 将形成一个独立的作用域,所以参数中的y会等于第一个参数中的x,而不是上面定义的1。

5. 箭头函数

ES6中引入了箭头函数,用来简化函数的定义:

const counter = (x, y) => x + y;

相对于普通函数,箭头函数有以下特点:

(1)更加简洁
  • 如果没有参数,就直接写一个空括号即可

  • 如果只有一个参数,可以省去参数的括号

  • 如果有多个参数,用逗号分割

  • 如果函数体的返回值只有一句,可以省略大括号

// 1. 不传入参数

const funcA = () => console.log(‘funcA’);

// 等价于

const funcA = function() {

console.log(‘funcA’);

}

// 2. 传入参数

const funcB = (x, y) => x + y;

// 等价于

const funcB = function(x, y) {

return x + y;

}

// 3. 单个参数的简化

const funcC = (x) => x;

// 对于单个参数,可以去掉 (),简化为

const funcC = x => x;

// 等价于

const funcC = function(x) {

return x;

}

// 4. 上述代码函数体只有单条语句,如果有多条,需要使用 {}

const funcD = (x, y) => { console.log(x, y); return x + y; }

// 等价于

const funcD = function(x, y) {

console.log(x, y);

return x + y;

}

(2)不绑定 this

箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。

var id = ‘GLOBAL’;

var obj = {

id: ‘OBJ’,

a: function(){

console.log(this.id);

},

b: () => {

console.log(this.id);

}

};

obj.a();    // ‘OBJ’

obj.b();    // ‘GLOBAL’

new obj.a()  // undefined

new obj.b()  // Uncaught TypeError: obj.b is not a constructor

对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。同样,使用call()、apply()、bind()等方法也不能改变箭头函数中this的指向:

var id = ‘Global’;

let fun1 = () => {

console.log(this.id)

};

fun1();                     // ‘Global’

fun1.call({id: ‘Obj’});     // ‘Global’

fun1.apply({id: ‘Obj’});    // ‘Global’

fun1.bind({id: ‘Obj’})();   // ‘Global’

(3)不可作为构造函数

构造函数 new 操作符的执行步骤如下:

  1. 创建一个对象

  2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)

  3. 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)

  4. 返回新的对象

实际上第二步就是将函数中的this指向该对象。但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。

(4)不绑定 arguments

箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。

6. 扩展运算符

扩展运算符:… 就像是rest参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。spread 扩展运算符有以下用途:(1)将数组转化为用逗号分隔的参数序列:

function  test(a,b,c){

console.log(a); // 1

console.log(b); // 2

console.log©; // 3

}

var arr = [1, 2, 3];

test(…arr);

(2)将一个数组拼接到另一个数组:

var arr1 = [1, 2, 3,4];

var arr2 = […arr1, 4, 5, 6];

console.log(arr2);  // [1, 2, 3, 4, 4, 5, 6]

(3)将字符串转为逗号分隔的数组:

var str=‘JavaScript’;

var arr= […str];

console.log(arr); // [“J”, “a”, “v”, “a”, “S”, “c”, “r”, “i”, “p”, “t”]

7. Symbol

ES6中引入了一个新的基本数据类型Symbol,表示独一无二的值。它是一种类似于字符串的数据类型,它的特点如下:

  • Symbol的值是唯一的,用来解决命名冲突的问题

  • Symbol值不能与其他类型数据进行运算

  • Symbol定义的对象属性不能使用for...in遍历循环,但是可以使用Reflect.ownKeys 来获取对象的所有键名

let s1 = Symbol();

console.log(typeof s1); // “symbol”

let s2 = Symbol(‘hello’);

let s3 = Symbol(‘hello’);

console.log(s2 === s3); // false

基于以上特性,Symbol 属性类型比较适合用于两类场景中:常量值和对象属性

(1)避免常量值重复

getValue 函数会根据传入字符串参数 key 执行对应代码逻辑:

function getValue(key) {

switch(key){

case ‘A’:

case ‘B’:

}

}

getValue(‘B’);

这段代码对调用者而言非常不友好,因为代码中使用了魔术字符串(Magic string,指的是在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值),导致调用 getValue 函数时需要查看函数代码才能找到参数 key 的可选值。所以可以将参数 key 的值以常量的方式声明:

const KEY = {

alibaba: ‘A’,

baidu: ‘B’,

}

function getValue(key) {

switch(key){

case KEY.alibaba:

case KEY.baidu:

}

}

getValue(KEY.baidu);

但这样也并非完美,假设现在要在 KEY 常量中加入一个 key,根据对应的规则,很有可能会出现值重复的情况:

const KEY = {

alibaba: ‘A’,

baidu: ‘B’,

tencent: ‘B’

}

这就会出现问题:

getValue(KEY.baidu) // 等同于 getValue(KEY.tencent)

所以在这种场景下更适合使用 Symbol,不需要关心值本身,只关心值的唯一性:

const KEY = {

alibaba: Symbol(),

baidu: Symbol(),

tencent: Symbol()

}

(2)避免对象属性覆盖

函数 fn 需要对传入的对象参数添加一个临时属性 user,但可能该对象参数中已经有这个属性了,如果直接赋值就会覆盖之前的值。此时就可以使用 Symbol 来避免这个问题。创建一个 Symbol 数据类型的变量,然后将该变量作为对象参数的属性进行赋值和读取,这样就能避免覆盖的情况:

function fn(o) { // {user: {id: xx, name: yy}}

const s = Symbol()

o[s] = ‘zzz’

}

8. 集合 Set

ES6提供了新的数据结构Set(集合)。它类似于数组,但是成员的值都是唯一的,集合实现了iterator接口,所以可以使用扩展运算符和 for…of 进行遍历。Set的属性和方法:

| 属性和方法 | 概述 |

| — | — |

| size | 返回集合的元素个数 |

| add | 增加一个新的元素,返回当前的集合 |

| delete | 删除元素,返回布尔值 |

| has | 检查集合中是否包含某元素,返回布尔值 |

| clear | 清空集合,返回undefined |

//创建一个空集合

let s = new Set();

//创建一个非空集合

let s1 = new Set([1,2,3,1,2,3]);

//返回集合的元素个数

console.log(s1.size);       // 3

//添加新元素

console.log(s1.add(4));     // {1,2,3,4}

//删除元素

console.log(s1.delete(1));  //true

//检测是否存在某个值

console.log(s1.has(2));     // true

//清空集合

console.log(s1.clear());    //undefined

由于集合中元素的唯一性,所以在实际应用中,可以使用set来实现数组去重:

let arr = [1,2,3,2,1]

Array.from(new Set(arr))  // {1, 2, 3}

这里使用了Array.form()方法来将数组集合转化为数组。可以通过set来求两个数组的交集和并集:

// 模拟求交集

let intersection = new Set([…set1].filter(x => set2.has(x)));

// 模拟求差集

let difference = new Set([…set1].filter(x => !set2.has(x)));

用以下方法可以进行数组与集合的相互转化:

// Set集合转化为数组

const arr = […mySet]

const arr = Array.from(mySet)

// 数组转化为Set集合

const mySet = new Set(arr)

9. Map

ES6提供了Map数据结构,它类似于对象,也是键值队的集合,但是它的键值的范围不限于字符串,可以是任何类型(包括对象)的值,也就是说, Object 结构提供了“ 字符串—值” 的对应, Map 结构提供了“ 值—值” 的对应, 是一种更完善的 Hash 结构实现。如果需要“ 键值对” 的数据结构, Map 比 Object 更合适。Map也实现了iterator接口,所以可以使用扩展运算符和 for…of 进行遍历。Map的属性和方法:

| 属性和方法 | 概述 |

| — | — |

| size | 返回Map的元素个数 |

| set | 增加一个新的元素,返回当前的Map |

| get | 返回键名对象的键值 |

| has | 检查Map中是否包含某元素,返回布尔值 |

| clear | 清空Map,返回undefined |

//创建一个空 map

let m = new Map();

//创建一个非空 map

let m2 = new Map([

[‘name’, ‘hello’],

]);

//获取映射元素的个数

console.log(m2.size);          // 1

//添加映射值

console.log(m2.set(‘age’, 6)); // {“name” => “hello”, “age” => 6}

//获取映射值

console.log(m2.get(‘age’));    // 6

//检测是否有该映射

console.log(m2.has(‘age’));    // true

//清除

console.log(m2.clear());       // undefined

需要注意, 只有对同一个对象的引用, Map 结构才将其视为同一个键:

let map = new Map();

map.set([‘a’], 555);

map.get([‘a’]) // undefined

上面代码的set和get方法, 表面是针对同一个键, 但实际上这是两个值, 内存地址是不一样的, 因此get方法无法读取该键, 所以会返回undefined。由上可知, Map 的键实际上是跟内存地址绑定的, 只要内存地址不一样, 就视为两个键。这就解决了同名属性碰撞( clash) 的问题,在扩展库时, 如果使用对象作为键名, 就不用担心自己的属性与原来的属性同名。如果 Map 的键是一个简单类型的值( 数字、 字符串、 布尔值), 则只要两个值严格相等, Map 将其视为一个键, 包括0和 - 0。另外, 虽然NaN不严格相等于自身, 但 Map 将其视为同一个键。

let map = new Map();

map.set(NaN, 123);

map.get(NaN) // 123

map.set(-0, 123);

map.get(+0) // 123

10. 模块化

ES6中首次引入模块化开发规范ES Module,让Javascript首次支持原生模块化开发。ES Module把一个文件当作一个模块,每个模块有自己的独立作用域,那如何把每个模块联系起来呢?核心点就是模块的导入与导出。

(1)export 导出模块
  • 正常导出:

// 方式一

export var first = ‘test’;

export function func() {

return true;

}

// 方式二

var first = ‘test’;

var second = ‘test’;

function func() {

return true;

}

export {first, second, func};

  • as关键字:

var first = ‘test’;

export {first as second};

as关键字可以重命名暴露出的变量或方法,经过重命名后同一变量可以多次暴露出去。

  • export default

export default会导出默认输出,即用户不需要知道模块中输出的名字,在导入的时候为其指定任意名字。

// 导出

export default function () {

console.log(‘foo’);

}

// 导入

import customName from ‘./export-default’;

注意: 导入默认模块时不需要大括号,导出默认的变量或方法可以有名字,但是对外无效。export default只能使用一次。

(2)import 导入模块
  • 正常导入:

import {firstName, lastName, year} from ‘./profile’;

导入模块位置可以是相对路径也可以是绝对路径,.js可以省略,如果不带路径只是模块名,则需要通过配置文件告诉引擎查找的位置。

  • as关键字:

import { lastName as surname } from ‘./profile’;

import 命令会被提升到模块头部,所以写的位置不是那么重要,但是不能使用表达式和变量来进行导入。

  • 加载整个模块(无输出)

import ‘lodash’; //仅仅是加载而已,无法使用

  • 加载整个模块(有输出)

import * as circle from ‘./circle’;

console.log(‘圆面积:’ + circle.area(4));

console.log(‘圆周长:’ + circle.circumference(14));

注意: import * 会忽略default输出

(3)导入导出复合用法
  • 先导入后导出

export { foo, bar } from ‘my_module’;

// 等同于

import { foo, bar } from ‘my_module’;

export { foo, boo};

  • 整体先导入再输出以及default

// 整体输出

export * from ‘my_module’;

// 导出default,正如前面所说,export default 其实导出的是default变量

export { default } from ‘foo’;

// 具名接口改default

export { es6 as default } from ‘./someModule’;

(4)模块的继承

export * from ‘circle’;

export var e = 2.71828182846;

export default function(x) {

return Math.exp(x);

}

注意: export * 会忽略default。

二、ES7 新特性(2016)


1. Array.prototype.includes

includes() 方法用来判断一个数组是否包含一个指定的值,如果包含则返回 true,否则返回false。该方法不会改变原数组。其语法如下:

arr.includes(searchElement, fromIndex)

该方法有两个参数:

  • searchElement:必须,需要查找的元素值。

  • fromIndex:可选,从fromIndex 索引处开始查找目标值。如果为负值,则按升序从 array.length + fromIndex 的索引开始搜 (即使从末尾开始往前跳 fromIndex 的绝对值个索引,然后往后搜寻)。默认为 0。

[1, 2, 3].includes(2);  //  true

[1, 2, 3].includes(4);  //  false

[1, 2, 3].includes(3, 3);  // false

[1, 2, 3].includes(3, -1); // true

在 ES7 之前,通常使用 indexOf 来判断数组中是否包含某个指定值。但 indexOf 在语义上不够明确直观,同时 indexOf 内部使用 === 来判等,所以存在对 NaN 的误判,includes 则修复了这个问题:

[1, 2, NaN].indexOf(NaN);   // -1

[1, 2, NaN].includes(NaN);  //  true

注意:使用 includes()比较字符串和字符时是区分大小写。

2. 指数操作符

ES7 还引入了指数操作符 ,用来更为方便的进行指数计算,它与 Math.pow() 等效:

Math.pow(2, 10));  // 1024

2**10;           // 1024

三、ES8 新特性(2017)


ES8中引入了异步函数的解决方案async/await,这里不在多介绍,参考文章:《万字长文,重学JavaScript异步编程》[4]

1. padStart()和padEnd()

padStart()和padEnd()方法用于补齐字符串的长度。如果某个字符串不够指定长度,会在头部或尾部补全。

(1)padStart()

padStart()用于头部补全。该方法有两个参数,其中第一个参数是一个数字,表示字符串补齐之后的长度;第二个参数是用来补全的字符串。如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串:

‘x’.padStart(1, ‘ab’) // ‘x’

如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串:

‘x’.padStart(5, ‘ab’) // ‘ababx’

‘x’.padStart(4, ‘ab’) // ‘abax’

如果省略第二个参数,默认使用空格补全长度:

‘x’.padStart(4, ‘ab’) // 'a   ’

padStart()的常见用途是为数值补全指定位数,笔者最近做的一个需求就是将返回的页数补齐为三位,比如第1页就显示为001,就可以使用该方法来操作:

“1”.padStart(3, ‘0’)   // 输出结果: ‘001’

“15”.padStart(3, ‘0’)  // 输出结果: ‘015’

(2)padEnd()

padEnd()用于尾部补全。该方法也是接收两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串:

‘x’.padEnd(5, ‘ab’) // ‘xabab’

‘x’.padEnd(4, ‘ab’) // ‘xaba’

2. Object.values()和Object.entries()

在ES5中就引入了Object.keys方法,在ES8中引入了跟Object.keys配套的Object.values和Object.entries,作为遍历一个对象的补充手段,供for…of循环使用。它们都用来遍历对象,它会返回一个由给定对象的自身可枚举属性(不含继承的和Symbol属性)组成的数组,数组元素的排列顺序和正常循环遍历该对象时返回的顺序一致,这个三个元素返回的值分别如下:

  • Object.keys():返回包含对象键名的数组;

  • Object.values():返回包含对象键值的数组;

  • Object.entries():返回包含对象键名和键值的数组。

let obj = {

id: 1,

name: ‘hello’,

age: 18

};

console.log(Object.keys(obj));   // 输出结果: [‘id’, ‘name’, ‘age’]

console.log(Object.values(obj)); // 输出结果: [1, ‘hello’, 18]

console.log(Object.entries(obj));   // 输出结果: [[‘id’, 1], [‘name’, ‘hello’], [‘age’, 18]

注意

  • Object.keys()方法返回的数组中的值都是字符串,也就是说不是字符串的key值会转化为字符串。

  • 结果数组中的属性值都是对象本身可枚举的属性,不包括继承来的属性。

3. 函数扩展

ES2017 规定函数的参数列表的结尾可以为逗号:

function person( name, age, sex, ) {}

该特性的主要作用是方便使用git进行多人协作开发时修改同一个函数减少不必要的行变更。

四、ES9 新特性(2018)


1. for await…of

for await...of方法被称为异步迭代器,该方法是主要用来遍历异步对象。for await...of 语句会在异步或者同步可迭代对象上创建一个迭代循环,包括 String,Array,类数组,Map, Set和自定义的异步或者同步可迭代对象。这个语句只能在 async function内使用:

function Gen (time) {

return new Promise((resolve,reject) => {

setTimeout(function () {

resolve(time)

},time)

})

}

async function test () {

let arr = [Gen(2000),Gen(100),Gen(3000)]

for await (let item of arr) {

console.log(Date.now(),item)

}

}

test()

输出结果:99078a54e053ba0f7dbd78e65cf5b199.png

2. Promise.prototype.finally()

ES2018 为 Promise 添加了 finally() 方法,表示无论 Promise 实例最终成功或失败都会执行的方法:

const promise = new Promise((resolve, reject) => {

setTimeout(() => {

const one = ‘1’;

reject(one);

}, 1000);

});

promise

.then(() => console.log(‘success’))

.catch(() => console.log(‘fail’))

.finally(() => console.log(‘finally’))

finally() 函数不接受参数,finally() 内部通常不知道 promise 实例的执行结果,所以通常在 finally() 方法内执行的是与 promise 状态无关的操作。

3. 对象的扩展运算符

在ES6中就引入了扩展运算符,但是它只能作用于数组,ES2018中的扩展运算符可以作用于对象:(1)将元素组织成对象

const obj = {a: 1, b: 2, c: 3};

const {a, …rest} = obj;

console.log(rest);    // 输出 {b: 2, c: 3}

(function({a, …obj}) {

console.log(obj);    // 输出 {b: 2, c: 3}

}({a: 1, b: 2, c: 3}));

(2)将对象扩展为元素

const obj = {a: 1, b: 2, c: 3};

const newObj ={…obj, d: 4};

console.log(newObj);  // 输出 {a: 1, b: 2, c: 3, d: 4}

(3)可以用来合并对象

const obj1 = {a: 1, b:2};

const obj2 = {c: 3, d:4};

const mergedObj = {…obj1, …obj2};

console.log(mergedObj);  // 输出 {a: 1, b: 2, c: 3, d: 4}

五、ES10 新特性(2019)


1. trimStart() 和 trimEnd()

在ES10之前,JavaScript提供了trim()方法,用于移除字符串首尾空白符。在ES9中提出了trimStart()和trimEnd() 方法用于移除字符串首尾的头尾空白符,空白符包括:空格、制表符 tab、换行符等其他空白符等。

(1)trimStart()

trimStart() 方法的的行为与trim()一致,不过会返回一个从原始字符串的开头删除了空白的新字符串,不会修改原始字符串:

const s = ’  abc  ';

s.trimStart()   // "abc  "

(2)trimEnd()

trimEnd() 方法的的行为与trim()一致,不过会返回一个从原始字符串的结尾删除了空白的新字符串,不会修改原始字符串:

const s = ’  abc  ';

s.trimEnd()   // "  abc"

注意,这两个方法都不适用于null、undefined、Number类型。

2. flat()和flatMap()

(1)flat()

在ES2019中,flat()方法用于创建并返回一个新数组,这个新数组包含与它调用flat()的数组相同的元素,只不过其中任何本身也是数组的元素会被打平填充到返回的数组中:

[1, [2, 3]].flat()        // [1, 2, 3]

[1, [2, [3, 4]]].flat()   // [1, 2, [3, 4]]

在不传参数时,flat()默认只会打平一级嵌套,如果想要打平更多的层级,就需要传给flat()一个数值参数,这个参数表示要打平的层级数:

[1, [2, [3, 4]]].flat(2)  // [1, 2, 3, 4]

如果数组中存在空项,会直接跳过:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

总结

根据路线图上的重点去进行有针对性的学习,在学习过程中,学会写笔记,做总结。

这里分享一些前端学习笔记:

  • html5 / css3 学习笔记

  • JavaScript 学习笔记

  • Vue 学习笔记

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

[1, 2, [3, 4]]

在不传参数时,flat()默认只会打平一级嵌套,如果想要打平更多的层级,就需要传给flat()一个数值参数,这个参数表示要打平的层级数:

[1, [2, [3, 4]]].flat(2)  // [1, 2, 3, 4]

如果数组中存在空项,会直接跳过:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-R3E6Qcb1-1712808995618)]
[外链图片转存中…(img-La1dRNng-1712808995618)]
[外链图片转存中…(img-9ZKvQ8D4-1712808995619)]
[外链图片转存中…(img-fzx5qtru-1712808995619)]
[外链图片转存中…(img-Yeed7HoR-1712808995619)]
[外链图片转存中…(img-OYTKmdT6-1712808995620)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-hVRkOij1-1712808995620)]

总结

根据路线图上的重点去进行有针对性的学习,在学习过程中,学会写笔记,做总结。

这里分享一些前端学习笔记:

  • html5 / css3 学习笔记

  • JavaScript 学习笔记

  • Vue 学习笔记

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-lpJODiTn-1712808995620)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值