javaScript中必须掌握(函数、函数作用域、闭包、匿名函数、对象、原型链、继承、克隆)

一、函数定义
二、局部函数预编译 AO
三、全局函数预编译 GO
四、作用域
五、闭包
六:匿名函数
七:对象 --》7.1实例对象 --》 7.2继承
八:包装类
九:克隆

一、函数定义

  <!-- 定义 function test() {} 
      函数声明
      函数表达式  var test = function() {}
      组成形式
        函数名称 test
        参数 
          形参 function one(a,b){ // 形参中如果没有可接收的,都有一个 arguments ,接收所有实参传过来的值}
          实参    one(1, 2)
          
        返回值  return
  -->
 <script>
    var a = 1 //window GO
    function test() {
      var b = 123 // 且在函数内,是 AO ,局部
      c = 345 // 未经声明的变量归 window 所有
      console.log(window);
      console.log(window.b);
    }
    test()
  </script>

二、局部函数预编译 AO

  <script>

    // 二、局部函数预编译 AO
     /*1语法分析 → 2预编译 → 3解释执行*/

    // 预编译发生在函数执行前一刻 预编译也叫执行期上下文
    // 预编译发生在函数执行前一刻 预编译也叫执行期上下文
    // 预编译发生在函数执行前一刻 预编译也叫执行期上下文
    // 预编译发生在函数执行前一刻 预编译也叫执行期上下文
    // 预编译发生在函数执行前一刻 预编译也叫执行期上下文

    /*
      局部 4 部曲
      1.创建 AO 对象 Activation Object(执行期上下文,作用是理解的作用域,函数产生
        的执行空间库)
      2.找形参和变量声明,将变量和形参名作为 AO 属性名,值为 undefined
        相当于 AO{
        a : undefined,
        b : undefined
        }
      3.将实参值和形参统一(把实参值传到形参里)
      4.在函数体里面找函数声明,值赋予函数体 (所以为什么说 两个相同的函数后一个函数会覆盖前一个函数)
      (先看自己的 AO,再看全局的 GO)
    */

    
    function test(a, b) {
      console.log(a) // function a
      console.log(b) // function b // b 函数是声明的才会提升,赋值则不会
      var b = 234
      console.log(b); // 234
      a = 123
      console.log('a: ', a); // 123
      function a() {}
      var a;
      b = 233
      var b = function(){}
      console.log('a: ', a); //  123
      console.log('b: ', b); // 233 // b函数 通过 = 赋值,所以不会变量提升
    }
    // test(1)
    // 局部的AO {
    //   a: undefined -> 1 -> function a(){} -> 123
    //   b: undefined -> function b(){} - 234 -> 233 错误的
    //   b: undefined - 234 -> 233 -》 function b(){} 正确的
    // }
  </script>

三、全局函数预编译 GO

<script>


    // 三、全局函数预编译 GO

    // 预编译发生在函数执行前一刻 预编译也叫执行期上下文
    // 预编译发生在函数执行前一刻 预编译也叫执行期上下文
    // 预编译发生在函数执行前一刻 预编译也叫执行期上下文
    // 预编译发生在函数执行前一刻 预编译也叫执行期上下文
    // 预编译发生在函数执行前一刻 预编译也叫执行期上下文
    
    /*
      全局的预编译三部曲:
      
      1、生成了一个 GO 的对象 Global Object(window 就是 GO)
      2、找形参和变量声明,将变量和形参名作为 GO 属性名,值为 undefined
      3、在函数体里面找函数声明,值赋予函数体 (所以为什么说 两个相同的函数后一个函数会覆盖前一个函数)
    */

    // function test(){
    //   var a = b = 123;  // b 没有声明就是赋值了,归 window 所有,就是在 GO 里面预编译
    //   console.log('a', a)
    //   console.log('window', window)
    //   console.log(window.b);
    // }
    // test();

    // console.log(test); // GO 的test
    function test(test){
      console.log(test); // AO的 function test(){}
      var test = 234;
      console.log(test);  // 234
      function test(){}
    }
    // test(1);
    var test = 123;
    /*
      GO: {
        test: undefined -> function test ->

        AO: {
          test: undefined -> function test -> 234
        }
      }
    */
  </script>

四、作用域

 <script>

    // 四、作用域
    /*
      [[scope]]:每个 javascript 函数都是一个对象,对象中有些属性我们可以访问,但有些 不可以,
      这些属性仅供 javascript 引擎存取,[[scope]]就是其中一个。[[scope]]指的就 是我们所说的作用域,
      其中存储了运行期上下文的集合。

      作用域链: [[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我 们把这种链式链接叫做作用域链。
      运行期上下文: 当函数在执行的前一刻,会创建一个称为执行期上下文的内部对象。 一个执行期上下文定义了一个函数执行时的环境,
      函数每次执行时对应的执行上下 文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,
      当函数执 行完毕,执行上下文被销毁。 
      
      查找变量:在哪个函数里面查找变量,就从哪个函数作用域链的顶端依次向下查找。

      函数类对象,我们能访问 test.name

      什么是函数对象 ====>  通过函数调用
        function test() {}
        test.call()


      什么是实例对象 ====> 创建出的实例调用,就是实例对象
        Person.prototype.test = function() {}
        function Person() {}
        const person = new Person()
        person.test()
    */

    // 作用域 ---》 函数中执行函数会产生作用域链 [[scope]] 就是将 GO AO链起来


    function a() {
      function b() {
        var bb = 234;
        aa = 0  // 未经过声明的变量归window所有(全局也没有定义)否则还是不归window所有,当前函数 a1 执行时 scope 中查找GO 上一级有声明,所以会取上一级的值
        // var aa = 0
        console.log('window', window)
      }
      var aa = 123
      b()
      console.log(aa)
    }
    var glob = 100
    // a()


    /*

      1、当这个文件先要执行时,先创建出执行期上下文全局 Gloab Object,
      a defined --> [[scope]] --> 0 GO{
                                    glob: undefined --》 100
                                    a: function a(){}
                                  }

      2、当a函数开始执行时,在当前函数内会产生 aAO放在作用域链的最顶端,
      a doing   --> [[scope]] --> 0 AO{ // 放在作用域链的最顶端
                                    aa: undefined 
                                    b: function(){}
                                  }    
                                  1 GO{
                                    glob: undefined--》 100
                                    a: function a(){}
                                  }    
      3、当看到b函数被定义 function b(){} ,会将 a函数执行之后的 scope 地址引用跟 b函数定义时的 scope 地址相关联
      b defined --> [[scope]] --> 0 aAO{ // 放在作用域链的最顶端
                                    aa: undefined --> 123 --> 0
                                    b: function(){}
                                  }    
                                  1 GO{
                                    glob: undefined--》 100
                                    a: function a(){}
                                  }   
                     
      4、当a函数开始执行时,在当前函数内会产生 bAO放在作用域链的最顶端,                                              
      b doing   --> [[scope]] --> 0 bAO {
                                    b: undefined
                                  }     
                                  1 aAO{ // 放在作用域链的最顶端
                                    aa: undefined --> 123 --> 0
                                    b: function(){}
                                  }    
                                  2 GO{
                                    glob: undefined--》 100
                                    a: function a(){}
                                  }

      5、当b函数执行完之后,销毁 b 的执行期上下文,将指向b的地址清除                        
      6、当b函数执行完之后,销毁 a 的执行期上下文,将指向b的地址清除  
      7、如果重新执行a函数,那么重新定义执行期上下文                                         

    
    */

    // function a1() {
    //   cc = 0 // 未经过声明的变量归window所有(全局也没有定义)否则还是不归window所有,当前函数 a1 执行时 scope 中查找GO 上一级有声明,所以会取上一级的值
    // }
    // var cc = 123
    // a1()
    // console.log(cc)
  </script>

五、闭包

闭包的概念、使用场景、优缺点、解决方案

<script>
    // 五、闭包
    // 当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄露。 最后可将返回的函数 = null,释放空间
    实践角度上闭包的定义:
    一、即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回) 
    	如何解释:每个函数执行都会产生执行期上下文,将每个执行期上下文通过scope链起来,就是产生作用域链
	二、在代码中引用了自由变量
	参考链接:https://juejin.cn/post/6844903475998900237
    /*
      一、实现公有变量
      二、可以做缓存(存储结构)
      三、可以实现封装,属性私有化。
      四、模块化开发,防止污染全局变量

      闭包的防范:闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生。



    */
    // 一、实现公有变量

      function add() {
        var count = 0
        function demo() {
          count ++ // 内部函数中访问外部变量,形成闭包,可以打断点查看
          console.log(count)
        }
        return demo
      }
      var counter = add()
      // counter()
        
    // 二、可以做缓存(存储结构)
    function fun(){
        var count = 1;
        function fun2(){//条件1:函数嵌套
        //形成条件2:内部函数引用外部函数
          console.log(count);
        }
        fun2()
    }
    // fun();
      
    // 三、可以实现封装,属性私有化。 
    
    /*
      // 函数中定义的属性不会被销毁,因为每个函数都有一个原型链,属性跟方法都存在__proto__中,将内部的属性方法保存在外部,
      // new 中发生了什么??  全局搜索:  new实现过程 ,答案自然揭晓
      // function Deng(name,wife){
      //   var prepareWife = "xiaozhang";
      //   this.name = name;
      //   this.wife = wife;
      //   this.divorce = function(){
      //       this.wife = prepareWife;
      //   }
      //   this.changePrepareWife = function (target){
      //     prepareWife = target;
      //   }
      //   this.sayPrepare = function (){
      //     console.log(prepareWife);
      //   }
      // }
      // var deng = new Deng("deng","xiaoliu");
      // console.log('deng.wife: ', deng.wife);
      // deng.divorce()
      // console.log('deng.wife: ', deng.wife);
    */

    var name = '123456'
    function fun1() {
      let that = this
      var name, age, modulesName;
      name = '张三'
      age = '10'
      modulesName = 'fun1'
      var findValue = function () {
        console.log(name, age, modulesName);
      }
      return function () {
        findValue()
      }
    }
    fun1()()
    function fun2() {
      var name, age, modulesName;
      name = '李四'
      age = '20'
      modulesName = 'fun2'
      return function () {
        console.log(name, age, modulesName);
      }
    }
    fun2()()

    // 四、模块化开发,防止污染全局变量
    var zing_obj = {
      gzm: {
        name: '我是哈哈?',
        abc: function() {
          console.log(this.name)
        }
      },
      www: {
        name: '就这个吧?'
      }
    }
    zing_obj.gzm.abc()


    // 闭包的防范:闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生。
    
  </script>

六:匿名函数

  <script>
    // 六:匿名函数
    /* */
    
      // 一(function (){}()); //在 W3C 建议使用这一种
      // 二(function (){})(); // 浏览器已经报错了,无法支持
      (function() {
        console.log('11111')
      }())

      //这是表达式,可以被执行,此时在控制台执行 test 的结果是 undefined,这个函 数的名字就会被放弃
      var test = function (){
        console.log('这是表达式,声明了变量,用函数接收了'); 
      }()

      // 这个执行不了,报错,还是用 W3C写法吧
      // (function(){
      //   console.log('这是函数表达式代扣括号,执行吧');
      // })()

      // 加了个“正“,在趋势上要把他转换成数字,就是表达式了,既然是表达式就 能被执行,就会放弃名字,此时 console.log (test),就会报错;这就是立即执行函数 同样放了正号,负号,!就会放弃函数名字,转换成表达式;但是*和/不行,&&||前 面放东西也行
      + function(){
        console.log('+++++这也是表达式');
      }()

      let obj = { name: 'zhangsan', age: 12}
      console.log( obj.name && obj.age)
      console.log( obj.name || obj.age)

      if ('undefined') {
        console.log('我能打印嘛')
      }
   
  </script>

七:对象 --》7.1实例对象 --》 7.2继承

在这里插入图片描述

所有实例对象都通过__proto__指向的都是原型对象,可通过constructor指向当前构造函数

<script>
    // 原型链该怎么解释: 每个函数都有一个prototype属性,每个属性中都有一个constructor指向函数对象本身,每个实例对象都有一个__proto__,指向他的 prototype,
    // 七:对象
    /*

    // 一、对象
      var obj = {} // 字面量创建
      var obj1 = new Object() // 构造函数创建

    //二、构造函数
    Car.prototype.run = function() {
      this.height --
      console.log('height',  this.height);
    }
    // Car.prototype = {
    //   constructor: Car,
    //   run: function() {},
    //   start: function() {}
    // }
    function Car() {
      this.name = 'BMW'
      this.color = 'red'
      this.height = 100
      // this.run = function() {
      //   this.height --
      //   console.log('height',  this.height);
      // }
    }
    const car1 = new Car()
    console.log('__proto__', car1.__proto__);
    console.log('prototype', Car.prototype);
    car1.run()
    const car2 = new Car()
    car2.run()
    */


    
    /*
      原型 prototype 
        定义:原型是 function 对象的一个属性,它定义了构造函数制造出的对象的公共祖
        先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
      实例对象 __proto__
      

      一、内存浪费?
        如果调用两次new就会单独开辟两个空间来存放this.run ,如果创建100个呢,那么就容易内存浪费。
        this.run = function() {
          this.height --
          console.log('height',  this.height);
        } 

      二、如何解决?
        一般情况下,我们公共的属性定义到构造函数中,公共方法放在原型对象上(prototype)
        Car.prototype.run = function() {
          this.height --
          console.log('height',  this.height);
        }
        
      三、每个实例对象都有一个 __proto__ 、指向构造函数的 prototype 原型对象,实例对象中的
        car1.__proto__ === Car.prototype
        
      四、如果有多个原型对象,改为对象形式,那么constructor就会丢失,这时候就需要我们修改constructor
      */
   Car.prototype = {
     constructor: Car,
     run: function() {},
     start: function() {}
   }
   function Car() {
    console.log('478-this', this);
   }
   const car1 = new Car()
   console.log(car1.__proto__); // constructor丢失
   console.log(Car.prototype); // constructor丢失



    // 三、原型链
    /*

    Person.prototype.run = function() {
      that = this
    }
    Object.prototype.age = 15
    // Person.prototype.age = 14
    function Person() {}
    const person1 = new Person()
    // person1.age = 13
    person1.run()
    console.log('age', person1.age);
    console.log('this指向实例对象', person1 === that);
    //  原型链: 访问age,实例对象上没有,向Person.prototype中查找,还没有,向Object.prototype中查找,如果都没有就没有
     // 原型链 ,最后直到访问 null,就是最顶级的。Person.prototype指向Person原型对象,在向上访问到__proto__指向Object原型对象,在向上访问为null
    console.log('person.proto指向Person原型对象', person1.__proto__);
    console.log('Person.prototype指向Person原型对象', Person.prototype);
    console.log('Person.prototype.__proto__指向Object原型对象', Person.prototype.__proto__);
    console.log('Object原型对象.__proto__指向 null 万物皆是空 null', Person.prototype.__proto__.__proto__);
    */

    

    // 四、es5中使用实例对象继承、es6中使用class 类继承, 如何实现?
    /*
    // 1、如何让孩子继承父亲的属性
    // 2、如何让孩子继承父亲的方法,(存在问题?) 自己来一遍吧
    Father.prototype.run = function() { console.log('都需要运动')}
    function Father(name, age) {
      this.name = name
      this.age = age
    }
    const father = new Father()
    function Son(name, age) {
      //1、 继承属性
      Father.call(this, name, age)
      this.study = '学习'
    }
      // // 2、继承方法 存在的问题,给孩子添加方法时,父级也会添加,不可取
      // Son.prototype = Father.prototype
      // Son.prototype.start = function() { console.log('开始运动')}
   
    Son.prototype = father // 相当于 son.prototype = {},重新修改了原型对象,需要执行constructor
    Son.prototype.constructor = Son
    const son = new Son('孩子', 10)
    console.log('实例对象', son);
    console.log('原型对象', Son.prototype);
    son.run()
    console.log(Father.prototype);
    console.log(Son.prototype.constructor);

    */

    /*
    // 第一种
      Person.prototype.name = '小一'
      function Person() {

      }
      Son.prototype = Person.prototype
      function Son() {}
      Person.prototype.name = '小二'
      const son = new Son()
      console.log('son.name: ', son.name); // 观察Person.prototype 赋值之后打印的name
      Person.prototype.name = '小三'
    */

    /*
     // 第二种 引用赋值
     Person.prototype.name = '小一' //let obj = { name: 'a'}
      function Person() {
        // this = {
        //   __proto__ : Person.prototype // 隐士调用 
        // }
        
      }
      Son.prototype = Person.prototype // let obj1 = obj
      function Son() {}
      Person.prototype = { // obj = {name: 'b'}
        name: '小二'
      }
      const son = new Son()
      console.log('son.name: ', son.name); // 会影响原来 Son。prototype 指向的name 吗?
      

      let obj = { name: 'a'}
      let obj1 = obj
      obj = {name: 'b'}
      console.log(obj1.name);
    */



    /*

    一、...args剩余参数(展开运算符)  返回一个数组,可以使用数组原型中的方法

      允许一个表达式在某处展开。展开运算法 在 多个参数(函数调用)、多个元素(用于数组和字面量)和多个变量(用于解构赋值) 地方使用。剩余参数语法允许我们将一个不定数量的参数表示为一个数组。

      如果函数的最后一个命名参数以 ... 为前缀,则它会将所有后面剩余的是实参个数包裹成一个数组。

      // 例子

      function test(a, b, ...args) {

      console.log(args)

      }

      test(1,2,3,4) // [3, 4]

      二、arguments对象  返回一个 类数组对象,不具有数据的方法

      在函数代码中,使用特殊对象 arguments,开发者无需明确指出参数名,就能访问它们。arguments对象并不是一个数组,是一个类数组对象,在调用时请注意。

      arguments 中包含了函数传递的参数、length、和 callee 属性。

      length 属性表示的是实参的长度,即调用函数的时候传入的参数个数。

      callee 属性则指向的函数自身,我们可以通过它来调用自身函数。

      arguments 是一个经典的类数组对象,我们可以通过Function.call 或者 Function.apply 方法来间接调用数组的方法,也可以直接通过 Array.prototype.slice 或 Array.prototype.splice 等方法把类数组对象转换成真正的数组。
   
    */


    // new实现过程
    /* 
     
      function myNew(_constructor, ...args) {
        debugger
  
        // 1. 创建一个空对象
        const obj = {}; 

        // 2. 将obj的_proto_属性指向构造函数的原型对象
        obj.__proto__ = _constructor.prototype; 
        
        // 3.将_constructor执行的上下文this绑定到obj上,并执行
        const result = _constructor.apply(obj, args);

        //4. 如果构造函数返回的是对象,则使用构造函数执行的结果。否则,返回新创建的对象
        return result instanceof Object ? result : obj;  // 如果使用自己函数的对象,那么原型中的函数将无法使用

      }

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

        this.age = age;
        return { name: 123}

      }

      Person.prototype.sayHello = function(){

        console.log("hello" + this.name)

      }
      const person1 = myNew(Person, 'Tom', 20)
      const person2 = new Person('Tom', 20)

      console.log(person1) //Person {name: "Tom", age: 20}
      console.log(person2) //Person {name: "Tom", age: 20}
      // person1.sayHello() //helloTom
      // person2.sayHello() //helloTom

      function myNew1(_construtor, ...args) {

      }

    */

     // 注意
    /*
   
    Object.create() //在括号里面只能放 null 或者 Object,其余会报错

    // 练习题 执行 test()  跟   new test() 会返回什么结果。
    var a = 5
    function test() {
      a = 0
      console.log(a)
      console.log(this.a)
      var a
      console.log(a)
    }
    // 分析:只要new 就会隐士 var obj = {}    obj.__proto__ = test.prototype  return this,访问 obj.a = undefined
    */

  </script>

八:包装类

 <script>
    // 八、包装类 //https://blog.csdn.net/qq_42284274/article/details/127143590
    /*
      JS为我们提供了三个包装类:
      String() :将基本数据类型字符串,转换为String对象。
      Number() :将基本数据类型的数字,转换为Number对象。
      Boolean() :将基本数据类型的布尔值,转换为Boolean对象。
    */

    // //通过上面这这三个包装类,我们可以将基本数据类型的数据转换为对象。
    // var num = new Number(3);
    // var str = new String("he11o");
    // var bool = new Boolean(true);
    // console.log(typeof num); //打印结果: object

    //PS:我们在实际应用中不会使用基本数据类型的对象。如果使用基本数据类型的对象,在做一些比较时可能会带来一些不可预期的结果。



    // var str = 123;
    // // str = str.toString(); //将number类型转换为string 类型
    // str.hello = "千古壹号"; //添加属性
    // console.log(typeof str); //打印结果: string
    // console.log(str.he11o); //打印结果: undefined

  </script>

九:克隆

js对象克隆的几种方法


  <script>
    function deepCopy(obj, cache = []) {
      // 如果obj是不可变值,只返回
      if (obj === null || typeof obj !== 'object') {
          return obj
      }

      // 如果obj被击中,则其为圆形结构
      const hit = find(cache, c => c.original === obj)
      if (hit) {
          return hit.copy
      }

      const copy = Array.isArray(obj) ? [] : {}
      
      // 首先将副本放入缓存,因为我们希望以递归方式引用它
      // deepCopy
      cache.push({original: obj, copy})

      Object.keys(obj).forEach(key => {
          copy[key] = deepCopy(obj[key], cache)
      })

      return copy
    }


    /*
      为什么有深拷贝以及前拷贝??
        这里主要区分原始值和引用值的拷贝,原始值的拷贝,拷贝的是整个原始值,而引用值的拷贝,拷贝的是地址,如果是浅拷贝,一旦obj对象中含有引用值。拷贝之后,如果对obj1进行改变,则obj的值也会发生改变。
        参考地址:https://www.cnblogs.com/kkw-15919880007/p/14666416.html

      深拷贝封装考虑三个点:
        1,是否有长度,没有直接return
        2,对象属性中是否具有自己添加的指定的属性 Person.prototype.name = '123',返回true,toString 就返回false
        3,判断是否为 object,if 存在( 继续判断是否为 Object,Array,Function 新增属性)  else 将值直接复制到 对象或者数组中
        4、
    */


    function clone(obj, obj_clone = {}){
      // 判断被克隆的对象是不是一个空对象,如果是空对象就直接return,不在继续往下执行
      if(Object.keys(obj).length == 0) return obj_clone;
      // 遍历对象
      for(var key in obj){
          // 判断当前遍历到的属性是不是对象的,(不是原型上的,原型上的不克隆)
          if(obj.hasOwnProperty(key)){
              // 判断obj当前属性是原始类型还是引用类型
              // 使用typeof 可以简单区分原始类型和引用类型
              if(typeof obj[key] == 'function' || typeof obj[key] == 'object'){
                  // 调用toString方法
                  toString = Object.prototype.toString;
                  if(toString.call(obj[key]) == '[object Object]'){
                      obj_clone[key] = {};
                  } else if(toString.call(obj[key]) == '[object Array]') {
                      obj_clone[key] = [];
                  } else if(toString.call(obj[key]) == '[object Function]'){
                      obj_clone[key] = new Function();
                  }
                  // 递归,当有数组或者对象的时候继续往里面深入,重复调用当前的函数
                      // 第一种方法
                      // callee是arguments对象的一个属性,指向arguments对象的函数这个函数就是clone(clone=arguments.callee)
                      // arguments.callee(obj[key], obj_clone[key]);
                  // 第二种方法
                  clone(obj[key], obj_clone[key]);
              } else { // 原始数据类型直接赋值
                  obj_clone[key] = obj[key];
              }
          }
      }
      return obj_clone;
    }


    var obj = {
      original : 2,
      arr : [1,2,3],
      fun : function(){
          return 'fun';
      },
      obj_son : {
          original_son : 3,
          obj_son_son : {}
      }
    }
  
    let newobj = clone(obj);
    
    console.log(newobj === obj); // false
  </script>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值