如何获取元素的文本节点?

130
<div class="title">
   I am text node
   <a class="edit">Edit</a>
</div>
我希望获取“我是文本节点”,不想删除“编辑”标记,并且需要一个跨浏览器的解决方案。

这个问题与https://dev59.com/snA75IYBdhLWcg3wqK__几乎完全相同-请参阅那些答案以获取James答案的纯JS版本。 - Mala
12个回答

94
var text = $(".title").contents().filter(function() {
  return this.nodeType == Node.TEXT_NODE;
}).text();

这将获取所选元素的内容,并对其应用一个筛选函数。筛选函数仅返回文本节点(即nodeType == Node.TEXT_NODE的节点)。


@Val - 抱歉,我在原始代码中漏掉了那个。我会更新答案以显示它。你需要使用 text(),因为 filter 函数返回的是节点本身,而不是节点的内容。 - James Allardice
1
不确定为什么,但在测试上述理论时我失败了。我运行了以下代码:jQuery("*").each(function() { console.log(this.nodeType); })并且我得到了所有节点类型的 1 - Batandwa
能否获取点击节点的文本以及其所有子节点的文本? - Jenna Kwon
这很有趣并且解决了这个问题,但是当情况变得更加复杂时会发生什么?有一种更灵活的方式来完成工作。 - Anthony Rutledge
2
没有jQuery,可以使用document.querySelector(".title").childNodes[0].nodeValue。 - Balaji Gunasekaran
另外,document.querySelector(".title").childNodes[0].wholeText将给出整个值,即使存在多个文本节点。 - Balaji Gunasekaran

68

您可以使用以下方法获取第一个childNode的nodeValue:

$('.title')[0].childNodes[0].nodeValue

http://jsfiddle.net/TU4FB/


11
虽然那样做可以起作用,但它取决于子节点的位置。如果(当)位置发生变化,它将会出错。 - Armstrongest
2
如果文本节点不是第一个子节点,则可能会获得null作为返回值。 - Anthony Rutledge

29

另一个可以对“复杂”或深度嵌套元素有用的本地JS解决方案是使用NodeIterator。将NodeFilter.SHOW_TEXT作为第二个参数(“whatToShow”),并迭代该元素的仅文本节点子节点。

var root = document.querySelector('p'),
    iter = document.createNodeIterator(root, NodeFilter.SHOW_TEXT),
    textnode;

// print all text nodes
while (textnode = iter.nextNode()) {
  console.log(textnode.textContent)
}
<p>
<br>some text<br>123
</p>

您还可以使用TreeWalker。两者之间的区别在于,NodeIterator是一个简单的线性迭代器,而TreeWalker则允许您通过兄弟和祖先进行导航。


迭代器将深度获取文本节点,如果您只想获取顶层子元素,则不理想。 - ggorlen

24

返回第一个 #text 节点的内容的 ES6 版本

const extract = (node) => {
  const text = [...node.childNodes].find(child => child.nodeType === Node.TEXT_NODE);
  return text && text.textContent.trim();
}

1
我想知道效率和灵活性。(1) 使用 .from() 创建一个浅复制的数组实例。(2) 使用 .find() 进行使用 .nodeName 进行字符串比较时更好的选择是使用 node.NodeType === Node.TEXT_NODE。(3) 如果没有值,返回空字符串,null,如果没有找到文本节点,则更为准确。如果没有找到文本节点,则可能需要创建一个!如果返回空字符串"",就会给人一种虚假印象,认为存在文本节点可以正常操作。从本质上讲,返回空字符串是一种善意的谎言,最好避免。 - Anthony Rutledge
(4)如果nodeList中有多个文本节点,则无法在此处指定要使用哪个文本节点。您可能想要第一个文本节点,但您很可能也想要最后一个文本节点。 - Anthony Rutledge
你建议用什么替换 Array.from? - jujule
@Snowman请添加您自己的答案以进行实质性更改,或向OP提供建议,让他们有机会将其纳入他们的答案中。 - TylerH
@jujule - 最好使用 [...node.childNodes]HTMLCollection 转换为数组。 - vsync
@jujule,非常好的回答,谢谢;不确定是打字错误还是规格发生了变化,但现在 child.NodeType 应该是 child.nodeType(小写的 nodeType)。 - Vaviloff

19
如果你想获取元素中第一个文本节点的值,这段代码会起作用:
var oDiv = document.getElementById("MyDiv");
var firstText = "";
for (var i = 0; i < oDiv.childNodes.length; i++) {
    var curNode = oDiv.childNodes[i];
    if (curNode.nodeName === "#text") {
        firstText = curNode.nodeValue;
        break;
    }
}

你可以在这里看到它的运行效果:http://jsfiddle.net/ZkjZJ/


我认为你也可以使用 curNode.nodeType == 3 而不是 nodeName - Nilloc
1
@Nilloc 可能是这样,但有什么好处呢? - Shadow The Spring Wizard
6
@ShadowWizard和@Nilloc建议的最佳方法是使用常量... curNode.nodeType == Node.TEXT_NODE(数字比较速度更快,但是curNode.nodeType== 3不易读——哪个节点具有数字3?) - mikep
2
@ShadowWizard 使用 curNode.NodeType === Node.TEXT_NODE。这个比较是在一个未知可能迭代的循环中发生的。比较两个小数比比较不同长度的字符串更好(时间和空间考虑)。在这种情况下正确的问题是“我有什么类型的节点?”,而不是“我叫什么名字?” https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType - Anthony Rutledge
2
@ShadowWizard 如果您要使用循环来筛选 childNodes,请注意一个元素节点可能有多个文本节点。在通用解决方案中,您可能需要指定要针对元素节点内的哪个文本节点实例进行目标定位(第一个、第二个、第三个等等...)。 - Anthony Rutledge

16

纯 JavaScript:极简主义

在查找 DOM 中的文本时,始终牢记此事项。

MDN - DOM 中的空格

这个问题会让您注意您的 XML / HTML 结构。

在这个纯 JavaScript 示例中,我考虑到可能存在与其他节点交错的多个文本节点。然而,最初我对空格不作判断,留下过滤任务给其他代码处理。

在这个版本中,我从调用 / 客户端代码中传递一个 NodeList

/**
* Gets strings from text nodes. Minimalist. Non-robust. Pre-test loop version.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @param nodeList The child nodes of a Node, as in node.childNodes.
* @param target A positive whole number >= 1
* @return String The text you targeted.
*/
function getText(nodeList, target)
{
    var trueTarget = target - 1,
        length = nodeList.length; // Because you may have many child nodes.

    for (var i = 0; i < length; i++) {
        if ((nodeList[i].nodeType === Node.TEXT_NODE) && (i === trueTarget)) {
            return nodeList[i].nodeValue;  // Done! No need to keep going.
        }
    }

    return null;
}

当然,通过首先测试 node.hasChildNodes(),就没有必要使用预测试的for循环。

/**
* Gets strings from text nodes. Minimalist. Non-robust. Post-test loop version.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @param nodeList The child nodes of a Node, as in node.childNodes.
* @param target A positive whole number >= 1
* @return String The text you targeted.
*/
function getText(nodeList, target)
{
    var trueTarget = target - 1,
        length = nodeList.length,
        i = 0;

    do {
        if ((nodeList[i].nodeType === Node.TEXT_NODE) && (i === trueTarget)) {
            return nodeList[i].nodeValue;  // Done! No need to keep going.
         }

        i++;
    } while (i < length);

    return null;
}

纯JavaScript:可靠

这里的getTextById()函数使用了两个辅助函数:getStringsFromChildren()filterWhitespaceLines()


getStringsFromChildren()

/**
* Collects strings from child text nodes.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @version 7.0
* @param parentNode An instance of the Node interface, such as an Element. object.
* @return Array of strings, or null.
* @throws TypeError if the parentNode is not a Node object.
*/
function getStringsFromChildren(parentNode)
{
    var strings = [],
        nodeList,
        length,
        i = 0;

    if (!parentNode instanceof Node) {
        throw new TypeError("The parentNode parameter expects an instance of a Node.");
    }

    if (!parentNode.hasChildNodes()) {
        return null; // We are done. Node may resemble <element></element>
    }

    nodeList = parentNode.childNodes;
    length = nodeList.length;

    do {
        if ((nodeList[i].nodeType === Node.TEXT_NODE)) {
            strings.push(nodeList[i].nodeValue);
         }

        i++;
    } while (i < length);

    if (strings.length > 0) {
        return strings;
    }

    return null;
}

filterWhitespaceLines()

/**
* Filters an array of strings to remove whitespace lines.
* Generic, cross platform solution.
*
* @author Anthony Rutledge
* @version 6.0
* @param textArray a String associated with the id attribute of an Element.
* @return Array of strings that are not lines of whitespace, or null.
* @throws TypeError if the textArray param is not of type Array.
*/
function filterWhitespaceLines(textArray) 
{
    var filteredArray = [],
        whitespaceLine = /(?:^\s+$)/; // Non-capturing Regular Expression.

    if (!textArray instanceof Array) {
        throw new TypeError("The textArray parameter expects an instance of a Array.");
    }

    for (var i = 0; i < textArray.length; i++) {
        if (!whitespaceLine.test(textArray[i])) {  // If it is not a line of whitespace.
            filteredArray.push(textArray[i].trim());  // Trimming here is fine. 
        }
    }

    if (filteredArray.length > 0) {
        return filteredArray ; // Leave selecting and joining strings for a specific implementation. 
    }

    return null; // No text to return.
}

getTextById()

/**
* Gets strings from text nodes. Robust.
* Generic, cross platform solution.
*
* @author Anthony Rutledge
* @version 6.0
* @param id A String associated with the id property of an Element.
* @return Array of strings, or null.
* @throws TypeError if the id param is not of type String.
* @throws TypeError if the id param cannot be used to find a node by id.
*/
function getTextById(id) 
{
    var textArray = null;             // The hopeful output.
    var idDatatype = typeof id;       // Only used in an TypeError message.
    var node;                         // The parent node being examined.

    try {
        if (idDatatype !== "string") {
            throw new TypeError("The id argument must be of type String! Got " + idDatatype);
        }

        node = document.getElementById(id);

        if (node === null) {
            throw new TypeError("No element found with the id: " + id);
        }

        textArray = getStringsFromChildren(node);

        if (textArray === null) {
            return null; // No text nodes found. Example: <element></element>
        }

        textArray = filterWhitespaceLines(textArray);

        if (textArray.length > 0) {
            return textArray; // Leave selecting and joining strings for a specific implementation. 
        }
    } catch (e) {
        console.log(e.message);
    }

    return null; // No text to return.
}

接下来,返回值(数组或null)将发送到客户端代码,并应该进行处理。希望该数组应该具有实际文本的字符串元素,而不是空格行。

不会返回空字符串(""),因为需要文本节点来正确指示有效文本的存在。返回("")可能会给出误导,让人们认为存在文本节点,从而假设他们可以通过更改.nodeValue的值来更改文本。但在空字符串的情况下不存在文本节点,这是错误的。

示例1:

<p id="bio"></p> <!-- There is no text node here. Return null. -->

例子2:

<p id="bio">

</p> <!-- There are at least two text nodes ("\n"), here. -->

当您希望通过添加空格来使HTML易于阅读时,问题就出现了。尽管没有可读的有效文本,但仍然存在包含换行符("\n")的文本节点在它们的.nodeValue属性中。

对于人类来说,示例一和示例二是功能等效的-空元素正在等待填充。DOM与人类推理不同。这就是为什么getStringsFromChildren()函数必须确定是否存在文本节点,并将.nodeValue值收集到数组中的原因。

for (var i = 0; i < length; i++) {
    if (nodeList[i].nodeType === Node.TEXT_NODE) {
            textNodes.push(nodeList[i].nodeValue);
    }
}
在示例二中,存在两个文本节点,getStringFromChildren()将返回它们的.nodeValue"\n")。然而,filterWhitespaceLines()使用正则表达式过滤出纯空格字符组成的行。
返回null而不是换行符("\n")对客户端/调用代码来说是否属于欺骗?从人类的角度来看,不是。从DOM角度来看,是的。但是,这里的问题是获取文本,而不是编辑它。没有人类可读的文本可以返回给调用代码。
在某人的HTML中可能有多少换行符永远无法知道。创建一个查找“第二个”换行符的计数器是不可靠的。它可能不存在。
当然,在空的<p></p>元素中编辑文本并且有额外的空白(例如2),可能意味着破坏(或跳过)段落标记之间除了一个文本节点以外的所有内容,以确保元素包含应该显示的内容。
无论如何,除非你正在做一些特别的事情,否则你需要一种确定哪个文本节点的.nodeValue属性具有你想要编辑的真实、可读的文本的方法。filterWhitespaceLines可以帮助我们完成其中一半。
var whitespaceLine = /(?:^\s+$)/; // Non-capturing Regular Expression.

for (var i = 0; i < filteredTextArray.length; i++) {
    if (!whitespaceLine.test(textArray[i])) {  // If it is not a line of whitespace.
        filteredTextArray.push(textArray[i].trim());  // Trimming here is fine. 
    }
}

此时,您可能已经看到了类似于以下内容的输出:

["Dealing with text nodes is fun.", "Some people just use jQuery."]

不能保证这两个字符串在DOM中相邻,因此使用.join()合并它们可能会产生一个不自然的组合。相反,在调用getTextById()的代码中,您需要选择要使用的字符串。

测试输出。

try {
    var strings = getTextById("bio");

    if (strings === null) {
        // Do something.
    } else if (strings.length === 1) {
        // Do something with strings[0]
    } else { // Could be another else if
        // Do something. It all depends on the context.
    }
} catch (e) {
    console.log(e.message);
}
.trim()可添加在getStringsFromChildren()内,以消除前导和尾随空格(或将一堆空格转换为零长度字符串("")),但如何事先知道每个应用程序找到文本(字符串)后需要发生什么?你不知道,所以让具体实现来处理,让getStringsFromChildren()保持通用。
有时可能不需要这种特定级别的细节(目标等)。那很好,在这些情况下使用简单的解决方案。然而,一个广义算法使您能够适应简单和复杂的情况。

3

.text() - 适用于 jQuery

$('.title').clone()    //clone the element
.children() //select all the children
.remove()   //remove all the children
.end()  //again go back to selected element
.text();    //get the text of element

1
我认为标准 JavaScript 的方法应该是 'innerText'。 - Reporter
2
这个代码不符合原帖作者的要求 - 它会获取a元素内的文本:http://jsfiddle.net/ekHJH/ - James Allardice
1
@James Allardice - 我已经完成了jQuery的解决方案,现在它会起作用................. - Pranay Rana
这个几乎可以工作,但是你在选择器的开头缺少了 .,这意味着你实际上获取的是 title 元素的文本,而不是具有 class="title" 的元素。 - James Allardice
@reporter 我的评论更多是针对普通读者。我注意到了我发表评论时的2011年日期。 - Anthony Rutledge
显示剩余3条评论

3

只使用原生JavaScript:

const el = document.querySelector('.title');
const text = el.firstChild.textContent.trim();

这是硬编码为OP的单一用例,但如果文本节点在子代的任何其他位置,则不能推广。 - ggorlen

2
这将忽略空白符,因此您不会得到空白的文本节点... 使用核心JavaScript的代码。
var oDiv = document.getElementById("MyDiv");
var firstText = "";
for (var i = 0; i < oDiv.childNodes.length; i++) {
    var curNode = oDiv.childNodes[i];
    whitespace = /^\s*$/;
    if (curNode.nodeName === "#text" && !(whitespace.test(curNode.nodeValue))) {
        firstText = curNode.nodeValue;
        break;
    }
}

在 jsfiddle 上查看:- http://jsfiddle.net/webx/ZhLep/


(注:此链接为关于 IT 技术的内容)

curNode.nodeType === Node.TEXT_NODE 会更好。在循环中使用字符串比较和正则表达式是低效的解决方案,特别是当 oDiv.childNodes.length 的数量级增加时。这个算法解决了 OP 的具体问题,但是可能会付出可怕的性能代价。如果文本节点的排列或数量发生变化,则无法保证此解决方案返回准确的输出。换句话说,您无法定位到所需的确切文本节点。您要看 HTML 结构和文本排列的情况。 - Anthony Rutledge

2
这是不太健壮的一行代码: Array.from(document.querySelector("#title").childNodes).find(n => n.nodeType == Node.TEXT_NODE).textContent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接