JQuery - Sizzle选择器引擎原理分析

Sizzle源码分析: jQuery工具方法 Sizzle作为一个独立的选择器引擎,为jQuery库提供了强大的选择功能。它通过定义不同类型的查找方法,并利用缓存来提高性能,实现了高效的元素查找功能。在本篇文章中,我们将深入分析Sizzle的源代码,该代码是jQuery库中用于选择器引擎的一部分。函数内部,根据传入的选择器表达式和上下文,它会调用适当的查找方法来获取匹配的元素。这些方法会根据给定的选择器表达式和上下文来查找匹配的元素。对象还包含其他许多方法,用于处理不同类型的选择器,例如类选择器、标签选择器等。对象来存储选择器表达式的属性和方法。 阅读详情

说明:14年学习的jquery源码,搬到这里供大家交流。原文地址:https://segmentfault.com/a/1190000003933990

一、前言

Sizzle原来是jQuery里面的选择器引擎,后来逐渐独立出来,成为一个独立的模块,可以自由地引入到其他类库中。我曾经将其作为YUI3里面的一个module,用起来畅通无阻,没有任何障碍。Sizzle发展到现在,以jQuery1.8为分水岭,大体上可以分为两个阶段,后面的版本中引入了编译函数的概念,Sizzle的源码变得更加难读、不再兼容低版本浏览器,而且看起来更加零散。本次阅读的是Sizzle第一个阶段的最终版本jQuery1.7,从中收获颇多,一方面是框架设计的思路,另外一方面是编程的技巧。

二、jQuery constructor

Sizzle来源于jQuery,并且jQuery是一个基于DOM操作的类库,那么在研究Sizzle之前很有必要看看jQuery的整体结构:

(function(window, undefined) {
    var jQuery = function (selector, context) {
        return new  jQuery.fn.init(selector, context, rootjQuery);
    }
    jQuery.fn = jQuery.prototype = {...}
    jQuery.fn.init.prototype = jQuery.fn;
    // utilities method
    // Deferred 
    // support 
    // Data cache
    // queue
    // Attribute
    // Event
    // Sizzle about 2k lines
    // DOM
    // css operation
    // Ajax
    // animation
    // position account
    window.jQuery  = window.$ = jQuery;
})

jQuery具有很强的工程性,一个接口可以处理多种输入,是jQuery容易上手的主要原因,相应的,这样一个功能庞大的API内部实现也是相当复杂。要想弄清楚jQuery与Sizzle之间的关系,首先就必须从jQuery的构造函数入手。经过整理,理清楚了构造函数的处理逻辑,在下面的表中,jQuery的构造函数要处理6大类情况,但是只有在处理选择器表达式(selector expression)时才会调用Sizzle选择器引擎。

三、Sizzle的设计思路

对于一个复杂的选择器表达式(下文的讨论前提是浏览器不支持querySelectorAll) ,如何对其进行处理?

3.1 分割解析

对于复杂的选择器表达式,原生的API无法直接对其进行解析,但是却可以对其中的某些单元进行操作,那么很自然就可以采取先局部后整体的策略:把复杂的选择器表达式拆分成一个个块表达式和块间关系。在下图中可以看到,1、选择器表达式是依据块间关系进行分割拆分的;2、块表达式里面有很多伪类表达式,这是Sizzle的一大亮点,而且还可以对伪类进行自定义,表现出很强的工程性;3、拆分后的块表达式有可能是简单选择器、属性选择器、伪类表达式的组合,例如div.a、.a[name = "beijing"]。

3.2 块表达式查找

经过块内查找, 得到了一个基本的元素集合,那如何处理块间关系呢?通过观察可以发现,对一个复杂的选择器表达式存在两种顺序:

  • 从左到右:对得到的集合,进行内部逐个遍历,得到新的元素集合,只要还有剩余的代码块,就需要不断地重复查找、过滤的操作。总结下就是:多次查找、过滤。

  • 从右到左:对得到的元素集合,肯定包括了最终的元素,而且还有多余的、不符合条件的元素,那么接下来的工作就是不断过滤,把不符合条件的元素剔除掉。

对于“相邻的兄弟关系(+)”、“之后的兄弟关系(~)”,哪种方式都无所谓了,效率没什么区别。但是对于“父子关系”、“祖先后代关系”就不一样了,此时Sizzle选择的是以从右到左为主,下面从两个维度进行解释:

a、设计思路

  • 左到右:不断查询,不断缩小上下文,不断地得到新的元素集合

  • 右到左:一次查询,多次过滤,第一查找得到的元素集合不断缩小,知道得到最终的集合

b、DOM树

  • 左到右:从DOM的上层往底层进行的,需要不断遍历子元素或后代元素,而一个元素节点的子元素或后代元素的个数是未知的或数量较多的

  • 右到左:从DOM的底层往上层进行的,需要不断遍历父元素或祖先元素,而一个元素的父元素或者祖先元素的数量是固定的或者有限的

但是从右到左是违背我们的习惯的,这样做到底会不会出现问题呢?答案是会出现错误,请看下面的一个简单DOM树:

在上面的例子中,我们看到当选择器表达式中存在位置伪类的时候,就会出现错误,这种情况下没有办法,准确是第一位,只能选择从左到右。

结论: 从性能触发,采取从右到左; 为了准确性,对位置伪类,只能采取从左到右。

四、Sizzle具体实现

4.1 Sizzle整体结构

if(document.querySelectorAll) {
    sizzle = function(query, context) {
        return makeArray(context.querySelectorAll(query));
     }
} else {
    sizzle 引擎实现,主要模拟querySelectorAll
}

通过上述代码可以看到,Sizzle选择器引擎的主要工作就是向上兼容querySelectorAll这个API,假如所有浏览器都支持该API,那Sizzle就没有存在的必要性了。

关键函数介绍:

  • Sizzle = function(selector, context, result, seed) : Sizzle引擎的入口函数

  • Sizzle.find: 主查找函数

  • Sizzle.filter: 主过滤函数

  • Sizzle.selectors.relative: 块间关系处理函数集 {“+”: function() {}, “ ”:function() {}, “>”: function() {}, “~”: function() {}}

4.2 分割解析

chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g

就是靠这样一个正则表达式,就可以把复杂多样的选择器表达式分割成若干个块表达式和块间关系,是不是觉得块表达式是一项神奇的技术,可以把复杂问题抽象化。正则的缺点是不利于阅读和维护,下图对其进行图形分析:

再来看看是如何具体实现的呢:

do {
        chunker.exec( "" ); // chunker.lastIndex = 0
        m = chunker.exec( soFar );
        if ( m ) {
            soFar = m[3];
            parts.push( m[1] );
            if ( m[2] ) {
                extra = m[3];
                break;
            }
        }
    } while ( m );
 
for example:
$(‘#J-con  ul>li:gt(2)’) 解析后的结果为:
parts = ["#J-con", "ul", ">", “li:gt(2)"]
extra = undefined
    
$(‘#J-con  ul>li:gt(2), div.menu’) 解析后的结果为:
parts = ["#J-con", "ul", ">", “li:gt(2)"]
extra = ‘div.menu’ 

4.3 块表达式处理

4.3.1 块内查找

在查找环节,通过Sizzle.find来实现,主要逻辑如下:

  • 依据DOM API性能决定查找依据: ID > Class> Name> Tag, 其中要考虑浏览器是否支持getElementsByClassName

  • Expr.leftMatch:确定块表达式类型

  • Expr.find:具体的查找实现

  • 结果: {set: 结果集合, expr: 块表达式剩余的部分,用于下一步的块内过滤}

// Expr.order = [“ID”, [ “CLASS”], “NAME”, “TAG ]
for ( i = 0, len = Expr.order.length; i < len; i++ ) {
    ……
    if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
        set = Expr.find[ type ]( match, context);
        expr = expr.replace( Expr.match[ type ], "" );
    }
}
4.3.2块内过滤

该过程通过Sizzle.filter来进行,该API不仅可以进行块内过滤,还可以进行块间过滤,通过inplace参数来确定。主要逻辑如下:

  • Expr.filter: {PSEUDO, CHILD, ID, TAG, CLASS, ATTR, POS} , 选则器表达式的类型

  • Expr.preFilter: 过滤前预处理,保证格式的规范化

  • Expr.filter: 过滤的具体实现对象

  • 内过滤、块间从左到后: inplace=false,返回新对象;块间从右到左: inplace=true, 原来的元素集合上过滤

Sizzle.filter = function( expr, set, inplace, not ) {
    for ( type in Expr.filter ) { //filter: {PSEUDO, CHILD, ID, TAG, CLASS, ATTR, POS}
        //   Expr.leftMatch:确定selector的类型
        if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
            // 过滤前预处理,保证格式的规范化
            match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
            // 进行过滤操作    
            found = Expr.filter[ type ]( item, match, i, curLoop );
            // if inplace== true,得到新数组对象;
            if ( inplace && found != null ) {
                if ( pass ) { anyFound = true; } else { curLoop[i] = false; }
            } else if ( pass ) {
                result.push( item );
            }
        }
    }
}

4.4 块间关系处理

4.4.1 判断处理顺序

满足下面的正则,说明存在位置伪类,为了保证计算的准确定,必须采取从左到后的处理顺序,否则可以为了效率尽情使用从右到左。

origPOS = /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/

4.4.2 左到右处理

首先依据parts的第一个元素进行查询,然后对得到的元素集合进行遍历,利用位置伪类处理函数posProcess进行伪类处理,直到数组parts为空。

// parts是selector expression分割后的数组
set = Expr.relative[ parts[0] ] ? [ context ] :   Sizzle( parts.shift(), context );
// 对元素集合多次遍历,不断查找
while(parts.length) {
 
    selector = parts.shift();
    ……
    set = posProcess(selector, set, seed);

}

接下来在看下posProcess的内部逻辑:如果表达式内部存在位置伪类(例如p:first),在DOM的API中不存在可以处理伪类(:first)的API,这种情况下就先把伪类剔除掉,依照剩余的部分进行查询(p),这样得到一个没有伪类的元素集合,最后在以上述中的伪类为条件,对得到的元素集合进行过滤。

// 从左到后时,位置伪类处理方法
var posProcess = function( selector, context, seed ) {
    var match,
       tmpSet = [],
       later = "",
       root = context.nodeType ? [context] : context;
    // 先剔除位置伪类,保存在later里面
    while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
        later += match[0];
       selector = selector.replace( Expr.match.PSEUDO, "" );
    }
         
    selector = Expr.relative[selector] ? selector + "*" : selector;
    // 在不存在位置伪类的情况下,进行查找
    for ( var i = 0, l = root.length; i < l; i++ ) {
        Sizzle( selector, root[i], tmpSet, seed );
    }
    // 以位置伪类为条件,对结果集合进行过滤
    return Sizzle.filter( later, tmpSet );
};
4.4.3 右到左的处理顺序

其实Sizzle不完全是采用从右到左,如果选择器表达式的最左边存在#id选择器,就会首先对最左边进行查询,并将其作为下一步的执行上下文,最终达到缩小上下文的目的,考虑的相当全面。

// 如果selector expression 最左边是#ID,则计算出#ID选择器,缩小执行上下文
if(parts[0] is #id) {
    context = Sizzle.find(parts.shift(), context)[0];
}
if (context) {
    // 得到最后边块表达式的元素集合
    ret = Sizzle.find(parts.pop(), context);
    // 对于刚刚得到的元素集合,进行块内元素过滤
    set = Sizzle.filter(ret.expr, ret.set) ;
    // 不断过滤
    while(parts.length) {
        pop = parts.pop();
        ……
        Expr.relative[ cur ]( checkSet, pop );
    }
}

对于块间关系的过滤,主要依据Expr.relative来完成的。其处理逻辑关系是:判断此时的选择器表达式是否为tag,如果是则直接比较nodeName,效率大增,否则只能调用Sizzle.filter。下面以相邻的兄弟关系为例进行说明:

"+": function(checkSet, part){
    var isPartStr = typeof part === "string",
        isTag = isPartStr && !rNonWord.test( part ),   //判断过滤selector是否为标签选择器
        isPartStrNotTag = isPartStr && !isTag;
    if ( isTag ) {
        part = part.toLowerCase();
    }
    for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
        if ( (elem = checkSet[i]) ) {
            while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
            checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
                elem || false :
                elem === part;
        }
    }
    if ( isPartStrNotTag ) {
        Sizzle.filter( part, checkSet, true );
    }
}

4.5 拓展性

Sizzle的另外一大特性就是可以自定义选择器,当然仅限于伪类,这是Sizzle工程型很强的另外一种表现:

$.extend($.selectors.filters, {
    hasLi: function( elem ) {
        return $(elem).find('li').size() > 0;
    }
});
var e = $('#J-con :hasLi');
console.log(e.size());    // 1

上述代码中的$.extend相当远YUI3中的augment、extend、mix的合体,功能相当强大,只需要对$.selectors.filters(即Sizzle.selectors.filters)对象进行拓展就可以,里面每个属性的返回值为Boolean类型,用于判断伪类的类型。

基于深度学习的树木种类识别 本文介绍了基于深度学习的树木种类识别技术。我们将通过深度学习模型和图像处理技术实现对树木的自动识别与分类。首先,我们将介绍深度学习的基本原理和相关算法。接着,我们将详细说明树木种类识别的数据准备、模型构建和训练过程。最后,我们将给出代码示例,帮助读者深入了解和实践该技术。 阅读详情

相关推荐

ADS版图优化中的参数化设计技巧

本文详细介绍了在ADS中进行射频电路版图参数化设计的核心技巧。通过将版图关键尺寸定义为变量,并与电磁仿真及优化器联动,实现从手动修改到自动迭代的转变,极大提升了PA、LNA等电路的设计与优化效率。文章以阶梯阻抗变换器为例,阐述了变量创建、绑定及协同仿真的完整流程,并提供了避免网格冲突、变量混乱等常见问题的实用指南。

weixin_29296241的博客 405

SE-0353 约束存在类型:让 `any Collection<String>` 成为现实的 Swift 泛型与抽象能力进阶

SE-0353(Constrained Existential Types,约束存在类型)为 Swift 的存在类型(existential types)引入了对协议主要关联类型(primary associated types)的约束能力,使开发者可以写出 `any Collection<String>` 这样的类型。本指南以 swift-evolution 仓库中的 [SE-0353 提案原文

gitblog_00061的博客 501

哈希拓展攻击CTF题做法

kali下载相关工具hash-ext-attack:hash拓展题目特征:2023楚慧杯upload_shell实验吧之让我进去:前言:具体怎么加密解密补位的原理我不懂,深入理解的部分我在文末挂了链接,以下也只是我做题的结论和通解,可能不准确(但是做题还挺准确),有错误欢迎指出:是在密码加密过程中引入的一个随机值,它与密码结合使用,目的是增加密码的复杂性和安全性。在密码加密中,常见的做法是将用户提供的密码进行哈希处理,然后存储哈希值而不是明文密码

2301_76690905的博客 2422

jQuery中的选择器引擎Sizzle

读Sizzle的源码,分析的Sizzle版本号是2.3.3。 Sizzle的Github主页 浏览器原生支持的元素查询方法: 方法名 方法描述 兼容性描述 getElementById 根据元素ID查询元素 IE6+, Firefox 2+, Chrome 4+, Safari 3.1+ getElementsByTagNa...

weixin_33910137的博客 260

告别第三方插件:官方Fused Library如何重塑Android库的合并与发布流程

本文详细介绍了Android官方推出的Fused Library如何替代第三方fat-aar插件,优化Android库的合并与发布流程。通过原生支持、智能依赖管理等优势,Fused Library解决了版本兼容性、依赖传递等核心痛点,提供了更稳定高效的开发体验。文章还提供了迁移实操指南和工程实践建议,帮助开发者顺利过渡到官方方案。

weixin_42561040的博客 259

OpenClaw本地部署教程

本文详细介绍了在Windows 11系统上部署OpenClaw与Ollama本地模型的完整流程。主要内容包括:硬件要求说明、Node.js环境配置、Ollama模型安装与定制(重点扩展上下文窗口至32768 tokens)、OpenClaw核心框架安装与对接、常见问题解决(如模型上下文窗口过小报错)以及可选技能扩展和飞书接入方案。部署全程约需1-2小时,强调使用备用设备或虚拟机以确保安全,并提供关键配置参数和排错命令。该方案实现了"数据不出门、推理本地化"的私有化部署目标。

技术引领业务创新 2587

Sizzle选择器引擎解读

一、前言       Sizzle原来是jQuery里面的选择器引擎,后来逐渐独立出来,成为一个独立的模块,可以自由地引入到其他类库中。我曾经将其作为YUI3里面的一个module,用起来畅通无阻,没有任何障碍。Sizzle发展到现在,以jQuery1.8为分水岭,大体上可以分为两个阶段,后面的版本中引入了编译函数的概念,Sizzle的源码变得更加难读、不再兼容低版本浏览器,而且看起来更加零

winston love big brother 716

【保姆级教程/代码】使用PRIDE PPP-AR软件解算ZTD、反演GNSS PWV,并评估精度

在本算例中,我下载了BJFS站2024年的IGS ZTD产品,用于评估PRIDE PPP-AR软件解算出ZTD结果的精度。

weixin_43910227的博客 2375

Sizzle详解

今天重新看了一些博客对sizzle解析,有了一点新收获。 jquery的sizzle选择器引擎是一个很强大的功能,兼容了很多种选择器匹配的写法,比如可以使用它们的 id, 类, 类型, 属性, 属性值、伪类等来选取元素。废话补多说,下面来对sizzle做个大概的分析: 1、Sizzle整体结构 if(document.querySelectorAll) { sizzle = fun...

Mortimer's Blogs 2667

VMware ESXi 7.0下Intel网卡SR-IOV配置实战:解决虚拟化网络延迟问题

本文详细介绍了在VMware ESXi 7.0环境下为Intel网卡配置SR-IOV(单根I/O虚拟化)的完整实战流程。通过硬件辅助直通技术,虚拟机可直接访问物理网卡资源,有效绕过虚拟化层瓶颈,从而显著降低网络延迟和CPU开销,适用于金融交易、实时计算等对性能敏感的场景。

embedding5hiker的博客 555

【CANoe】CANoe的 I/O功能-以VN1640A为例

Canoe的 I/O功能-以VN1640A为例

星Yeah 的博客 7883

[转]JQuery - Sizzle选择器引擎原理分析

原文: https://segmentfault.com/a/1190000003933990 ----------------------------------------------------------------------- 说明:14年学习的jquery源码,搬到这里供大家交流。 一、前言 Sizzle原来是jQuery里面的选择器引擎,后来逐渐独立出来,成为一个独立的模块,可以自...

weixin_34318272的博客 170

人工智能训练师三级考试备考全攻略|报名条件+真题+补贴申领一站式通关(2026版)

本专栏是人工智能训练师三级考证最完整的备考指南,共77篇文章,覆盖从职业认知、报名实操、AI基础知识、数据标注、模型训练测试部署、考前冲刺到考后进阶的全链路。本导读按模块整理了全部文章链接,帮你快速定位所需内容。人工智能训练师是国家认定的正式职业,证书可在osta.mohrss.gov.cn查询,三级证书可申领最高3120元技能补贴。

白话机器学习 1813

jQuery源码分析之Sizzle--css选择器引擎

null

大橙子的专栏 879

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理

  这一节要分析的东东比较复杂,篇幅会比较大,也不知道我描述后能不能让人看明白。这部分的源码我第一次看的时候也比较吃力,现在重头看一遍,再分析一遍,看能否查缺补漏。   看这一部分的源码需要有一个完整的概念后去看才比较容易看懂,所以我们先把整个编译的原理阐述以后再进行解析。   还是以上次...

chulinhu9266的博客 173

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

深入学习jquery源码之jQuery的选择器引擎Sizzle(一)

深入学习jquery源码之jQuery的选择器引擎Sizzle Sizzle是一个纯javascript CSS选择器引擎。jquery1.3开始使用sizzle,Sizzle一反传统采取了相反的Right To Left的查询匹配方式,效率提高.Sizzle是jQuery作者John Resig新写的DOM选择器引擎,速度号称业界第一.Sizzle完全独立于jQuery,若不想用jQuery,...

悦分享 1万+

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理续(伪类选择器“PSEUDO”和子伪类选择器"CHILD"原子选择器详解)

我们知道Sizzle支持的伪类有有很多,为了便于处理被Sizzle分成了两类:单个单词的伪类“PSEUDO”和多个词语使用“-”链接的“CHILD”。我们下面一一分析。     先看"PSEUDO": a. Sizzle的伪类选择器"PSEUDO"     我们先整体看一下有哪些个伪类      伪类生成匹配器的源码如下: matcher = Expr.filter[ to

chen_hua89的博客 988

jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理续(伪类分割器setMatcher)

我也看过很多Sizzle源码分析的博客,伪类分割器setMatcher介绍的比较少。但是本人认为这是一个比较重要的难点,我第一遍看源码的时候也忽略了。现在回来看第二遍,一定要把这个东东弄懂。    a. 伪类分割器setMatcher   伪类分隔器对伪类选择器进行分隔处理,返回处理后的最终匹配器。   伪类和其他原子匹配器有些不同,伪类具有强制的位置关系,特别是和别的选择器并列的时候

chen_hua89的博客 684

jQuery选择器探究:Sizzle构造函数

什么情况下jQuery会调用传统的通用的Sizzle逻辑呢?前几篇分析过,ID选择器会在jQuery构造函数中直接被处理,TAG选择器和CLASS选择器会在新式浏览器中(支持并实现document.querySelectorAll强大功能)直接被处理,甚至只要遵循CSS规范的选择器都会被新式浏览器直接处理,那么剩下的场景将交给传统Sizzle引擎处理了:不支持document.querySelec

warhin的专栏 942

jQuery中的Sizzle引擎分析

我分析的jQuery版本是1.8.3。Sizzle代码从3669行开始到5358行,将近2000行的代码,这个引擎的版本还是比较旧,最新的版本已经到v2.2.2了,代码已经超过2000行了。并且还有个专门的Sizzle主页。 从一个demo开始,HTML代码如下: <div id="grand_father"> <div id...

weixin_33726318的博客 105
上一篇: jquery实现表格拖拽排序
下一篇: jQuery 自定义选择器 于大等于和小于等于
ole_triangle_java
博客等级 码龄10年 82粉丝 83原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值