javascript基础面试总结

本文总结了JavaScript面试中常见的知识点,包括undefined与null的区别、变量提升、GET与POST请求的区别、DOM事件处理、数组方法、数据类型、原型链、作用域、this指向、函数调用方式、深拷贝与浅拷贝、ES6及ES8的新特性等,旨在帮助开发者准备面试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

undefined和null的区别

undefined 定义未赋值
null 定义并赋值,值为空

变量提升

用var声明变量存在变量提升的情况,用let和const不存以上情况

var a = 10;

function foo() {
    
    console.log(a);
    var a = 20;
    console.log(a);
}
foo();

// 变量提升后的代码
var a = 10;
function foo() {
    var a; //变量提升  提升函数作用域的最前面
    console.log(a); //结果为undefined 因为var声明的变量提升后会在内存开辟空间,由于没有赋值 默认为undefined  var声明的变量的初始化在赋值的位置
    a = 20
    console.log(a); //结果为20
}
foo();

//打印的结果
undefined
20

换成let或const的情况

let a = 10;

function foo() {

    console.log(a); // 不能声明之前就进行访问
    let a = 20;  //let和const 让变量限制在他所在的作用域
  
}
foo();


//此种情况会报错
//ReferenceError: Cannot access 'a' before initialization

GET和POST的区别

1get请求把传输的数据放在url中,所有人可见的,不安全。而post请求把传输的数据放在请求体中,不可见,比较安全
2get请求对传输的数据的长度有限制,post请求没有限制
3get请求的参数会被完整的保留在浏览器的历史记录中,post请求的参数不会保留
4get请求的参数的数据类型只接受ASCLL字符,post请求的参数的数据类型没有限制
5get请求只能进行url编码,post请求可接受多种编码方式

document.onload() 和 document.ready()两个事件的区别

document.ready() 指的是文档结构加载完成,不包括图片等非文字媒体文件
document.onload() 指的是页面包含的图片等所有的文件都被加载完成

js中数组的常用方法

1、reduce 累加器

var nums = [1,2,3,4,5,6];

var sum = nums.reduce((preValue,currentValue) => {
   return preValue + currentValue;
},0);

console.log(sum);
//打印结果
21

2、some 遍历

var nums = [1,2,3,4,5,6]

var result = nums.some((item) => {
    return item > 7 //数组里面的值都不大于7 所以result的值为false
})
//打印结果
false

var result1 = nums.some((item) => {
    return item > 3 //存在大于3的item result1的值为true
})
//打印结果为
false

//总结 有任意一项的返回值为true或者都返回true 则最终结果为true 否则为false

3、every 遍历

var nums = [1,2,3,4,5,6]

var result = nums.every((item) => {
    return item > 0 //所有返回值为true result的值才为true
})

console.log(result)
//打印结果为
true

var result1 = nums.every((item) => {
    return item > 3//有任意一项的返回值为false为false result1的值为false
})

console.log(result1)

//打印结果
false

//总结 每一项返回值为true 最终结果为true 有任意一项的返回值为false 最终结果为false

4、filter 遍历

//遍历的结果会返回一个符合条件的新数组
var nums = [1,2,3,4,5,6]

var newArray = nums.filter((item) => {
   return item > 3 //把返回值为true的item留在新数组中 最后返回这个新数组
})

console.log(newArray)
//打印结果
[4,5,6]

5、map 循环


 var nums = [1,2,3,4,5,6] 
 var newArray = nums.map((item) => {
    return item + 1
})
console.log(newArray)
//打印结果
[2,3,4,5,6,7]

6、forEach 循环

//遍历数组的每一项 无返回值 不支持IE
var nums = [1,2,3,4,5,6] 
nums.forEach((item) => {
   item += 1
    console.log(item)
})
//打印结果
[2,3,4,5,6,7]

7 splice

//splice(index,参数1,参数2,参数3)
//1、参数1不为0
      //(1)无后续的参数 从数组的index开始删除(参数1)个元素
      //(2)有后续的参数 从数组的index开始删除(参数1)个元素,同时从数组的index处把后续的参数全添加进数组中
//2、参数1为0
     //把后续的参数从数组的index开始添加进数组中
var nums = [1,2,3,4,5,6]
nums.splice(1,2) 
console.log(nums)
//打印结果
[1,4,5,6]
nums.splice(1,0,7,8,9,10)
console.log(nums)
//打印结果
[
  1, 7, 8, 9, 10,
  2, 3, 4, 5,  6
]

8、sort()

var nums = [1,2,3,4,5,6]
nums.sort((a,b) => {
    return b-a //从大到小排列 return a-b 从小到大排列
})
console.log(nums)
//打印结果
[6,5,4,3,2,1]

9、push() 返回数组元素的个数,从数组尾部添加元素
10、pop() 返回数组元素的个数,从数组尾部删除元素
11、unshift() 返回数组元素的个数,从数组头部添加元素
12、shift() 返回数组元素的个数,从数组头部添加删除元素
13、slice(start,end) 截取数组 start为负 从数组尾部开始截取 -1表示为最后一个元素,-2表示为倒数第二个
14、reverse() 翻转数组 改变原来的数组 不会创建新的数组
15、concat() 连接数组 不改变原先的数组
16、join(’-’) 将数组转化为字符串 以参数进行拼接

js中的数据类型有哪些?并说明是如何存储的?

js中有两个数据类型  分别是基本数据类型和引用数据类型
基本数据类型:string number boolean null undefined symbol(ES6) bigint(ES10)
引用数据类型: object
基本数据类型: 直接存储在栈内存中
引用数据类型: 每创建一个新的对象会在堆内存中创建一个新的空间,其内存地址在栈内存中存储

判断数据类型的方法

1、Object.prototype.toStrig.call(判断的对象)
2typeof
3instanceof
4===

解释下js中的原型链

每一个函数都有一个prototype属性,当他作为构造函数时,
他实例化出来的函数对象会有一个__proto__属性(该属性和构造函数的prototype属性为同一个)。
当访问某个实例化对象的属性或者方法时,会在该实例化对象的自身查找,如果没有找到,
就回去他的__proto__隐式原型上去找,如果还没找到,
就会去构造函数的prototype属性中__proto__中查找。再没找到就返回undefined
这个链式查找的过程就就被称为原型链。

原型链继承的缺点?

function Parent(name) {
        this.name = name
    }
    Parent.prototype.getName = function () {
        return this.name
    }
    function Child() {
        Parent.call(this,'jerry')
    }

    // Child.prototype = new Parent()
    //Child.prototype = Parent.prototype
    Child.prototype = Object.create(Parent.prototype)

    Child.prototype.constructor = Child

    let child1 = new Child()
    let child2 = new Child()

    child2.name = 'tom'
    console.log(child1.name);
    console.log(child2.name);
1、子类原型对象指向同父类原型对象的实例,当子类有多个实例化对象时,修改其中一个会影响到其他的
2、在创建子类的时候,无法像继承元素传递参数

this指向

1、以函数形式调用时,this指向window
2、以方法调用时,this指向该方法的调用者
3、以构造函数调用时,this指向新创建的对象
4、使用call()和apply()调用是,this指向指定的对象(call()和apply()的第一个参数)

call()和apply()、bind()的区别

这三个方法都需要通过函数对象来进行调用
在调用这三个方法时,指定一个对象为这两个方法第一个参数
此时该函数对象的this就会指向这个指定的对象。
call()可以将实参在指定的对象之后依次传递给该函数对象: fun.call(obj,1,2)
apply()可以将实参在指定的对象之后以数组的方式传递给该函数对象: fun.apply(obj,[1,2])
bind() :
    //使用方法
    let bind = fun.bind(obj) 
    bind();

深拷贝和浅拷贝的区别

1、浅拷贝:
  重新在堆中创建一块内存,拷贝前后对象中的基本数据类型互不影响,
  拷贝前后对象中引用数据类型共享同一块内存,会相互影响。
  实现方法: 
   Object.assign()
   ...扩展运算符
   slice()方法
   concat()方法
  //浅拷贝
  var obj = {
      name: 'jack',
      age: [1,2,3,3,4,5],
      say: function () {
          console.log('tom')
      }
  }

  function qianCopy(obj) {
      var copy = {}
      for (var k in obj) {
        if (obj.hasOwnProperty(k)) {
            copy[k] = obj[k]
        }
      }
      return copy
  }
  var firstCopy = qianCopy(obj)
  firstCopy.age[0] = 9
  console.log(obj)
  console.log(firstCopy)
2、深拷贝:建一个对象从内存中完整的拷贝一份出来,从堆内存中重新创建一个新的内存存放新对象,修改新对象不会影响原对象。
 实现的方法:
    递归
    JSON.parse(JSON.stringify())
    //深拷贝
    var obj = {
      name: 'jack',
      age: [1,2,3,3,4,5]
  }
    function deepCopy(obj){
//      定义一个变量 并判断是数组还是对象
	      var cloneObj = Array.isArray(obj) ? [] : {};
	      if(obj && typeof obj === "object" && obj != null){
	          // 判断obj存在并且是对象类型的时候 因为null也是object类型,所以要单独做判断
	          for(var key in obj){  // 循环对象类型的obj
	              if(obj.hasOwnProperty(key)){  // 判断obj中是否存在key属性
	                  if(obj[key] && typeof obj[key] === "object"){  //  判断如果obj[key]存在并且obj[key]是对象类型的时候应该深拷贝,即在堆内存中开辟新的内存
	                      cloneObj[key] = deepCopy(obj[key]);
	                  }else{  //  否则就是浅复制
	                      cloneObj[key] = obj[key];
	                  }
	              }
	          }
	      }
      return cloneObj;
  }
  var cloneObj = deepCopy(obj)
  obj.name = 'tom'
  cloneObj.age[0] = 789
  console.log(obj)
  console.log(cloneObj)

    

封装js代码读取元素当前显示的css样式(考虑兼容性)

1、currentStyle()方法只适合IE浏览器,并不支持其他浏览器 (只能读取)
2、getComputedStyle() 适合IE9+以及其他正常的浏览器 (只能读取)
封装函数适合各种浏览器读取元素当前显示的css样式
function getStyle(obj,name) { // obj为读取样式的元素 name样式名
  return window.getComputedStyle ? getComputedStyle(obj,null)[name] : obj.currentStyle[name] 
}

3、元素.style.样式名 = 样式值  //读取或设置元素的内联样式  (可以设置)

怎样解决事件对象的兼容性?

事件对象:当事件的响应函数被触发时,浏览器每次都会将一个事件对象作为函数的实参
传递进响应函数,在事件对象中封装了当前事件相关的一切信息。比如鼠标的坐标、哪个键盘按键按下。

//在IE8浏览器中响应函数被触发时不会向该函数传递事件对象
//在IE8及以下的浏览器中事件对象作为window的属性存在的(window.event)
事件元素.事件 = function (event) {
     event = event || window.event //解决事件兼容性的代码
 }

两种事件绑定的区别?

1、元素.事件 =  function () {] //此种方法只能给该元素同一个事件绑定一个响应函数,
                               //后面绑定会覆盖前面绑定的
2、元素.addEventListener(事件,callback ,false)  
//参数
    1、事件类型;(不要on)
    2、响应事件的回调函数
    3、是否在事件捕获阶段响应事件,默认false
//此种方法可以为该元素的同一个事件绑定多个回调函数,按顺序触发  this指向事件绑定的对象
//不支持IE8及以下的浏览器 支持IE8及以下浏览器可以使用attachEvent(事件,callback) 
// 可以为该元素的同一个事件绑定多个回调函数 此事件要加on  后绑定先执行的顺序触发
// this指向window

3、addEventListener()和attachEvent() 进行封装进而适合所有的浏览器
function bind(obj,eventStr,callback) {
   if (obj.addEventListener) {
        obj.addEventListener(eventStr,callback,false)
   } else {
       obj.attachEvent("on" + eventStr, function () {
             callback.call(obj) //将this指向绑定的事件
      })
   }
}

事件传播的流程

1、捕获阶段: 从最外层的祖先元素,向目标元素进行事件的捕获,默认此阶段不会触发事件
2、目标阶段:捕获到目标元素,捕获结束后在目标元素上触发事件
3、事件冒泡:从目标元素向祖先元素进行传递,依次触发祖先元素的事件

如果在捕获阶段想要触发事件,addEventListener()第三个参数设置为true,
一般情况下为false

阐述下事件委托

事件委托就是:把事件绑定给祖先元素,然后通过后代元素进行事件冒泡到该祖先元素进而触发事件。

BOM对象

1、window 网页中的全局对象
2、Navigator 通过该对象来识别不同的浏览器信息
Navigator对象中大部分属性都已不能用来识别浏览器,但是可以用navigator.userAgent
来识别,不同浏览器有不同的userAgent
3、History 操作浏览器进行向前向后翻页
4、Loaction 操作浏览器的url等相关的操作

判断数据类型的方法?

1typeof 返回值为数据类型的字符串 不能区别null和object object和Array
2instanceof 判断对象的具体类型
3===  判断 nullundefined

作用域与作用域链

1、理解:变量的使用范围就是作用域
2、分类:
     全局作用域
     函数作用域
     块作用域(ES6才有)
3、作用:隔离变量,不同作用域下的同名变量不会有冲突
4、作用域链:多个嵌套的作用域形成的链,查找变量时是沿着作用域链来查找的。
       查找规则:在当前作用域下查找要访问的变量,如果有直接返回,否则进入上一层
       作用域查找,如果有直接返回,否者依次往上直至全局作用域为止,找到就返回,
       找不到旧报错。
面试题
var obj = {
    fn2: function () {
        console.log(fn2)
   }
}
obj.fn2()
上述执行的结果: 报错
var obj = {
    fn2: function () {
        //console.log(fn2)
        console.log(this.fn2) //才可以打印对象中的方法
   }
}
obj.fn2()

将伪数组转化为数组的方法

1、Array.prototype.slice.call(伪数组) //不支持IE8以下浏览器
2[...伪数组]
2、考虑函数的兼容性封装函数
function fun(伪数组) {
    var array = []
    try {
        array = Array.prototype.slice.call(伪数组) 
    } catch (e) {
        for (var i = 0 ; i < 伪数组的长度 ; i++) {
            array[array.length] = 伪数组[i] 
        }
    }
    return array
}

event-loop机制

event-loop :也就是js中的代码的执行顺序=>事件循环机制的运作原理,主要分为三大部分:
	(1)、调用栈
	(2)、微任务队列
	(3)、消息队列
1、调用栈
event-loop开始的时候,会从全局一行一行的执行,遇到函数调用,会压到调用栈中,被压入的函数称之为帧,当函数返回后会从调用栈中弹出。

2、消息队列
js中的一些异步操作,比如fetch,setTimeout,setInterval,在被压入到栈中的时候里面的消息会进入到消息队列中去,消息队列中会等到调用栈清空之后再执行(也就是调用栈中的函数全执行完了后再执行)。

3、微任务队列
promise asyncawait等异步操作,在操作的时候会加入到微任务中去,也会在栈清空的时候立即执行,但是!!!=>调用栈中加入的微任务会立马执行,不需等待栈清空。

问题来了:如果同时存在微任务和消息队列,那么等栈结束的时候,微任务和消息队列事件谁先执行?
其机制是微任务队列会在消息队列前执行。微任务队列的优先级比消息队列高

闭包

1、如何产生闭包?的变量
   当一个嵌套的内部函数引用了嵌套的外部函数的变量时,就会产生闭包。
2、常见的闭包
   (1)、函数的返回值为函数
   (2)、函数作为实参,传递给另一个函数调用
3、闭包的作用
   (1)、使函数内部的局部变量在函数执行完后,仍然存活在内存中。
   (2)、让函数外部可以操作函数内部的局部变量
4、缺点:
   (1)、 函数执行完后,函数的局部变量没有释放,占用内存的时间变长
   (2)、容易造成内存泄漏。
  

函数执行完后,函数内部声明的变量是否还存在?

一般情况下是不存在的,存在于闭包中的变量并才可能存在
                  (包含该引用变量的内部函数有引用指向时才存在)

内存溢出和泄漏

溢出:当程序运行需要的内存超过了剩余的内存时,会抛出错误
泄漏:占用的内存没有及时释放(内存泄漏过多导致内存溢出)
      (1)、意外的全局变量
      (2)、没有及时清理定时器或回调函数
      (3)、闭包
       (4)、脱离dom的引用

new 一个对象的背后有什么操作

1、创建一个空对象
2、给对象设值__proto__属性,并指向构造函数的prototype的属性值
3、执行函数构造题(给对象添加属性或方法)

rest参数和arguments参数的区别

1ES5中用arguments来获取函数的实参 他的返回值是一个对象(类数组)
    把类数组转化为对象
      Array.prototype.slice.call(arguments)
2ES6中用rest参数来代替arguments参数 返回是以一个数组 可以使用数组各种API
   使用方法: rest参数要放在所有的参数之后
   function fun(...args) {
     console.log(args)
   }
   fun(1,2,3,4)

防抖和节流函数

1、防抖函数
function debounce(delay,callback) {
   let timer = null
   return function(...args) {
       if (timer) clearTimeout(timer)
       timer = setTimeout(() => {
         callback && callback(args) 
       },delay) 
   }
}
2、节流函数
function throttle(delay,callback) {
   let timer = null
   return functon(...args) {
       if (!timer) {
           timer = setTimeout(() => {
               callback && callback(args)
               timer = null 
           }) 
       } 
   } 
}

ES6新特性之声明变量

ES6中用letconst声明变量(具有块级作用域)
let: 
    (1)、使用let声明的变量具有块级作用域,只能在声明的代码块中使用,必须先声明再使用
    (2)、具有暂时性死区特性: 代码如下
                             var num = 1;
                             if (true) {
                                console.log(num); //报错 'num未初始化'
                                let num = 2; 
                             }
         暂时性死区: if的这个块级作用域外用var声明num变量,内部用let声明了同名的变量,
         在这时console.log()不会上一级作用域查找num变量,因为内部用let声明该变量,
         使用let声明的num变量就与if内的代码块进行了绑定。所以输出'num 未初始化'
const: 
    (1)、 使用const声明的常量具有块级作用域,只能在声明的代码块中使用,必须先声明再使用
    (2)声明常量时必须赋值,否则报错。
    (3)、 使用const声明的常量一但赋值,其值为基本数据类型不可更改,如果为引用类型,不能
    重新赋值,但是可以更改结构内部的值。

ES6新特性之变量的解构赋值和扩展运算符

1、数组
const F4 = ['a','b','c','d']
let [a,b,c,d] = F4
console.log(a,b,c,d) //打印 a,b,b,d
2、对象
const Person = {
  name:'jack',
  age: 45,
  setName: function () {
      console.log(this.name)
  }
}

let {name,age,setName} = Person
console.log(name)
console.log(age)
setName()


3、扩展运算符
 let people = ['jack','tom']
 let dog = ['hehe','pipi']
 let concatArray = [...people,...dog]
 console.log(concatArray ) 

ES6新特性之模板字符串

1、声明字符串的新方法 反引号 ``
2、内容中可以直接出现换行符,其他方法不行
3、变量拼接必须要用${}
   let name = 'tom'
   let age = 18
   let str = `${name}今天${age}岁了`
   console.log(str) //打印 tom今天18岁了

ES6新特性之箭头函数

1、声明: let fun = (arguments) => {}
2this的指向是静态的,this始终指向函数声明时所在作用域下的this的值
  
    function getName() {
        console.log(this.name)
    }
    let getName2 = () => {
        console.log(this.name)
    }
    window.name = 'jack'
    const school = {
        name: 'tom'     
   }
   getName() //jack
   getName2() // jack
   getName.call(school) // tom
   getName2.call(school) // jack
3、不能作为构造函数实例化对象  
4、没有arguments对象

ES6新特性之Promise

promise是ES6引入的异步编程的解决方案
1、需要执行异步操作的时候,new一个Promise对象
2、给新创建的Promise对象,设定一个在成功条件下回调和一个失败条件下的回调
3、Promise对象可以通过调用then()方法获得成功的回调信息
4、Promise对象可以通过调用catch()方法获得失败的回调信息
const p = new Promise((resolve,reject) => {
      if (成功) resovle()  // 成功条件下 返回成功回调的信息
      else {   
         reject() //失败条件下返回  失败的回调信息
     }
})
p.then((value) => {]
    //获取成功回调的信息
)
p.catch((error) => {
  //获取成功回调的信息
})

** then()方法返回的结果仍是一个Promise对象,对象的状态有then方法中的回调函数的执行结果
**决定
   (1)、回调函数返回值是非Promise对象,then方法返回对象的状态为成功,
   该返回值为then方法返回对象成功状态下的值
   (2)、回调函数返回值是Promise对象,then方法返回对象的状态有该promise对象的状态来决定,
   (3)、回调函数抛出错误,then方法返回对象的状态为失败,抛出错误的值为then方法返回对象失败状态的值


then方法的返回结果决定可以链式调用 如下
const fs = require('fs')
const p = new Promise((resolve, reject) => {
    fs.readFile('./11.md', (err, data) => {
        if (err) reject('读取失败')
        resolve(data)
    })
})

p.then((value) => {
    console.log(value.toString())
    return new Promise((resolve, reject) => {
        fs.readFile('./22.md', (err, data) => {
            if (err) reject('读取失败')
            resolve(data)
        })
    })
}).then((value) => {
    console.log(value.toString())
    return new Promise((resolve, reject) => {
        fs.readFile('./33.md', (err, data) => {
            if (err) reject('读取失败')
            resolve(data)
        })
    })
}).then((value) => {
    console.log(value.toString())
}).catch((reason) => {
    console.log(reason)
})
5、中断promise的链式调用,在其中返回一个pending状态的promise对象

ES6新特性之新的数据结构

1set 集合
  let s = new Set()
  s.size() // 集合中元素个数
  s.add(新元素) //添加元素
  s.delete(元素) //删除元素
  s.has(元素) //判断元素是否存在集合中 返回值为Boolean
  s.clear() //清空集合
2、map
 let m = Map()
//添加元素
1、m.set(属性名,属性值)
//删除元素
2、m.delete(属性名)
//获取元素
3、m.get(属性名)
//元素的个数
m.size()
//清空
4、m.clear()

ES6新特性之类

class Phone {
  static name = '手机' //static 标注的属性 属于类不属于实例 实例无法访问
  static change() {
    console.log('我能改变世界')
 }
  constructor(brand,price) { // 构造函数  new 一个实例时 会自动执行
     this.brand = brand
     this.price = price
  }
  //方法必须使用改语法,
  call(){
    console.log('打电话')
  }
}

class SmartPhone extends Phone {
   constructor(brand,price,color,size) {
       super(brand,price) //调用父类的构造函数进行初始化
       this.color = color
       this.size = size
   }
   photo() {
      console.log('我能照相')
   }
   call() {  //子类的方法于父类同名时 不会调用父类的方法 会重写该方法
      console.log('我可以进行视频通话')
   }
}

//let onePlus = new Phone('onePlus',4656)

let xiaomi = new SmartPhone('xiaomi',7895,'黑色',6.5)

xiaomi.photo()
xiaomi.call()

ES6新特性之模块化代码

优点:
    (1)、防止命名冲突
    (2)、代码复用
    (3)、高维护性
export命令用于规定模块的对外接口
import命令用于输入其他模块的功能

ES8新特性之async和await

async函数
    1async函数的返回值为Promise对像,其状态和值由函数的的返回值来决定
          async function fun() {
            1return  非Promise对象
            2return Promise对象
            3throw err 抛出错误
         }
         let result = fun() //result 为Promise对象1)、函数返回值是非Promise对象,async函数返回的Promise对像(result)的状态为成功,
     该返回值为对象成功状态下的值
       
        (2)、函数返回值是Promise对象,async函数返回的Promise对像(result)的状态和值由函数返回的promise对象的状态和值来决定,
        (3)、函数抛出错误,async函数返回的Promise对像(result)的状态为失败,抛出错误的值为async函数返回的Promise对像(result)失败状态的值
await 表达式
   (1)、必须写在async函数中
   (2)、await 右侧的表达式一般为Promise对象
   (3)、await 返回值为Promise对象成功的值
   (4)、await的Promise对象失败了,抛出异常 通过try..catch来捕获
    try {
       let result = await Promise对象
    } catch(e) {
         console.log(e)
    }
例子:
 const p = new Promise((resolve,reject) => {
        // resolve('成功了')
        reject('失败了')
    })
    async function fun() {
      try {
          let result = await p
          console.log(result) 
      } catch (e) {
          console.log(e)
      }
    }
    fun()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值