javascript - 当我用js给元素添加className时,浏览器发生了什么?

 书友68570125 发布于 2022-11-09 11:14

我想知道浏览器的渲染过程,JS线程与UI线程是怎么交互的?
最想知道什么样的样式操作,会被合并到一次渲染中。

例如我想一个图片hover的时候即刻变小,然后过渡放大到原来大小

p.onmouseover = function(){
    p.className = 'small';
    p.className = 'transition'
}

但浏览器会将上面两个操作合并了成p.className = 'transition',而没有分别渲染两个效果,

所以很想了解浏览器的渲染过程,以及JS什么时候会让浏览器渲染?

10 个回答
  • 往往我们在操作Dom的时候,应该尽量连续的写操作,或连接的读操作,而不应该读一个值,写一个值,这样如果涉及到需要重绘的值,那么对性能影响很大。因为浏览器会对连续的操作一次性处理。
    那么针对题主的问题,两次更新Class,需要重绘两次才会得到预期的效果,只需要在中间做一次读操作就行了。

    p.className = 'small';
    p.offsetWidth;
    p.className = 'transition'
    

    为什么会这样,原理很简单,因为如果我们先改了p的样式,然后再去读取它的某个值,因为样式变了会导致很多属性值会变,比如高,宽等等,那浏览器需要返回给我们正确的值,就必须先强制对前面的操作重绘。

    2022-11-12 01:46 回答
  • 这是浏览器渲染优化的一个手段。

    也就是说,当你在运行一段JS代码时,浏览器并不会每一次改动就渲染页面,而是会将这些改变缓存下来,直到代码运行之后才会将这些改变进行渲染。

    所以你的代码缓存后就变成transtion了。

    这篇文章应该能完美的解决你的疑惑。

    2022-11-12 01:46 回答
  •   可能是作用域的问题。js运行过程可分为编译阶段和运行阶段。在编译阶段(js是有编译阶段的),他会对这些函数进行声明,声明这个函数时内部会有"提升"。当然本题没有涉及到。但是在函数作用域里,这里有变量的覆盖,即后来的p.className覆盖了原先了p.className。这些是在编译阶段做的优化。

      在js执行阶段,会直接使用第二个p.className的了。

      详情可以看我写的一篇博客。http://blog.csdn.net/real_bir...

      我不知道这是否有用,但我希望是有用的。

    2022-11-12 01:46 回答
  • p.className ="…";这样可以吗,好像没见过这样写的

    2022-11-12 01:46 回答
  • 嘿嘿,这样呢
    p.class = a;
    setTimeout(function(){p.class=b},0);

    2022-11-12 01:46 回答
  • 我也来补充一下。。。
    浏览器里面存在两个线程,请允许我叫他们为:渲染哥和js哥。
    很明显,渲染哥就是负责渲染web页面的工作,比如css和html结合后的dom树再进行渲染,或者开启@keyframe动画,还是简简单单的transition,都是渲染哥在负责计算和绘制的。
    而我们的js哥呢,顾名思义,就是只是处理一下js代码,然后将处理结果提交给渲染哥渲染(假如出现变动的话)。
    因为浏览器里面只有一张办公桌(堆)和一张椅子(栈),对于包吃包住在浏览器里的渲染哥和js哥来说,就只能一个人工作另一个人休息了,所以他们优化了他们的工作模式:平时没什么事的时候渲染哥在办公桌(堆)工作,当浏览器读到JS代码的时候,渲染哥把办公桌让给JS哥搞,他自己坐在椅子上,当JS哥处理完代码,又重新将办公桌让给渲染哥。
    但真正让渲染哥和js哥交接工作的,是事件。
    我们知道,JS的事件驱动的,很懒,没有事件发生的时候似乎天塌下来也跟他无关。一旦页面存在相关事件,且被用户触发了,这时候渲染哥很高兴啊,立马通知JS哥上班,自己拍拍屁股下班了。
    然后在JS线程工作期间,如果JS哥遇到了 @外籍杰克 所说的p.clientHeight这类强制重绘的代码,就会让渲染哥画一遍后再获取它。

    • 最后指出一下你那个className的错误。。。并不是说什么帮你合并了class,而是你自己把className给替换了。

        op.onmouseover=function(){
            this.className="small";//op的className真的是small,
            this.className="transition";//op的className本来是small,现在变成了transition了
            /*事件结束,js哥向渲染哥交接工作,渲染哥拿到这个op的className是transition*/
        }

    而那个setTimeout

        op.onmouseover=function(){
            this.className="small";
            /*事件结束,渲染哥拿到的class是small*/
            /*因为遇到了setTimeout,可以看成一个定时器事件,强制规定一个函数隔几秒后执行*/
            /*因为setTimout会将代码带到异世界去,5s后才会回来,所以js哥不会在那里傻等,直接把工作交给渲             染哥*/
            setTimeout(function(){
                /*5s后这些代码从异世界归来,渲染哥通知JS哥上班*/
                op.className="transition";
                /*定时器事件结束,渲染哥拿到的class是transition*/
            },5000)
        }
    2022-11-12 01:46 回答
  • 浏览器一般分JS线程、UI线程、事件线程、HTTP请求线程。JS线程和UI线程是互斥的,当JS线程在运行时,UI线程是无法运行的,所以你的代码实际上是等JS运行完之后再去渲染,此时的class为transition,你可以这样

    p.onmouseover = function(){
        p.className = 'small';
        setTimeout(function () {
            p.className = 'transition'
        }, 0)
    }
    2022-11-12 01:46 回答
  • 事实上浏览器的渲染是周期,并不是每时每刻都随着dom的变化进行渲染,所以当你两次变化间间隔太短,就会在同一个渲染周期之中,自然只会有最后一个的变化。

    2022-11-12 01:46 回答
  • 有人说是因为电脑运行太快,你没有看到,这个回答是错误的。
    如果是因为这个原因,你可以试着把代码改成这样,看看是什么样子:

    function delay(ms) {
        for(let i = 0; i < ms; i++) {
            for(let j = 0; j < ms; j++)
        }
    }
    p.onmouseover = function(){
        p.className = 'small';
        delay(1000);
        p.className = 'transition'
    }

    结果是,网页将会在卡住短暂时间之后渲染出p.className = 'transition'的效果。

    这方面我也没有找到相关的资料,但是我自己试验过很多代码,得出一个结论:网页的渲染是在脚本主进程结束之后才会进行。(虽然不知道是不是完全正确,但是个人觉得能和我观察到的现象相符合,如果不对还请指正)
    也就是说,对于你的代码而言,虽然你给className赋值了两次,但是实际上只有当主进程结束之后网页才会重新渲染,而那时候你的className是等于transition的。

    如果你想要看到两个class的效果,那么就要给两次赋值中间将主进程空闲出来留给网页渲染。这就需要异步的过程。代码只需要改成这样就行了:

    p.onmouseover = function(){
        p.className = 'small';
        setTimeout(function(){
            p.className = 'transition';
        }, 5000);
    }

    setTimeout这个异步函数将会给主进程空闲出5秒的时间用于网页渲染,这样你就能看到两个效果了。

    2022-11-12 01:46 回答
  • 补充下@xiaoboost 的答案:

    首先肯定他说的,

    有人说是因为电脑运行太快,你没有看到,这个回答是错误的。

    然后部分同意他说的,

    只有当主进程结束之后网页才会重新渲染

    最后开始我的忽悠(之前读过这类主题的东西,但是现在不记得了,my poor memory...):浏览器很聪明,会对Paint进行优化,如果允许,会在代码栈退出后再进行刷新绘制。为什么setTimeout里面的class赋值会生效,原因就是setTimeout里面的函数放在了另外的代码栈,由事件去异步调用。

    但是,我们也可以强制浏览器在同一个代码栈中进行重新绘制。这里就是上面说的“如果允许”的例外情况,看一下代码:

    p.onmouseover = function () {
        p.className = 'small';
        console.log(p.clientHeight); // 这句话强制浏览器进行重新绘制
        p.className = 'transition';
    }

    上面的代码console语句让浏览器知道,“他要获得p的布局或者样式信息了,我必须先把之前的样式设定刷新一下,才能给你正确的值”,于是浏览器就重新绘制了。后面等函数结束后,又一次重新绘制transition的效果。如果这里你还是没有肉眼看出变化,这个真的就是“太快了,你看不到”。

    为了让你能看到,其实可以开启chrome的Paint Flashing选项,这样在页面被重新绘制时,会被高亮:

    2022-11-12 01:46 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有