《 JavaScript高级程序设计》第六章 DOM基础

截取自读书频道:

http://book.csdn.net/bookfiles/110/index.htm

6.1  什么是DOM

在开始详细介绍什么是DOM之前,你首先要了解是什么促使了它的诞生。尽管DOM很大程度上受到浏览器中动态HTML发展的影响,但W3C还是将它最先应用于XML

6.1.1  XML简介

XML可扩展标记语言)是从称为SGML标准通用标记语言)的更加古老的语言派生出来的。SGML的主要目的是定义使用标签来表示数据的标记语言的语法。

XML去掉了之前令许多开发人员头疼的SGML的随意语法。

1  任何的起始标签都必须有一个结束标签。

2  可以采用另一种简化语法,可以在一个标签中同时表示起始和结束标签。这种语法是在大于符号之前紧跟一个斜线(/),例如<tag />XML解析器会将其翻译成<tag></tag>

3  标签必须按合适的顺序进行嵌套,所以结束标签必须按镜像顺序匹配起始标签,例如 <b>this is a <i>sample</i> string</b>

4  所有的特性都必须有值。

5  所有的特性都必须在值的周围加上双引号。

xml的主要目的是使用文本以结构化的方式来表示数据。在某些方面,XML文件也类似于数据库,提供数据的结构化视图。

每个XML文档都由XML序言开始,在前面的代码中的第一行便是XML序言<?xml version="1.0"?>

第二行代码,<books>,则是文档元素document element),它是文件中最外面的标签(我们认为元素(element)是起始标签和结束标签之间的内容)。

然后其中的具体内容就可能包括注释,处理指令,<![CDATA[ ]]>代码.

6.1.2  针对XMLAPI

XML定义为一种语言之后,就出现了使用常见的编程语言(如Java)来同时表现和处理XML代码的需求。

首先出现的是Java上的SAXSimple API for XML)项目。SAX提供了一个基于事件的XML解析的API。从其本质上来说,SAX解析器从文件的开头出发,从前向后解析,每当遇到起始标签或者结束标签、特性、文本或者其他的XML语法时,就会触发一个事件。然后,当事件发生时,具体要怎么做就由开发人员决定。

DOM是针对XML的基于树的API。它关注的不仅仅是解析XML代码,而是使用一系列互相关联的对象来表示这些代码,而这些对象可以被修改且无需重新解析代码就能直接访问它们。DOM是语言无关的API,他没有与某种语言绑定。

6.1.3  点的层次

DOM定义Node的接口以及许多种节点类型来表示XML节点的多个方面:

1  Document——最顶层的节点,所有的其他节点都是附属于它的。

2  DocumentType——DTD引用(使用<!DOCTYPE >语法)的对象表现形式,例如<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">。它不能包含子节点。

3  DocumentFragment——可以像Document一样来保存其他节点。

4  Element——表示起始标签和结束标签之间的内容,例如<tag></tag>或者<tag/>。这是唯一可以同时包含特性和子节点的节点类型。

5  Attr——代表一对特性名和特性值。这个节点类型不能包含子节点。

Text——代表XML文档中的在起始标签和结束标签之间,或者CData Section内包含的普通文本。这个节点类型不能包含子节点。

8  CDataSection——<![CDATA[ ]]>的对象表现形式。这个节点类型仅能包含文本节点Text作为子节点。

9  Entity——表示在DTD中的一个实体定义,例如<!ENTITY foo "foo">。这个节点类型不能包含子节点。

10  EntityReference——代表一个实体引用,例如&quot;这个节点类型不能包含子节点。

11  ProcessingInstruction——代表一个PI。这个节点类型不能包含子节点。

12  Comment——代表XML注释。这个节点类型不能包含子节点。

13  Notation——代表在DTD中定义的记号。这个很少用到,所以在本书中不会讨论。

Node接口定义了对应不同节点类型的12个常量(它们会在即将讨论的nodeType特性中使用到):

1  Node.ELEMENT_NODE (1)

2  Node.ATTRIBUTE_NODE (2)

3  Node.TEXT_NODE (3)

4  Node.CDATA_SECTION_NODE (4)

5  Node.ENTITY_REFERENCE_NODE (5)

6  Node.ENTITY_NODE (6)

7  Node.PROCESSING_INSTRUCTION_NODE (7)

8  Node.COMMENT_NODE (8)

9  Node.DOCUMENT_NODE (9)

10  Node.DOCUMENT_TYPE_NODE (10)

11  Node.DOCUMENT_FRAGMENT_NODE (11)

12  Node.NOTATION_NODE (12)

Node接口也定义了一些所有节点类型都包含的特性和方法。我们在下面的表格中列出了这些特性和方法:

 

 

特性/方法

类型/返回类型

   

nodeName

String

节点的名字;根据节点的类型而定义

nodeValue

String

节点的值;根据节点的类型而定义

nodeType

Number

节点的类型常量值之一

ownerDocument

Document

指向这个节点所属的文档

firstChild

Node

指向在childNodes列表中的第一个节点

lastChild

Node

指向在childNodes列表中的最后一个节点

childNodes

NodeList

所有子节点的列表

previousSibling

Node

指向前一个兄弟节点;如果这个节点就是第一个兄弟节点,那么该值为null

nextSibling

Node

指向后一个兄弟节点;如果这个节点就是最后一个兄弟节点,那么该值为null

hasChildNodes()

Boolean

childNodes包含一个或多个节点时,返回真

attributes

NamedNodeMap

包含了代表一个元素的特性的Attr对象;仅用于Element节点

appendChild(node)

Node

node添加到childNodes的末尾

removeChild(node)

Node

childNodes中删除node

replaceChild
(newnode, oldnode)

Node

childNodes中的oldnode替换成newnode

insertBefore
(newnode, refnode)

Node

childNodes中的refnode之前插入newnode

除节点外,DOM还定义了一些助手对象,它们可以和节点一起使用,但不是DOM文档必有的部分。

1  NodeList——节点数组,按照数值进行索引;用来表示一个元素的子节点。

2  NamedNodeMap——同时用数值和名字进行索引的节点表;用于表示元素特性。

6.1.4  特定语言的DOM

任何基于XML的语言,如XHTMLSVG,因为它们从技术上来说还是XML,仍然可以利用刚刚介绍的核心DOM。然而,很多语言会继续定义它们自己的DOM来扩展XML核心以提供语言的特色功能。

6.2  DOM的支持

        并不是所有的浏览器对 DOM 的支持都一样。一般来说,Mozilla DOM 标准支持最好,

这个领域中落在最后的是IE,它对DOM Level 1的实现都还不完整,尚有很多方面有待完善。

6.3  使用DOM

document 对象是BOM 的一部分,同时也是HTML DOM HTMLDocument 对象的一种表现形式,反过来说,它也是XML DOM Document 对象

6.3.1  访问 节点

在下面的几节中考虑下面的HTML页面:

要访问<html/>元素(你应该明白这是该文件的document元素),你可以使用documentdocumentElement特性:

var oHtml = document.documentElement;

现在变量oHtml包含一个表示<html/>HTMLElement对象。如果你想取得<head/><body/>元素,下面的可以实现:

也可以使用childNodes特性来完成同样的工作。只需把它当成普通的JavaScript Array,使用方括号标记:

你还可以通过使用childNodes.length特性来获取子节点的数量:

注意方括号标记其实是NodeListJavaScript中的简便实现。实际上正式的从childNodes列表中获取子节点的方法是使用item()方法:

HTML DOM页定义了document.body作为指向<body/>元素的指针:

有了oHtmloHeadoBody这三个变量,就可以先尝试确定它们之间的关系:

这一小段代码测试并验证了oBodyoHeadparentNode特性都是指向oHtml变量,同时使用previousSiblingnextSibling特性来建立它们之间的关系。最后一行确认了oHeadownerDocument特性事实上是指向该文档。

6.3.2  检测节点类型

我们可以通过使用nodeType特性检验节点类型:

这个例子中,document.nodeType返回9,等于Node.DOCUMENT_NODE;同时document. documentElement.nodeType返回1,等于Node.ELEMENT_NODE

也可以用Node常量来匹配这些值:

这段代码可以在Mozilla 1.0+Opera 7.0+Safari 1.0+上正常运行。不幸的是,IE不支持这些常量,所以这些代码在IE上会产生错误。所幸,可以通过定义匹配节点类型的常量来纠正这种情况,正如下面这样:

6.3.3  处理特性

只有Element节点才能有特性。Element节点的attributes属性其实是NamedNodeMap,它提供一些用于访问和处理其内容的方法:

1  getNamedItem(name)——返回nodeName性值等于name的节点;

2  removeNamedItem(name)——删除nodeName性值等于name的节点;

3  setNamedItem(node)——将node添加到列表中,按其nodeName性进行索引;

4 item(pos)——像NodeList一样,返回在位置pos的节点;

NamedNodeMap对象也有一个length属性来指示它所包含的节点的数量。

NamedNodeMap用于表示特性时,其中每个节点都是Attr节点,它的nodeName性被设置为特性名称,而nodeValue性被设置为特性的值。例如,假设有这样一个元素:

同时,假设变量oP包含指向这个元素的一个引用。于是可以这样访问id特性的值:

还可以通过给nodeValue性赋新值来改变id性:

Attr节点也有一个完全等同于(同时也完全同步于)nodeValue属性的value属性,并且有name属性和nodeName属性保持同步。我们可以随意使用这些属性来修改或变更特性。

因为这个方法有些累赘,DOM又定义了三个元素方法来帮助访问特性:

q  getAttribute(name)——等于attributes.getNamedItem(name).value

q  setAttribute(name, newvalue)——等于attribute.getNamedItem(name).value = newvalue

q  removeAttribute(name)——等于attributes.removeNamedItem(name)

这些方法相当有用,可以直接处理特性值,完全地隐藏Attr节点。所以,要获取前面用的<p/>id特性,只需这样做:

var sId = oP.getAttribute("id");

同时要更改ID,可以这样做

正如你所看到的,这些方法要比使用NamedNodeMap的方法简洁得多。

6.3.4  访问指定节点

1. getElementsByTagName()

tagName特性总是等于小于号之后紧随的名称

下一行代码返回文档中所有<img />元素的列表:

var oImgs = document.getElementsByTagName("img");

在把所有图形都存于oImgs后,只需使用方括号标记或者item()方法(getElementsByTag- Name()返回一个和childNodes一样的NodeList),就可以像访问子节点那样逐个访问这些节点了:

2. getElementsByName()

HTML DOM 定义了 getElementsByName() ,它用来获取所有 name 特性等于指定值的元素的。

3. getElementById()

这是 HTML DOM 定义的第二种方法,它将返回 id 特性等于指定值的元素。在 HTML 中, id 特性是唯一的——这意味着没有两个元素可以共享同一个 id

6.3.5  创建和操作节点

1. 创建新节点

DOM Document (文档)中有一些方法用于创建不同类型的节点

面的表格列出了包含在DOM Level 1中的方法,并列出不同的浏览器是否支持项。

   

   

IE

MOZ

OP

SAF

createAttribute
(name)

用给定名称name创建特性节点

×

×

×

createCDATASection
(text)

用包含文本text的文本子节点创建一个CDATA Section

×

createComment(text)

创建包含文本text的注释节点

×

×

×

×

(续)

   

   

IE

MOZ

OP

SAF

createDocument
Fragment()

创建文档碎片节点

×

×

×

×

createElement
(tagname)

创建标签名为tagname的元素

×

×

×

×

createEntity
Reference(name)

创建给定名称的实体引用节点

×

createProcessing
Instruction(target,
data)

创建包含给定targetdataPI节点

×

createTextNode(text)

创建包含文本text的文本节点

×

×

×

×

最常用到的几个方法是:createDocumentFragment()createElement()createText- Node()

2. createElement()createTextNode()appendChild()

假设有如下HTML页面:

现在想使用DOM来添加下列代码到上面这个页面中:

这里可以使用createElement()createTextNode()来达到目的。下面是实现步骤:

首先,创建<p/>元素:

var oP = document.createElement("p");

第二,创建文本节点:

下一步,把文本节点加入到元素中。可以用在本章前面简要提到的appendChild()方法来完成这个任务。每种节点类型都有appendChild()方法,它的用途是将给定的节点添加到某个节点的childNodes列表的尾部。在这个例子中,应将文本节点追加到<p/>元素中:

oP.appendChild(oText);

不过还没完成全部操作。已经创建了一个<p/>元素和一个文本节点,并且将它们关联在一起了,但这个元素在文档中仍然没有一席之地。要实际可见,必须将这个元素附加到document.body元素或者其中任意子节点上。然后,可以再次使用appendChild()方法:

document.body.appendChild(oP);

在这里,我必须谨慎地告诉你所有的DOM操作必须在页面完全载入之后才能进行。当页面正在载入时,要向DOM插入相关代码是不可能的,因为在页面完全下载到客户端机器之前,是无法完全构建DOM树的。因为这个原因,必须使用onload事件句柄来执行所有的代码。

3. removeChild()replaceChild()insertBefore()

如果有个已经包含"Hello World!"消息的页面,要把这个消息删除,可以使用类似下面方法:

replaceChild()方法有两个参数:被添加的节点和被替换的节点。


可能想让两个消息同时出现。如果想让新消息出现在老消息之后,只要使用appendChild()方法:


insertBefore()方法。这个方法接受两个参数:要添加的节点和插在哪个节点之前。

4. createDocumentFragment()

一旦把节点添加到document.body(或者它的后代节点)中,页面就会更新并反映出这个变化。对于少量的更新,这是很好的,就像在前面的例子中那样。然而,当要向document添加大量数据时,如果逐个添加这些变动,这个过程有可能会十分缓慢。为解决这个问题,可以创建一个文档碎片,把所有的新节点附加其上,然后把文档碎片的内容一次性添加到document中。

假设你想创建十个新段落。若使用前面学到的方法,可能会写出这种代码:

这段代码运行良好,但问题是它调用了十次document.body.appendChild(),每次都要产生一次页面刷新。这时,文档碎片就十分有用:

在这段代码中,每个新的 <p/> 元素都被添加到文档碎片中。然后,这个碎片被作为参数传递给 appendChild() 。这里对 appendChild() 的调用实际上并不是把文档碎片节点本身追加到 <body/> 元素中;而是仅仅追加碎片中的子节点。

6.4  HTML DOM特征功能

核心DOM的特性和方法是通用的,是为了在各种情况下操作所有XML文档而设计的。HTML DOM的特性和方法是在专门针对HTML的同时也让一些DOM操作更加简便。这包括将特性作为属性进行访问的能力,以及特定于元素的属性和方法,这些扩展可以完成一些常见的任务,例如搭建表格,更加简便快速。

6.4.1  让特性像属性一样

大部分情况下,HTML DOM元素中包含的所有特性都是可作为属性。例如,假设有如下图像元素:

<img src="mypicture.jpg"border=0" _fcksavedurl=""mypicture.jpg"border=0"" />

如果要使用核心的DOM来获取和设置srcborder特性,那么要用getAttribute()setAttribute()方法:

然而,使用HTML DOM,可以使用同样名称的属性来获取和设置这些值:

唯一的特性名和属性名不一样的特例是class特性,它是用来指定应用于某个元素的一个CSS类,例如:

<div class="header"></div>

因为classECMAScript中是一个保留字,在JavaScript中,它不能被作为变量名、属性名或者函数名。于是,相应的属性名就变成className

6.4.2  table 方法

假设想使用DOM来创建如下的HTML表格:


如果想通过核心 DOM 方法来完成这个任务, 代码十分的冗长而且有些难于理解。为了协助建立表格,HTML DOM <table/> <tbody/> <tr/> 等元素添加了一些特性和方法。

<table/>元素添加了以下内容:

q  caption——指向<caption/>元素如果存在);

q  tBodies——<tbody/>元素的集合

q  tFoot——指向<tfoot/>元素(如果存在);

q  tHead——指向<thead/>元素(如果存在);

q  rows——表格中所有行的集合;

q  createTHead()——创建<thead/>元素并将其放入表格;

q  createTFoot()——创建<tfoot/>元素并将其放入表格;

q  createCaption()——创建<caption/>元素并将其放入表格;

q  deleteTHead()——删除<thead/>元素;

q  deleteTFoot()——删除<tfoot/>元素;

q  deleteCaption()——删除<caption />元素;

q  deleteRow(position)——删除指定位置上的行;

q  insertRow(position)——在rows集合中的指定位置上插入一个新行。

<tbody/>元素添加了以下内容:

q  rows——<tbody/>中所有行的集合;

q  deleteRow(position)——删除指定位置上的行;

q  insertRow(position)——在rows集合中的指定位置上插入一个新行。

<tr/>元素中添加了以下内容:

q  cells——<tr/>元素中所有的单元格的集合;

q  deleteCell(position)——删除给定位置上的单元格;

q  insertCell(position)——在cells集合的给定位置上插入一个新的单元格。

6.5  遍历DOM

到目前为止,我们讨论的功能都仅仅是 DOM Level 1 的部分。本节将介绍一些DOM Level 2 的功能,尤其是和遍历DOM 文档相关的DOM Level 2 遍历(traversal )和范围(range )规范中的对象。这些功能只有在Mozilla Konqueror/Safari 中才有
6.5.1  NodeIterator

第一个有关的对象是NodeIterator,用它可以对DOM树进行深度优先的搜索,如果要查找页面中某个特定类型的信息(或者元素),这是相当有用的。要理解NodeIterator到底做了什么,考虑下面的HTML页面:

这个页面可以转换成由图6-2表示的DOM树。

  6-2

当使用NodeIterator时,可以从document元素(<html/>)开始,按照一种系统的路——也就是大家熟知的深度优先搜索——遍历整个DOM树。在这种搜索方式中,遍历从父节点开始,到子节点,再到子节点的子节点,如此继续,尽可能往深入,直到不能再往下走为止。然后,遍历过程向上回退一层,并进入下一个子节点。例如,在前面展示的DOM树中,遍历过程在回退到<body/>前,先访问了<html/>,然后<head/>,然后<title/>,然后是文本节点"example"。图6-3显示了这个遍历过程的完整路径。

  6-3

最佳的思考深度优先搜索的方法就是从第一个节点左边的第一个节点起沿着树的最外沿画线。只要这条线从左侧经过某个节点,那么这个节点就是搜索中下一个出现的节点(图6-3中的粗线代表了这条线)。

要创建NodeIterator对象,请使用document对象的createNodeIterator()方法。这个方法接受四个参数:

(1) root——从树中开始搜索的那个节点。

(2) whatToShow——一个数值代码,代表哪些节点需要访问。

(3) filter——NodeFilter对象,用来决定需要忽略哪些节点。

(4) entityReferenceExpansion——布尔值,表示是否需要扩展实体引用。

通过应用下列一个或多个常量,whatToShow参数可以决定哪些节点可以访问:

q  NodeFilter.SHOW_ALL——显示所有的节点类型;

q  NodeFilter.SHOW_ELEMENT——显示元素节点;

q  NodeFilter.SHOW_ATTRIBUTE——显示特性节点;

q  NodeFilter.SHOW_TEXT——显示文本节点;

q  NodeFilter.SHOW_CDATA_SECTION——显示CData section节点;

q  NodeFilter.SHOW_ENTITY_REFERENCE——显示实体引用节点;

q  NodeFilter.SHOW_ENTITY——显示实体节点;

q  NodeFilter.SHOW_PROCESSING_INSTRUCTION——显示PI节点;

q  NodeFilter.SHOW_COMMENT——显示注释节点;

q  NodeFilter.SHOW_DOCUMENT——显示文档节点;

q  NodeFilter.SHOW_DOCUMENT_TYPE——显示文档类型节点;

q  NodeFilter.SHOW_DOCUMENT_FRAGMENT——显示文档碎片节点;

q  NodeFilter.SHOW_NOTATION——显示记号节点。

可以通过使用二进制或操作符来组合多个值:

createNodeIterator()filter(过滤器)参数可以指定一个自定义的NodeFilter对象,但是如果不想使用它的话,也可以留空(null)。

要创建最简单的访问所有节点类型的NodeIterator对象,可以使用下面的代码:

要在搜索过程中前进或者后退,可以使用nextNode()previousNode()方法:

假设想列出某个区域内指定 <div/> 中包含的所有元素。下列代码可以完成这个任务:

当点击了按钮后,将使用包含在div1中的元素的标签名来填充<textarea/>

P

B

UL

LI

LI

LI

但假设不想在结果中包含<p/>元素。这就不能仅使用whatToShow参数来完成。这种情况下,你需要自定义一个NodeFilter对象。

NodeFilter对象只有一个方法:acceptNode()如果应该访问给定的节点,那么该方法返回NodeFilter.FILTER_ACCEPT;如果不应该访问给定节点,则返回NodeFilter.FILTER_REJECT。然而,不能使用NodeFilter类来创建这个对象,因为这个类是一个抽象类。在Java或一些其他的语言中,必须重新定义一个NodeFilter的子类,不过,因为这是在JavaScript中,就不必这么做了。

现在,只要创建任意一个有acceptNode()方法的对象,就可以将它传给createNodeIter- ator()方法,如下所示:

若要禁止<p/>元素节点,只需检查tagName属性,如果它等于"P",就返回NodeFilter. FILTER_REJECT

如果将这段代码包含在前面的例子中,代码如下:

当这次再点击按钮时,<textarea/>中就会出现下列内容:

UL

LI

LI

LI

注意"P""B"都没有出现在列表中。这是因为排除<p/>元素后,不仅从迭代搜索中去掉了它,也去掉了它的所有后代节点。因为<b/><p/>的一个子节点,所以它也被跳过。

NodeIterator对象展示了一种有序的自顶向下遍历整个DOM树(或者仅仅其中一部分)的方式。然而可能想遍历到树的特定区域时,再看看某个节点的兄弟节点或者子节点。如果是这种情况,可以使用TreeWalker

6.5.2  TreeWalker

TreeWalker有点像NodeIterator的大哥:它有NodeIterator所有的功能(nextNode()previousNode()),并且添加了一些遍历方法:

q  parentNode()——进入当前节点的父节点;

q  firstChild()——进入当前节点的第一个子节点;

q  lastChild()——进入当前节点的最后一个节点;

q  nextSibling()——进入当前节点的下一个兄弟节点;

q  previousSibling()——进入当前节点的前一个兄弟节点。

要开始使用TreeWalker,其实完全可以像使用NodeIterator那样,只要把createNode- Iterator()的调用改为调用createTreeWalker(),这个函数接受同样的参数:

假设只想访问前面所显示的HTML页面中的<li/>元素。可以写一个只接受标签名为"LI"的元素的过滤器,而这里,可以使用TreeWalker来进行更有目的性的遍历:

            

在这个例子中,创建了TreeWalker并立刻调用了firstChild()方法,将walker指到<p/>元素(因为<p/>div1的第一个子节点)。在接下来的一行里调用nextSibling()后,walker指到了<p/>的下一个兄弟节点<ul/>上。然后,调用firstChild()返回到<ul/>下的第一个<li/>元素。接下来,在循环内部,通过使用nextSibling()方法对剩下的<li/>元素进行迭代。

点击按钮后,应该会看到如下输出:

LI

LI

LI

最后要说的是,如果对将要遍历的DOM树的结构有所了解的话,TreeWalker更加有用;而同时,如果并不知道这个结构的话,使用NodeIterator更加实际有效。

6.6  测试与DOM标准的一致性

现在你肯定可以说出DOM的许多方面。正因如此,你需要一种方法来确定给定的DOM实现到底支持DOM的哪些部分。有趣的是,这个对象就叫做implementation

implementation对象是DOM文档的一个特性,因此,也是浏览器document对象的一部分。implementation唯一的方法是hasFeature(),它接受两个参数:要检查的特征和特征的版本。例如,如果想检查对XML DOM Level 1的支持,可以这样调用:

var bXmlLevel1 = document.implementation.hasFeature("XML", "1.0");

6.7  DOM Level 3

2004 4 月, DOM Level 3 作为 W3C 的一个推荐标准被提出。目前为止,还没有哪个浏览器已经完全实现该标准,只有 Mozilla 已实现了一部分
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值