V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
shubei
V2EX  ›  程序员

请教一下,如何用 js 合并连续的相同的 span

  •  
  •   shubei · 2021-11-02 09:44:05 +08:00 · 2414 次点击
    这是一个创建于 1119 天前的主题,其中的信息可能已经有所发展或是发生改变。

    =>

    情已 逐晓云空

    29 条回复    2021-11-02 19:11:07 +08:00
    shubei
        1
    shubei  
    OP
       2021-11-02 09:45:49 +08:00
    <\span>高<\/span>
    <\span class="del">情<\/span>
    <\span class="del">已<\/span>
    <\span class="match"><\span class="add">逐<\/span><\span class="add">晓<\/span><\/span>
    <\span>云</span>
    <\span>空</span>
    shubei
        2
    shubei  
    OP
       2021-11-02 09:46:17 +08:00
    .. 可以注入的竟然
    shubei
        3
    shubei  
    OP
       2021-11-02 09:48:48 +08:00
    <\span>高<\/span>
    <\span class="del">情<\/span>
    <\span class="del">已<\/span>
    <\span class="match"><\span class="add">逐<\/span><\span class="add">晓<\/span><\/span>
    <\span class="match"><\span>云</span><\span>空</span><\/span>

    =>

    <\span>高<\/span>
    <\span class="del">情已<\/span>
    <\span class="match"><\span class="add">逐晓<\/span><\span>云空</span><\/span>
    AoEiuV020
        4
    AoEiuV020  
       2021-11-02 09:52:21 +08:00
    感觉只能普通的循环遍历然后一个一个判断构造新的,
    kkocdko
        5
    kkocdko  
       2021-11-02 09:57:41 +08:00 via Android
    @shubei 不是注入,是 markdown
    zhea55
        6
    zhea55  
       2021-11-02 10:01:09 +08:00
    用 jquery 的话,非常简单。

    找到这些所有 span 的父节点

    $(parentNode).text()
    waiaan
        7
    waiaan  
       2021-11-02 10:02:53 +08:00
    @zhea55
    它要合并相同 class 的文字,这个就只能遍历了。
    zhea55
        8
    zhea55  
       2021-11-02 10:07:13 +08:00   ❤️ 1
    @waiaan 哦,感觉有发帖这个时间,代码都写完了。


    浪费时间、浪费生命。
    shubei
        9
    shubei  
    OP
       2021-11-02 10:12:39 +08:00
    主要有 match 这种的嵌套,双指针好像不好使
    zhea55
        10
    zhea55  
       2021-11-02 10:20:17 +08:00
    @shubei 迭代的时候,把当前循环元素的 classname 拿出来,

    在当前循环元素的 siblings 里面查找这个选择器。就拿到了所有同类,然后.text(),然后去空格。



    然后下一个的 classname 要是和上次循环的一样,continue


    再说下去,代码都给你写好了。
    rongchuan
        11
    rongchuan  
       2021-11-02 10:28:49 +08:00
    最简单的方式,获取 dom 字符串,用正则匹配处理,想咋弄都行。弄完如果没有光标需求,直接 innerHTML 完事。要操作光标就用 appendChild
    shubei
        12
    shubei  
    OP
       2021-11-02 10:29:55 +08:00
    @zhea55 额,首先我这个就是单纯的字符串,并没有渲染,所以 siblings 、text 这些都不能用(因为就是 dom 节点过多,所以在渲染前用 js 合并一下的),然后对于字串符而言,我遇到的第一个问题就是用什么 split ,我最早的版本是用</span>,加了 match 之后不好使了
    zhea55
        13
    zhea55  
       2021-11-02 10:33:34 +08:00
    @shubei 你这些数据,应该是入库之前就应该把数据整理好。

    入库前爬取程序,一定是有 dom 节点的。


    你要处理字符串的话,即使你用正则表达式,还是容易出现很多异常情况。


    远远没有处理 dom 节点的代码可靠。
    zjsxwc
        14
    zjsxwc  
       2021-11-02 10:34:19 +08:00
    搞个 html parser https://github.com/andrejewski/himalaya
    后当普通数据处理呗
    shubei
        15
    shubei  
    OP
       2021-11-02 10:36:31 +08:00
    @zhea55 唉,这就孩子没娘,说来话长了呀。反正现在就堆到我这里了 [大哭]
    cyrbuzz
        16
    cyrbuzz  
       2021-11-02 10:38:06 +08:00
    这个用 CSS 选择器啦。
    ```
    let plus = document.querySelectorAll("span + span")
    let temp1 = plus[0]
    temp1.previousSibling.innerText += temp1.innerText
    temp1.parentElement.removeChild(temp1)
    ```

    首先遍历一遍 span ,没有 class 的一律先加一个虚拟 class ,然后把所有 class 找出来,去重,所有 class 挨个跑上面的代码,最后把虚拟的 class 去掉就完事了。
    shubei
        17
    shubei  
    OP
       2021-11-02 10:45:34 +08:00
    @cyrbuzz 兄弟 看 12 楼,我想在渲染之前通过 js 直接合并好
    cyrbuzz
        18
    cyrbuzz  
       2021-11-02 10:51:47 +08:00
    @shubei

    那你这不 parse 一下处理 DOM ,DOM 没有那么脆弱。刚打开一个 13WDOM 的页面只占用 31MB(当然每个的内容比较少)。
    shubei
        19
    shubei  
    OP
       2021-11-02 10:56:13 +08:00
    @cyrbuzz 也可能是我别的地方有问题?我是用的 v-html 。原文 span 比较多,接口 200ms ,js 循环一遍 140ms ,但是渲染出来就要 2s 左右了
    ookkxw
        20
    ookkxw  
       2021-11-02 11:17:24 +08:00
    感觉纯 string 处理难点在于切割同时还要区分有没有子节点,我写的话先全部<\/span>切割掉变成数组,然后循环变成 VDOM tree ,接下来就简单了
    cyrbuzz
        21
    cyrbuzz  
       2021-11-02 11:24:00 +08:00
    @shubei

    2s 是可接受范围吧,正常浏览网站,像是 bilibili, DOMContentLoad 也要 1s 左右。
    siweipancc
        22
    siweipancc  
       2021-11-02 11:41:35 +08:00 via iPhone
    两个栈与一个计数器。源数据全部入栈 1 ; 1 出栈做中间计数然后入 2 栈; 1 出栈与计数匹配则 2 出栈合并后入 2 栈不匹配则跳过,同时清空计数。
    while 打上去。
    结尾 2 栈整个导出来渲染。
    效率不会很高。
    iPhone12
        23
    iPhone12  
       2021-11-02 14:29:13 +08:00
    可以只创建 dom ,不渲染。
    再通过 dom 的 api 进行 siblings 、text 操作拿到 text 。
    iPhone12
        24
    iPhone12  
       2021-11-02 14:32:40 +08:00
    let temp = document.createElement('div')
    temp.innerHTML = '题上 html 内容'

    // example

    let text = temp.getElementsByClassName('match')[0].innerText
    aguesuka
        25
    aguesuka  
       2021-11-02 14:58:46 +08:00
    首先使用 DOMParser 解析成 dom 树, 先序遍历元素, 如果前一个元素和后一个元素的 tag 和 attr 相同, 则将后一个元素的子元素都 append 到前一个元素中, 并删除后一个元素.
    2i2Re2PLMaDnghL
        26
    2i2Re2PLMaDnghL  
       2021-11-02 15:51:39 +08:00
    同上,new DOMParser().parseFromString(str, 'text/html') ,用 DOM 操作改完之后再转回 html 插入
    然后从根节点开始对 children 作两次循环,第一遍吸收 nextSibling ,第二遍递归下降调用自身
    autoxbc
        27
    autoxbc  
       2021-11-02 16:42:59 +08:00
    强行在 String 阶段处理,本质就是写了一个简陋的 DOM 解析器
    aguesuka
        28
    aguesuka  
       2021-11-02 16:43:05 +08:00
    简单地写了一下, 还可以继续优化, 不过这样应该够用了.

    function isText(target, node) {
    return target !== null && target.nodeType === Node.TEXT_NODE
    && node.nodeType === Node.TEXT_NODE
    }

    function shouldMerge(target, node) {
    if (target === null) {
    return false
    }

    return target.nodeType === Node.ELEMENT_NODE
    && node.nodeType === Node.ELEMENT_NODE
    && node.getAttribute('class') === target.getAttribute('class')
    }

    /**
    * @param rootNode {Node}
    */
    function mergeChildrenNodes(rootNode) {
    if (!rootNode.hasChildNodes()) {
    return;
    }
    let preChild = rootNode.firstChild;
    const markToRemove = []
    for (let i = 1; i < rootNode.childNodes.length; i++) {
    const child = rootNode.childNodes[i];
    if (isText(preChild, child)) {
    preChild.textContent += child.textContent;
    markToRemove.push(child);
    } else if (shouldMerge(preChild, child)) {
    child.childNodes.forEach(node => preChild.appendChild(node));
    markToRemove.push(child);
    } else {
    preChild = child;
    }
    }
    markToRemove.forEach(node => node.parentNode.removeChild(node));
    for (const node of rootNode.childNodes) {
    mergeChildrenNodes(node);
    }
    }

    function test() {
    const dom = new DOMParser().parseFromString(`<\span>高<\/span>
    <\span class="del">情<\/span>
    <\span class="del">已<\/span>
    <\span class="match"><\span class="add">逐<\/span><\span class="add">晓<\/span><\/span>
    <\span class="match"><\span>云</span><\span>空</span><\/span>`, 'text/html')
    mergeChildrenNodes(dom)
    const theSpanWitchClassIsDel = dom.lastChild.lastChild.childNodes[1];
    console.info(theSpanWitchClassIsDel)
    console.assert(theSpanWitchClassIsDel.getAttribute)
    console.assert(theSpanWitchClassIsDel.textContent === '情已')
    }
    shubei
        29
    shubei  
    OP
       2021-11-02 19:11:07 +08:00
    @aguesuka 学习了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1374 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 23:40 · PVG 07:40 · LAX 15:40 · JFK 18:40
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.