V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Alander
V2EX  ›  前端开发

浏览器渲染问题疑惑

  •  
  •   Alander · 2021-07-22 10:27:16 +08:00 · 1158 次点击
    这是一个创建于 999 天前的主题,其中的信息可能已经有所发展或是发生改变。
    <body>
    <div id="root">
    </div>
    </body>
    <script>
      const div = document.querySelector('#root')
      div.innerHTML = '1'
      const now = performance.now()
      console.log(div.innerHTML);
      while (performance.now() - now < 100) {
        console.log(now)
      }
      console.log(div.innerHTML);
      div.innerHTML = '2'
      console.log(div.innerHTML);
    </script>
    

    不懂就问:

    这段代码在 chrome 渲染直接渲染成 2,没有 1 的过程,是为什么?这段 while 阻塞不生效? js 上是符合代码的逻辑的,但是渲染上是直接 2,有什么资料可供查看?

    第 1 条附言  ·  2021-07-22 13:36:22 +08:00
    我没表示明白我的想法:确实不应该有 1 这个过程,因为宏任务执行结束后再渲染,此时应该是 2,我的疑惑是上述 while 循环在我的理解里面应该阻塞住浏览器渲染,也就是 100ms 以后再渲染出 2,实际上页面一进入就直接渲染出 2 了。
    第 2 条附言  ·  2021-07-22 13:41:14 +08:00
    是我理解错误了现象,实际上就是如第一次 append 所说的,会先空白等待再渲染出 2,而非直接渲染出 2,我以为的直接渲染出 2 是因为刷新遗留的 2,并非本次渲染,此贴终结,感谢耐心回复的人🙏
    runze
        1
    runze  
       2021-07-22 10:36:24 +08:00
    cyrbuzz
        2
    cyrbuzz  
       2021-07-22 12:05:43 +08:00
    参考下这个: https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model

    说下自己的理解,里面提到第 11 步是更新渲染,11 之后还有两步,一个是执行浏览器空闲回调(requestsIdleCallback)一个是 worker 的回调。

    11 步之前是执行 JS,JS 主线程就一个线程,UI 渲染会等待 JS 执行,所以你这个阻塞也阻塞了渲染,

    ```
    const div = document.querySelector('#root')
    div.innerHTML = '1'
    const now = performance.now()
    console.log(div.innerHTML);
    ```
    执行到这里如果没有后续代码,浏览器会尝试执行微任务栈,然后执行 UI 渲染,此时就会渲染 1 。

    但要注意 innerHTML 实时改变了 DOM,但不是触发渲染的条件,改变 DOM != 渲染,走完这个循环才是执行渲染的条件。

    ```
    while (performance.now() - now < 100) {
    console.log(now)
    }
    console.log(div.innerHTML);
    div.innerHTML = '2'
    console.log(div.innerHTML);
    ```

    加上下面这些,只是多阻塞了主线程一会,此时走完前面的 1-10,去执行 UI 渲染,而是不断执行 console.log(now),之后执行到了:

    ```
    div.innerHTML = '2'
    console.log(div.innerHTML);
    ```
    执行渲染时的 innerHTML 已经是 2,所以渲染出来 2 了。

    一般可以用 setTimeout+Promise,这样不会阻塞 UI 渲染,只会阻塞后续代码执行:

    ```
    function sleep(times) {
    return new Promise((resolve) => {
    setTimeout(() => {
    resolve()
    }, times)
    })
    }
    ```

    ```
    async function draw() {
    const div = document.querySelector('#root')
    div.innerHTML = '1'
    //const now = performance.now()
    //console.log(div.innerHTML);
    //while (performance.now() - now < 100) {
    // console.log(now)
    //}
    await sleep(100)
    console.log(div.innerHTML);
    div.innerHTML = '2'
    console.log(div.innerHTML);
    }

    draw()
    ```

    相关扩展可以了解一下 Vue 的$nextTick,曾经某一个版本把它从微任务改到了宏任务,此时用$nextTick 改变 DOM 的 Style 会出现抖动(因为 宏-> 微 -> UI 渲染 -> 宏 2),本来任务应该在 UI 渲染前结果成了 UI 渲染后。
    Alander
        3
    Alander  
    OP
       2021-07-22 12:25:25 +08:00
    @cyrbuzz 我可能没表述明白这个事情,页面的表现是:已进入页面直接显示 2,同时控制台不停在打印 console.log(now);

    我的疑惑是为什么不是。先渲染出 1,100 毫秒后渲染出 2 或者 先是空白,100ms 后直接渲染 2 ?有点不明白,我去看下你提到的规范说明
    cyrbuzz
        4
    cyrbuzz  
       2021-07-22 13:09:10 +08:00
    @Alander

    emm,感觉我有说明= =,我简化一下:

    innerHTML 并非触发渲染的条件,执行完整个循环才是,包裹你写的阻塞在内的所有代码都属于渲染前执行的任务。
    Alander
        5
    Alander  
    OP
       2021-07-22 13:29:53 +08:00
    @cyrbuzz 我理解你的意思,现在的 chrome 表现情况是 js 在执行中,但是页面已经渲染出来了 2,不好意思哈,可能是我没理解你的意思,我只是再确认一下我有没有表述明白:这段 html 在浏览器中执行页面的渲染并没有被阻塞直接渲染出<div id='root'>2</div>,同时 js 代码运行 while 循环,而非先 js 运行完 while 循环再执行渲染。如果说是你明白了我的意思但是我没有明白你的解释真的不好意思哈,我再仔细找下其他资料。
    Alander
        6
    Alander  
    OP
       2021-07-22 13:31:57 +08:00
    @cyrbuzz innerHTML 并非触发渲染的条件,执行完整个循环才是,包裹你写的阻塞在内的所有代码都属于渲染前执行的任务。
    你的这段话也是我的想法,但是实际页面表现与我的预期不一致,按照这个想法是否是先 while 循环结束后再渲染出字符串 2 ?但是实际上是页面一加载就直接渲染出了 2
    Alander
        7
    Alander  
    OP
       2021-07-22 13:39:55 +08:00
    @cyrbuzz 哈哈哈,不好意思,是我理解错浏览器了,我看见的是刷新前渲染的结果误以为是一进来就是 2,不好意思哈,你的解释是正确的
    3dwelcome
        8
    3dwelcome  
       2021-07-22 13:46:55 +08:00
    我测试了一下,阻塞没问题啊。

    div.innerHTML = '1'
    这句正常执行了,但是被后面的 while()语句阻塞了,所以 1 没办法正常显示出来。

    你之所以老是看到页面是 2,是因为上次的页面没刷新。而不是 JS 跳过 while(),自己去执行了后面的 div.innerHTML = '2'
    hazardous
        9
    hazardous  
       2021-07-22 13:54:50 +08:00
    100 毫秒太短所以感觉不明显,改成 3000 或者 5000 再试。
    Alander
        10
    Alander  
    OP
       2021-07-22 14:27:49 +08:00
    @3dwelcome 是的是的,是上次页面没刷新导致的我的错觉
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4226 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 05:24 · PVG 13:24 · LAX 22:24 · JFK 01:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.