前端面试中的技能点——节流中的细节和浏览器底层一篇文章给你讲明白

136 阅读9分钟

前言

在前端开发领域,性能优化始终是提升用户体验的关键环节。其中,节流(throttling)作为一种常见的优化手段,能够有效地控制函数执行的频率,避免在短时间内高频触发事件导致的资源浪费和性能下降。同时,深入了解浏览器底层原理,尤其是内核的运作机制,对于开发者来说至关重要,它不仅关乎代码的兼容性和性能,还能帮助我们更好地理解前端技术的底层实现。

正文

节流与防抖不同

防抖适用于那些需要在用户操作完全停止后才进行处理的情况

而节流的目的是限制函数的执行频率,确保在一定时间内函数最多只能执行一次。无论事件触发多少次,只要在设定的时间间隔内,函数都不会再次执行。一旦过了这个时间间隔,函数就会执行,并重新计时。

  • 节流适用于那些即使在用户持续操作中也需要定期执行的任务,例如:

    • 滚动事件:在用户快速滚动时,每隔一定时间才执行一次相关逻辑,避免过度消耗资源。
    • 鼠标移动:在用户鼠标移动时,每隔一定时间才更新位置信息,避免过多的DOM操作。
    • 触摸屏滑动:在用户滑动屏幕时,控制更新频率,确保响应速度的同时避免资源浪费。
  • 防抖关注的是“静止”,即在事件完全停止触发一段时间后才执行一次函数。

  • 节流关注的是“频率”,即在规定时间内,无论事件触发多少次,函数都只能执行一次。

开始实现

而在处理这类如滚动、调整窗口大小、键盘按键等高频事件时,我们前面提到的闭包和定时器就显得尤为重要了起来

节流(throttle)

我们先写出一个HTML页面让其显示一个不带节流的和一个带节流的输入框

<body>
    <div class="row">
        <div>
            没有节流的input <input type="text" id = "inputa"/>

        </div>
        <div>
            节流后的input <input type="text" id = "inputc"/>

        </div>
    </div>
    
</body>

我们先将不带节流的输入框的功能写出js内容

 <script>
        const inputa = document.getElementById('inputa')
        const ajax = (content) => {
            console.log(`ajax request ${content}`);
        }
     
        inputa.addEventListener('keyup', (e)=>{
           ajax(e.target.value)
        })

    </script>

image.png

而这样频繁的调用函数,就会导致以下几点:

  1. 性能瓶颈:在用户快速或连续触发某些事件(如滚动、鼠标移动、键盘输入等)时,如果不加以控制,可能会导致同一函数被高频调用,这将极大地消耗CPU资源,降低应用的响应速度,甚至可能导致浏览器卡顿或崩溃。
  2. 资源浪费:对于涉及网络请求的操作,如实时搜索建议、上传数据等,如果不节流,每一次用户微小的操作都可能触发一次网络请求,这不仅增加了服务器负担,也浪费了带宽资源。
  3. 用户体验下降:在一些敏感的用户交互场景中,如调整窗口大小、拖拽元素等,如果没有适当的节流,应用的响应可能会变得迟钝,影响用户的操作流畅度,降低整体的用户体验。

所以我们需要节流,这也是面试中常考的知识点

 const inputc = document.getElementById('inputc')
const throttle = (func,delay)=> {

            let last, deferTimer 
           
            return (args) => {
                
                let now = +new Date();
                if(last && now < last + delay){
                   
                    clearTimeout(deferTimer)
                    deferTimer = setTimeout(function() {
                        last = now
                        func(args)
                    },delay)

                }else {
                    last = now // 第一次时间
                    func(args) // 先执行一次

                }

            }

        }
        let throttledFunc = throttle(ajax, 1000)
        
        inputc.addEventListener('keyup', (e) => {
            let value  = e.target.value
            throttledFunc(value)
        })

重点来了!

我们先拿到输入框的值

接着定义一个throttle函数,传入两个参数,一个是func需要执行的函数,另一个是delay需要空余的时间

我们在函数体内返回一个函数,形成闭包

函数体内我们先放着,先写调用

定义一个变量,传入throttle函数的返回值,也是一个函数

然后,对inputc写一个事件监听,addEventListener,对keyup做触发,获取到输入框的输入值,并传给throttle的返回值

最后就是特别巧妙的地方了——各函数的链式调用与throttle函数的精妙算法

let throttledFunc = throttle(ajax, 1000)意味着,将ajax函数和1000ms这个值传给了throttle函数,分别作为了funcdelay

而在throttle函数内,声明了两个变量:

  • last 用于存储上一次实际执行 func 函数的时间戳。
  • deferTimer 是一个 setTimeout 的引用,用于管理延时任务。a

返回了一个新的函数,形成了闭包,获取到传入的值args

紧接着获取当前时间的时间戳new Date()

真正的精妙之处便在这个if

这个条件检查既考虑了 last 是否已经被初始化(即函数是否至少执行过一次),也检查了自上次执行以来是否已经过去了 delay 指定的时间。这种双重检查确保了函数在首次调用时会立即执行,并在后续调用中根据时间间隔进行限制。

当条件满足时,即在 delay 时间内再次触发函数调用,代码会先清除已存在的延时任务(通过 clearTimeout(deferTimer)),然后重新设置一个新的延时任务(通过 setTimeout)。这样做的好处是,即使在 delay 时间内有多个连续的事件触发,函数也只会被安排在最后一个延时任务结束后执行一次,避免了不必要的重复执行。

这种实现方式不仅限定了函数的最小执行间隔,还允许在首次调用时立即执行,提高了用户交互的即时响应性。同时,通过合理使用 setTimeoutclearTimeout,避免了不必要的函数调用,提升了整体性能。

最后,我们将调用这个返回函数写在触发函数 inputc.addEventListener里也就是说,这个函数只会在我们结束按键时调用一次

确保了 func 不会过于频繁地执行,而是按照 delay 参数指定的时间间隔进行调用。这种节流机制可以有效防止在短时间内重复执行相同的任务,从而节省系统资源,提高应用性能。

怎么样,看着这段代码是不是特别的爽,节流就是如此美妙

小知识点:浏览器底层原理

我们在写前端时,和浏览器一定是紧密相连的,所以我们需要去了解它,了解它干了什么事情

浏览器内核详解

浏览器内核主要由两个核心组件构成:渲染引擎和JavaScript引擎。它们分别负责解析和渲染网页内容,以及执行JavaScript代码。

  • 渲染引擎:负责解析HTML、CSS和图像文件等,构建出文档对象模型(DOM)和渲染树,并最终将其渲染到用户的屏幕上。
  • JavaScript引擎:专门用于解析和执行JavaScript代码,使网页能够实现动态效果和用户交互。
不同内核特性
  • WebKit: 最初由苹果公司为Safari开发,后来Google在Chrome中也采用了WebKit。它具有高性能和跨平台特性,是移动设备上最常用的内核之一。
  • Blink: Google基于WebKit开发的独立内核,用于Chrome和其他一些浏览器。Blink通过移除WebKit中的一些冗余模块,显著提升了性能和增强了安全性。
  • Trident: 微软Internet Explorer的默认内核,虽然已被EdgeHTML取代,但许多老旧系统和企业环境仍在使用。
  • Gecko: Mozilla Firefox的内核,以开源和高度可定制性著称,支持广泛的Web标准。

渲染引擎工作流程

浏览器解析和渲染网页的过程非常复杂,主要包括以下几个阶段:

  1. HTML解析:浏览器接收到HTML数据后,开始解析HTML文档,构建DOM树。这个过程是增量式的,即浏览器不需要等待整个HTML文档下载完毕就开始解析和渲染。
  2. CSS解析:同时,CSS解析器读取CSS规则,构建CSSOM(CSS对象模型)。CSS的加载和解析是阻塞的,意味着在CSSOM未构建完成之前,渲染引擎无法继续后续工作。
  3. 构建渲染树:结合DOM树和CSSOM,浏览器创建一个渲染树,其中包含所有可见元素及其样式信息。注意,这个阶段不会包括那些不可见的元素(如<script><style>标签)。
  4. 布局计算:接下来,浏览器需要确定每个元素在屏幕上的确切位置和尺寸,这一过程称为布局或回流。布局树是基于渲染树构建的,它反映了每个元素的几何信息。
  5. 绘制:最后,浏览器将布局树的信息转化为实际的像素点,绘制到屏幕上,完成最终的视觉呈现。这个过程称为绘制或重绘。

性能优化与CSS选择器

CSS选择器的性能对渲染速度有着直接的影响。以下是一些关键点:

  • 避免使用通用选择器(*) :通用选择器会导致全树遍历,大大增加渲染时间和CPU负载。
  • 减少ID选择器的使用:虽然ID选择器速度快,但如果使用不当(如在JavaScript中频繁查询),也会导致性能问题。
  • 优先使用类型选择器和类选择器:这些选择器性能较好,因为它们只需要查找DOM树的一级或几级即可找到目标元素。
  • 避免复杂的CSS选择器链:长而复杂的CSS选择器链会导致渲染引擎做更多的工作来定位元素,从而影响性能。

总结

节流技术和对浏览器底层原理的深入理解,是提升前端应用性能和用户体验不可或缺的两把钥匙。通过合理运用这些知识,我们可以编写出更加高效、流畅的网页应用。

求点赞评论收藏,有问题随时私信博主!