kconfig中depend on_关于Vue在面试中常常被提到的几点(持续更新……

现在Vue几乎公司里都用,所以掌握Vue至关重要,这里我总结了几点,希望对大家有用

1、Vue项目中为什么要在列表组件中写key,作用是什么?

我们在业务组件中,会经常使用循环列表,当时用v-for命令时,会在后面写上:key,那么为什么建议写呢?

key的作用是更新组件时判断两个节点是否相同。相同则复用,不相同就删除旧的创建新的。正是因为带唯一key时每次更新都不能找到可复用的节点,不但要销毁和创建节点,在DOM中还要添加移除节点,对性能的影响更大。所以才说,当不带key时,性能可能会更好。因为不带key时,节点会复用(复用是因为Vue使用了Diff算法),省去了销毁或创建节点的开销,同时只需要修改DOM文本内容而不是移除或添加节点。既然如此,为什么我们还要建议带key呢?因为这种不带key的模式只适合渲染简单的无状态的组件。对于大多数场景来说,列表都得必须有自己的状态。避免组件复用引起的错误。带上key虽然会增加开销,但是对于用户来说基本感受不到差距,「为了保证组件状态正确,避免组件复用」,这就是为什么建议使用key。

2、Vue的双向绑定,Model如何改变View,View又是如何改变Model的?

我们先看一幅图,下面一幅图就是Vue双向绑定的原理图。230e4163937872d78623209bc67f6c94.png

第一步,使数据对象变得“可观测”

我们要知道数据在什么时候被读或写了。

   let person = {
        'name': 'maomin',
        'age': 23
    }
    let val = 'maomin';
    Object.defineProperty(person, 'name', {
        get() {
            console.log('name属性被读取了')
            return val
        },
        set(newVal) {
            console.log('name属性被修改了')
            val = newVal
        }
    })
    // person.name
    // name属性被读取了
    // "maomin"
    // person.name='xqm'
    // name属性被修改了
    // "xqm"

通过Object.defineProperty()方法给person定义了一个name属性,并把这个属性的读和写分别使用get()set()进行拦截,每当该属性进行读或写操作的时候就会触发get()set()。这样数据对象已经是“可观测”的了。

核心是利用es5Object.defineProperty,这也是Vue.js为什么不能兼容IE8及以下浏览器的原因。

Object.defineProperty方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。

    Object.defineProperty(
        obj, // 定义属性的对象
        prop, // 要定义或修改的属性的名称
        descriptor // 将要定义或修改属性的描述符【核心】
    )

「写一个简单的双向绑定:」

html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Documenttitle>
head>
<body>
    <input type="text" id="input"/>
    <div id="text">div>
body>
<script>let input = document.getElementById('input');let text = document.getElementById('text');let data = {value:''};Object.defineProperty(data,'value',{set:function(val){
            text.innerHTML = val;
            input.value = val;
        },get:function(){return input.value;
        }
    });
    input.onkeyup = function(e){
        data.value = e.target.value;
    }script>
html>
第二步,使数据对象的所有属性变得“可观测”

上面,我们只能观测person.name的变化,那么接下来我们要让所有的属性都变得可检测。

    let person = observable({
        'name': 'maomin',
        'age': 23
    })
    /**
     * 把一个对象的每一项都转化成可观测对象
     * @param { Object } obj 对象
     */
    function observable(obj) {
        if (!obj || typeof obj !== 'object') {
            return;
        }
        let keys = Object.keys(obj); //返回一个表示给定对象的所有可枚举属性的字符串数组
        keys.forEach((key) => {
            defineReactive(obj, key, obj[key])
        })
        return obj;
    }
    /**
     * 使一个对象转化成可观测对象
     * @param { Object } obj 对象
     * @param { String } key 对象的key
     * @param { Any } val 对象的某个key的值
     */
    function defineReactive(obj, key, val) {
        Object.defineProperty(obj, key, {
            get() {
                console.log(`${key}属性被读取了`);
                return val;
            },
            set(newVal) {
                console.log(`${key}属性被修改了`);
                val = newVal;
            }
        })
    }
    // person.age
    // age属性被读取了
    // 23
    // person.age=24
    // age属性被修改了
    // 24

我们通过Object.keys()将一个对象返回一个表示给定对象的所有可枚举属性的字符串数组,然后遍历它,使得所有对象可以被观测到。

第三步,依赖收集,制作一个订阅器

我们就可以在数据被读或写的时候通知那些依赖该数据的视图更新了,为了方便,我们需要先将所有依赖收集起来,一旦数据发生变化,就统一通知更新。创建一个依赖收集容器,也就是消息订阅器Dep,用来容纳所有的“订阅者”。订阅器Dep主要负责收集订阅者,然后当数据变化的时候后执行对应订阅者的更新函数。

「设计了一个订阅器Dep类:」

   class Dep {
        constructor(){
            this.subs = []
        },
        //增加订阅者
        addSub(sub){
            this.subs.push(sub);
        },
        //判断是否增加订阅者
        depend () {
            if (Dep.target) {
                this.addSub(Dep.target)
            }
        },
        //通知订阅者更新
        notify(){
            this.subs.forEach((sub) =>{
                sub.update()
            })
        }
    }
Dep.target = null;

创建完订阅器,然后还要修改一下defineReactive

function defineReactive (obj,key,val) {
        let dep = new Dep();
        Object.defineProperty(obj, key, {
            get(){
                dep.depend(); //判断是否增加订阅者
                console.log(`${key}属性被读取了`);
                return val;
            },
            set(newVal){
                val = newVal;
                console.log(`${key}属性被修改了`);
                dep.notify() //数据变化通知所有订阅者
            }
        })
    }

我们将订阅器Dep添加订阅者的操作设计在get()里面,这是为了让订阅者初始化时进行触发,因此需要判断是否要添加订阅者。

第四步,订阅者Watcher

「设计一个订阅者Watcher类:」

  class Watcher {
    // 初始化
        constructor(vm,exp,cb){
            this.vm = vm; // 一个Vue的实例对象
            this.exp = exp; // 是node节点的v-model或v-on:click等指令的属性值。如v-model="name",exp就是name;
            this.cb = cb; // 是Watcher绑定的更新函数;
            this.value = this.get();  // 将自己添加到订阅器的操作
        },
  // 更新
        update(){
            let value = this.vm.data[this.exp];
            let oldVal = this.value;
            if (value !== oldVal) {
                this.value = value;
                this.cb.call(this.vm, value, oldVal);
            },
        get(){
            Dep.target = this;  // 缓存自己
            let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
            Dep.target = null;  // 释放自己
            return value;
        }
    }

订阅者Watcher在初始化的时候需要将自己添加进订阅器Dep中,如何添加呢?我们已经知道监听器Observer是在get()执行了添加订阅者Wather的操作的,所以我们只要在订阅者Watcher初始化的时候触发对应的get()去执行添加订阅者操作即可。那要如何触发监听器get(),再简单不过了,只要获取对应的属性值就可以触发了。

订阅者Watcher运行时,首先进入初始化,就会执行它的 this.get() 方法, 执行Dep.target = this;,实际上就是把Dep.target 赋值为当前的渲染 Watcher ,接着又执行了let value = this.vm.data[this.exp];。在这个过程中会对数据对象上的数据访问,其实就是为了触发数据对象的get()

每个对象值的get()都持有一个dep,在触发 get()的时候会调用 dep.depend()方法,也就会执行this.addSub(Dep.target),即把当前的 watcher订阅到这个数据持有的dep.subs中,这个目的是为后续数据变化时候能通知到哪些 subs 做准备。完成依赖收集后,还需要把 Dep.target恢复成上一个状态Dep.target = null; 因为当前vm的数据依赖收集已经完成,那么对应的渲染Dep.target 也需要改变。

update()是用来当数据发生变化时调用Watcher自身的更新函数进行更新的操作。先通过let value = this.vm.data[this.exp];获取到最新的数据,然后将其与之前get()获得的旧数据进行比较,如果不一样,则调用更新函数cb进行更新。


总结:

实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。

230e4163937872d78623209bc67f6c94.png
在这里插入图片描述
实现一个Vue数据绑定:

「index.html」

html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Documenttitle>
head>
<body>
    <h1 id="name">h1>
    <input type="text">
    <input type="button" value="改变data内容" onclick="changeInput()">
<script src="observer.js">script>
<script src="watcher.js">script>
<script>function myVue (data, el, exp) {this.data = data;
        observable(data);                      //将数据变的可观测
        el.innerHTML = this.data[exp];         // 初始化模板数据的值new Watcher(this, exp, function (value) {
            el.innerHTML = value;
        });return this;
    }var ele = document.querySelector('#name');var input = document.querySelector('input');var myVue = new myVue({name: 'hello world'
    }, ele, 'name');//改变输入框内容
    input.oninput = function (e) {
        myVue.data.name = e.target.value
    }//改变data内容function changeInput(){
        myVue.data.name = "改变后的data"
    }script>
body>
html>

「observer.js」(为了方便,这里将订阅器与监听器写在一块)

     // 监听器
     // 把一个对象的每一项都转化成可观测对象
     // @param { Object } obj 对象
     
    function observable (obj) {
        if (!obj || typeof obj !== 'object') {
            return;
        }
        let keys = Object.keys(obj);
        keys.forEach((key) =>{
            defineReactive(obj,key,obj[key])
        })
        return obj;
    }
     // 使一个对象转化成可观测对象
     // @param { Object } obj 对象
     // @param { String } key 对象的key
     // @param { Any } val 对象的某个key的值
     
    function defineReactive (obj,key,val) {
        let dep = new Dep();
        Object.defineProperty(obj, key, {
            get(){
                dep.depend();
                console.log(`${key}属性被读取了`);
                return val;
            },
            set(newVal){
                val = newVal;
                console.log(`${key}属性被修改了`);
                dep.notify()                    //数据变化通知所有订阅者
            }
        })
    }

 // 订阅器Dep 
    class Dep {
        
        constructor(){
            this.subs = []
        }
        //增加订阅者
        addSub(sub){
            this.subs.push(sub);
        }
        //判断是否增加订阅者
        depend () {
            if (Dep.target) {
                this.addSub(Dep.target)
            }
        }

        //通知订阅者更新
        notify(){
            this.subs.forEach((sub) =>{
                sub.update()
            })
        }
        
    }
    Dep.target = null;

「watcher.js」

 class Watcher {
        constructor(vm,exp,cb){
            this.vm = vm;
            this.exp = exp;
            this.cb = cb;
            this.value = this.get();  // 将自己添加到订阅器的操作
        }
        get(){
            Dep.target = this;  // 缓存自己
            let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
            Dep.target = null;  // 释放自己
            return value;
        }
        update(){
            let value = this.vm.data[this.exp];
            let oldVal = this.value;
            if (value !== oldVal) {
                this.value = value;
                this.cb.call(this.vm, value, oldVal);
            }
    }
}

3、Vue的computed与watch的区别在哪里?

我们先看一个例子:

html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Documenttitle>
head>
<body>
    <div id="app">
       <p>{{a}}p>
       <p>{{b}}p>
       <p>{{c}}p>
       <button @click='change'>changebutton>
    div>
body>
<script src="vue.js">script>
<script>var vm =new Vue({el:'#app',data:{a:1,b:2
        },methods:{
            change(){this.a = 5;
            }
        },watch:{
            a(){console.log('watch');
            }
        },computed:{
            c(){console.log('computed');return this.a + this.b;
            }
        }
    })script>
html>

一开始的时候,910a97447f424fdd11a272089932e03c.png点击按钮时,37659b5fabb511015023032dc6fd4f2e.png我们可以看到一开始的时候,打印出了computed,当点击按钮时,data内的属性值a发生变化,打印出watch,接着我们不停点击按钮,并没有打印。(?查看总结4)

我们来总结一下,

  1. 最本质的区别,computed为计算属性,watch为监听属性。
  2. watch就是单纯的监听某个数据的变化,支持深度监听。computed是计算属性,是依赖于某个或者某些属性值,当依赖值发生变化时,也会发生变化。
  3. 计算属性不在data中,计算属性依赖值在data中。watch监听的数据在data中。(不一定在只是data,也可能是props)
  4. watch用于观察和监听页面上的vue实例,当你需要在数据变化响应时,执行异步操作,或高性能消耗的操作,那么watch为最佳选择。computed可以关联多个实时计算的对象,当这些对象中的其中一个改变时都会触发这个属性,具有缓存能力,所以只有当数据再次改变时才会重新渲染,否则就会直接拿取缓存中的数据。
  5. computed是在Dep.update()执行之后,数据更新之前,对数据重新改造。watch是在set刚开始发生的时候添加的回调,可以监听数据的变化。

4、为什么在Vue3.0采用了Proxy,抛弃了Object.defineProperty?

Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。为了解决这个问题,经过Vue内部处理后可以使用以下几种方法来监听数组。

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

由于只针对以上八种方法进行了hack处理,所以其他数组的属性方法也是检测不到的,还是具有一定的局限性。

这里我们举个例子,可以看得更加明白:

html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Documenttitle>
head>
<body>
    <div id="app">
        <ul>
            <li v-for='item in watchArr'>{{item.name}}li>
        ul>
        <button @click='change'>changebutton>
    div>
body>
<script src="vue.js">script>
<script>var vm = new Vue({el: '#app',data: {watchArr: [{name: '1',
            },{name: '2',
            }],
        },methods: {
            change() {this.watchArr =[{name: '3',
                }];this.watchArr.splice(0, 1);this.watchArr[0].name = 'xiaoyue'; // 无法监听this.watchArr.length = 5; // 无法监听
            }
        },watch: {
            watchArr(newVal) {console.log('监听了');
            },
        }
    })script>
html>

想必看到上面的例子我们会更加明白Object.defineProperty的局限性。接下来,我们接着说Object.defineProperty只能劫持对象的属性,因此,我们需要对每个对象的每个属性进行遍历。Vue2.0里,是通过「递归+遍历data对象」来实现对数据的监控的,如果属性值也是对象的话,那么需要深度遍历。显然如果能够劫持一个完整的对象才是更好的选择。

那么Proxy有以下两个优点:

  1. 可以劫持整个对象,并返回一个新对象
  2. 有13种劫持操作

摒弃 Object.defineProperty,基于Proxy的观察者机制探索

5、为什么Vuex的mutation不能做异步操作?

因为更改state的函数必须是纯函数,纯函数既是统一输入就会统一输出,没有任何副作用;如果是异步则会引起额外的副作用,导致更改后的state不可预测。

6、Vue中的computed是如何实现的?

实质是一个惰性的wather,在取值操作时根据自身标记dirty属性返回上一次计算结果或重新计算值在创建时就进行一次取值操作,收集依赖变动的对象或属性(将自身压入dep中),在依赖的对象或属性变动时,仅将自身标记dirty致为true

7、Vue的父组件和子组件的生命周期钩子函数执行顺序是什么?

  1. 加载渲染过程 (父)beforeCreate 「→」  (父)created 「→」  (父)beforeMount 「→」 (子)beforeCreate 「→」 (子)created 「→」 (子)beforeMount 「→」 (子)mounted 「→」 (父)mounted
  2. 子组件更新过程 (父)beforeUpdate 「→」 (子)beforeUpdate 「→」 (子)Updated 「→」  (父)Updated
  3. 父组件更新过程 (父)beforeUpdate 「→」   (父)Updated
  4. 销魂过程 (父)beforeDestroy 「→」  (子)beforeDestory 「→」 (子)destroyed  「→」 (父)destroyed

作者:「Vam的金豆之路」

主要领域:「前端开发」

我的微信:「maomin9761」

微信公众号:「前端历劫之路」

a3ad3d67a1e3005ff04903b8f3b68618.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
linux2.6.x的配置文件kconfig语法linux在2.6版本以后将配置文件由原来的config.in 改为kconfig,对于kconfig的语法在/Documentation/kbuild/kconfig-language.txt做了详细的说 明,在这里给出kconfig-language.txt的文版。 介绍 ---- 在配置数据库的配置选项是以树的形式组织的: +- Code maturity level options | +- Prompt for development and/or incomplete code/drivers +- General setup | +- Networking support | +- System V IPC | +- BSD Process Accounting | +- Sysctl support +- Loadable module support | +- Enable loadable module support | +- Set version information on all module symbols | +- Kernel module loader +- ... 每个选项都有其自己的依赖关系。这些依赖关系决定了选项是否是可见的。父选项可见,子选项才能可见。 菜单选项 -------- 大多数的选项都定义了一个配置选项,其它选项则有助于对它们进行组织。(原文:Most entries define a config option, all other entries help to organize them.)一个配置选项定义可以是下面 的形式: config MODVERSIONS bool "Set version information on all module symbols" depends MODULES help Usually, modules have to be recompiled whenever you switch to a new kernel. ... 每行都是以关键字开始,并可以接多个参数。"config" 为定义了一新的配置选项。下面的几行定义了该配置 选项的属性。属性可以是该配置选项的类型,输入提示(input prompt),依赖关系,帮助信息和默认值。一 配置选项可以用相同的名字定义多次,但每个定义只能有一个输入提示并且类型还不能冲突。 菜单属性 -------- 一菜单选项可以有多个属性。并不要求这些属性可以用在任何地方(见语法)。 - 类型定义:"bool"/"tristate"/"string"/"hex"/"int" 每个配置选项都必须指定类型。有两个基本类型:tristate 和 string,其他类型都是基于这两个基本 类型。类型定义可以用输入提示,所以下面的两个例子是等价的: bool "Networking support" 和 bool prompt "Networking support" - 输入提示: "prompt" ["if" ] 每个菜单选项最多只能有一个显示给用户的输入提示。可以用 "if" 来表示该提示的依赖关系,当然这是 可选的。 - 默认值:"default" ["if" ] 一个配置选项可以有任意多个默认值。如果有多个默认值,那么只有第一个被定义的值是可用的。默认值并 不是只限于应用在定义他们的菜单选项。这就意味着默认值可以定义在任何地方或被更早的定义覆盖。 如果用户没有设置(通过上面的输入提示),配置选项的值就是默认值。如果可以显示输入提示的话,就会把 默认值显示给用户,并可以让用户进行修改。 默认值的依赖关系可以用 "if" 添加。(可选项) - 依赖关系:"depends on"/"requires" 为一菜单选项定义依赖关系。如果定义了多个依赖关系,它们之间用 '&&' 间隔。依赖关系也可以应用到 该菜单所有的其它选项(同样接受一if表达式),所以下面的两个例子是等价的: bool "foo" if BAR default y if BAR and depends on BAR bool "foo" default y - 反向依赖关系:"select" ["if" ] 尽管普通的依赖关系可以降低一选项的上限,反向依赖能将这一限制降的更低。当前菜单选项的值是symbol 的最小值。如果symbol被选择了多次,上限就是其的最大值。 反向依赖只能用在 boolean 或 tristate 选项上。 - 数据范围:"range" ["if" ] 为int和hex类型的选项设置可以接受输入值范围。用户只能输入大于等于第一个symbol,小于等于第二个 symbol的值。 - 帮助信息: "help" or "---help---" 定义一帮助信息。帮助信息的结束就由缩进的水平决定的,这也就意味着信息是在第一个比帮助信息开始行 的缩进小的行结束。 "---help---" 和 "help" 在实现的作用上没有区别,"---help---" 有助于将文件的配置逻辑与 给开发人员的提示分开。 菜单依赖关系 ------------ 依赖关系决定了菜单选项是否可见,也可以减少tristate的输入范围。tristate逻辑比boolean逻辑在表 达式用更多的状态(state)来表示模块的状态。依赖关系表达式的语法如下: ::= (1) '=' (2) '!=' (3) '(' ')' (4) '!' (5) '&&' (6) '||' (7) 表达式是以优先级的降序列出的。 (1) 将symbol赋给表达式。boolean和tristate类型的symbol直接赋给表达式。所有其它类型的symbol 都赋 'n'。 (2) 如果两个symbol相等,返回'y',否则为'n'。 (3) 如果两个symbol相等,返回'n',否则为'y'。 (4) 返回表达式的值。用于改变优先级。 (5) 返回 (2-/expr/) 的结果。 (6) 返回 min(/expr/,/expr/) 的结果。 (7) 返回 max(/expr/,/expr/) 的结果。 一个表达式的值可以是'n','m'或'y'(或者是计算的结果 0,1,2)。当表达式的值为'm'或'y'的时候,菜 单项才是可见的。 symbol有两种类型:不可变的和可变的。不可变的symbol是最普通的,由'config'语句定义,完全由数字 、字母和下划线组成(alphanumeric characters or underscores)。 不可变的symbol只是表达式的一部分。经常用单引号或双引号括起来。在引号,可以使用任何字符,使用引 号要用转义字符'\'。 菜单结构 -------- 菜单在树的位置可由两种方法决定。第一种可以是这样: menu "Network device support" depends NET config NETDEVICES ... endmenu 所有的在"menu" ... "endmenu" 之间都是"Network device support"的子菜单。所有的子菜单选项 都继承了父菜单的依赖关系,比如,"NET"的依赖关系就被加到了配置选项NETDEVICES的依赖列表。 还有就是通过分析依赖关系生成菜单的结构。如果菜单选项在一定程度上依赖于前面的选项,它就能成为该选 项的子菜单。首先,前面的(父)选项必须是依赖列表的一部分并且它们必须有满足下面两个条件的选项: - 如果父选项为'n',子选项必须不可见。 - 如果父选项可见,子选项才能可见。 config MODULES bool "Enable loadable module support" config MODVERSIONS bool "Set version information on all module symbols" depends MODULES comment "module support disabled" depends !MODULES MODVERSIONS 直接依赖 MODULES,这就意味着如果MODULES不为'n',该选项才可见。换句话说,当 MODULES可见时,选项才可见(MODULES的(空)依赖关系也是选项依赖关系的一部分)。 Kconfig 语法 ------------ 配置文件描述了菜单选项,每行都是以一关键字开头(除了帮助信息)。下面的关键字结束一菜单选项: - config - menuconfig - choice/endchoice - comment - menu/endmenu - if/endif - source 前5个同样可以用在菜单选项定义的开始。 config: "config" 定义了一配置选项 并且可以接受任何前面介绍的属性。 menuconfig: "menuconfig" 此关键字和前面的关键字很相似,但它在前面的基础上要求所有的子选项作为独立的行显示。(This is similar to the simple config entry above, but it also gives a hint to front ends, that all suboptions should be displayed as a separate list of options.) choices: "choice" "endchoice" 该关键字定义了一组选择项,并且选项可以是前面描述的任何属性。尽管boolean只允许选择一个配置选项, tristate可以抒多个配置选项设为'm',但选项只能是boolean或tristate类型。这可以在一个硬件有多 个驱动的情况下使用,最终只有一个驱动被编译进/加载到内核,,但所有的驱动都可以编译成模块。 选项可以接受的另一个选项是"optional",这样选项就被设置为'n',没有被选的。 comment: "comment" 这里定义了在配置过程显示给用户的注释,该注释还将写进输出文件。唯一可用的可选项是依赖关系。 menu: "menu" "endmenu" 这里定义了一个菜单,详细信息请看前面的"菜单结构"。唯一可用的可选项是依赖关系。 if: "if" "endif" 这里定义了if结构。依赖关系被加到所有在if ... endif 的菜单选项
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值