今日の質問:手ぶれ防止機能と絞り機能の書き方

序文

Loadshをグローバルに導入することで、手ぶれ防止と絞り込みはすでに実装できますが、インタビュー中に手書きの揺れ防止と絞り込みが頻繁に発生します。

実際、面接では誰もが手書きで書くことができるだけでなく、実際のプロジェクトでのパフォーマンスの最適化にも役立つため、習得する必要があります防抖节流一方で、それらには多くのアプリケーションシナリオがあり、他方で、それらの背後にあるコードには、基本的なスキルと実用性を評価するために使用される高階関数、タイマー、ロジック、コンテキストなどの特定の問題もあります。インタビュイーの能力。選択します。

くるみ

 <style>
    #content {
        height: 400px;
        width: 400px;
        margin: 0 auto;
        line-height: 400px;
        text-align: center;
        color: #fff;
        background-color: pink;
        font-size: 80px;
    }
</style>

<div id="content">
</div>
<script>

    let num = 1;
    const content = document.getElementById('content');

    function count() {
        content.innerHTML = num++;
    };

    content.onmousemove = count

</script>
复制代码

他の処理がない場合、関数は頻繁に実行され、ページ上のデータが非常に速く変化します。それでは、アンチシェイクとスロットルがこの問題をどのように解決するかを見てみましょう。

効果はここには表示されません。興味がある場合は、実行結果をコピーして確認できます。

揺れ防止

いわゆるアンチシェイクとは、イベントがトリガーされてからn秒後に関数が実行されることを意味します。イベントがn秒以内に再度トリガーされると、関数の実行時間が再計算されます。

アンチシェイク機能は、非即時実行バージョンと即時実行バージョンに分けられます。

非即時実行

   <style>
        #content {
            height: 400px;
            width: 400px;
            margin: 0 auto;
            line-height: 400px;
            text-align: center;
            color: #fff;
            background-color: pink;
            font-size: 80px;
        }
    </style>
    
<div id="content">
</div>
<script>
    /**
        * @description: 防抖函数  非立即执行
        * @param {type} fn  函数
        * @param {type} wait  延迟时间 单位毫秒
        * @return: 返回 延时函数
        */
    // 非立即执行
    let num = 1;
    const content = document.getElementById('content');
    function debounce(fn, wait) {
        let timerID
        return function () {
            
            // 如果有定时器id清除定时器
            if (timerID) clearTimeout(timerID)
            timerID = setTimeout(() => {
                // 修改this指向
                fn()
            }, wait)
        }
    }
    function count() {
        content.innerHTML = num++;
    };

    content.onmousemove = debounce(count, 1000)

</script>
复制代码
  1. 防振機能は完了しましたが、問題が発生します。count()印刷している場合、私thisたちが行っていることthisが指しWindowていることがわかります。

  2. apply必要なものではなく、方向を変更するために使用する必要がthisあります。また、実行関数のパラメーターを考慮する必要があります。これは、関数ごとに異なるパラメーターが確実に渡されるためです。パラメーターについては、arguments処理を使用できます。

完全版

<style>
        #content {
            height: 400px;
            width: 400px;
            margin: 0 auto;
            line-height: 400px;
            text-align: center;
            color: #fff;
            background-color: pink;
            font-size: 80px;
        }
    </style>
    
<div id="content">
</div>
<script>
    /**
        * @description: 防抖函数  非立即执行
        * @param {type} fn  函数
        * @param {type} wait  延迟时间 单位毫秒
        * @return: 返回 延时函数
        */
    // 非立即执行
    let num = 1;
    const content = document.getElementById('content');
    function debounce(fn, wait) {
        let timerID
        return function () {
            // this指向依然指向原来的函数
            const context = this
            // // 不同的函数会有不同的参数传入,对于参数我使用arguments处理。
            const args = [...arguments]
            // 如果有定时器id清除定时器
            if (timerID) clearTimeout(timerID)
            timerID = setTimeout(() => {
                // 修改this指向
                fn.apply(context, args)
            }, wait)
        }
    }
    function count() {
        content.innerHTML = num++;
    };

    content.onmousemove = debounce(count, 1000)

</script>
复制代码

非即時実行とは、イベントがトリガーされた直後に関数が実行されるのではなく、n秒後に実行されることを意味します。n秒以内にイベントが再度トリガーされると、関数の実行時間が再計算されます。

すぐに実行

即時実行バージョンとは、イベントがトリガーされた直後に関数が実行され、n秒以内にイベントがトリガーされない場合でも関数の効果を実行し続けることができることを意味します。

<style>
    #content {
        height: 400px;
        width: 400px;
        margin: 0 auto;
        line-height: 400px;
        text-align: center;
        color: #fff;
        background-color: pink;
        font-size: 80px;
    }
</style>

 <div id="content">
</div>
<script>
    /**
    * @description: 防抖函数  立即执行
    * @param {type} fn  函数
    * @param {type} wait  延迟时间 单位毫秒
    * @return: 返回 延时函数
    */
    // 立即执行
    let num = 1;
    const content = document.getElementById('content');
    function debounce(fn, wait) {
        let timerID
        return function () {
            // this指向依然指向原来的函数
            const context = this
            // 不同的函数会有不同的参数传入,对于参数我使用arguments处理。
            const args = [...arguments]
            // 如果有定时器id清除定时器
            if (timerID) clearTimeout(timerID)
            const callNow = !timerID
            timerID = setTimeout(() => {
                // 相当于清空定时器
                timerID = null
            }, wait)
            // 没有定时器id  修改this指向
            if (callNow) fn.apply(context, args)
        }
    }
    function count() {
        content.innerHTML = num++;
    };
    content.onmousemove = debounce(count, 1000)

</script>
复制代码

マージ

開発の過程で、さまざまなシナリオに応じて、どのバージョンの手ぶれ防止機能を使用するかを決定する必要があります。一般的に、上記の手ぶれ防止機能は、ほとんどのシナリオのニーズを満たすことができます。ただし、非即時バージョンと即時バージョンの防振機能を組み合わせることもできます。

    <style>
    #content {
        height: 400px;
        width: 400px;
        margin: 0 auto;
        line-height: 400px;
        text-align: center;
        color: #fff;
        background-color: pink;
        font-size: 80px;
    }
</style>

 <div id="content">
</div>
<script>
    /**
        * @description: 防抖函数  非立即执行
        * @param {type} fn  函数
        * @param {type} wait  延迟时间 单位毫秒
        * @param {type} promptly  true : 立即  false 非立即
        * @return: 返回 延时函数
        */
    // 非立即执行
    let num = 1;
    const content = document.getElementById('content');
    function debounce(fn, wait, promptly) {
        let timerID
        return function () {
            // this指向依然指向原来的函数
            const context = this
            // // 不同的函数会有不同的参数传入,对于参数我使用arguments处理。
            const args = [...arguments]
            // 如果有定时器id清除定时器
            if (timerID) clearTimeout(timerID)
            if (promptly) {
                const callNow = !timerID
                timerID = setTimeout(() => {
                    // 相当于清空定时器
                    timerID = null
                }, wait)
                // 没有定时器id  修改this指向
                if (callNow) fn.apply(context, args)
            } else {
                timerID = setTimeout(() => {
                    // 修改this指向
                    fn.apply(context, args)
                }, wait)
            }
        }
    }
    function count() {
        content.innerHTML = num++;
        console.log(this);
    };

    content.onmousemove = debounce(count, 1000, true)

</script>
复制代码

スロットル

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。  节流会稀释函数的执行频率。

对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版。

时间戳

 <style>
        #content {
            height: 400px;
            width: 400px;
            margin: 0 auto;
            line-height: 400px;
            text-align: center;
            color: #fff;
            background-color: pink;
            font-size: 80px;
        }
    </style>
    
<div id="content">
</div>
<script>
    /**
        * @description: 节流函数  时间戳
        * @param {type} fn  函数
        * @param {type} wait  延迟时间 单位毫秒
        * @return: 返回 延时函数
        */
    // 时间戳
    let num = 1;
    const content = document.getElementById('content');
    function throttle(fn, wait, promptly) {
    let previous = 0
    return function () {
        let now = Date.now()
        // this指向依然指向原来的函数
        let context = this
        // // 不同的函数会有不同的参数传入,对于参数我使用arguments处理。
        let args = [...arguments]
        if (now - previous > wait) {
            // 修改this指向
            fn.apply(context, args)
            // 修改时间为当前时间
            previous = now
        }

    }
}
    function count() {
        content.innerHTML = num++;
    };

    content.onmousemove = throttle(count, 1000)

</script>
复制代码

在持续触发事件的过程中,函数会立即执行,并且每 1s 执行一次。

定时器版

<style>
        #content {
            height: 400px;
            width: 400px;
            margin: 0 auto;
            line-height: 400px;
            text-align: center;
            color: #fff;
            background-color: pink;
            font-size: 80px;
        }
    </style>
    
<div id="content">
</div>
<script>
    /**
        * @description: 节流函数  定时器版
        * @param {type} fn  函数
        * @param {type} wait  延迟时间 单位毫秒
        * @return: 返回 延时函数
        */
    // 定时器版
    let num = 1;
    const content = document.getElementById('content');
    function throttle(fn, wait) {
    let timerID;
    return function () {
        // this指向依然指向原来的函数
        let context = this;
        // 不同的函数会有不同的参数传入,对于参数我使用arguments处理
        let args = [...arguments];
        if (!timerID) {
            timerID = setTimeout(() => {
                timerID = null;
                fn.apply(context, args)
            }, wait)
        }

    }
}
    function count() {
        content.innerHTML = num++;
    };

    content.onmousemove = throttle(count, 1000)

</script>
复制代码

在持续触发事件的过程中,函数不会立即执行,并且每 1s 执行一次,在停止触发事件后,函数还会再执行一次

其实时间戳版和定时器版的节流函数的区别就是,时间戳版的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候

和防抖函数一样,也可以进行合并

合并

   <style>
            #content {
                height: 400px;
                width: 400px;
                margin: 0 auto;
                line-height: 400px;
                text-align: center;
                color: #fff;
                background-color: pink;
                font-size: 80px;
            }
        </style>
    
    <div id="content">
    </div>
    <script>
    //**
    * @description: 节流函数  合并版
    * @param {type} fn  函数
    * @param {type} wait  延迟时间 单位毫秒
    * @param {type} type 1 表时间戳版,2 表定时器版
    * @return: 返回 延时函数
    */
   
    let num = 1;
    const content = document.getElementById('content');
    function throttle(fn, wait, type) {
    if (type === 1) {
        let previous = 0;
    } else if (type === 2) {
        let timerID;
    }
    return function () {
            // this指向依然指向原来的函数
            let context = this;
            // 不同的函数会有不同的参数传入,对于参数我使用arguments处理
            if (type === 1) {
                let now = Date.now();
                if (now - previous > wait) {
                    func.apply(context, args);
                    // 修改时间为当前时间
                    previous = now;
                }
            } else if (type === 2) {
                if (!timerID) {
                    timerID = setTimeout(() => {
                        timerID = null;
                        func.apply(context, args)
                    }, wait)
                }
            }
        }
    }
    function count() {
        content.innerHTML = num++;
    };

    content.onmousemove = throttle(count, 1000)

</script>
复制代码

总结

  1. 对于防抖节流一个最主观的判断方法就是:防抖则会只执行一次,而节流则会每隔一段时间执行一次
  • 其实本质上都是为了节省程序的性能(防止高频函数调用)

  • 借助了闭包的特性来缓存变量(状态)

  • 都可以使用setTimeout实现

おすすめ

転載: juejin.im/post/7084235599074295839