前端知识整理(1)js篇

1 声明

1-1 [js函数声明的三种方式:]

(1)Function()构造器

var f = new Function()

(2)函数声明

function f (){
            console.log(111);
        }

(3)函数表达式

var f = function(){
    console.log(222);
}

1-2 [js变量声明:]

 1.var声明的变量会挂载在window上,而let和const声明的变量不会

2.var声明变量存在变量提升,let和const不存在变量提升(严格来说,let也存在)

3.let和const声明形成块作用域

4.let存在暂存死区

5.const声明必须赋值

 (1)var声明的变量会挂载在window上,而let和const声明的变量不会:

        var a = 1;
        console.log(a,window.a); // 1 1
        const b = 10;
        console.log(b,window.b); // 10 undefined
        let c = 100;
        console.log(c,window.c); // 100 undefined

 (2)var声明变量存在变量提升,let和const不存在变量提升

        console.log(a); // undefined ==> a已经声明还没有赋值,默认得到undefined值
        var a = 1;
        console.log(b); // 报错:b is not defined ==> 找不到b这个变量
        const b = 10;
        console.log(c); // 报错:c is not defined ==> 找不到c这个变量
        let c = 100;

(3)let和const声明形成块作用域

        f(1){
            var a = 100;
            let b = 10;
        }
        console.log(a); //100
        console.log(b); //b is not defined

        if(1){
            var a = 100;
            const b = 1;
        }
        console.log(a); //100
        console.log(c); //b is not defined

(4)同一作用域下let和const不能重复声明,而var可以

        var a = 100;
        console.log(a); //100
        var a = 10;
        console.log(a); //10
        let b = 100;
        console.log(b);
        let b = 10;
        console.log(b); //Identifier 'b' has already been declared ==> 标识符已经被声明过了

(5)暂存死区

       var a = 100;
       if(1){
        a = 10;
        let a = 1;
        //在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域查找变量a,
        // 而这时,还未到声明时候,所以控制台Error:a is not defined
        // 即let 和 const 不会声明提前
       }

 (6)const

一旦声明必须赋值,不能使用null占位。
声明后不能再修改
如果声明的是引用类型数据,可以修改其属性
const a = 100; 
const list = [];
list[0] = 10;
console.log(list);  // [10]
const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj);  // {a:10000,name:'apple'}

1-3 js为什么要进行变量提升

造成变量声明提升的本质原因是:

js 引擎在代码执行前有一个解析的过程,创建了执行上下文初始化了一些代码执行时需要用到的对象

访问一个变量时,会到当前执行上下文中作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。

首先要知道,JS在拿到一个变量或者一个函数的时候,会有两步操作,即解析执行

在解析阶段,JS会检查语法,并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。

在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不通过函数执行上下文会多出this,arguments和函数参数。

  • 全局上下文:变量定义,函数声明
  • 函数上下文:变量定义,函数声明,this,arguments

 在执行阶段,就是按照代码的顺序依次执行。

那为什么会进行变量提升你呢?主要有以下两个原因:

  • 提高性能
  • 容错性更好

 1.提高性能

在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。

2.容错性更好

变量提升可以在一定程度上提高JS的容错性,看下面的代码:

a = 1;var a;console.log(a);

如果没有变量提升,这两行代码就会报错,但是因为有了变量提升,这段代码就可以正常执行。 虽然,在可以开发过程中,可以完全避免这样写,但是有时代码很复杂的时候。可能因为疏忽而先使用后定义了,这样也不会影响正常使用。由于变量提升的存在,而会正常运行。

总结:

  • 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
  • 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行

 2 数据类型的分类:

2-1 基本类型:

  • string(字符串)--原始类型
  • boolean(布尔值)--原始类型
  • number(数字)--原始类型
  • symbol(符号)--原始类型
  • null(空值)
  • undefined(未定义)
  • BigInt(BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值,精度在(2^53-1)范围内,BigInt(10) 值为:10n) ES2020新出的

其中 SymbolBigIntES6 中新增的数据类型:

  • Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
  • BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。

注意:NaN不是数据类型

typeof NaN === 'number' //true
NaN == NaN  //false

2-2 对象类型(引用类型),有以下3种:

A.内置对象/原生对象

String Number Boolean Array Date RegExp Math Error Object Function Global

B.宿主对象

  • (1)BOM对象: Window、Navigator、Screen、History、Location
  • (2)DOM对象:Document、Body、Button、Canvas等

C.自定义对象--(指由用户创建的对象,兼容性问题需要由编写者注意)

创建自定义对象几种方式:

(1)对象直接量:

var obj1 = {};
var obj2 = {x:0,y:0};
var obj3 = {name:‘Mary’,age:18}

(2)工厂模式--用函数来封装以特定接口创建对象的细节:

function createPerson(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    return o;
}
var person1 = createPerson('zhang',30,'java');

(3)构造函数模式:

function Person(name,age,job){
    this.name= name;
    this.age = age;
    this.job = job;
}
var person1 = new Person('zhang',30,'java');

(4)原型模式:

function Person(){}
Person.prototype.name = 'zhang';
Person.prototype.age = '22';
Person.prototype.job = 'html5';
var person1 = new Person();

 2-3 数据类型的判断:

2-3-1:typeof 

 一般通过typeof操作符来判断一个值属于哪种基本类型

缺点:无法分辨对象类型 

typeof 'seymoe'    // 'string'
typeof true        // 'boolean'
typeof 10          // 'number'
typeof Symbol()    // 'symbol'
typeof null        // 'object' 无法判定是否为 null
typeof undefined   // 'undefined'
typeof {}           // 'object'
typeof []           // 'object'
typeof(() => {})    // 'function'

 为什么typeof null 为object:

js在底层储存变量的时候,会在变量的机器码的低位1-3位储存其类型信息

  • 000:对象
  • 010:浮点数
  • 100:字符串
  • 110: 布尔值
  • 1:整数

 但是,对于undefined 和 null 来说,这两个值得信息存储是有点特殊的。

  • null:所有机器码均为0
  • undefined:用 −2^30 整数来表示

 所以,typeof在判断null的时候问题就出现了,由于null的所有机器码均为0,因此直接被当做了对象来看待。

2-3-2:instanceof

判断对象类型:测试构造函数的 prototype 是否出现在被检测对象的原型链上。

缺点:无法判断一个值到底属于数组还是普通对象

[] instanceof Array            // true
({}) instanceof Object         // true
(()=>{}) instanceof Function   // true
let arr = []
let obj = {}
arr instanceof Array    // true
arr instanceof Object   // true
obj instanceof Object   // true

在这个例子中,arr 数组相当于 new Array() 出的一个实例,
所以 arr.__proto__ === Array.prototype,
又因为 Array 属于 Object 子类型,
即 Array.prototype.__proto__ === Object.prototype,
所以 Object 构造函数在 arr 的原型链上

判断不了原始类型

console.log(true instanceof Boolean);// false
console.log(undefined instanceof Object); // false
console.log(arr instanceof Array);  // true
console.log(null instanceof Object); // false
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function);// true

2-3-3: Object.prototype.toString.call()

全能型,几乎都能判断

Object.prototype.toString.call({})// '[object Object]'
Object.prototype.toString.call([])// '[object Array]'
Object.prototype.toString.call(() => {})// '[object Function]'
Object.prototype.toString.call('abc')// '[object String]'
复制代码

传入原始类型却能够判定出结果是因为对值进行了包装。

「那么,什么是包装对象:」

所谓“包装对象”,指的是与数值、字符串、布尔值分别相对应的Number、String、Boolean三个原生对象。这三个原生对象可以把原始类型的值变成(包装成)对象。

详细点击这里

3 js运算符:

3-1 delete 运算符

delete 运算符用来删除对象属性或者数组元素,如果删除成功或所删除的目标不存在,delete 将返回 true。 然而,并不是所有的属性都可删除:

  • 一些内置核心和客户端属性是不能删除的
  • 通过 var 语句声明的变量不能删除
  • 通过 function 语句定义的函数也是不能删除的。

例如:

var o = { x: 1, y: 2};          // 定义一个对象
console.log(delete o.x);        // true,删除一个属性
console.log(delete o.x);        // true,什么都没做,x 在已上一步被删除
console.log("x" in o);          // false,这个属性在对象中不再存在
console.log(delete o.toString); // true,什么也没做,toString是继承来的
console.log(delete 1);          // true,无意义

var a = [1,2,3];                // 定义一个数组
console.log(delete a[2]);       // true,删除最后一个数组元素
console.log(2 in a);            // false,元素2在数组中不再存在
console.log(a.length);          // 3,数组长度并不会因 delete 而改变
console.log(a[2]);              // undefined,元素2所在的位置被空了出来
console.log(delete a);          // false,通过 var 语句声明的变量不能删除

function f(args){}              // 定义一个函数
console.log(delete f);          // false,通过 function 语句声明的函数不能删除
复制代码

3-2 void 运算符

void 运算符可以应用于任何表类型的表达式,表达式会被执行,但计算结果会被忽略并返回undefined

例如:

void 0;
void "you are useless?";
void false;
void [];
void /(useless)/ig;
void function(){ console.log("you are so useless?"); }
void alert(1)
// always return undefined
复制代码

3-3 ++ -- 运算符

++ -- 递增递减运算符借鉴自 C 语言,它们分前置型和后置型,作用是改变一个变量的值。

例如:

var a = 5;
console.log(a++);   // 5   后加表不加
console.log(a);     // 6  
console.log(++a);   // 7   先加,都有加
console.log(a)      // 7
console.log(a--);   // 7
console.log(a)      // 6
console.log(--a);   // 5
console.log(a)      // 5
复制代码

奇淫技巧先家都有家,后家表不家 (加号在前面,本身和表达式都加1;加号在后面,表达式不加1,本身加1 ),减法同理。

3-4 valueOf

var a = '你好', b = 1, c = [], d = {}, e = function (){}
a.valueOf()  // '好'
b.valueOf()  // 1
c.valueOf()  //[]
d.valueOf()  // {}
e.valueOf()  //ƒ (){}
复制代码

3-5 +和-

"+" 操作符,如果有一个为字符串,那么都转化到字符串然后执行字符串拼接

"-" 操作符,转换为数字,相减 (-a, a * 1 a/1) 都能进行隐式强制类型转换

[] + {}     // "[object Object]"
{} + []     // 0
1 + true    //2
1 + false   //1

4 内存

4-1 执行上下文

当代码运行时,会产生一个对应的执行环境,在这个环境中,所有变量会被事先提出来(变量提升),有的直接赋值,有的为默认值 undefined,代码从上往下开始执行,就叫做执行上下文。

「执行环境有三种」:

  • 1.全局环境:代码首先进入的环境
  • 2.函数环境:函数被调用时执行的环境
  • 3.eval函数

「执行上下文特点:」

  • 1.单线程,在主进程上运行
  • 2.同步执行,从上往下按顺序执行
  • 3.全局上下文只有一个,浏览器关闭时会被弹出栈
  • 4.函数的执行上下文没有数目限制
  • 5.函数每被调用一次,都会产生一个新的执行上下文环境

「执行3个阶段:」

1.创建阶段

  • (1).生成变量对象

  • (2).建立作用域链

  • (3).确定 this 指向

2.执行阶段

  • (1).变量赋值

  • (2).函数引用

  • (3).执行其他代码

3.销毁阶段

  • (1).执行完毕出栈,等待回收被销毁

详细点击这里

4-2 堆栈

「概念:」

  • 栈: 栈会自动分配内存空间,它由系统自动释放;存放基本类型,简单的数据段,占据固定大小的空间

  • 堆: 动态分配的内存,大小不定也不会自动释放。存放引用类型,那些可能由多个值构成的对象,保存在堆内存中

详细点击这里

5 垃圾回收机制

MDN上有说: 从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。所有对于js垃圾回收算法的改进都是基于标记-清除算法的改进

「什么是垃圾:」 一般来说,没有引用的对象就是垃圾,就是要才清除的。但有个例外,如果几个对象相互引用形成一个环,但根访问不到他们,他们也是垃圾(引用计数法,无法清除他们)

「垃圾回收的几种算法:」

5-1引用计数法

概念: 记录有多少“程序”在引用自己,当引用的数值为0时,就开始清除它。

优势:

  • 马上回收垃圾,当被引用数值为0时,对象马上会把自己作为空闲空间连到空闲链表上,也就是说。在变成垃圾的时候就立刻被回收。
  • 因为是即时回收,那么‘程序’不会暂停去单独使用很长一段时间的GC,那么最大暂停时间很短
  • 不用去遍历堆里面的所有活动对象和非活动对象

劣势:

  • 计数器需要占很大位置,因为不能预估被引用的上限,打个比方,可能出现32位即2的32次方个对象同时引用一个对象,那么计数器就需要32位。
  • 最大的劣势是无法解决循环引用无法回收的问题 这就是前文中IE9之前出现的问题

5-2 标记清除法

主要将GC的垃圾回收过程分为两个阶段

标记阶段:把所有活动对象做上标记

清除阶段:把没有标记(也就是非活动对象)销毁

优势:

  • 实现简单,打标记也就是打或者不打两种可能,所以就一位二进制位就可以表示
  • 解决了循环引用问题

缺点

详细点击这里1

详细点击这里2

  • 造成碎片化(有点类似磁盘的碎片化)
  • 再分配时遍次数多,如果一直没有找到合适的内存块大小,那么会遍历空闲链表(保存堆中所有空闲地址空间的地址形成的链表)一直遍历到尾端
  • 5-3 复制算法

  • 将一个内存空间分为两部分,一部分是From空间,另一部分是To空间
  • From空间里面的活动对象复制To空间
  • 释放掉整个From空间
  • 再将From空间和To空间的身份互换,那么就完成了一次GC。

6 内存泄漏

「概念:」 申请的内存没有及时回收掉,造成系统内存浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

「内存泄漏发生的场景:」

(1) 意外的全局变量

function leaks(){  
  leak = 'xxxxxx';//leak 成为一个全局变量,不会被回收
}
复制代码

(2) 遗忘的定时器

setTimeout 和 setInterval 是由浏览器专门线程维护它的生命周期,如果在某个页面使用了定时器,当销毁页面时,没有手动去释放清理这些定时器的话,那么这些定时器还是存活着的

(3) 使用不当的闭包

var leaks = (function(){  
    var leak = 'xxxxxx';// 被闭包所引用,不会被回收
    return function(){
        console.log(leak);
    }
})()
复制代码

(4) 遗漏的 DOM 元素

<div id="container">  
</div>
$('#container').bind('click', function(){
    console.log('click');
}).remove();//dom移除了,但是js还持有对它的引用
复制代码

解决:

$('#container').bind('click', function(){
    console.log('click');
}).off('click').remove();
//把事件清除了,即可从内存中移除
复制代码

(5) 网络回调

「如何监控内存泄漏」

使用控制台

详细点击这里

7 作用域

扩展

JavaScript是门动态语言,跟Java不一样,JavaScript可以随意定义全局变量和局部变量,每一个函数都是一个作用域,当函数执行时会优先查找当前作用域,然后逐级向上。

JavaScript是静态作用域,在对变量进行查询时,变量值由函数定义时的位置决定,和执行时的所处的作用域无关。

ES6已经有块级作用域了,而且用 let 和 const 定义的变量不会提升。

概念

作用域:变量或者函数的有效作用范围

作用域链:我们需要查找某个变量值,会先在当前作用域查找,如果找不到会往上一级查,如果找到的话,就返回停止查找,返回查找的值,这种向上查找的链条关系,叫作用域

7-1 相关案例

(1)变量提升/变量由函数定义时的位置决定

var a = 1;
function fn() {
  console.log('1:' + a);
  var a = 2;
  bar()
  console.log('2:' + a)
}
function bar() {
  console.log('3:' + a)
}
fn()
复制代码

分别打印:1:undefined 3:1 2:2

「解析:」

第一个 a 打印的值是 1:undefined 而不是 1。因为我们在 fn() 中定义了变量 a,用 var 定义的变量会提升到当前作用域的顶部(即当前函数作用域顶部),但是赋值还在原地,所以是undefined。

第二个a 打印的值是 3:1 而不是 2。因为函数 bar 是定义在全局作用域中的,所以作用域链是 bar -> global,bar 里面没有定义a,所以就会顺着作用域链向上找,然后在 global 中找到了 a。注意:查找是在其定义的执行上下文环境中查找。

第三个 a 打印的值是 2:2。这句话所在的作用域链是 fn -> global,执行 console.log('2:' + a) 会首先在 fn 作用域里查找 a,找到有 a,并且已经赋值为2,所以结果就是2。

(2)变量赋值

var a = 1;
function fn() {
  console.log('1:' + a);
  a = 2
}
a = 3;
function bar() {
  console.log('2:' + a);
}
fn();
bar();
复制代码

分别打印:1:3 2:2

「解析:」

第一个 打印的值是 1:3。首先, fn 中的 a = 2 是给变量 a 赋值,并不是声明变量。然后,执行函数 fn,此时 a 已经赋值为3了,注意,fn()是在a=3后面执行。

第二个 打印的值是 2:2。函数 bar 所能访问的作用域链为 bar->global,在执行函数 bar 时,由于在bar前执行了fn()将a修改为2了,所以这个时候拿到的a为2。

(3)全局变量声明提前

if(!(a in window)){
    var a = 10;
}
console.log(a);
复制代码

打印:undefined

「解析:」

相当于:

var a;
if(!(a in window)){
    a = 10;
}
console.log(a);
复制代码

用 var 定义的变量会提升到当前作用域的顶部(即当前全局作用域), 所以a会声明提前到window中,但值还是在原地,即为undefined。 所以if得到是a in window是ture 故不走里面赋值 console.log(a) == undefined

上一个例子的变种:

(function(){
 var  x = c =  b = {a:1}
})()
console.log(c,b) // {a: 1} {a: 1}
console.log(x.a); // error , x is not defined
复制代码

注意: x是在函数中声明的,是局部变量,c和b未声明,直接赋值,所以是全局变量。 赋值过程是从右往左的,即b={a:1},c=b,x=c

(4)变量提升/运算符顺序

(function(){
  var a = b = 3;
})()
console.log(typeof a === "undefined"); // true
console.log(typeof b === "undefined"); // false
console.log(typeof b === "number" && b ===3); // true
复制代码

// 这里涉及的就是立即执行和闭包的问题,还有变量提升,运算符执行方向(=号自右向左)

// 那个函数可以拆成这样

(function()
  var a; /* 局部变量,外部没法访问*/
  b = 3; /* 全局变量,so . window.b === 3 , 外部可以访问到*/
  a = b;
})()
复制代码

(5)变量提升/运算符顺序

var x = 1;
if (function f(){console.log(2)}) {
x += typeof f;  
}
console.log(x);  // 1undefined
复制代码

//因为函数体在()中会以表达式去运行,fn函数不起作用,函数不会执行。

//最后表达式转换为true,f未声明(上面的函数没起作用),值为undefined

「知识点:」

(1) 在JavaScript中,通过 let 和 const 定义的变量具有块级作用域的特性。

(2) 通过 var 定义的变量会在它自身的作用域中进行提升,而 let 和 const 定义的变量不会。

(3) 每个JavaScript程序都具有一个全局作用域,每创建一个函数都会创建一个作用域。

(4) 在创建函数时,将这些函数进行嵌套,它们的作用域也会嵌套,形成作用域链,子作用域可以访问父作用域,但是父作用域不能访问子作用域。

(5) 在执行一个函数时,如果我们需要查找某个变量值,那么会去这个函数被 定义 时所在的作用域链中查找,一旦找到需要的变量,就会停止向上查找。

(6) “变量的值由函数定义时的位置决定”这句话有歧义,准确说是查找变量时,是去定义这个函数时所在的作用域链查找。

8 闭包

「概念:」

一个function里面return了一个子函数子函数访问了父函数的变量。

「应用场景:」

  • 函数防抖
  • 封装私有变量
  • 解决for循环变量相互影响的问题

目标:想要依次打印0-9

for(var i = 0; i < 10; i++){
    setTimeout(()=>console.log(i),0)
}
// 实际控制台输出了10遍10.
复制代码

用闭包解决

for(var i = 0; i < 10; i++){
    (function(a){
    setTimeout(()=>console.log(a),0)
    })(i)
}
 // 控制台输出0-9
复制代码

闭包是如何产生的

当前作用域产产生了对父作用域的引用

JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。

image.png

9 this

9-1 this的指向:

ES5中:

this 永远指向最后调用它的那个对象

ES6箭头函数:

箭头函数的 this 始终指向函数定义时的 this,而非执行时

9-2 怎么改变this的指向:

  • 使用 ES6 的箭头函数
  • 在函数内部使用 _this = this
  • 使用 apply、call、bind
  • new 实例化一个对象

案例1:

var name = "windowsName";
    var a = {
        name : "Cherry",
        func1: function () {
            console.log(this.name)     
        },
        func2: function () {
            setTimeout(  function () {
                this.func1()
            },100);
        }
    };
    a.func2()     // this.func1 is not a function
复制代码

在不使用箭头函数的情况下,是会报错的,因为最后调用 setTimeout 的对象是 window,但是在 window 中并没有 func1 函数。可以看做window.setTimeout

案例2:

var webName="long";
let func=()=>{
  console.log(this.webName);
}
func();//long
复制代码

//箭头函数在全局作用域声明,所以它捕获全局作用域中的this,this指向window对象

案例3:

var webName = "long";
function wrap(){
  let func=() => {
    console.log(this.webName);
  }
  func();
}
wrap();//long
复制代码

//wrap函数执行时,箭头函数func定义在wrap中,func会找到它最近一层非箭头函数的this

//也就是wrap的this,而wrap函数作用域中的this指向window对象。

9-3 箭头函数:

「Tips:」 众所周知,ES6 的箭头函数是可以避免 ES5 中使用 this 的坑的。箭头函数的 this 始终指向函数定义时的 this,而非执行时

箭头函数需要记着这句话:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值(箭头函数本身没有this,但是在它声明时可以捕获别人的this供自己使用。),如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”。

特点

  • 没有this
  • 没有arguments
  • 不能通过new关键字调用
  • 没有new.target
  • 没有原型
  • 没有super

详细点击这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值