Atualização 2018
Como essa é uma resposta muito popular, decidi atualizá-la e embelezá-la um pouco adicionando o seletor de textnode ao jQuery como um plugin.
No trecho abaixo você pode ver que eu defino uma nova função jQuery que obtém todos (e apenas) os textNodes. Você pode encadear esta função também com, por exemplo, ofirst()
função. Eu faço um corte no nó de texto e verifico se não está vazio após o corte porque espaços, tabulações, novas linhas, etc. também são reconhecidos como nós de texto. Se você precisar desses nós também, basta removê-los da instrução if na função jQuery.
Eu adicionei um exemplo de como substituir o primeiro nó de texto e como substituir todos os nós de texto.
Essa abordagem torna mais fácil ler o código e usá-lo várias vezes e com finalidades diferentes.
A atualização de 2017 (adrach) ainda deve funcionar bem, se você preferir.
Como extensão jQuery
//Add a jQuery extension so it can be used on any jQuery object
jQuery.fn.textNodes = function() {
return this.contents().filter(function() {
return (this.nodeType === Node.TEXT_NODE && this.nodeValue.trim() !== "");
});
}
//Use the jQuery extension
$(document).ready(function(){
$('#replaceAll').on('click', () => {
$('#testSubject').textNodes().replaceWith('Replaced');
});
$('#replaceFirst').on('click', () => {
$('#testSubject').textNodes().first().replaceWith('Replaced First');
});
});
p {
margin: 0px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="testSubject">
**text to change**
<p>text that should not change</p>
<p>text that should not change</p>
**also text to change**
<p>text that should not change</p>
<p>text that should not change</p>
**last text to change**
</div>
<button id="replaceFirst">Replace First</button>
<button id="replaceAll">Replace All</button>
Javascript (ES) eqivilant
//Add a new function to the HTMLElement object so it cna be used on any HTMLElement
HTMLElement.prototype.textNodes = function() {
return [...this.childNodes].filter((node) => {
return (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim() !== "");
});
}
//Use the new HTMLElement function
document.addEventListener('DOMContentLoaded', () => {
document.querySelector('#replaceAll').addEventListener('click', () => {
document.querySelector('#testSubject').textNodes().forEach((node) => {
node.textContent = 'Replaced';
});
});
document.querySelector('#replaceFirst').addEventListener('click', function() {
document.querySelector('#testSubject').textNodes()[0].textContent = 'Replaced First';
});
});
p {
margin: 0px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="testSubject">
**text to change**
<p>text that should not change</p>
<p>text that should not change</p>
**also text to change**
<p>text that should not change</p>
<p>text that should not change</p>
**last text to change**
</div>
<button id="replaceFirst">Replace First</button>
<button id="replaceAll">Replace All</button>
Atualização de 2017 (adrach):
Parece que várias coisas mudaram desde que isso foi postado. Aqui está uma versão atualizada
$("div").contents().filter(function(){ return this.nodeType == 3; }).first().replaceWith("change text");
Resposta original (não funciona para as versões atuais)
$("div").contents().filter(function(){ return this.nodeType == 3; })
.filter(':first').text("change text");
Fonte: http://api.jquery.com/contents/