Implementation and application scenario of anti chattering and throttling and simple implementation of lazy loading

1, Essence

  • The essence is that an event is triggered frequently, resulting in frame loss or performance waste. Anti shake or throttling is used to reduce the frequency of executing events.
  • The essence of anti chattering throttling is to preserve the upper scope after closure execution.

2, Anti shake:

1. Concept

  • If this kind of event is triggered again within a certain time, the timer of the previous event will be cleared and re timed. If it is not triggered again within the specified time, this event will be executed.
  • Simple understanding: prevent jitter to avoid mistaking one event trigger for multiple times
  1. If the scroll event is not triggered again within xxms, the function is executed
  2. If the rolling event is triggered again within 200ms, the current timing will be cancelled and the timing will be restarted

2. Most basic version

Note that this is just an example of scroll. In fact, it saves a little more money

  • p.addEventListener('scroll', debounce(fn, 1000));
  • Execute debounce(fn, 1000)), and bind the internal function to the event. Because of the closure, the debounce scope is retained in the internal function, that is, timer
  • For the second trigger event, the last timer will be cleared and re timed whether it is time or not.
  • Note that setTimeOut will implicitly lose this point
  <style>
        .p {
            width: 600px;
            height: 100px;
            overflow-y: auto;
        }
        
        .son {
            width: 600px;
            height: 500px;
            background-color: pink;
        }
    </style>
</head>
<div class="p">
    <div class="son"></div>
</div>

<body>

    <script>
        function debounce(fn, wait) {
            let timer = null;
            return function() {
                console.log(this); //this is p
                if (timer) {
                    clearTimeout(timer);
                }
                timer = setTimeout(fn, wait);
            }

        }

        function fn() {
            console.log(this); //this is window
            console.log(233);
        }
        let p = document.querySelector('.p');
        p.addEventListener('scroll', debounce(fn, 1000));
    </script>
</body>

At this time, note that this points to fn as window

3. Fix this pointing version

Solve the loss of this in the above situation and bind it back with apply.

	function debounce(fn, wait ) {
            let timer = null;
            return function() {
                let context = this;
                let args = arguments;
                if (timer) {
                    clearTimeout(timer);
                }
                timer = setTimeout(() => {
                    fn.apply(context, args)
                }, wait);
            }

        }

     function fn() {
         console.log(this); //This is the p node
         console.log(233);
     }

4. Point to the line version immediately

  • What is immediate execution?

  • Suppose there is a user who seems to have a pain. He has been pulling the scroll bar up and down to play. At this time, you will find that the above anti shake can not be triggered once, which is not in line with the reality. So let the first time you trigger an event, execute the event first, and then anti shake to ensure that you can execute an event callback at least once.

  • immediate represents whether to choose to execute immediately. In fact, it is the leading edge anti shake concept. The execution action cycle starts and the action is executed after the trailing edge anti shake event cycle ends. Borrow one my dear friend Throttling also has corresponding.

  • This is actually done using timer==null

		function debounce(fn, wait, immediate) {
            let timer = null;
            return function() {
            	let context = this;
                let args = arguments;
                if (immediate) {
                    if (timer === null) {
                        fn.apply(context, args);//Execute immediately for the first time
                    } else {
                        clearTimeout(timer);
                    }
                    timer = setTimeout(() => {
                        timer = null;//It is not triggered again during this period. Set it to null to prepare for the next trigger
                    }, wait);
                } else {//Not immediately
                    if (timer) {
                        clearTimeout(timer);
                    }
                    timer = setTimeout(() => {
                        fn.apply(context, args)
                    }, wait);
                }
          }
     }

5. Merge optimized version

  • One thing to note here is the order of callnow - > cleartimeout - > setTimeout
  • In fact, there is another version that maxTime must be executed once within the maximum time. You can refer to a great God Writing method , the general idea is to use Date to record time.
		function debounce(fn, wait, immediate) {
            let timer = null;
            return function() {
                let context = this;
                let args = arguments;                
                let callNow = immediate && !timer;//Execute immediately 
                if(callNow){
                    fn.apply(context,args);
                }             
                clearTimeout(timer);
                timer = setTimeout(waitfun,wait);
                //Execute immediately = > timer = = null = > callnow = true
                //Immediate = false = > wait = > execute fn  
                function waitfun() {
                    timer = null;//Ensure that the second wait is executed immediately
                    if (!immediate) { //It is not executed immediately after the waiting time fn
                        fn.apply(context, args);
                    }
                }           
            }
       }

6. Application scenario (avoid jitter to avoid mistaking one event trigger for multiple times)

  • Search intelligent prompt: it is impossible for users to send an ajax request every time they press it. They can wait for a period of time without input before initiating the request.
  • Text editor saves in real time.
  • resize when resizing the browser window

3, Throttling:

1. Concept

The same event can only be triggered once within a certain time

If the same event is triggered in a large number in a short time, after the function is executed once, the function will not work for the specified time period until it takes effect again.

2. Compare versions using two dates

		function throttle(fn, wait) {
            //A function that is opened regularly like a control valve, that is, after the function is executed once, it will temporarily fail in a certain period of time, and then reactivate after this period of time

            let timeout;
            let startTime = new Date();

            return function() {
                let context = this;
                let args = arguments;
                let curTime = new Date();
                // If the specified trigger interval is reached, the handler is triggered
                if (curTime - startTime >= wait) {
                    fn.apply(context, args);
                    startTime = curTime;
                } 
            };
        }

3. timer comparison version

 		function throttle(fn, wait) {
            let timer = null;
            let startTime = new Date();

            return function() {
                let context = this;
                let args = arguments;
                if (timer === null) {
                    //timer alive means it's still cooling
                    timer = setTimeout(() => {
                        fn.apply(context, args);//Put this line before setTimeOut and click OK
                        timer = null;
                    }, wait)
                }

            };
        }

4. Application scenario (control the flow of water. Control the frequency of events)

  • The verification code can only be obtained once every 60 seconds
  • scroll event, calculate the location information every second (for lazy loading)
  • The browser plays events and calculates progress information every second

4, Summary

  • Anti shake to prevent shaking and multiple accidental injury triggering.
  • Throttling, flow control, only performed once per unit time.
  • Principle: using closure property.

5, Let's have a question

How to implement lazy loading:)

Principle:

  • img's src should not be set or set to a small thumbnail (data src = 'your picture path' is set to simulate ajax)
  • Monitor the scroll event, get the distance between the picture and the browser viewport, and load the picture when it arrives.
  • Here, we use the element.getBoundingClientRect()api to obtain the distance between the picture and the top of the browser viewport. This has compatibility problems and can be replaced by other properties of scoll. MDN

schematic diagram:

Simple implementation:

    <div class="pic">
        <img class='lazyImg' data-src="./can-use-is-ok.jpg" data-index='1'>
    </div>
    <div class="pic">
        <img class='lazyImg' data-src="./can-use-is-ok.jpg" data-index='2'>
    </div>
    <div class="pic">
        <img class='lazyImg' data-src="./can-use-is-ok.jpg" data-index='3'>
    </div>
    <div class="pic">
        <img class='lazyImg ' data-src="./can-use-is-ok.jpg" data-index='4'>
    </div>
    <div class="pic">
        <img class='lazyImg ' data-src="./can-use-is-ok.jpg" data-index='5'>
    </div>
    <div class="pic">
        <img class='lazyImg ' data-src="./can-use-is-ok.jpg" data-index='6'>
    </div>
 		function fn() {
            var viewHeight = window.innerHeight
                //Get browser viewport height
            var imgs = document.querySelectorAll('.lazyImg ')
            imgs.forEach((cur_img, index) => {
                if (cur_img.src !== '') {
                    return; //The picture has been loaded
                }
                var rect = cur_img.getBoundingClientRect()
                    //Gets the position of the element relative to the viewport
                if (rect.bottom > 0 && rect.top < viewHeight) {
                    cur_img.src = cur_img.dataset.src
                    cur_img.removeAttribute('data-src')
                }
            })
        }

        function throttle(fn, wait) {
            var timer = null;

            return function() {
                var _this = this
                var args = arguments
                if (timer === null) {
                    fn.apply(_this, args)
                    timer = setTimeout(() => {

                        timer = null
                    }, wait)
                }

            }
        }
        fn()//Execute once first
        window.addEventListener('scroll', throttle(fn, 1000))

Tags: Javascript

Posted on Wed, 17 Nov 2021 00:32:32 -0500 by Rex__