面试中的手撕代码(一)

总结了一些面面试中常见的手写代码题,如有更好见解欢迎评论指正~

手动实现map

Array.prototype._map = function (callbackFn, thisArg) {
    
    if (this === null || this === undefined) {
        throw new TypeError("Cannot read property 'map' of null");
    }
    
    if (Object.prototype.toString.call(callbackFn) != "[object Function]") {
        throw new TypeError(callbackFn + ' is not a function')
    }
    
    let O = Object(this);
    let receiver = thisArg;
    
    let len = O.length >>> 0;
    
    let A = new Array(len);
    for (let k = 0; k < len; k++) {
        
        if (k in O) {
            let kValue = O[k];
            
            let mappedValue = callbackFn.call(receiver, kValue, k, O);
            A[k] = mappedValue;
        }
    }
    
    return A;
}

const list = [1,2,3]

const mapRes = list._map(e=>e)

获取元素的Z-index

function getZIndex(){
    return [...document.all].reduce((r, e) => Math.max(r, +window.getComputedStyle(e).zIndex || 0), 0)
}

翻转DOM

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>反转DOM结构</title>
</head>

<body>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
    </ul>
    <button onclick="reverseChildrenNodes()">反转DOM</button>

    <script>

        function reverseChildrenNodes() {
            let ele = document.querySelector('ul')

            let len = ele.childNodes.length

            while (len-- > 0) {
                
                ele.appendChild(ele.childNodes[len])
            }

        }
    </script>
</body>

</html>

或者

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>反转DOM结构</title>
</head>

<body>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
    </ul>
    <button onclick="reverseChildrenNodes()">反转DOM</button>

    <script>

        function reverseChildrenNodes() {
            let ele = document.querySelector('ul')

            let children = Array.from(ele.childNodes)

            for (const child of children) {
                ele.removeChild(child)
            }

            children.reverse()

            for (const child of children) {
                ele.appendChild(child)
            }
        }
    </script>
</body>

</html>

每间隔1s打印

function sleep(wait, val) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(val)
        }, wait)
    })
}

async function test() {
    for (let i = 0; i < 5;) {
        i++
        console.log(await sleep(1000,i))
    }
}

test()

手写代码实现红绿灯效果

红灯3秒,绿灯1秒,黄灯2秒,循环重复

function sleep(wait, val) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(val)
        }, wait)
    })
}

async function test() {
    let flag = 0;
    const dict = [
        {name:'红灯',wait:3000},
        {name:'绿灯',wait:1000},
        {name:'黄灯',wait:2000},
    ]

    while (true) {
        console.log(await sleep(dict[flag].wait,dict[flag].name),new Date().getTime())

        if (flag === 2) {
            flag = 0
        } else {
            flag++
        }
    }

}

test()

数组去重排序

  • 取得两个数组⾥相同的部分, 并去重

  • 然后按照从⼩到⼤顺序排序, 最后结果返回 (注意, 是返回结果, 不是把结果打印出来)

const arrayA = [4, 2, 1, 2, 5];
const arrayB = [2, 3, 1, 6];
function process(arrayA, arrayB) {
    let res = []
    for (let i = 0; i < arrayA.length; i++) {
        if (arrayB.includes(arrayA[i])&&!res.includes(arrayA[i])) {
            res.push(arrayA[i])
        }
    }

    return res.sort((a, b) => a - b)
}

应该返回 [1, 2]
*/
console.log(process(arrayA, arrayB))

数组转树状结构

const data = [
    { id: '01', name: '张大大', pid: '00', job: '项目经理' },
    { id: '02', name: '小亮', pid: '01', job: '产品leader' },
    { id: '03', name: '小美', pid: '01', job: 'UIleader' },
    { id: '04', name: '老马', pid: '01', job: '技术leader' },
    { id: '05', name: '老王', pid: '01', job: '测试leader' },
    { id: '06', name: '老李', pid: '01', job: '运维leader' },
    { id: '07', name: '小丽', pid: '02', job: '产品经理' },
    { id: '08', name: '大光', pid: '02', job: '产品经理' },
    { id: '09', name: '小高', pid: '03', job: 'UI设计师' },
    { id: '10', name: '小刘', pid: '04', job: '前端工程师' },
    { id: '11', name: '小华', pid: '04', job: '后端工程师' },
    { id: '12', name: '小李', pid: '04', job: '后端工程师' },
    { id: '13', name: '小赵', pid: '05', job: '测试工程师' },
    { id: '14', name: '小强', pid: '05', job: '测试工程师' },
    { id: '15', name: '小涛', pid: '06', job: '运维工程师' }
]


function transToTree(list, root) {
    return list
        .filter(item => item.pid === root)
        .map(item => ({ ...item, children: transToTree(list, item.id) }))
}



function transToTree1(list, root) {
    let res = [];
    let map = {};

    for (let item of list) {
        map[item.id] = { ...item }
    }

    for (let item of list) {
        const { pid, id } = item
        if (pid === root) {
            res.push(map[id])
        } else {
            map[pid].children ? map[pid].children.push(map[id]) : map[pid].children = [map[id]]
        }
    }
    return res
}

console.log(JSON.stringify(transToTree1(data, '00')))

函数柯里化

function curry(fn, ...rest) {
    const length = fn.length

    return function () {
        const args = [...rest, ...arguments]
        console.log([...rest], [...arguments])
        if (args.length < length) {
            return curry.call(this, fn, ...args)
        } else {
            return fn.apply(this, args)
        }
    }
}

const fn = (a, b, c) => {
    return a + b + c
}
const f = curry(fn)
console.log(f(1)(2)(3))
console.log(f(1)(2, 3))
console.log(f(1, 2, 3))

实现emitterEvent

class EventEmitter {
    constructor() {
        this.events = {}
    }

    on(name, cb) {
        this.events[name] = this.events[name] ?? []
        this.events[name].push(cb)
    }

    emit(name, ...rest) {
        if(!this.events[name]){
            console.log('不存在emit事件名称')
            return
        }
        this.events[name].forEach(fn => fn(...rest))
    }

    off(name, cb) {
        if(!this.events[name]){
            console.log('不存在off事件名称')
            return
        }
        this.events[name] = this.events[name].filter(e => e !== cb)
    }

    once(name, cb) {
        const fn = (...args) => {
            cb(...args)
            this.off(name, fn)
        }
        this.on(name, fn)
    }
}
const eventBus = new EventEmitter()










function testOff(params) {
    console.log(5, params)
}
function testOff1(params) {
    console.log(6, params)
}

eventBus.on("testOff", testOff)
eventBus.on("testOff", testOff1)
eventBus.emit('testOff', 'off1')
eventBus.off('testOff', testOff)
eventBus.emit('testOff', 'off2')


异步加法

function asyncAdd(a, b, cb) {
    setTimeout(() => {
        cb(null, a + b)
    }, Math.random() * 1000)
}

async function total() {
    const res1 = await sum(1, 2, 3, 4, 5, 6, 4)
    const res2 = await sum(1, 2, 3, 4, 5, 6, 4)
    console.log([res1, res2])
    return [res1, res2]
}


function sum(){}

function asyncAdd(a, b, cb) {
    setTimeout(() => {
        cb(null, a + b)
    }, Math.random() * 1000)
}

async function total() {
    const res1 = await sum(1, 2, 3, 4, 5, 6, 4)
    const res2 = await sum(1, 2, 3, 4, 5, 6, 4)
    console.log([res1, res2])
    return [res1, res2]
}

let caches = {}
total()


async function sum(...rests) {
    let res = 0;
    let key = rests.join('+')
    if (caches.hasOwnProperty(key)) {
        console.log('有缓存直接读取缓存')
        return caches[key]
    }
    console.log('没有缓存')
    for (let item of rests) {
        res = await calculate(res, item)
    }

    caches[key] = res

    return res
}

function calculate(num1, num2) {
    return new Promise((res, rej) => {
        asyncAdd(num1, num2, (err, result) => {
            if (err) {
                rej(err)
                return
            }
            res(result)
        })
    })
}

非立即执行防抖函数

function debounce(fn, wait) {
  var timeout;

  return function () {
    let context = this;
    let args = arguments;

    if (timeout) clearTimeout(timeout);

    timeout = setTimeout(() => {
      fn.apply(context, args);
    }, wait);
  };
}


立即执行防抖函数

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        function debounce(fn, wait) {
            let timeout = null
            let flag = 0
            
            return function () {
                let context = this
                let args = [...arguments]

                if (timeout) {
                    clearTimeout(timeout)
                }
                if (flag) {
                    timeout = setTimeout(() => {
                        fn.apply(context, args)
                        console.log('非立即执行')
                    }, wait)
                } else {
                    fn.apply(context, args)
                    console.log('立即执行')
                    flag++
                }
            }
        }
        function con() {
            console.log('log--->', Date.now())
        }
        document.addEventListener('scroll', debounce(con, 1000))
    </script>
</head>

<body>
    <div style="background-color: red;height:30000px">

    </div>

</body>

</html>

立即执行节流函数

function throttle(fn, wait) {
  let previous = 0;

  return function () {
    let now = Date.now();
    let context = this;
    let args = arguments;

    if (now - previous > wait) {
      fn.apply(context, args);
      previous = now;
    }
  }
}

非立即执行节流函数

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        function throttle(fn, wait) {
            let previous = 0;

            return function () {
                let now = Date.now();
                let context = this;
                let args = arguments;
                if (previous === 0) {
                    previous = now
                } else if (now - previous > wait) {
                    fn.apply(context, args);
                    previous = now;
                }
            }
        }
        function con() {
            console.log('log--->', Date.now())
        }
        document.addEventListener('scroll', throttle(con, 2000))
    </script>
</head>

<body>
    <div style="background-color: red;height:30000px">

    </div>

</body>

</html>

浅拷贝

function clone(o) {
    const obj = {};
    for (i in o) {
        if(o.hasOwnProperty(i)){  
            obj[i] = o[i]
        }        
    }
    return obj
};

深拷贝

function deepCopy(obje) {
    if(typeof obje === 'object'){
        let afterClone = Array.isArray(obje) ? [] : {};
        for(let item in obje){
            if(obje.hasOwnProperty(item)){
                afterClone[item] = deepCopy(obje[item]);
            }
        }
        return afterClone;
    }else{
        return obje;
    }
}

var theObj = {
    name:'jerry',
    age:15,
    a:undefined,
    b:{
        c:456
    }
};

深拷贝的循环引用问题

但是上述方法循环引用会报错,for example:

var A = {
    name:'jerry',
    age:15,
    a:undefined,
    b:{
        c:456
    }
};
A.A=A

利用Map的key可以是引用数据类型,将要拷贝的对象当做key存起来,value是深拷贝后的对象:

function deepCopy(obje,map=new Map()) {
    if(typeof obje === 'object'){
        let afterClone = Array.isArray(obje) ? [] : {};
        
        if(map.get(obje)){
        return map.get(obje);
    }
    map.set(obje,afterClone);
    
    
        for(let item in obje){
            if(obje.hasOwnProperty(item)){
                afterClone[item] = deepCopy(obje[item],map);
            }
        }
        
        return map.get(obje);
    }else{
        return obje;
    }
}

如上代码解决了循环引用的问题。

在node环境下会打印如下内容:

afterClone <ref *1> {
  name: 'jerry',
  age: 15,
  a: undefined,
  b: { c: 456 },
  A: [Circular *1]
}

node通过cirular来标识是循环引用,而浏览器会正常输出处理完的循环引用对象。

解决循环引用的原理是:在每次对复杂数据类型进行深拷贝前保存其值,如果下次又出现了该值,就不再进行拷贝,直接截止。

Date和RegExp以及null

function deepClone(target, map = new Map()) {
    if (typeof target !== 'object' || !target) {
        return target
    } else {
        let res = Array.isArray(target) ? [] : {};

        if (map.get(target)) {
            return map.get(target)
        }
        map.set(target, res)
        for (let k in target) {
            if (target.hasOwnProperty(k)) {
                const element = target[k]
                if (element instanceof Date) {
                    
                    let time = new Date(element.getTime()); 
                    res[k] = time;
                } else if (element instanceof RegExp) {
                    
                    
                    res[k] = new RegExp(element);
                } else {
                    res[k] = deepClone(target[k], map)
                }
            }
        }
        return map.get(target)
    }
}

let obj = {
    strAttr: '字符串',
    numAttr: 6,
    booleanAttr: false,
    nullAttr: null,
    undefinedAttr: undefined,
    symbolAttr: Symbol(1),

    objAttr: {
        a: 1,
        b: 2,
    },
    arrAttr: [3, 4, 5],
    dateAttr: new Date(),
    funAttr: function () { console.log('1') },
    regAttr: new RegExp()
}
obj.objAttr.c = obj.objAttr

let cloneObj = deepClone(obj)
cloneObj.objAttr.a = 6

console.log(obj, '克隆结果', cloneObj)

Object.create()

    Object.mycreate = function (proto, properties) {
      function F() { };
      F.prototype = proto;

      let res = new F()
      if (properties) {
        Object.defineProperties(res, properties);
      }
      return res;
    }
    var hh = Object.mycreate({ a: 1 }, {mm:{value:'isme'}});
    console.dir(hh);

创建对象不同方式及存在的差异

日常开发中,经常会使用如下方式创建对象:

  • 字面量

  • new构造函数

  • Object.create

那么上述三种方式存在什么区别呢?

  • 字面量创建和new关键字创建并没有区别,创建的新对象的__proto__都指向Object.prototype,只是字面量创建更高效一些。

  • Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。———— MDN
    • 也就是说Object.create接受两个参数,第一个参数是新创建对象的原型(新创建的对象__proto__指向第一个参数),第二个对象指的是像新创建的对象中添加的属性。

    • 若是第一个参数数是null,那新对象就彻彻底底是个空对象,没有继承Object.prototype上的任何属性和方法,如hasOwnProperty()、toString()等。

排序

选择

选择排序的大体思路是每次选出最小的一个数字,放在最前边。前i个元素是已经选出来的最小的。

function selectSort(arr) {
  for (let i = 0; i < arr.length - 1; i++) {
    let min_index = i;
    for (let j = i + 1; j < arr.length; j++) {
      min_index = arr[j] < arr[min_index] ? j : min_index;
    }
    [arr[i], arr[min_index]] = [arr[min_index], arr[i]];
  }
  return arr;
}

冒泡

冒泡排序的具体思路是前后两个数相比较,把较大的一个放到后边。后边i个元素是已经冒泡到后边的较大的元素。

function bubbleSort(arr) {
  for (let i = arr.length - 1; i > 0; i--) {
    for (let j = 0; j < i; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  return arr
}

快排

选择一个基准,将数组基于基准分为两个部分,递归进行排序。

function quickSort(arr) {
  
  if (arr.length <= 1) {
    return arr
  }

  let pivotIndex = Math.floor(arr.length / 2);
  let pivot = arr.splice(pivotIndex, 1)[0];

  let left = [];
  let right = [];

  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }

  return quickSort(left).concat([pivot], quickSort(right));
}

new函数

function _new(fn) {
  
  let res = {};
  
  
  if (fn.prototype !== null) {
    res.__proto__ = fn.prototype;
  }

  
  let ret = fn.apply(res, Array.prototype.slice.call(arguments, 1));

  
  if ((typeof ret === "object" || typeof ret === "function") && ret != null) {
    return ret;
  }

  return res;
}


function Person(name, age) {
  this.name = name;
  this.age = age;
}

let obj = _new(Person, "jerry", 22);
console.log(obj); 

function Another(name, age) {
  return {
    name: name,
    age: age,
    class: 3,
  };
}

let obj2 = _new(Another, "tom", 23);
console.log(obj2) 

new的过程都干了什么

  1. 创建空对象

  2. 链接原型

  3. 改变this指向,并向对象中添加属性

  4. 返回对象(若构造函数中返回的是引用数据类型,则返回构造函数返回的内容,否则返回新创建的对象)

instanceof

function instanceOf(left, right) {
  let prototype = right.prototype;
  left = left.__proto__;
  while (true) {
    if (left === null) {
      return false;
    }
    if (prototype === left) {
      return true;
    }
    left = left.__proto__;
  }
}
console.log(instanceOf([], Object));

call、apply、bind

以下实现的主要思路是对象中的方法,this指向对象。

call

    Function.prototype._call = function (obj) {
      obj = obj ? obj : window; 
      obj.fn = this; 
      let args = [...arguments].slice(1);
      let result = obj.fn(...args);
      delete obj.fn;
      return result;
    };

    
    function console_() {
      console.log(this.name);
    }

    let name = 'windowwwww' 
    let obj1 = {
      name: "obj",
    };
    console_._call(obj1) 
    console_.call() 

apply

    Function.prototype._apply = function (obj, arr) {
      obj = obj ? obj : window;
      obj.fn = this;
      let result;

      if (!arr) {
        result = obj.fn();
      } else {
        result = obj.fn(...arr);
      }

      delete obj.fn;
      return result;
    };

    function console_() {
      console.log(this.name);
    }

    let name = 'windowwwww' 
    let obj1 = {
      name: "obj",
    };
    console_._apply(obj1) 
    console_._apply() 

bind

Function.prototype._bind = function (obj) {
  var args = [...arguments].slice(1);
  var fn = this; 
  
  return function F(){
    if(this instanceof F){
        return new fn(...args,...argumrnts)
    }
    return fn.apply(obj,[...args,...arguments])
  }
};

sleep

setTimeout

function sleep(fn, wait) {
    setTimeout(fn, wait)
}

let testFun = () => { console.log('sleep',Date.now()) }
sleep(testFun,1000)
console.log('time',Date.now())


time 1661666516858
sleep 1661666517860
*/

promise

function sleep(wait) {
    return new Promise(res => {
        setTimeout(res, wait)
    })
}

sleep(1000).then(() => {
    console.log('sleep', Date.now())
})
console.log('time', Date.now())


time 1661666684579
sleep 1661666685582
*/

反转字符串

function reverseStr(str){
    if (str === '') return '';
    return reverseStr(str.substr(1)) + str.charAt(0);
}

console.log(reverseStr('123'))

function reverseStr(str){
    return str.split('').reverse().join('')
}

console.log(reverseStr('123'))

let reverseString = function (s) {
    let res = s.split('')
    let left = 0, right = res.length - 1;
    while (left < right) {
        let t = res[right]
        res[right] = res[left]
        res[left] = t

        left++
        right--
    }
    return res.join('')
};

console.log(reverseString('1234567'))

二叉树

数组节点生成二叉树

array = [1, 2, 3, 4, 5];

function creatTree(arr) {
  //创建节点数组
  function Node(key) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
  var nodeArr = [];
  arr.forEach((e) => {
    nodeArr.push(new Node(e));
  });
  console.log(nodeArr);










// 生成二叉树


  var lastParent = parseInt(nodeArr.length / 2 - 1);

  for (let i = 0; i < lastParent; i++) {
    nodeArr[i].left = nodeArr[i * 2 + 1];
    nodeArr[i].right = nodeArr[i * 2 + 2];
  }

  nodeArr[lastParent].left = nodeArr[2 * lastParent + 1];


  if (nodeArr.length % 2 === 1) {
    nodeArr[lastParent].right = nodeArr[2 * lastParent + 2];
  }

  return nodeArr;
}

console.log('结果--->',creatTree(array));


















二叉树的层次遍历(广度遍历)

二叉树的广度遍历借助队列完成

function levelOrder(root) {

  var que = [];
  que.push(root);
  
  while (que.length != 0) {
  
    var tmp = que.shift();
    console.log(tmp.data);
    
    if (tmp.left != null) {
      que.push(tmp.left);
    }
    if (tmp.right != null) {
      que.push(tmp.right);
    }
  }
}


二叉树的深度遍历

二叉树的深度遍历借助的是栈,二叉树深度遍历结果与前序遍历结果相同



function deepOrder(root) {
  var stack = [];
  stack.push(root);

  while (stack.length != 0) {

    var tmp = stack.pop();
    console.log(tmp.data);

    if (tmp.right != null) {
      stack.push(tmp.right);
    }

    if (tmp.left != null) {
      stack.push(tmp.left);
    }
    
  }
}


二叉树的前序遍历

pre(root){
    if(root != null){
        console.log(root.data);
        pre(root.left);
        pre(root.right)
    }
}

二叉树中序遍历

mid(root){
    if(root != null){
        mid(root.left);
        console.log(root.data);
        mid(root.right)
    }
}

二叉树后序遍历

behind(root){
    if(root != null){
        behind(root.left);
        behind(root.right);
        console.log(root.data)
    }
}

链表

链表翻转

function res(head){

  if(head==null || head.next ==null){
    return head
  }
  var newHead = res(head.next)
  head.next.next = head
  head.next = null
  return newHead
}


查找第K个节点

function look(k, head) {
  var tmp = head
  for (let i = 1
    tmp = tmp.next
  }
  return tmp
}

找倒数第k个节点

function look(k, head) {
  var p1 = head
  var p2 = head

  for (let i = 0
    p2 = p2.next
  }

  while (p2 != null) {
    p1 = p1.next
    p2 = p2.next
  }

  return p1
}

中间节点

function look(head) {
  var p1 = head
  var p2 = head
  while (p2 != null) {
    p1 = p1.next
    p2 = p2.next.next
  }
  return p1
}

判断是否交叉

不是 x 交叉,是 > 交叉。

反向输出链表

log(head){
    if(head == null){
        return
    }
    log(head.next)
    console.log(head.data)
}

var stack = function () {
  this.data = []

  this.push = push
  this.pop = pop

  this.clear = clear
  this.length = length
}

var push = function (e) {
  this.data.push(e)
}

var pop = function () {
  this.data.pop()
}

var clear = function () {
  this.length = 0
}

var length = function () {
  return this.data.length
}

队列

var que = function () {
  this.data = []

  this.enQuw = enQuw
  this.deQue = deQue

  this.clear = clear
  this.length = length
}

var enQuw = function (e) {
  this.data.push(e)
}

var deQue = function () {
  this.data.shift()
}

var clear = function () {
  this.length = 0
}

var length = function () {
  return this.data.length
}

栈实现队列

var stackA = [];
var stackB = [];

function push(node){
  stackA.push(node);
}

function pop(){
  if(stackB.length){
    return stackB.pop();
  }else{
    if(stackA.length){
      var len = stackA.length;
      for(i=0;i<len;i++){
        stackB.push(stackA.pop());
      }
      return stackB.pop()
    }else{
      return null;
    }
  }
}

push(1);

push(2);
push(3);
console.log(stackA);  
console.log(pop()) 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Web面试那些事儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值