MS问题汇总小结~(持续更新记录)

// ******************* 伪数组转化为数组的方法*******************
function test () {
  console.log(arguments)
  let aa = arguments[Symbol.iterator]()
  console.log(aa.next())
  console.log(aa.next())
  console.log(aa.next())
  console.log(aa.next())
  console.log(Array.from(arguments))
  console.log([...arguments])
  console.log([].slice.call(arguments,0))
  console.log(Array.prototype.slice.call(arguments,0))
  var arr1 = [],
  len1 = arguments.length;
  for (var i = 0; i < len1; i++) {
    arr1.push(arguments[i]);
  }
  console.log(arr1)
}
test(45,765,6)

// 数组去重,数组中的项为对象
// 闭包
// useReaction
// hook的更新原理
// 一键切换mock数据
// let,const,var
// 可枚举与不可枚举
// html加载顺序


//***************************** v-for为什么要用key **********************
// key必须稳定且唯一,用来唯一标识同父同层级的兄弟元素,同级节点添加唯一的key进行区分至关重要,直接决定react组件的性能。当React作diff时,只要子元素有key属性,便会去原v-dom树中相应位置(当前横向比较的层级)寻找是否有同key元素,比较它们是否完全相同,若是则复用该元素,免去不必要的操作。
// key 属性是React用来匹配新旧组件树中子组件的标识,能够让 React 了解哪些组件需要被变更, key 属性能够大大提高启发式算法的性能,并保证 React 正常运行。

// *******传统的diff算法,使用递归遍历,复杂度可能会极高,如果想用Fiber的方式中断任务,在递归中是很难处理的;
// *******而新的React diff算法,《由于在前端当中,很少出现跨越层级移动DOM元素的情况,所以React采用了简化的diff算法》将复杂度降为一维,使用大循环的方式,只会对virtual dom中同一个层级的元素进行对比,且任务可中断。
// ********大循环的方式:即执行某个fiber后, 会执行他的子元素, 如果没有子元素, 则兄弟元素, 然后又回到父元素, 父兄弟元素...,而寻找元素则是根据其上面几个属性return(父元素),child(子元素),sibiling(兄弟元素)


// ******************************* react fiber ****************************
//  俗话可以理解为===》(虚拟domreact 中叫Fiber, diff算法react 中叫协调,新的任务调和器)。Fiber 架构就是用 异步的方式解决旧版本 同步递归导致的性能问题。
// Fiber实际上就是一个任务调和器,把一次更新拆成一个个的单元任务,每次做完一个单元任务后,就询问是否有更高的优先级任务,有就去执行,回头再来干这件事。
// (1)说下 react16 之前 stack架构,递归遍历组件树成本很高,会造成主线程被持续占⽤,结果就是主线程 上的布局、动画等周期性任务就⽆法⽴即得到处理,造成视觉上的卡顿,影响⽤户体验
// (2)Fiber架构 任务分解,把一个耗时长的任务分解为一个个的工作单元,避免主线程的持续占用造成卡顿问题,由浏览器判断是否有空余时间执行,有时间就执行工作单元
// (3)增量渲染,把渲染任务拆分成多块
// (4)更新时候能够暂停,终止,复用渲染任务。会在浏览器空闲时期依次调用函数, 这就可以让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这样延迟触发而且关键的事件产生影响。函数一般会按先进先调用的顺序执行,除非函数在浏览器调用它之前就到了它的超时时间
// (5)给不同类型的更新赋予优先级


// ******************************事件循环********************************
//JavaScript是一门单线程语言,分为同步任务和异步任务
// (1)同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
// (2)异步任务指的是,不进入主线程、而进入"任务队列"的任务
// (3)只有等主线程任务全部执行完毕,"任务队列"的任务才会进入主线程执行。而且"任务队列"是一个先进先出的数据结构
// (4)而异步任务分为宏任务和微任务:
//		macro-task(宏任务):包括整体代码script,setTimeout,setInterval
//		micro-task(微任务):Promise,process.nextTick
//		(Promise并不是完全的同步,在Promise中是同步任务,执行resolve或者reject回调的时候,此时是异步操作,会先将then/catch等放到异步任务中的微任务队列)

//执行过程:
//1.先执行所有同步任务,碰到异步任务放到任务队列中
//2.同步任务执行完毕,开始执行当前所有的异步任务
//3.先执行任务队列里面所有的微任务
//4.然后执行一个宏任务
//5.然后再执行所有的微任务
//6.再执行一个宏任务,再执行所有的微任务·······依次类推到执行结束。
//  3-6的这个循环称为事件循环Event Loop


// ***************************call,apply,bind的区别************************
// 1.call和apply返回的都是立即执行的函数,但是bind返回的只是一个函数,需要再次调用才可以,故bind的参数可以分多次传入调用
// 2.三个的存在都是为了改变this的指向,且第一个参数都是this要指向的新对象,如果没有这个参数或参数为undefined或null,则默认指向全局window
// 3.三个方法都接受参数,call是参数列表,apply是一个参数数组,且apply和call是一次性传入参数,而bind可以分为多次传入。


// ***************************** setTimeout有最小延时 *******************
// setTimeout有最小延时4ms,前提是在一个执行的嵌套层级超过5层的结构里,并且设置的timeout时间小于4ms时,才会设置成4ms
// 但是在不同的浏览器中可能会有实际不相同的执行结果,比如在非嵌套5层的结构中,谷歌浏览器设置的最小timeout时间为1ms;嵌套层级超过或者等于5层的结构里,才会设置timeout时间最小为4ms
// 实现一个0ms的setTimeout使用postMessage


// ***************************** Set数据/Map数据*******************
// set内部不能有重复的值,是通过三等运算符来确认的,所以两个对象总是不相等的,除非两个对象的内存地址相同;但是在set内部两个NaN是相等,所以只能存在一个NaN;
// 常用的方法:size(长度)/add(添加)/delete(删除)/has(存在)/clear(清空)

// Map类似于对象的结构,是键值对的集合,但是键的范围不限于字符串,各种类型的值都可以当作键名。判断键名是否相同时,使用全等进行判断(除了NaN)。Map 可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。[['name':'dd'],['age',18]]
// 注意⚠️:当对象作为键名出现时,实际是以对象的内存地址绑定的,所以使用对象作为键名时,就不用慌和别人的键名同名的情况了
//  常用的方法:size(长度)/set(设置)/get(获取)/delete(删除)/has(存在)/clear(清空)/keys(遍历键名)/values(遍历键值)/entries(遍历键值对数组)/forEach(遍历所有成员)


// *************************** for...of...***************************
// for...of循环可以使用的范围包括字符串、数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象),是用来循环具有iterator接口的数据(以上均是)
// for...in...可以用来循环数组和对象(但主要是为遍历对象而生的)
// for...of...和for...in...的区别:of是直接循环得到对象的键值,in是直接循环得到对象的键名;for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性,而for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上可枚举的键,比如这种产生的键名name ===》Object.protoType.name = 'liming'


// *************************统计页面中最多的html标签***********************
// 或者使用document.getElementsByTagName('*')
// let arr = Array.from(document.querySelectorAll('*')).map(v => v.tagName)
// let obj1;
// // reduce的第二个参数是累加的初始值
// Object.entries(arr.reduce((total,next,index,arr) => {
//   if(total[next]) {
//     total[next]++
//   }else {
//     total[next] = 1
//   }
//   return total
// },{})).sort((a,b)=> b[1] - a[1])[0][0]


// **************************** Map数据结构的创造 **************************
let obj1 = new Map([[{sex:'2'},'女']]);
const key1 = {age:12};
obj1.set('name','jj')
obj1.set(key1,90)
obj1.set('name','gg')
console.log(obj1)


// ******************* Object.prototype.toString.call(value) ********************
// 用来检测数据类型:
// 返回值如下:
// '[object Array]','[object Object]','[object Null]','[object Undefined]','[object String]','[object Set]'等等


// *****************************call,apply,bind区别**********************************
var xw = {name : "小王", gender : "男", age : 24, say : function(school,grade) { 
	alert(this.name + " , " + this.gender + " ,今年" + this.age + " ,在" + school + "上" + grade);
	}
 }
 var xh = {
 	name : "小红",
 	gender : "女",
 	age : 18
  }
  // 下面的打印结果都是 ‘小红,女,今年18,在中海中学上二年级’
  xw.say.call(xh, '中海中学', '二年级');
  xw.say.apply(xh, ['中海中学', '二年级']);
  xw.say.bind(xh, '中海中学', '二年级')(); // 两种都可以
  xw.say.bind(xh)('中海中学', '二年级'); // 两种都可以
  

// ****************require/module.exports和import/export的区别********************
// require是Commonjs的语法,动态加载,在运行的时候调用,导入的是值的拷贝;import是ES6语法,静态加载,编译的时候调用,所以必须放在最上面,导入的是值的引用。
// import语句会在内存中创建一个只读的引用,多次导入同一个模块不会重复执行该模块的代码; require语句会将导入的模块缓存起来,多次导入同一个模块会返回同一个导出对象,避免了重复执行模块代码的开销
// 值得注意的是,由于import语句是静态执行的,因此在代码中不能使用变量或表达式作为模块路径,而只能使用字符串字面量。而require函数则可以接受变量或表达式作为模块路径,从而动态地确定所需的模块
// 从 ES6 开始,JavaScript的import 语句支持动态引入模块。动态引入允许你在运行时根据需要加载模块,而不是在代码的顶层静态地引入。但是实际上,动态引入的语法是通过 import() 函数来实现的,而 import 关键字仍然可以用于静态引入模块。因此,静态引入仍然需要在代码的顶层进行,而动态引入可以根据需要在任何地方进行。

// ***************Proxy对象***********************
// const proxy = new Proxy({},{
//   get: function(target,proKey,receiver){
//     return Object.prototype.toString.call(target)
//   }
// })
// console.log(proxy.gg)

// **********************广度优先遍历******************************
  let tree = [
    {
      id: '1',
      title: '节点1',
      children: [
        {
          id: '1-1',
          title: '节点1-1',
          children: [
              {
                  id:'1-1-1',
                  title:'节点1-1-1'
              },
              {
                id:'1-1-2',
                title:'节点1-1-2'
            },
          ]
        },
        {
          id: '1-2',
          title: '节点1-2',

        }
      ]
    },
    {
      id: '2',
      title: '节点2',
      children: [
        {
          id: '2-1',
          title: '节点2-1'
        }
      ]
    }
  ]
//   function treeForeach(tree){
//     let node,list = [...tree]
//     let aa = [];
//     while(node = list.shift()){
//       aa.push(node.title)
//       node.children && list.push(...node.children)
//     }
//       console.log(aa)
//   }
//   function treeForeach(tree){
//     let list = [...tree]
//     let aa = [];
//     for(let i of list){)
//         aa.push(i.title);
//         i.children && list.push(...i.children)
//     }
//       console.log(aa)
//   }
// **********************二叉树-中序遍历/前序遍历/后序遍历******************************

// **********************************二分法-查找指定数据(有序)******************************
const midSearch = (value, arr = [2,12,45,67,89,93]) => {
  arr.sort((a,b) => a-b);
  let min = 0;
  let max = arr.length - 1;
  while (min <= max) {
    let mid = Math.floor((max + min) / 2);
    if(arr[mid] > value) {
      max = mid - 1;
    }
    if(arr[mid] < value) {
      min = mid + 1;
    }
    if(arr[mid] === value ) {
      return mid;
    }
  }
  return -1;
}


//****************************二分法查找最大/最小值(无序)****************************
let arr = [2,45,1,3,23,43,67,12,24,44,38];
let min = 0, max = arr.length - 1;
const maxSearch = (arr, min, max) => {
	if(min < max) {
		let mid = Math.floor((min+max) / 2), max1, max2;
		max1 = maxSearch(arr, min, mid);
		max2 = maxSearch(arr, mid+1, max);
		return (max1 > max2) ? max1 : max2;
	} else {
		return arr[min];
	}
}
// 最大/最小值
console.log(maxSearch(arr, min, max));



// **********************实现一个类似reduce的函数**********************
// const reduce = (list,cb,...base) => {
//   console.log(base.length);
//   let num = base.length?base[0]:list[0];
//   for(let i = base.length?0:1;i++;i<list.length) {
//     num = cb(num,base[i])
//   }
//   return num
// }
// // 测试调用
// reduce([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], (x, y) => x + y, undefined)
//********************手动实现trim*****************
// const trim = (str) => str.replace(/^\s+|\s+$/,'')
function debounce (method, delay) {
  return (...argus) => {
    setTimeout(() => {
      method(...argus)
    },delay)
  }
}
debounce((a,b) => a+b,1000)(1,3)



// ***********************************手写实现vue3的响应式****************************
function reacctive () {
  const handler = {
    // 读取方法
    get: function (target, key, receiver) {
      const data = Reflect.get(target, key, receiver);
      if (typeof data === 'object') {
        return reactive(data)
      }
      return data
    },
    // 修改方法
    set: function (target, key, val, receiver) {
      const oldVal = Reflect.get(target, key, reactive);
      let data = true;
      if (oldVal !== val) {
        data = Reflect.set(target, key, value, receiver)
        // 监听更新
      }
      return data
    },
    // 删除对象属性
    deleteProperty(target, key) {
      // 先判断是否有key
      const isHasKey = target.hasOwnProperty(key);
      const data = Reflect.deleteProperty(target, key)
      if (isHasKey && data) {
          // 监听更新
      }
      return data;
    }
  }
  return new Proxy(target, handler);
}

// ************************react和vue的区别 diff算法(为什么vue不用fiber)****************
// 1.react使用jsx,没有办法做静态节点分析;vue使用template进行模版编译,可以很好对静态节点进行分析然后进行打补丁标记,对于vue2在遇到静态节点会跳过过循环的diff对比,而vue3会把整个静态节点进行提升处理,Diff 的时候是不过进入循环的,故性能更快
// 2.react使用的fiber算法是基于单向的链表结构进行diff对比,无法进行反向链表结构查找,故是从左到右进行对比查找;而vue2是采用的双端对比算法,vue3是更快的最长递增子序列算法(基于双端对比)
// 3.Vue 通过响应式依赖跟踪,在默认的情况下可以做到只进行组件树级别的更新计算;而react则需要对比整颗的dom树,渲染开销过大,需要结合一些hook进行性能优化,并使用fiber
// 4.fiber主要针对100ms以上的开销计算,才能达到肉眼可感知的效果,而react会经常有,vue很少有,故vue不需要


//****************************vue3的提升点(相比vue2)***************************************
//    一、diff算法方面:
// 1.patchFlag对虚拟DOM节点进行类型静态标记
// 2.静态节点提升,不参与更新的元素静态提升到了全局,重渲染的时候复用即可,无须重新创建,加快代码的执行速度(进行diff预处理,降低diff的操作量)
// 3.新增一个source数组来记录新节点的位置索引(针对节点顺序调换的情况,非重新渲染,而是复用原节点,进行节点的移动),而新增的节点索引为-1
// 4.事件缓存,cacheHandler可在第一次渲染后缓存我们的事件,相比于 Vue2,无需每次渲染都传递一个新函数
// (注:vue2是双端对比算法,vue3是最长递增子序列算法)
// 5.响应式使用proxy监听对象,返回的是一个新对象,可以直接操作新对象而达到目标。虽然Proxy不能直接监听到内部深层次的对象变化,但是在getter中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归;vue2使用Object.defineProperty 操作的是原对象,需要递归遍历所有属性,然后逐级向下访问,才能劫持每一个属性;并且无法监听对象新增属性以及无法跟踪数组索引以及数组 length 的问题(需要使用$set、$delete),proxy 正好解决了该问题
//   二、打包优化方面:
// tree-shaking进行模块化打包时,移除 JavaScript 上下文中未引用的代码,主要依赖于import和export语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用;vue2没有动态引入相应的api写法,全局 API 暴露在 Vue 实例上,诸如nextTick、version、observable(v2.6新增,v3.0先替换为reactive)即使没有未使用,也会被打包;但是vue3使用动态引入,未使用的api将从最终的捆绑包中消除,获得最佳文件大小,更加轻量级


// 事件委托/事件冒泡/事件捕获/react合成事件 target
// 发布订阅模式/观察者模式
// 服务端推送的过程(短轮询/长轮询/websocket)
// 发布订阅模式/观察者模式(区别/使用场景/手写vue的发布订阅)
// http2.0和http1.1的区别/http和https的区别
// 协商缓存和强缓存
// history/hash路由模式的原理
// react的函数组件和类组件的区别
// mixins(vue2使用中优先级问题)/vue3的组合式api的优点
// vue3和vue2的区别-响应式/算法区别
// 闭包的理解
// useRef的作用/useState为什么不能写在条件语句中
// react的新版生命周期
// this的指向问题





// ************************* 数据去重 ***************************
let arr1 = [1,2,4,6,2]
function removeRepeat (arr,num) {
  // return Array.from(new Set(...arr))
  // let obj = {};
  // let newArr = arr.map((item,index,arr) => {
  //   if(obj.hasOwnProperty(item) && obj[item] < num) {
  //       obj[item]++;
  //       return item;
  //   } else if(!obj.hasOwnProperty(item)){
  //     obj[item] = 1;
  //     return item;
  //   }
  // })
  // return newArr.filter((item) => !!item)
  let obj = {};
  arr.forEach((item,index,arr) => {
    if(obj.hasOwnProperty(item) && obj[item] < num) {
        obj[item]++;
        return false;
    } else if(!obj.hasOwnProperty(item)){
      obj[item] = 1;
      return false;
    }
    arr.splice(index,1)
  })
  return arr
}
// function judge(arr) {
//   for(let i=0;i<arr.length;i++) {
//     console.log(arr[i])
//     if(arr[i] === 1) {
//       throw new Error('ff')
//     }
//   }
// }

// ************************压缩前端项目中 JS 的体积方法****************

 1. gzip 或者 brotli 压缩,在网关处(nginx)开启
 2. 使用 webpack-bundle-analyzer 分析打包体积,替换占用较大体积的库,如 moment -> dayjs
 3. 使用支持 Tree-Shaking 的库,对无引用的库或函数进行删除,如 lodash -> lodash/es
 4. 对无法 Tree Shaking 的库,进行按需引入模块,如使用 import Button from
    'antd/lib/Button',此处可手写 babel-plugin 自动完成,但不推荐
 5. 使用 babel (css 为 postcss) 时采用 browserlist,越先进的浏览器所需要的 polyfill
    越少,体积更小
 6. code spliting,路由懒加载,只加载当前路由的包,按需加载其余的 chunk,首页 JS 体积变小 (PS:
    次条不减小总体积,但减小首页体积)
 7. 使用 webpack 的 splitChunksPlugin,把运行时、被引用多次的库进行分包,在分包时要注意避免某一个库被多次引用多次打包。此时分为多个
    chunk,虽不能把总体积变小,但可提高加载性能 (PS: 此条不减小总体积,但可提升加载性能)
  // ##### 压缩混淆化js方法
  terser:https://github.com/terser/terser
   或者 
  uglify:https://github.com/mishoo/UglifyJS
  及流行的使用 Rust 编写的 swc 压缩混淆化 JS
  
  
// ********************************************rematch和redux的区别*******************
1. redux不支持model,复杂的state需要使用combineReducers来进行分块组合
2. rematch不需要每次创建action.type常量,简化了繁琐的switch条件语句,直接触发对应的reducer
3. redux如果想使用异步,需要借助thunk插件,rematch直接使用async await
4. rematch更加简洁的代码设计,初始化配置简单,不需要中间件
 
//**********************************强缓存、协商缓存、启发式缓存***************************
协商缓存一般发生在强缓存失效后,但是也不排除意外情况下:即使强缓存还在有效期内,客户端主动发起刷新页面或者其他强制忽略强缓存的行为,在这种
情况下,浏览器会发送带有条件标头(例如If-Modified-Since或If-None-Match)的请求到服务器,进行协商缓存验证。也就是说,每次http返回来 response header 中的 ETag和 Last-Modified,在下次请求时在 request header 就把这两个带上(但是名字变了ETag-->If-None-Match,Last-Modified-->If-Modified-Since ),服务端把你带过来的标识,资源目前的标识,进行对比,然后判断资源是否更改了


为什么需要ETag?
HTTP1.1中Etag的出现(也就是说,ETag是新增的,为了解决之前只有If-Modified的缺点)主要是为了解决几个Last-Modified比较难解决的问题:
(1) 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET
(2) 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)
(3) 某些服务器不能精确的得到文件的最后修改时间。


** 协商缓存 **:如果cache-control(优先取值),或者Expires判断时间为过期,即强缓存过期,那么客户端会携带If-Modified-Since,或
If-None-Match(优先级取值),即上次请求返回的last-Modified、Etag,去服务端请求,如果服务端对比一致,那么就会命中 304 Not Modified
响应,告诉客户端可以继续使用之前的本地缓存。但如果资源已经发生了变化,服务器会返回新的资源内容。协商缓存可能会产生一些网络流量,但是相比于
直接下载完整的资源内容,它通常会显著减少网络带宽的使用,以及服务器的负载,提高性能。

** 启发式缓存 **:如果响应头不存在 Expires 和 Cache-Control,浏览器会触发启发式缓存,缓存有效期计算公式: 
(date - last-modified ) * 10%,
取响应报头中 date 与 last-modified 值之差的百分之十作为缓存时间(date是响应生成的时间),启发式缓存的准确性可能不如强缓存和协商缓存
那么高,因为它是基于一些估计和规则来进行缓存的决策。

浏览器缓存一般存在于本地设备的硬盘或者内存中:
硬盘缓存:
长期缓存:大多数浏览器会将较大、不经常变化的资源(如图片、脚本文件、样式表等)存储在硬盘上,以节省内存并减少对网络的请求次数。这些资源通常会在用户访问网站时被下载并存储在硬盘缓存中,以便在以后的访问中重复使用。
持久性缓存:浏览器还会将一些具有明确过期时间或缓存控制指令的资源存储在硬盘上,以便在适当的时候重新验证或刷新。
内存缓存:
短期缓存:一些较小、频繁访问的资源(如脚本、样式表、页面片段等)通常会被存储在内存中,以提高访问速度和性能。这些资源可能会在用户关闭浏览器时被丢弃,或者在内存不足时被清除以释放内存。
会话缓存:有些浏览器会将页面内容存储在会话期间的内存中,以便在用户会话期间重复使用。这些缓存通常在用户关闭标签页或浏览器时被清除。

// *********************** defer和async的区别 ******************
1.两者都会先异步下载资源,且下载过程中,不会阻塞页面的解析(注意⚠️:普通无标签的js脚本,在页面解析过程中,遇到js脚本会立即下载,然后按照写入顺序执行脚本,整个下载和执行的过程都是非异步的,会阻塞页面的解析)
2. 但是async的执行会阻塞页面解析;defer的执行不会阻塞,defer会等到文档渲染完毕后,DOMContentLoaded事件调用前才会执行
3. async会在js脚本下载完毕后立即执行,且跟async的脚本写入顺序无关,单纯谁先下载完谁就立马执行;defer会按照顺序执行所有的js脚本,跟下载完毕的顺序无关
4. defer一定在DOMContentLoaded之前调用;但是async不一定,可能在页面渲染完毕前执行,也可能在页面渲染完毕后执行,只看它本身的下载速度

普通脚本,defer脚本,async脚本执行顺序对比如下!(文档渲染 更正为 文档解析)
请添加图片描述
普通:

请添加图片描述
defer:
请添加图片描述
async,下面两种情况都有可能:
请添加图片描述
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值