JavaScript学习笔记(14):DOM2和DOM3
DOM API的组成
DOM1(DOM Level 1)主要定义了HTML和XML文档的底层结构。
DOM2(DOM Level 2)和DOM3(DOM Level 3)在这些结构之上加入更多交互能力,提供了更高级的XML特性。
实际上,DOM2和DOM3是按照模块化的思路来制定标准的,每个模块之间有一定关联,但分别针对某个DOM子集。这些模块主要分为:
- DOM Core:在DOM1核心部分的基础上,为节点增加方法和属性。
- DOM Views:定义基于样式信息的不同视图。
- DOM Events:定义通过事件实现DOM文档交互。
- DOM Style:定义以编程方式访问和修改CSS样式的接口。
- DOM Traversal and Range:新增遍历DOM文档及选择文档内容的接口。
- DOM HTML:在DOM1 HTML部分的基础上,增加属性、方法和新接口。
- DOM Mutation Observers:定义基于DOM变化触发回调的接口。这个模块是DOM4级模块,用于取代Mutation Events。
在“事件”章节会专门介绍DOM Events,在“DOM基础”章节已经介绍过了DOM Mutation Observers。下文将介绍其余所有模块,即核心、视图、HTML、样式、遍历和范围。
DOM3还有额外的XPath模块和Load and Save模块,将在“JavaScript API”章节介绍。
核心、视图和HTML
DOM API的基础组成就是核心、视图和HTML三部分,核心模块的目标是扩展DOM API,在满足XML所有需求的同时提供更好的错误处理和特性检测。
DOM View和HTML模块也丰富了DOM接口,定义了新的属性和方法,但这两个模块很小,因此合并讨论。
XML命名空间
当一个XML文档中使用了多种XML语言时,需要指明标签和属性的命名空间。如在HTML中使用SVG,此时需要使用xmlns属性为svg指明它的命名空间:
1 |
|
那么对于这样的文档,如果调用某个方法与节点交互,就会出现一些问题,如:
- 创建了一个新元素,那这个元素属于哪个命名空间?
- 查询特定标签名时,结果中应该包含哪个命名空间下的元素?
因此从DOM2开始,引入了一些新的扩展。
Node扩展
对于命名空间的支持,Node增加了以下属性:
- localName:不包含命名空间前缀的节点名。
- namespaceURI:节点的命名空间URL,如果未指定则为null。
- prefix:命名空间前缀,如果未指定则为null。
那么节点的nodeName等于prefix + ":"
+ localName。
还有以下方法:
isDefaultNamespace(namespaceURI)
:返回布尔值,表示namespaceURI是否为节点的默认命名空间。lookupNamespaceURI(prefix)
:返回给定prefix的命名空间URI。lookupPrefix(namespaceURI)
:返回给定namespaceURI的前缀。
Document扩展
DOM2在Document类型上新增了如下命名空间特定的方法:
createElementNS(namespaceURI, tagName)
:以给定的标签名tagName创建指定命名空间namespaceURI的一个新元素。createAttributeNS(namespaceURI, attributeName)
:以给定的属性名attributeName创建指定命名空间namespaceURI的一个新属性。getElementsByTagNameNS(namespaceURI, tagName)
:返回指定命名空间namespaceURI中所有标签名为tagName的元素的NodeList。
使用这些方法都需要传入相应的命名空间URI,如:http://www.w3.org/2000/svg
。
Element扩展
Element类型上有了以下关于命名空间的扩展:
getAttributeNS(namespaceURI, localName)
:取得指定命名空间namespaceURI中名为localName的属性。getAttributeNodeNS(namespaceURI, localName)
:取得指定命名空间namespaceURI中名为localName的属性节点。getElementsByTagNameNS(namespaceURI, tagName)
:取得指定命名空间namespaceURI中标签名为tagName的元素的NodeList。hasAttributeNS(namespaceURI, localName)
:返回布尔值,表示元素中是否有命名空间namespaceURI下名为localName的属性(注意,DOM2 Core也添加不带命名空间的hasAttribute()
方法)。removeAttributeNS(namespaceURI, localName)
:删除指定命名空间namespaceURI中名为localName的属性。setAttributeNS(namespaceURI, qualifiedName, value)
:设置指定命名空间namespaceURI中名为qualifiedName的属性为value。setAttributeNodeNS(attNode)
:为元素设置(添加)包含命名空间信息的属性节点attNode。
这些方法与DOM1中对应的方法行为相同,除setAttributeNodeNS()
之外都只是多了一个命名空间参数。
NamedNodeMap扩展
NamedNodeMap也增加了以下处理命名空间的方法:
getNamedItemNS(namespaceURI, localName)
:取得指定命名空间namespaceURI中名为localName的项。removeNamedItemNS(namespaceURI, localName)
:删除指定命名空间namespaceURI中名为localName的项。setNamedItemNS(node)
:为元素设置(添加)包含命名空间信息的节点。
因为NamedNodeMap主要表示属性,而我们往往通过元素来访问属性,所以这些方法非常不常用。
其他扩展
除了增加了对命名空间的支持之外,DOM2和DOM3还增加了其他的一些特性。
DocumentType
DocumentType新增了3个属性:publicId、systemId和internalSubset。
publicId、systemId属性表示文档类型声明中有效但无法使用DOM1 API访问的数据。
比如下面这个HTML文档类型声明:
1 |
|
其publicId是"-// W3C// DTD HTML 4.01// EN"
,而systemId是"http://www.w3.org/TR/html4/strict.dtd"
。
internalSubset用于访问文档类型声明中可能包含的额外定义,如:
1 |
|
对于以上声明,document.doctype.internalSubset
会返回"<! ELEMENT name (#PCDATA)>"
。HTML文档中几乎不会涉及文档类型的内部子集,XML文档中稍微常用一些。
Document
Document类型的更新中唯一跟命名空间无关的方法是importNode()
。
这个方法的目的是从其他文档获取一个节点并导入到新文档,以便将其插入新文档。
之所以需要这个方法是因为每个节点都有一个ownerDocument属性,表示所属文档。如果调用appendChild()
方法时传入节点的ownerDocument不是指向当前文档,则会发生错误。而调用importNode()
导入其他文档的节点会返回一个ownerDocument属性正确的新节点。
importNode()
方法跟cloneNode()
方法类似,同样接收两个参数:要复制的节点和表示是否同时复制子树的布尔值,返回结果是适合在当前文档中使用的新节点。
这个方法同样是更常用在XML中。
DOM2 View给Document类型增加了新属性defaultView,是一个指向拥有当前文档的窗口(或窗格<frame>
)的指针。
DOM2 Core还针对document.implementation对象增加了两个新方法:
createDocumentType()
:用于创建DocumentType类型的新节点,接收3个参数分别为文档类型名称、publicId和systemId。createDocument()
:创建新文档,接收3个参数分别为文档元素的namespaceURI、文档元素的标签名和文档类型。
DOM2 HTML模块也为document.implamentation
对象添加了createHTMLDocument()
方法。使用这个方法可以创建一个完整的HTML文档,包含<html>
、<head>
、<title>
和<body>
元素。这个方法只接收一个参数,即新创建文档的标题。
Node
DOM3新增了两个用于比较节点的方法:isSameNode()
和isEqualNode()
。
这两个方法都接收一个节点参数,如果这个节点与参考节点相同或相等,则返回true。
节点相同,意味着引用同一个对象;节点相等,意味着节点类型相同,拥有相等的属性(nodeName、nodeValue等),而且attributes和childNodes也相等(即同样的位置包含相等的值)。
DOM3也增加了给DOM节点附加额外数据的方法:setUserData()
方法,接收3个参数:键、值、处理函数,用于给节点追加数据:
1 |
|
然后,可以通过相同的键再取得这个信息,比如:
1 |
|
setUserData()
的处理函数会在包含数据的节点被复制、删除、重命名或导入其他文档的时候执行,可以在这时候决定如何处理用户数据。
处理函数接收5个参数:表示操作类型的数值(1代表复制,2代表导入,3代表删除,4代表重命名)、数据的键、数据的值、源节点和目标节点。删除节点时,源节点为null;除复制外,目标节点都为null。
内嵌窗格
DOM2 HTML给HTMLIFrameElement(即<iframe>
,内嵌窗格)类型新增了属性contentDocument。这个属性包含代表子内嵌窗格中内容的document对象的指针。
contentDocument属性是Document的实例,拥有所有文档属性和方法,因此可以像使用其他HTML文档一样使用它。
还有一个新增属性contentWindow,返回相应窗格的window对象。
样式
HTML中的样式有3种定义方式:
- 外部样式表(通过
<link>
元素); - 文档样式表(使用
<style>
元素); - 元素特定样式(使用style属性)。
DOM2 Style为这3种应用样式的机制都提供了API。其中使用JS插入<style>
元素的相关内容已经在“DOM基础”章节陈述过。
操作元素内联样式
任何支持style属性的HTML元素在JavaScript中都会有一个对应的style属性,通过它可以改变元素的内联样式。
这个style属性是CSSStyleDeclaration类型的实例,其中包含通过HTML style属性为元素设置的所有样式信息,但不包含通过层叠机制从文档样式和外部样式中继承来的样式。
CSSStyleDeclaration中的样式使用小驼峰表示法,而非CSS中的连字符表示法,因为JS标识符中无法使用连字符。
此外,float是JavaScript的保留字,所以不能用作属性名,DOM2 Style规定它在style对象中对应的属性应该是cssFloat。
有以下修改元素内联样式的例子:
1 |
|
修改样式后,元素的外观会自动更新。
style对象
如上的div.style
就是一个style对象,其上除了包含元素的所有内联样式属性之外,还有如下属性:
- cssText,包含style属性中的CSS代码。
- length,应用给元素的CSS属性数量。
- parentRule,表示CSS信息的CSSRule对象(后文介绍CSSRule类型)。
此外还有以下方法:
getPropertyCSSValue(propertyName)
:(已废弃)返回包含CSS属性propertyName值的CSSValue对象。getPropertyPriority(propertyName)
:如果CSS属性propertyName使用了!important
则返回"important"
,否则返回空字符串。getPropertyValue(propertyName)
:返回属性propertyName的字符串值。item(index)
:返回索引为index的CSS属性名。removeProperty(propertyName)
:从样式中删除CSS属性propertyName,删除意味着该CSS属性被重置为默认值。setProperty(propertyName, value, priority)
:设置CSS属性propertyName的值为value, priority是"important"
或空字符串。
计算样式
style对象中包含元素的内联样式,但不包含外部样式表或style标签的样式,这样我们很难知道一个元素到底应用了怎样的样式。
DOM2 Style在document.defaultView
上增加了getComputedStyle()
方法,这个方法接收两个参数:要取得计算样式的元素和伪元素字符串(如":after"
)。它同样返回一个CSSStyleDeclaration对象,但这个对象描述的是最终生效在这个元素上的样式。
但要注意这个返回结果中不包含通过样式表规则应用的width和height属性。
在所有浏览器中计算样式都是只读的,不能通过修改getComputedStyle()
方法返回的对象来修改元素的表现样式。
操作外联样式表
除了行内样式之外,就是style标签规定的样式以及link标签链接的外部样式表,这两种我都将其称为外联样式表。
CSSStyleSheet类型
CSSStyleSheet类型描述了样式表信息,它继承自StyleSheet类型。
StyleSheet类型用作CSS样式表的基类,有以下属性:
- disabled:布尔值,表示样式表是否被禁用了(这个属性是可读写的,因此将它设置为true会禁用样式表)。
- href:如果是使用
<link>
包含的样式表,则返回样式表的URL,否则返回null。 - media:样式表支持的媒体类型集合,这个集合有一个length属性和一个
item()
方法,跟所有DOM集合一样。同样跟所有DOM集合一样,也可以使用中括号访问集合中特定的项。如果样式表可用于所有媒体,则返回空列表。 - ownerNode:指向拥有当前样式表的节点,在HTML中要么是
<link>
元素要么是<style>
元素(在XML中可以是处理指令)。如果当前样式表是通过@import
被包含在另一个样式表中,则这个属性值为null。 - parentStyleSheet:如果当前样式表是通过
@import
被包含在另一个样式表中,则这个属性指向导入它的样式表。 - title:ownerNode的title属性。
- type:字符串,表示样式表的类型。对CSS样式表来说,就是
"text/css"
。
以上只有disabled是可写的,其他都是只读的。
除了上面继承的属性,CSSStyleSheet类型还支持以下属性和方法。
cssRules
:当前样式表包含的样式规则的集合。ownerRule
:如果样式表是使用@import
导入的,则指向导入规则,否则为null。deleteRule(index)
:在指定位置删除cssRules中的规则。insertRule(rule, index)
:在指定位置向cssRules中插入规则。
获取文档的外联样式表
document.styleSheets
表示文档中可用的样式表集合,包括link和style两种样式表,每个样式表都是一个CSSStyleSheet类型的实例。这个集合的length属性保存着文档中样式表的数量,而每个样式表都可以使用中括号或item()
方法获取。
如以下例子输出每个样式表的href属性:
1 |
|
注意只有link元素样式表有该属性,style元素是没有的。
CSS规则
描述样式表CSSStyleSheet对象的cssRules属性中包含了该样式表中所有CSS规则的集合,一条CSS规则使用CSSRule类型表示,但是这个类派生了很多类,其中最常用的就是表示样式信息的CSSStyleRule类,它就是一个CSS选择器和大括号括起来的响应CSS属性的集合。
CSS规则还有@import
、@font-face
、@page
和@charset
等,不过这些规则很少需要使用脚本来操作,因此主要介绍CSSStyleRule类。
CSSStyleRule对象有以下属性:
- cssText:返回整条规则的文本。这里的文本可能与样式表中实际的文本不一样,因为浏览器内部处理样式表的方式也不一样。Safari始终会把所有字母都转换为小写。
- parentRule:如果这条规则被其他规则(如
@media
)包含,则指向包含规则,否则就是null。 - parentStyleSheet:包含当前规则的样式表。
- selectorText:返回规则的选择符文本。这里的文本可能与样式表中实际的文本不一样,因为浏览器内部处理样式表的方式也不一样。这个属性在Firefox、Safari、Chrome和IE中是只读的,在Opera中是可以修改的。
- style:返回CSSStyleDeclaration对象,可以设置和获取当前规则中的样式。
- type:数值常量,表示规则类型。对于样式规则,它始终为1。
这些属性中,使用最多的是cssText、selectorText和style。
这里的cssText属性与之前CSSStyleDeclaration类的cssText属性类似,不过并不完全一样。这里的cssText属性是包含选择符文本和环绕样式声明的大括号的,而CSSStyleDeclaration类的cssText属性不包括。
可以像这样对样式表内容进行修改:
1 |
|
但注意这样修改样式表会导致所有被选择器选中的元素样式都被改变。
可以使用样式表的insertRule()
方法向样式表中添加新规则,这个方法接收两个参数:规则的文本(包括一个选择器和大括号包围的样式集合)和表示插入样式表位置的索引值。
而deleteRule()
方法用来从样式表中删除规则,它只接收一个参数:要删除规则的索引。
元素尺寸
DOM规范中并没有对元素尺寸的支持。但最早从IE开始率先为DOM对象增加了一些属性,来提供元素的尺寸信息,现在这些尺寸已经得到了所有主流浏览器的支持。
偏移尺寸
偏移尺寸即元素在屏幕上占用的所有视觉空间,包括以下四个:
- offsetHeight:元素在垂直方向上占用的像素尺寸,包括它的高度、水平滚动条高度(如果可见)和上、下边框的高度。
- offsetLeft:元素左边框外侧距离包含元素左边框内侧的像素数。
- offsetTop:元素上边框外侧距离包含元素上边框内侧的像素数。
- offsetWidth:元素在水平方向上占用的像素尺寸,包括它的宽度、垂直滚动条宽度(如果可见)和左、右边框的宽度。
offsetLeft和offsetTop是相对于包含元素的,包含元素保存在offsetParent属性中。
offsetParent不一定是parentNode,如<td>
元素的offsetParent是作为其祖先的<table>
元素,因为<table>
是节点层级中第一个提供尺寸的元素。
这些属性都是只读的。
要确定一个元素在页面中的偏移量,可以把它的offsetLeft和offsetTop属性分别与offsetParent的相同属性相加,一直加到根元素。如以下例子:
1 |
|
客户端尺寸
客户端尺寸即元素的padding-box
所占的空间。有clientWidth和clientHeight两个。
客户端尺寸实际上就是元素内部的空间,因此不包含滚动条占用的空间。
和偏移尺寸一样,客户端尺寸也是只读的。
滚动尺寸
滚动尺寸提供了元素内容滚动距离的信息。滚动尺寸相关的属性有如下4个:
- scrollHeight:元素内容的总高度。
- scrollLeft:内容区左侧隐藏的像素数,设置这个属性可以改变元素的滚动位置。
- scrollTop:内容区顶部隐藏的像素数,设置这个属性可以改变元素的滚动位置。
- scrollWidth:元素内容的总宽度。
有些元素,比如<html>
无须任何代码就可以自动滚动,而其他元素则需要使用CSS的overflow属性令其滚动。
scrollLeft和scrollTop两个属性是可以读写的,其他两个是只读的。设置这两个属性可以控制滚动条滚动的位置。
确定元素的位置与尺寸
浏览器在每个元素上都暴露了getBoundingClientRect()
方法,可以通过这个方法来确定元素的位置与尺寸。
它返回一个DOMRect对象,包含6个属性:left、top、right、bottom、height和width。这些属性确定了元素在页面中相对于视口的位置。
注意这里的位置信息是相对于视口的。
遍历
DOM2 Traversal and Range模块定义了两个类型NodeIterator和TreeWalker用于顺序遍历DOM结构。
它们都是对DOM结构的深度优先遍历。
由于使用不多,本节只简单介绍其API。
NodeIterator
可以通过document.createNodeIterator()
方法创建NodeIterator类型的实例,这个方法接收以下4个参数:
- root:作为遍历根节点的节点。
- whatToShow:数值代码,表示应该访问哪些节点。
- filter:NodeFilter对象或函数,表示是否接收或跳过特定节点。
- entityReferenceExpansion:布尔值,表示是否扩展实体引用。这个参数在HTML文档中没有效果,因为实体引用永远不扩展。
NodeIterator的两个主要方法是nextNode()
和previousNode()
。
nextNode()
方法在DOM子树中以深度优先方式进前一步,而previousNode()
则是在遍历中后退一步。
创建NodeIterator对象的时候,会有一个内部指针指向根节点,第一次调用nextNode()
返回的是根节点。当遍历到达DOM树最后一个节点时,nextNode()
返回null。反之亦然。
TreeWalker
TreeWalker对象要调用document.createTreeWalker()
方法来创建,这个方法接收与document.createNodeIterator()
同样的参数。
TreeWalker是NodeIterator的高级版,除了包含同样的nextNode()
、previousNode()
方法,TreeWalker还添加了如下在DOM结构中向不同方向遍历的方法:
parentNode()
:遍历到当前节点的父节点。firstChild()
:遍历到当前节点的第一个子节点。lastChild()
:遍历到当前节点的最后一个子节点。nextSibling()
:遍历到当前节点的下一个同胞节点。previousSibling()
:遍历到当前节点的上一个同胞节点。
范围
为了支持对页面更细致的控制,DOM2 Traversal and Range模块定义了范围接口。由于使用不多,本节只简单介绍其API。
DOM范围
DOM2在Document类型上定义了一个createRange()
方法,暴露在document对象上。使用这个方法可以创建一个DOM范围对象:
1 |
|
创建的范围对象是与创建它的文档关联的,不能在其他文档中使用。
可以使用这个范围在后台选择文档特定的部分,创建范围并指定它的位置之后,可以对范围的内容执行一些操作,从而实现对底层DOM树更精细的控制。
每个范围都是Range类型的实例,有以下属性:
- startContainer:范围起点所在的节点(选区中第一个子节点的父节点)。
- startOffset:范围起点在startContainer中的偏移量。如果startContainer是文本节点、注释节点或CData区块节点,则startOffset指范围起点之前跳过的字符数;否则,表示范围中第一个节点的索引。
- endContainer:范围终点所在的节点(选区中最后一个子节点的父节点)。
- endOffset:范围起点在startContainer中的偏移量(与startOffset中偏移量的含义相同)。
- commonAncestorContainer:文档中以startContainer和endContainer为后代的最深的节点。
这些属性会在范围被放到文档中特定位置时获得相应的值。
简单选择
使用selectNode()
或selectNodeContents()
方法进行简单选择。
这两个方法都接收一个节点作为参数,selectNode()
方法选择整个节点,包括其后代节点,而selectNodeContents()
方法只选择节点的后代。
选定节点或节点后代之后,还可以在范围上调用相应的方法,实现对范围中选区的更精细控制:
setStartBefore(refNode)
:把范围的起点设置到refNode之前,从而让refNode成为选区的第一个子节点。startContainer属性被设置为refNode.parentNode,而startOffset属性被设置为refNode在其父节点childNodes集合中的索引。setStartAfter(refNode)
:把范围的起点设置到refNode之后,从而将refNode排除在选区之外,让其下一个同胞节点成为选区的第一个子节点。startContainer属性被设置为refNode.parentNode, startOffset属性被设置为refNode在其父节点childNodes集合中的索引加1。setEndBefore(refNode)
:把范围的终点设置到refNode之前,从而将refNode排除在选区之外、让其上一个同胞节点成为选区的最后一个子节点。endContainer属性被设置为refNode. parentNode, endOffset属性被设置为refNode在其父节点childNodes集合中的索引。setEndAfter(refNode)
:把范围的终点设置到refNode之后,从而让refNode成为选区的最后一个子节点。endContainer属性被设置为refNode.parentNode, endOffset属性被设置为refNode在其父节点childNodes集合中的索引加1。
调用这些方法时,所有属性都会自动重新赋值。不过,为了实现复杂的选区,也可以直接修改这些属性的值。
复杂选择
复杂选择需要使用setStart()
和setEnd()
两个方法。
这两个方法都接收两个参数:参照节点和偏移量。
对setStart()
来说,参照节点会成为startContainer,而偏移量会赋值给startOffset;对setEnd()
而言,参照节点会成为endContainer,而偏移量会赋值给endOffset。
操作范围
创建范围之后,浏览器会在内部创建一个文档片段节点,用于包含范围选区中的节点。
为操作范围的内容,选区中的内容必须格式完好。如果范围选择的不是完好的DOM结果,在操作范围时会自动确定缺失的开始和结束标签,从而可以重构出有效的DOM结构,以便后续操作,操作后的DOM结构也会自动补全缺失。
有以下方法用来操作范围:
deleteContents()
:从文档中删除范围包含的节点。extractContents()
:从文档中移除范围选区,但它会返回范围对应的文档片段,这样可以把范围选中的内容插入文档中其他地方。cloneContents()
:创建范围的一个副本,然后可以把这个副本插入到文档其他地方。
范围插入
向范围中插入内容有以下方法:
insertNode()
:在范围选区的开始位置插入一个节点。surroundContents()
:插入包含范围的内容,接收一个参数,即包含范围内容的节点。
范围折叠
如果范围并没有选择文档的任何部分,则称为折叠。
范围的选择有点类似文本框:如果文本框中有文本,那么可以用鼠标选中一些文本,这就对应着范围的选择;如果再单击鼠标,则选区会被移除,光标会落在某两个字符中间,这就对应着范围的折叠。
在折叠范围时,有一个折叠位置,折叠位置会被设置为范围与文档交界的地方,可能是范围选区的开始处或结尾处。
折叠范围可以使用collapse()
方法,这个方法接收一个参数:布尔值,表示折叠到范围哪一端,true表示折叠到起点,false表示折叠到终点。
要确定范围是否已经被折叠,可以检测范围的collapsed属性。
测试范围是否被折叠,能够帮助确定范围中的两个节点是否相邻。如两个节点1和2,创建节点1之后到节点2之前的范围,如果这个范围是折叠的,那么两个节点就是相邻的,反之则表明其中间有东西不相邻。
范围比较
如果有多个范围,则可以使用compareBoundaryPoints()
方法来比较范围的边界。
它接收的第一个参数来确定比较哪个边界,有以下可选的值:
Range.START_TO_START
:(0),比较两个范围的起点;Range.START_TO_END
:(1),比较第一个范围的起点和第二个范围的终点;Range.END_TO_END
:(2),比较两个范围的终点;Range.END_TO_START
:(3),比较第一个范围的终点和第二个范围的起点。
调用该方法的范围作为参照范围,第二个参数传入的是要和参照范围比较的范围。
该方法在参照范围的边界点位于比较范围的边界点之前时返回-1,在两个范围的边界点相等时返回0,在参照范围的边界点位于比较范围的边界点之后时返回1。
清理
在使用完范围之后,最好调用detach()
方法把范围从创建它的文档中剥离。调用detach()
之后,就可以放心解除对范围的引用(置为null),以便垃圾回收程序释放它所占用的内存。
剥离之后的范围就不能再次使用了。