ECMA新特性

ECMA新特性

在这里插入图片描述
在这里插入图片描述

名称标准版本发⾏时间
ECMAScript 2019(ES2019)102019年6⽉
ECMAScript 2018(ES2018)92018年6⽉
ECMAScript 2017(ES2017)82017年6⽉
ECMAScript 2016(ES2016)72016年6⽉
ECMAScript 2015(ES2015)62015年6⽉
ECMAScript 5.1(ES5.1)5.12011年6⽉
ECMAScript 5(ES5)52009年12⽉
ECMAScript 4(ES4)4被放弃
ECMAScript 3(ES3)31999年12⽉
ECMAScript 2(ES2)21998年6⽉
ECMAScript 1(ES1)11997年6⽉

1、const 特性

​ 1.可以用来声明一个只读的衡量/常量,在let的基础上多了只读(变量一旦被声明,就不可以被修改)特性;

​ 2.const 在声明时就必须赋值一个初始值;

​ 3.声明的变量不允许被修改:实际是声明以后不允许被指向一个新的内存地址,是可以修改常量的属性成员;

最佳时间:不用var,主用const,配合let

2、数组的解构:

​ 1.解构的位置定义变量位置一定要正确;
​ 2.在解构的位置前添加三个…:表示提取从当前后置往后的所有成员,只能在我们解构的最后一个位置使用;
​ 3.解构位置的成员个数小于被解构的数组长度,会按照从前往后的顺序去提取,多出的成员就不会被提取;
​ 4.解构位置的成员个数大于北界沟的数组长度,对出的解构成员就是undefined;
​ 5.初始化赋值:在解构成员后“=”直接赋值;

3、对象的解构

​ 1.通过变量名来解构对象:变量名去匹配被解构的对象中的成员,赋值指定成员的值;

​ 2.当前作用域中有同名的成员,就会产生冲突;
​ 3.变量名重命名:在变量的后面(:重命名)

//对象的解构
//const obj = { name: 'lc', age: 30 };
//const name = 'tom';
//const { name } = obj;
//console.log(name); //这样会报错
//对象的解构
const obj = { name: 'lc', age: 30 };
const name = 'tom';
const { name:objName = 'jack' //这里是对象的初始化赋值 } = obj;
console.log(name);
console.log(objName);

4、模板字符串

​ 1.允许换行;
​ 2.允许赋值

const name = 'tom';
const msg = `hi, ${name}`;//花括号内不仅可以是变量,还可以是js语句
console.log(msg);

​ 3.带标签的模板字符串:实际就是把字符串用标签的方法执行一遍;

const name = 'tom';
const gender = true;

let myTagFunc = stings => {
  console.log(stings);//标签里没有什么操作:会打印把字符串以变量分割后的数组 -> ['hey,', 'tom' 'is a', '.']
  gender ? '男' : '女';
}
const result = myTagFunc`hey,${name} is a ${gender}.`;

5.字符串的扩展方法

​ 1.includes():判断字符串中间是否包含某个字符;
​ 2.startsWith:判断字符串是否以某个字符开头;
​ 3.endsWith:判断字符串是否以某个字符结尾;

6.参数的默认值

function foo (enable = ture) {
	console.log('foo invoked - enable:' + enable);
}
//有多个参数的时候,参数默认值必须放最后(原因是:参数是按照次序传递的);
foo();//只有在我们调用的时候没有传递实参,或者实参为undefined时,才会被使用;

7.对象字面量的增强

const bar = '345';
const obj = {
    foo: 123,
    bar,//省略:bar
    method1 () {//省略 :function
        console.log('cs1');
    },
    [Math.random()]: 123//计算属性名
};

8.对象扩展方法

1.assign方法:将多个源对象中的属性复制到一个目标对象中(相同属性, 源对象会覆盖目标对象属性)
const source = {
	a: 123,
	b: 234
};
const target = {
	a: 456,
	c: 567
};
const result = Object.assign(target, source);//可以传递任意多个参数,第一参数为目标对象
console.log(target);//{a: 123, b: 234, c: 567 }
console.log(result === target);//true
2.Object.is 方法:同值比较方法;
Object.is(+0, -0);//false
Object.is(NaN, NaN);//true
3.Proxy 代理
const person = {
   name: 'lc', age: 29
};
/*porxy:实例的第一个参数是代理的对象,
第二个参数:也是一个对象(代理的处理对象)
   get方法:监听属性的访问
      默认接收两个参数:第一个是代理的目标对象
         第二个是外部所访问的属性名
      返回值会作为外部访问这个属性的结果    
   set方法:监听属性的设置
      默认接收三个参数:
         第一个:代理目标对象
         第二个:设置的属性名称
         第三个:设置的属性值
*/
const personProxy = new Proxy(person, {
   get (target, property) {//监视属性的访问
     /*  console.log(target, property);
      return '返回值做为访问的结果' */
      return property in target ? target[property] : undefined; 
   },
   set (target, property, value) {//监视属性的设置
      // console.log(target, property, value);
      if('age' === property) {
         //isInteger方法:判断传入参数是否为整数,如果是返回true,否则返回false
         if(!Number.isInteger(value)){
            throw new TypeError(`value not am int`);
         }
      }
      target[property] = value;
   } 
});

personProxy.age = 18;
personProxy.gender = 'man';

console.log(personProxy.name);//lc
console.log(personProxy.age);//18

​ 1.Proxy VS Object.defineProperty()
​ 1.defineProperty只能监视属性的读写;
​ Proxy能够监视到更多对象操作:

handler ⽅法触发⽅式
get读取某个属性
set写⼊某个属性
hasin 操作符
deletePropertydelete 操作符
getPropertyObject.getPropertypeOf()
setPropertyObject.setPrototypeOf()
isExtensibleObject.isExtensible()
preventExtensionsObject.preventExtensions()
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor()
definePropertyObject.defineProperty()
ownKeysObject.keys() 、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()
apply调⽤⼀个函数
construct⽤ new 调⽤⼀个函数

​ 2.Proxy 更好的支持数组对象的监视;
​ defineProperty 最常见的监视数组的操作:重写数组的操作方法(通过自定义的方法去覆盖数组原型对象上的方法,以此来劫持对应方法的过程);

//Proxy对数组的监视
const list = [];

const listProxy = new Proxy(list, {
    set (target, property, value) {
        console.log('set', property, value);
        target[property] = value;
        return true;//表示设置成功
    }
});

listProxy.push(100);

​ 3.Proxy是以非侵入的方式监管了对象的读写;

//Proxy 不需要侵入对象监视对象的读写
   //必须要通过特定的方式单独去定义对象中那些需要被监视的属性,对于一个存在的对象,我们要去做很多额外的操作
const testPerson = {};

Object.defineProperty(testPerson, 'name', {
   get () {
      console.log('name 被访问');
      return testPerson._name
   },
   set (value) {
      console.log('name 被设置');
      testPerson._name = value;
   }
});
Object.defineProperty(testPerson, 'age', {
   get () {
      console.log('age 被访问');
      return testPerson._age;
   },
   set (value) {
      console.log('age 被设置');
      return testPerson._age = value;
   }
});

testPerson.name = 'jack';
console.log(testPerson.name);

//Proxy 方式更为合理
   //一个已经定义好的对象,我们不需要对对象本身做过多的操作就能监视对象内部成员的读写
const testPerson2 = {
   name: 'lc', age: 20
};

const testPersonProxy = new Proxy(testPerson2, {
   get (target, property) {
      return target[property];
   },
   set (target, property, value) {
      target[property] = value;
   }
});

testPerson2.name = 'jack';
console.log(testPerson2.name);

9.Reflect:统一的对象操作API

​ Reflect 属于一个静态类;
​ 不能通过 new 的方式去构建一个实例对象, 只能去调用这个静态类的静态方法;
​ Reflect 内部封装了一系列对对象的底层操作:
​ Reflect 成员方法就是Proxy 处理对象的默认实现;

//Reflect对象
const Obj = {
    foo: 123, bar:456
};
const proxy = new Proxy(Obj, {
    get (target, property) {
		//在实现自定义的get或set方法时: 更标准的做法是: 先去实现自己的监控逻辑, 再去返回通过Reflect中对应方法的调用结果;
		console.log('watch logic~');
        
        return Reflect.get(target, property);
    }
});

console.log(proxy.foo);

​ Reflect最大的意义:统一提供一套用于操作对象的API;

handler ⽅法默认调⽤
getReflect.get()
setReflect.set()
hasReflect.has()
deletePropertyReflect.delete()
getPropertyReflect.getPrototypeOf()
setPropertyReflect.setPrototypeOf()
isExtensibleReflect.isExtensible()
preventExtensionsReflect.preventExtensions()
getOwnPropertyDescriptorReflect.getOwnPropertyDescriptor()
definePropertyReflect.defineProperty()
ownKeysReflect.ownKeys()
applyReflect.apply()
constructReflect.construct()

10. class类

/* function Person (name) {
   this.name = name;
}
Person.prototype.say = function () {
   console.log(`hi, my name is ${this.name}`);
}
let cs = new Person('lc');
cs.say(); */

class Person {
   constructor (name) {
      this.name = name;
   }
   say () {
      console.log(`hi, my name is ${this.name}`);
   }
    
}

let csa = new Person('lc');
csa.say();

​ 1.静态成员: ES2015中新增添加静态成员的 static 关键词:
​ 类型当中的方法: 实例方法 和 静态方法
​ 实例方法: 通过这个对象构造的实例对象去调用, 静态方法中的 this 指向当前类型;
​ 静态方法: 通过类型本身去调用;

11.类的继承: extends

​ 通过继承, 能够抽象出来相似类型之间重复的地方;
​ 继承对象会拥有被继承类型的所有成员;
​ 在继承对象中用: super() 始终指向被继承类型, 调用super方法就是调用的被继承类型的构造函数, 可以通过super. 调用被继承类型中的方法;

class Person {
   constructor (name) {
      this.name = name;
   }
   say () {
      console.log(`hi, my name is ${this.name}`);
   }  
};

class Student extends Person {
   constructor (name, number) {
      super(name);
      this.number = number;
   }

   hello () {
      super.say();
      console.log(`my school number is ${this.number}`);
   }
};

const student1 = new Student('YM', 30);
student1.hello();// hi, my name is YM //my school number is 30

12.Set数据结构

  1. 可以理解为集合, 里面的成员是不允许重复的;

  2. set是一个类型:
    1.add()方法: 向集合中添加数据: add方法返回集合本身, 可以链式调用继续添加值;
    2.遍历: 集合中的forEach, for of语法;
    3.size方法回去集合的长度;
    4.has()方法: 判断集合中是否存在某个特定的值;
    5.delete()方法: 用来删除集合中某个特定的值, 删除成功后返回true;
    6.clear()方法:用来清除集合中的所有元素;

  3. Array.from(): 将其他对象转化为数组, 返回值为数组;
    […其他对象] 也是放回数组;

    const mySet = new Set();
    
    mySet.add(1).add('lc').add('ym').add(6).add(1);
    console.log(mySet);// set (4) { 1, 'lc', 'ym', 6 }
    
    // mySet.forEach(i => console.log(i))
    // for (let i of mySet) {
    //    console.log(i);
    // }
    
    console.log(mySet.size);//4
    console.log(mySet.delete(1));//true
    console.log(mySet);// set (3) { 'lc', 'ym', 6 }
    mySet.clear();
    console.log(mySet);//set (0) {}
    
    const arr = [1, 2, 2, 3, 5, 8];
    const resultArr1 = Array.from(new Set(arr));
    const resultArr2 = [...new Set(arr)];
    
    console.log(resultArr1);//[1, 2, 3, 5, 8]
    console.log(resultArr2);//[1, 2, 3, 5, 8]
    

13.Map数据结构

const Obj = {};
Obj[true] = 'value1';
Obj[123] = 'value2';
Obj[{a: 1}] = 'value3';

console.log(Object.keys(Obj));//['123', 'true', '[object Object]']
console.log(Obj[{}]);// value3
/* 总结:
   对象的键是以字符串格式存储的, 是将 '[]' 里面的值 toString 后储存, 这样会造成错误;
*/
const MyMap = new Map();
const tom = { name: 'tom' };
//通过set()方法来设置键值对
//这里的键的值可以是任意类型的数据;
MyMap.set(tom, 90);
console.log(MyMap);// Map(1) { {tom:'tom} => 90 }
//通过 get() 方法获取值
console.log(MyMap.get(tom));//90
//通过 has() 方法判断否个键是否存在
console.log(MyMap.has(tom));//true
console.log(MyMap.has('ym'));//false
//delete() 方法 删除某个键
//clear() 方法去清空所有的键值
//size 判断Map的长度
console.log(MyMap.size);
//forEach方法遍历Map: 第一个参数为Map的值, 第二个参数为Map的键
MyMap.forEach((value, key) => {
   console.log(value, key);//90 {name: 'tom}
});

14.Symbol 数据类型

/* 最主要的作用:为对象添加独一无二的属性名;
   从ES2015开始,对象可以用Symbol作为对象的属性名
*/
const cache = {};//如果这个是全局的缓存
cache['foo'] = Math.random();
cache['foo'] = '123';//使用相同的属性名就会出现冲突; 本地一个文件下就会覆盖;
console.log(cache);//{ foo: 123 }

//以前解决这个问题:就是约定 -> a.js 存储缓存文件为 a_, b.js 存储缓存文件为 b_;
//但是问题还是存在;

//Symbol: 表示一个独一无二的值
const S = Symbol();
console.log(S);//Symbol()
console.log(typeof S);//symbol
//通过Symbol创建的值都是唯一的,永远不会重复
console.log(Symbol() === Symbol());// false
//为了方便开发调试,Symbol允许传入一个字符串作为Symbol的描述文本;
console.log(Symbol('a'));//Symbol(a)
console.log(Symbol('b'));//Symbol(b)

const Obj = {};
Obj[Symbol('a')] = '123';
Obj[Symbol('b')] = '456';
console.log(Obj);//{ [Symbol(a)]: '123', [Symbol(b)]: '456' }

//模拟实现对象的私有成员
//a.js ===================
const csName = Symbol();
const person = {
   [csName]: 'lc',
   say () {
      console.log(this[csName]);
   }
};
//b.js ====================
person.say();//lc

1.在全局去复用一个相同的Symbol值, 用Symbol.for():
Symbol.for()所返回的值得作用域是整个代码库(包括不同的iframe或者service worker), 是一个全局的变量, 第一次产生的时候就会登记下来;
调用Symbol.for() 的时候, 会在全局环境中检索给定的key是否存在, 如果不存在会新建一个值;
Symbol. for()维护了一个全局注册表, 为字符串和Symbol值提供了一一对应的关系,
如果for()中传入的不是一个字符串, 这个方法内部会把传入的值自动转化为字符串;

const s1 = Symbol.for('foo');
const s2 = Symbol.for('foo');

console.log(s1 === s2) //true
console.log(Symbol.for('true') === Symbol.for(true));//true

2.遍历对象中的Symbol()属性键值对:
通过for…in循环拿不到对象中Symbol键名, 通过Object.keys 也拿不到对象中的Symbol键名, 通过JSON.stringify()去序列化一个对象, 对象中的Symbol键值对也不能被序列化(转化为JSON格式)

const myObj = {
	[Symbol()]: 'Symbol value',
	foo: 'normal value'
};
for(let key in myObj) {
   console.log(key);// foo
}
console.log(Object.keys(myObj));//['foo']
console.log(JSON.stringify(myObj)); //{"foo": "normal value"}

​ Object.getOwnPropertySymbols(): 返回一个数组, 成员是当前对象所有Symbol键名;
​ Reflect.ownKeys(): 返回所有类型的键名, 包括常规键名和Symbol键名;

const myObj = {
	[Symbol('foo')]: 'Symbol value',
	foo: 'normal value'
};
console.log(Object.getOwnPropertySymbols(myObj));//[Symbol('foo')]
console.log(Reflect.ownKeys(myObj));//['foo', Symbol(foo)]

3.内置Symbol值:
Symbol.toStringTag: 用于对象的描述的字符串值. 被Object.propertype.toString() 使用;
Symbol.iterator: 返回对象的默认迭代器的方法; 被for… of使用

15.for…of循环

​ arr.forEach(): 不能终止遍历; arr.some(): 返回true, 可以终止遍历; arr.every(): 返回false, 可以终止遍历; for… of方法中: 可以用break终止遍历;
​ for…of 可以遍历数组、伪数组、Set对象、Map对象;

const arr = [100, 200, 300, 400];
for (let item of arr) {//100 200
   console.log(item);
   if (100 < item) {
      break;
   }
}
let testSet = new Set();
testSet.add('123').add('456').add('468');

for(let item of testSet) {
   console.log(item);
}

let testMap = new Map();
testMap.set('foo', 123).set({thisName: 'lc'}, 456);
for (let [key, value] of testMap) {
   console.log(key, value);
}

16.可迭代接口:

​ 为了给各种各样的数据结构提供统计遍历方式, ES2015提出了 iterable(可迭代) 接口;
实现 Iterable(可迭代) 接口是 for…of的前提;
​ 所有可以直接被for…of循环遍历的数据类型, 必须要实现 iterator 接口, 在数据类型内部要去挂载一个 iterator 的方法;
​ iterator 方法需要返回一个带有 next() 方法的对象, 我们不断调用 next() 就可以实现数据类型的遍历;

const mySet = new Set(['foo', 'bar', 'baz']);
const iterator = mySet[Symbol.iterator]();
console.log(iterator.next());//{value: 'foo', done: false}
console.log(iterator.next());//{value: 'bar', done: false}
console.log(iterator.next());//{value: 'baz', done: false}
console.log(iterator.next());//{value: undefined, done: true}
console.log(iterator.next());//{value: undefined, done: true}

//实现可迭代接口 (iterable)

const myObj = {//第一层对象: myObj -> 实现了可迭代接口: iterable -> 约定了内部必须要有一个用于返回迭代器的iterator方法;
   [Symbol.iterator]: () => {
      return {//第二层对象: iterator返回的对象 -> 实现了迭代器接口: iterator -> 约定了内部必须要有一个用于迭代的 next 方法
         next: () => {
            return {//第三层对象: next方法放回的对象 -> 实现了迭代结果接口: iterationResult -> 约定了在这个对象内部比必须要有一个value属性 -> 表示当前被迭代到的数据, 值可以为任意类型; 还必须要有一个done属性表示 -> 表示当前迭代有没有结束, 值为bool类型;
               value: 'lc',
               done: true
            }
         }
      }
   }
};
//优化迭代器
const myObjOne = {
   store: ['foo', 'lc', 'yh'],
   [Symbol.iterator]: function () {
      let index = 0;
      const self = this;
      return {
         next: function () {
            const result =  {
               value: self.store[index],
               done: index >= self.store.length
            };
            index++;
            return result;
         }
      }
   }
};

for (let item of myObjOne) {
   console.log('循环体', item);//循环体 foo 循环体 lc 循环体 yh
}

17.迭代器模式

//场景: 你我协同开发一个任务清单应用
//我的代码 =======================
//我的任务是: 设计一个用于存放所用数据的对象
//初始设计的对象: 无迭代器
const todosOne = {
   life: ['吃饭', '睡觉', '打豆豆'],
   learn: ['语文', '数学', '英语']
};
//优化设计1: each 方法 -> 只适用于当前数据结构
const todosTwo = {
   life: ['吃饭', '睡觉', '打豆豆'],
   learn: ['语文', '数学', '英语'],
   each: function (callback) {
      const all = [].concat(this.life, this.learn);
      for (const item of all) {
         callback(item);
      }
   }
};
//优化设计2: 添加迭代器 -> 迭代器模式: 对外提供统一遍历的接口, 让外部不用关心数据内部的结构; 迭代器模式是语言层面上去实现的, 适用于任何数据结构, 只需代码实现iterator 方法, 实现迭代逻辑;
const todosThree = {
   life: ['吃饭', '睡觉', '打豆豆'],
   learn: ['语文', '数学', '英语'],
   [Symbol.iterator]: function () {
      let all = [...this.life, ...this.learn], index = 0;
      return {
         next: function () {
            return {
               value: all[index],
               done: index++ >= all.length
            }
         }
      }
   }
};
//你的代码 =======================
//你的任务是: 把我定义的对象中所有的任务项罗列成列到界面上
//去遍历初始设设计: 如果我对象发生了变化, 我相应的遍历也会发生变化
for (let item of todosOne.life) {
   console.log(item);
}
for (let item of todosOne.learn) {
   console.log(item);
}
//去遍历优化设计1:
console.log('----------------------------');
todosTwo.each(item => console.log(item));
//去遍历优化设计2
console.log('------------------------------');
for (let item of todosThree) {
   console.log(item);
} 

18.生成器(Generator)

​ 功能: 避免异步编程中回调嵌套过深, 提供更好的异步编程解决方案;
​ 语法: 生成器函数: function * generatorModule() {}

function * foo () {
   console.log('start');
   try {
      const res = yield 'foo';//yield 后面的值可以是任意类型
      //第二次调用 next() 传递过来的参数作为上一句yield 语句的返回值
      console.log(res);//bar
   } catch (e) {
      console.log(e);   
   }
}
const myGenerator = foo();//不会立即去执行这个函数, 而是生成生成器对象;
//手动调用生成器对象的 next() 方法才会执行函数体;
const result = myGenerator.next();//start
//通过 next() 可以拿到类似于迭代器的返回值(生成器对象其实也实现了迭代器接口协议)
console.log(result);//{ value: foo, done: false }
//当我们下次调用 next() 方法的时候, 可以传入一个参数, 这个参数会作为上一个yield 语句的返回值, 参数可以是任意类型;
const result1 = myGenerator.next({myName: 'lc'});
//如果执行的是生成器对象的 thorw 方法, 函数体也会继续执行, thorw() 方法的作用是抛出一个异常;
myGenerator.throw(new Error('generator err'));

19.ECMA2016 -> includes:

​ 数组实例上的 includes() 方法: 检查数组中是否包含指定元素;
​ 在ES2016 查找数组中的元素, 用indexof() 方法, 但是不能查找数组中的NaN;
​ includes 可以查找数组中的NaN, 返回值为Bool类型;

const testArr = ['foo', 1, NaN, false];
console.log(testArr.indexOf('foo'));//0
console.log(testArr.indexOf(NaN));//-1 这里是个错误
console.log(testArr.indexOf(1));//1
console.log(testArr.includes(NaN));// true

20.ECMA2016 -> 指数运算符

console.log(Math.pow(2, 10) === 2 ** 10)//true

21.ECMA2017

//ECMA2017
const myObj = {
   foo: 'value1',
   bar: 'value2'
};
//Object.values(): 返回对象的值组成的数组
console.log(Object.values(myObj));//['value1', 'value2']
//Object.entries(): 返回对象的键值对组成的数组
console.log(Object.entries(myObj));//[['foo', 'value1'], ['bar', 'value2']]
//可以把对象转化为Map对象
console.log(new Map(Object.entries(myObj)));
//Object.getOwnPropertyDescriptors(): 获取对象中属性的完整描述信息
const p1 = {
   firstName: 'lc',
   lastName: 'yh',
   get fullName () {
      return this.firstName + ' ' + this.lastName;
   }
};
console.log(p1.fullName);//lc yh
const p2 = Object.assign({}, p1);
p2.firstName = 'liu';
console.log(p2.fullName);// lc yh: 原因是: assign复制的时候,将p1的fullName当做了一个普通的属性复制;
const descriptors = Object.getOwnPropertyDescriptors(p1);
console.log(descriptors);
const p3 = Object.defineProperties({}, descriptors);
p3.firstName = 'wang';
console.log(p3.fullName);

//String.propertype.padStart/String.propertype/padEnd: 字符串填充方法: 用指定的字符串去填充目标字符串的开始或结束位置, 直到字符串达到指定长度为止,可以为数字前面添加倒零或字符串长度对齐;
const testObj = {
   html: 5,
   css: 16,
   javaScript: 128
};

for (const [name, count] of Object.entries(testObj)) {
   console.log(name, count);
}

for(const [name, count] of Object.entries(testObj)) {
   console.log(`${name.padEnd(12, '-')}|${count.toString().padStart(3, 0)}`);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值