Làm thế nào để lấy nút văn bản của một phần tử?


98
<div class="title">
   I am text node
   <a class="edit">Edit</a>
</div>

Tôi muốn nhận "Tôi là nút văn bản", không muốn xóa thẻ "chỉnh sửa" và cần một giải pháp trình duyệt chéo.


câu hỏi này khá giống với stackoverflow.com/questions/3172166/… - hãy xem những câu trả lời đó để biết câu trả lời của James 'phiên bản JS đơn giản
Mala

Câu trả lời:


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

Điều này lấy contentsphần tử đã chọn và áp dụng một chức năng lọc cho phần tử đó. Hàm bộ lọc chỉ trả về các nút văn bản (tức là những nút có nodeType == Node.TEXT_NODE).


@Val - xin lỗi, tôi đã bỏ sót mã gốc. Tôi sẽ cập nhật câu trả lời để hiển thị nó. Bạn cần text()filterhàm trả về chính các nút, không phải nội dung của các nút.
James Allardice

1
Không chắc tại sao nhưng tôi không chắc chắn khi thử nghiệm lý thuyết trên. Tôi đã chạy phần sau jQuery("*").each(function() { console.log(this.nodeType); })và tôi nhận được 1 cho tất cả các loại nút.
Batandwa

Có thể nhận văn bản tại nút được nhấp và văn bản trong tất cả các nút con của nó không?
Jenna Kwon

Điều này thật thú vị và giải quyết được vấn đề này, nhưng điều gì sẽ xảy ra khi tình huống trở nên phức tạp hơn? Có một cách linh hoạt hơn để hoàn thành công việc.
Anthony Rutledge

Không có jQuery, document.querySelector (". Title"). ChildNodes [0] .nodeValue
Balaji Gunasekaran

53

Bạn có thể lấy nodeValue của childNode đầu tiên bằng cách sử dụng

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

http://jsfiddle.net/TU4FB/


4
Trong khi điều đó sẽ hoạt động, nó phụ thuộc vào vị trí của các nút con. Nếu (khi) điều đó thay đổi, nó sẽ bị hỏng.
Armstrongest

Nếu nút văn bản không phải là nút con đầu tiên, bạn có thể nhận được nullgiá trị trả về.
Anthony Rutledge

14

Nếu bạn muốn lấy giá trị của nút văn bản đầu tiên trong phần tử, thì mã này sẽ hoạt động:

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;
    }
}

Bạn có thể thấy điều này đang hoạt động tại đây: http://jsfiddle.net/ZkjZJ/


Tôi nghĩ rằng bạn có thể sử dụng curNode.nodeType == 3thay vì nodeNamelà tốt.
Nilloc 21/07/2017

1
@Nilloc có lẽ, nhưng lợi ích là gì?
Bóng Wizard là tai For You

5
@ShadowWizard @Nilloc cách được đề xuất cho đó là sử dụng hằng số ... curNode.nodeType == Node.TEXT_NODE(so sánh số nhanh hơn nhưng curNode.nodeType == 3 không thể đọc được - nút nào có số 3?)
mikep

@ShadowWizard Sử dụng curNode.NodeType === Node.TEXT_NODE. So sánh này xảy ra trong một vòng lặp của các lần lặp lại có thể chưa biết. So sánh hai số nhỏ sẽ tốt hơn so với các chuỗi có độ dài khác nhau (xem xét thời gian và không gian). Câu hỏi chính xác cần hỏi trong tình huống này là "tôi có loại / loại nút nào?", Chứ không phải "tôi có tên gì?" developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
Anthony Rutledge,

2
@ShadowWizard Ngoài ra, nếu bạn định sử dụng một vòng lặp để sàng lọc childNodes, hãy biết rằng một nút phần tử có thể có nhiều hơn một nút văn bản. Trong một giải pháp chung, người ta có thể cần chỉ định phiên bản nào của nút văn bản trong nút phần tử mà bạn muốn nhắm mục tiêu (nút đầu tiên, thứ hai, thứ ba, v.v.).
Anthony Rutledge

13

Một giải pháp JS gốc khác có thể hữu ích cho các phần tử "phức tạp" hoặc lồng nhau sâu là sử dụng NodeIterator . Đặt NodeFilter.SHOW_TEXTlàm đối số thứ hai ("whatToShow") và chỉ lặp lại các nút con văn bản của phần tử.

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>

Bạn cũng có thể sử dụng TreeWalker. Sự khác biệt giữa cả hai là NodeIteratormột trình lặp tuyến tính đơn giản, trong khi TreeWalkercho phép bạn điều hướng qua anh chị em và tổ tiên.


9

JavaScript thuần túy: Tối giản

Trước hết, hãy luôn ghi nhớ điều này khi tìm kiếm văn bản trong DOM.

MDN - Khoảng trắng trong DOM

Vấn đề này sẽ khiến bạn chú ý đến cấu trúc của XML / HTML.

Trong ví dụ JavaScript thuần túy này, tôi tính đến khả năng có nhiều nút văn bản có thể được xen kẽ với các loại nút khác . Tuy nhiên, ban đầu, tôi không chuyển phán quyết về khoảng trắng, để lại tác vụ lọc đó cho mã khác.

Trong phiên bản này, tôi chuyển NodeListvào từ mã gọi / khách hàng.

/**
* 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;
}

Tất nhiên, bằng cách thử nghiệm node.hasChildNodes()trước, sẽ không cần sử dụng forvòng lặp thử nghiệm trước .

/**
* 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 thuần túy: Mạnh mẽ

Ở đây hàm getTextById()sử dụng hai hàm trợ giúp: 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.
}

Tiếp theo, giá trị trả về (Mảng hoặc null) được gửi đến mã máy khách nơi nó sẽ được xử lý. Hy vọng rằng mảng phải có các phần tử chuỗi của văn bản thực, không phải các dòng có khoảng trắng.

Các chuỗi trống ( "") không được trả về vì bạn cần một nút văn bản để chỉ ra chính xác sự hiện diện của văn bản hợp lệ. Trả về ( "") có thể tạo ấn tượng sai rằng một nút văn bản tồn tại, khiến ai đó cho rằng họ có thể thay đổi văn bản bằng cách thay đổi giá trị của .nodeValue. Điều này là sai, vì một nút văn bản không tồn tại trong trường hợp chuỗi rỗng.

Ví dụ 1 :

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

Ví dụ 2 :

<p id="bio">

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

Vấn đề xảy ra khi bạn muốn làm cho HTML của mình dễ đọc bằng cách giãn cách. Bây giờ, mặc dù không có văn bản hợp lệ nào mà con người có thể đọc được, vẫn có các nút văn bản với các ký tự newline ( "\n") trong.nodeValue thuộc tính .

Con người coi ví dụ một và hai là tương đương về mặt chức năng - các phần tử trống đang chờ được lấp đầy. DOM khác với suy luận của con người. Đây là lý do tại sao getStringsFromChildren()hàm phải xác định xem các nút văn bản có tồn tại hay không và tập hợp các .nodeValuegiá trị vào một mảng.

for (var i = 0; i < length; i++) {
    if (nodeList[i].nodeType === Node.TEXT_NODE) {
            textNodes.push(nodeList[i].nodeValue);
    }
}

Trong ví dụ thứ hai, có hai nút văn bản tồn tại và getStringFromChildren()sẽ trả .nodeValuevề cả hai nút ( "\n"). Tuy nhiên,filterWhitespaceLines() sử dụng một biểu thức chính quy để lọc ra các dòng ký tự khoảng trắng thuần túy.

Việc trả về nullthay vì các ký tự dòng mới ( "\n") có phải là một hình thức nói dối mã khách hàng / mã cuộc gọi không? Về mặt con người, không. Trong điều kiện DOM, có. Tuy nhiên, vấn đề ở đây là lấy văn bản chứ không phải chỉnh sửa.Không có văn bản của con người để quay lại mã gọi.

Người ta không bao giờ có thể biết có bao nhiêu ký tự dòng mới có thể xuất hiện trong HTML của ai đó. Việc tạo bộ đếm tìm ký tự dòng mới "thứ hai" là không đáng tin cậy. Nó có thể không tồn tại.

Tất nhiên, xuống dòng sâu hơn, vấn đề chỉnh sửa văn bản trong <p></p>phần tử trống có thêm khoảng trắng (ví dụ 2) có thể có nghĩa là hủy (có thể, bỏ qua) tất cả trừ một nút văn bản giữa các thẻ của đoạn văn để đảm bảo phần tử chứa chính xác những gì nó đang có phải hiển thị.

Bất kể, ngoại trừ những trường hợp bạn đang làm điều gì đó phi thường, bạn sẽ cần một cách để xác định thuộc .nodeValuetính của nút văn bản nào có văn bản thực sự mà con người có thể đọc được mà bạn muốn chỉnh sửa. filterWhitespaceLinesđưa chúng tôi đến đó một nửa.

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. 
    }
}

Tại thời điểm này, bạn có thể có đầu ra giống như sau:

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

Không có gì đảm bảo rằng hai chuỗi này nằm liền kề nhau trong DOM, vì vậy việc kết hợp chúng với nhau .join()có thể tạo ra một hỗn hợp không tự nhiên. Thay vào đó, trong mã gọigetTextById() , bạn cần chọn chuỗi mà bạn muốn làm việc với.

Kiểm tra đầu ra.

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);
}

Người ta có thể thêm .trim()vào bên trong getStringsFromChildren()để loại bỏ khoảng trắng ở đầu và cuối (hoặc biến một loạt khoảng trắng thành một chuỗi có độ dài bằng không ( ""), nhưng làm thế nào bạn có thể biết trước những gì mọi ứng dụng có thể cần xảy ra với văn bản (chuỗi) khi nó được tìm thấy? Bạn không, vì vậy hãy để điều đó cho một triển khai cụ thể và hãy để getStringsFromChildren()chung chung.

Đôi khi có thể không cần đến mức độ đặc hiệu này ( targetvà những thứ tương tự). Điều đó thật tuyệt. Sử dụng một giải pháp đơn giản trong những trường hợp đó. Tuy nhiên, một thuật toán tổng quát cho phép bạn xử lý các tình huống đơn giản và phức tạp.


8

Phiên bản ES6 trả về nội dung nút #text đầu tiên

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

Tôi đang băn khoăn về tính hiệu quả và tính linh hoạt. (1) Việc sử dụng .from()để tạo một cá thể mảng được sao chép nông. (2) Việc sử dụng .find()để thực hiện một so sánh chuỗi bằng cách sử dụng .nodeName. Sử dụng node.NodeType === Node.TEXT_NODEsẽ tốt hơn. (3) Trả về một chuỗi trống khi không có giá trị, nullđúng hơn nếu không tìm thấy nút văn bản . Nếu không tìm thấy nút văn bản nào, người ta có thể cần tạo một nút! Nếu bạn trả về một chuỗi trống "", bạn có thể tạo ấn tượng sai rằng một nút văn bản tồn tại và có thể được thao tác bình thường. Về bản chất, trả về một chuỗi trống là một lời nói dối trắng trợn và tốt nhất nên tránh.
Anthony Rutledge

(4) Nếu có nhiều hơn một nút văn bản trong một nodeList, không có cách nào ở đây để chỉ định nút văn bản nào bạn muốn. Bạn có thể muốn nút văn bản đầu tiên , nhưng cũng có thể bạn muốn nút văn bản cuối cùng .
Anthony Rutledge

Bạn đề xuất gì để thay thế Array.from?
jujule

@Snowman vui lòng thêm câu trả lời của riêng bạn cho những thay đổi quan trọng như vậy hoặc đưa ra đề xuất cho OP để họ có cơ hội kết hợp chúng vào câu trả lời của mình.
TylerH

@jujule - Tốt hơn nên sử dụng [...node.childNodes]để chuyển đổi HTMLCollection thành Mảng
vsync

5

.text() - for 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
Tôi nghĩ rằng phương pháp javascript tiêu chuẩn phải là 'innerText'
Phóng viên

2
Điều này không làm việc theo cách OP muốn - nó sẽ nhận được nội dung bên trong ayếu tố quá: jsfiddle.net/ekHJH
James Allardice

1
@ James Allardice - Tôi đang thực hiện với các giải pháp jquery bây giờ điều này sẽ làm việc .................
Pranay Rana

Đó gần như sẽ làm việc, nhưng bạn đang thiếu .khi bắt đầu chọn của bạn, có nghĩa là bạn thực sự nhận được văn bản của các titleyếu tố, chứ không phải yếu tố vớiclass="title"
James Allardice

@reporter .innerTextlà một quy ước cũ của IE chỉ mới được thông qua gần đây. Về mặt kịch bản DOM tiêu chuẩn, node.nodeValuelà cách một người lấy văn bản của một nút văn bản.
Anthony Rutledge

2

Điều này cũng sẽ bỏ qua khoảng trắng, do đó bạn không bao giờ nhận được Mã trống văn bản..code bằng cách sử dụng Javascript cốt lõi.

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;
    }
}

Kiểm tra nó trên jsfiddle: - http://jsfiddle.net/webx/ZhLep/


curNode.nodeType === Node.TEXT_NODEsẽ tốt hơn. Sử dụng so sánh chuỗi và một biểu thức chính quy trong vòng lặp là một giải pháp hiệu suất thấp, đặc biệt là khi mức độ oDiv.childNodes.lengthtăng lên. Thuật toán này giải quyết câu hỏi cụ thể của OP, nhưng, có khả năng, với chi phí hiệu suất khủng khiếp. Nếu cách sắp xếp hoặc số lượng của các nút văn bản thay đổi, thì giải pháp này không thể được đảm bảo trả về kết quả chính xác. Nói cách khác, bạn không thể nhắm mục tiêu chính xác nút văn bản mà bạn muốn. Bạn đang sử dụng cấu trúc HTML và cách sắp xếp văn bản trong đó.
Anthony Rutledge

1

Bạn cũng có thể sử dụng text()kiểm tra nút của XPath để chỉ lấy các nút văn bản. Ví dụ

var target = document.querySelector('div.title');
var iter = document.evaluate('text()', target, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
var node;
var want = '';

while (node = iter.iterateNext()) {
    want += node.data;
}

0

Đây là giải pháp của tôi trong ES6 để tạo một chuỗi tương phản với văn bản được nối của tất cả các mã con (đệ quy) . Lưu ý rằng cũng truy cập vào màn hình hiển thị của các nút con.

function text_from(node) {
    const extract = (node) => [...node.childNodes].reduce(
        (acc, childnode) => [
            ...acc,
            childnode.nodeType === Node.TEXT_NODE ? childnode.textContent.trim() : '',
            ...extract(childnode),
            ...(childnode.shadowRoot ? extract(childnode.shadowRoot) : [])],
        []);

    return extract(node).filter(text => text.length).join('\n');
}

Giải pháp này được lấy cảm hứng từ giải pháp của https://stackoverflow.com/a/41051238./1300775 .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.