2016-11-20 54 views
3

我正在開發Google Chrome擴展程序,該擴展程序允許您自動將突出顯示的CSS規則應用於您選擇的單詞。使用.replace()突出顯示文字的一個詞

我有以下代碼

var elements = document.getElementsByTagName('*'); 

for (var i=0; i<elements.length; i++) { 
    var element = elements[i]; 

    for (var j=0; j<element.childNodes.length; j++) { 
     var node = element.childNodes[j]; 

     if(node.nodeType === 3) { 
      var text = node.nodeValue; 

      var fetchedText = text.match(/teste/gi); 

      if(fetchedText) { 
       var replacedText = element.innerHTML.replace(/(teste)/gi, "<span style=\"background-color: yellow\">$1</span>"); 

       if (replacedText !== text) { 
        element.innerHTML = replacedText; 
       } 
      } 
     } 
    } 
} 

打破並凍結Chrome標籤頁。但是,如果我從element.innerHTML = replacedText;切換到element.innerHTML = "text";這可行。

我似乎無法找到以下代碼有什麼問題。

+0

你記錄了'replacedText'具有的值嗎? –

+0

@ScottMarcus當我記錄'replacedText'時,它顯示正確的值,例如' teste'。但是,如果我在'element.innerHTML'上使用它,它會崩潰我的選項卡。 – rafaelcpalmeida

+0

您確定在控制檯中顯示「\」'轉義序列嗎?您是否嘗試將字符串修改爲:'' $ 1「'? –

回答

0

我正在經歷是由於一個遞歸循環,因爲,例如,我一直在尋找的關鍵字teste錯誤修正和我將內容爲<span style=\"background-color: #ffff00\">teste</span>的新元素將強制腳本嘗試再次替換新關鍵字teste等等。

我想出了這個功能:

function applyReplacementRule(node) { 
    // Ignore any node whose tag is banned 
    if (!node || $.inArray(node.tagName, hwBannedTags) !== -1) { return; } 

    try { 
     $(node).contents().each(function (i, v) { 
      // Ignore any child node that has been replaced already or doesn't contain text 
      if (v.isReplaced || v.nodeType !== Node.TEXT_NODE) { return; } 

      // Apply each replacement in order 
      hwReplacements.then(function (replacements) { 
       replacements.words.forEach(function (replacement) { 
        //if(!replacement.active) return; 
        var matchedText = v.textContent.match(new RegExp(replacement, "i")); 

        if (matchedText) { 
         // Use `` instead of '' or "" if you want to use ${variable} inside a string 
         // For more information visit https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals 
         var replacedText = node.innerHTML.replace(new RegExp(`(${replacement})`, "i"), "<span style=\"background-color: #ffff00\">$1</span>"); 

         node.innerHTML = replacedText; 
        } 
       }); 
      }).catch(function (reason) { 
       console.log("Handle rejected promise (" + reason + ") here."); 
      }); 

      v.isReplaced = true; 
     }); 
    } catch (err) { 
     // Basically this means that an iframe had a cross-domain source 
     if (err.name !== "SecurityError") 
     { throw err; } 
    } 
} 

我在哪裏修改節點屬性,並「告訴」我已經修改節點,所以我不要再最終在一個遞歸無限循環。

P.S.正如你可以看到這個解決方案使用jQuery。我會嘗試重寫這個只使用Vanilla JS。

+0

您的解決方案仍然使用RegExp來更改父元素的'.innerHTML'。因此,當文本節點也包含該單詞時,這仍將破壞包含要替換的單詞的任何HTML。換句話說,雖然它不會替換,除非替換將在實際文本中發生,但它並不妨礙替換也改變HTML(例如在'src =「yourWord」或'href =「http:// foo.com/yourWord/bar.html「')。 – Makyen

+0

只是一個評論,不打算成爲一個批評:你使用兩行註釋來解釋你對模板文字的使用。雖然很好解釋它,但在這種情況下使用它並沒有什麼意義,因爲你可以用''('+'+')'替換它。使用直接字符串連接不會讓您覺得您需要兩行註釋來解釋,並且不會將代碼限制爲Chrome> = ver。 41. – Makyen

+0

僅供參考:您目前正在迭代一個「單詞」列表,其中每個列表將被替換。當你只能使用一個時,你可以使用兩種不同的RegExp(存在的測試不會在意在捕獲組中有單詞)。預先創建一個包含「words」數組中所有單詞的RegExp會更有效率。這樣做只會爲所有單詞執行一個'.replace()'。這會在你的內部循環中節省相當多的時間。 [這個答案](http://stackoverflow.com/a/40576258/3773011)有一個這樣做的例子。 – Makyen

3

您首先正在測試#text節點以查看文本是否包含您試圖突出顯示的單詞,但隨後在父元素的.innerHTML上執行替換。這有幾個問題。

  • 無限替代:當您修改父元素的.innerHTML時,您將更改childNodes數組。這樣做的方式是在包含要替換的文本的數組中添加一個節點。因此,當您繼續掃描childNodes陣列時,您總是會找到一個包含要替換的文本的(新)節點。因此,您再次替換它,創建childNodes陣列中具有較高索引的另一個節點。無限重複。
  • 使用RegExp替換.innerHTML屬性中的文本。雖然您已經測試過以確保您想要替換的文本實際上包含在文本節點中,但這並不妨礙您的RegExp從替換元素的實際HTML中的任何匹配詞語(例如,在src="yourWord",href="http://foo.com/yourWord/bar.html",或者,如果試圖以突出話像stylecolorbackgroundspanidheightwidthbuttonforminput等)。
  • 您不檢查以確保您沒有更改<script><style>標籤中的文字。
  • 您正在檢查您是否僅在文本節點中進行了更改(即您檢查了node.nodeType === 3)。如果你不檢查這個你也必須因使用.innerHTML改變HTML以下可能出現的問題:
    • 你可能最終會改變屬性,或者實際的HTML標籤,這取決於你正在與.replace()變化。這可能會完全破壞頁面佈局和功能。
    • 當您更改.innerHTML時,該頁面部分的DOM將被完全重新創建。這意味着元素,而新元素可能是具有相同屬性的相同類型,任何附加到舊元素的事件偵聽器都不會附加到新元素。這可能會嚴重破壞頁面的功能。
    • 重複更改大部分DOM可能需要大量計算以重新呈現頁面。根據您的操作方式,您可能會遇到顯着的用戶感知性能問題。

因此,如果你要使用正規替換的文本,你只需要在#text節點的內容進行操作,而不是父母節點的.innerHTML。因爲你想創造更多的HTML元素(如新<span style="">元素,與子#text節點),也有一些併發症。

不能HTML文本分配到文本節點來創建新的HTML節點:

沒有辦法到新的HTML直接分配給文本節點,並把它評價爲HTML,創建新的節點。分配到文本節點的.innerHTML屬性將創建對象這樣的屬性(就像它會在任何對象),但不會改變屏幕(即#text節點的實際值)上顯示的文本。因此,它不會完成你想要做的事情:它不會創建父節點的任何新的HTML子節點。

這樣做對頁面的DOM影響最小(即最不可能破壞頁面上現有的JavaScript)的方法是創建一個<span>以包含您正在創建的新文本節點該#text節點,是不是在你的有色<span>)與您所創建的潛在多個<span>元素一起。這將導致用單個<span>元素替換單個#text節點。雖然這會創建更多的後代,但它將使父元素中的子元素數量保持不變。因此,任何依賴於此的JavaScript都不會受到影響。鑑於我們正在改變DOM,沒有辦法不可能破壞其他JavaScript,但這應該儘量減少這種可能性。

的你如何能做到這一些例子:見this answer(替換詞在按鈕的單詞列表)和this answer(地方的所有文字,其中由空格隔開成按鈕<p>元素)執行正則表達式充分擴展替換爲新的HTML。見this answer它基本上做同樣的事情,但讓一個鏈接(它有不同的實現橫貫用TreeWalker DOM來找到#text節點,而不是在其他兩個例子使用的NodeIterator的)。

這裏是代碼將執行你是在document.body希望每個文本節點上的更換和創造有style是在文本的一部分不同所需要的新的HTML:

有是其他方式來做到這一點。但是,它們將對該特定元素的子元素結構(例如父元素上的多個附加節點)產生更顯着的變化。這樣做有更高的潛力,可以打破依賴於當前頁面結構的頁面上的任何JavaScript。實際上,像這樣的任何改變都有可能打破目前的JavaScript。

對這個答案的代碼從代碼this other answer of mine

+0

其實我沒有收到那種類型的錯誤,因爲我修改了包含該文本節點的元素。如果我將我想要的任何內容替換爲我想要的內容 – rafaelcpalmeida

+0

@rafaelcpalmeida,是的,我的錯誤描述問題的一部分(做出假設,像往常一樣是一件壞事),它工作得很好。我已經用更正的問題描述更新了答案(不改變解決方案)。 – Makyen