jquery 使用off移除事件 使用one绑定一次事件,on绑定事件后触发多次相同的事件的异常

<!-- jquery 移除事件,绑定一次事件,搜狗 one -->
<!DOCTYPE html>
<html >
<head>
        <meta charset="UTF-8">
        <title>下拉菜单</title>
<style>

</style>
        <style>
                *{
                        padding: 0;
                        margin: 0;
                        list-style: none;
                }
                .container {
                        width: 500px;
                        height:230px;
                        margin: 100px auto 0;
                }
                button {
                        width: 50%;
                        height: 30px;
                        line-height: 30px;
                }
                h1{
                        width: 500px;
                        margin: auto;
                }
/*---上面不用看,重点在下面---*/
/* 以下是关于 .box对象 使用 transitionend(动画结束后执行的操作),
在jQuery中 使用on 绑定 transitionend ,.box 中有几个属性变化,transitionend就会执行几次。
如果只绑定一次,谨记使用 $box.off('transitionend').one('transitionend',function(){...}) */
                .box {
                        /* 还有一个属性在初始化的时候设置的,看下面JS的初始化 */
                        height: 200px;
                        background: #f0e;
                        padding-top: 20px;
                        padding-bottom: 20px;
                        overflow: hidden;
                }
                .fadeSlideUpDown {
                        height: 0 !important;
                        opacity: 0 !important;
                        width: 0 !important;
                        padding-top: 0 !important;
                        padding-bottom: 0 !important;
                /* 设置高度过度类,必须设定上下内边距,因为标准模式的宽 = 上下内边距 + 实际宽度,如果是怪异模式那就可能不需要设置内边距 */
                /* 同理:设置宽度过度类,必须设定左右内边距,因为标准模式的宽 = 上下内边距 + 实际宽度,如果是怪异模式那就可能不需要设置内边距 */
                }
                .transition {
                        -webkit-transition: all .5s;
                        -moz-transition: all .5s;
                        -ms-transition: all .5s;
                        -o-transition: all .5s;
                        transition: all .5s;
                }
        </style>
</head>
<body>
        <div class="container">
                <button class="show">显示</button><button class="hide">隐藏</button>
                <div class="box">
                        <p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p><p>s</p>
                </div>
                <h1>显示有</h1>
        </div>
        <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <script>
                //获取jQuery对象
                var $show = $('.show'),
                        $hide = $('.hide'),
                        $box  = $('.box');
                //初始化$el对象
                function init($ele){
                        //这里的$box的高度是由内容撑开的,即没有设定高度,就是说浏览器不知道$box的高度
                        //所以使用过度属性 transition 时,不会有过渡效果,
                        //浏览器解析 不知道的高度  0px 相互过度 ,就是瞬间显示隐藏。
                        //所以这里必须在初始化的时候就获取$box的宽高,并且显示的设置宽高即可解决。
                        //如果上下过度,就设定 height,左右过度就设置 width 即可。
                        $ele.width($ele.width());
                        $ele.height($ele.height());

                        //添加动画
                        $ele.addClass('transition');

                        //判断$ele对象是否显示,并且设置相应状态,并且添加相应转态必须添加的类
                        if ($ele.is(':hidden')) {
                                $ele.data('status','hidden');
                                $ele.addClass('fadeSlideUpDown');
                        }else{
                                $ele.data('status','shown');
                        }
                }
                //给$box对象绑定以下四个事件。
                $box.on('show shown hide hidden',function(e){
                        console.log(e.type)
                })
                //给显示按钮绑定事件
                $show.on('click',function(){
                        //如果状态相同,则不执行。
                        if ($box.data('status')==='show' || $box.data('status')==='shown') return;
                        //事件触发之前,设置状态,并且触发指定事件,应用于:按需加载,即显示之前加载。下拉菜单
                        $box.data('status','show').trigger('show');
                        //这里使用一个异步调用的假象,对象显示20毫秒后,就执行一个显示的动画欺骗用户,这就是显示的效果。
                        $box.show();
                        setTimeout(function(){
                //所有改变样式都使用 removeClass('xx') 添加类的方式防止浏览器重绘和回流,提高浏览器性能。
                                $box.removeClass('fadeSlideUpDown');
                        },20);
                        //动画结束之后,设置状态,并且触发指定事件
                        $box.off('transitionend').on('transitionend',function(){
                                //事件 shown 会触发5次,因为这里使用on进行绑定 transitionend 动画结束后执行的事件
                                //而 $box 有5个属性变化,即有5次动画,每一次执行完成都会触发一次shown事件。
                                $box.data('status','shown').trigger('shown');
                        })
                })
                //给隐藏按钮绑定事件
                $hide.on('click',function(){
                        //如果状态相同,则不执行。
                        if ($box.data('status')==='hide' || $box.data('status')==='hidden') return;
                        //事件触发之前,设置状态,并且触发指定事件。
                        $box.data('status','hide').trigger('hide');
                //所有改变样式都使用 addClass('xx') 添加类的方式防止浏览器重绘和回流,提高浏览器性能。
                        $box.addClass('fadeSlideUpDown');
                        $box.off('transitionend').on('transitionend',function(){
                                $box.hide();
                                //事件 hidden 会触发5次,因为这里使用on进行绑定 transitionend 动画结束后执行的事件
                                //而 $box 有5个属性变化,即有5次动画,每一次执行完成都会触发一次hidden事件。
                                //这里想要所有动画合并算一次,每次执行之前就移除之前的transitionend事件,重新开始执行
                                //所以这里要使用 $box.off('transitionend').one('transitionend') 进行绑定,
                                //只绑定一次,每次执行前,移除之前绑定的transitionend。
                                //动画基本都必须在执行前移除之前的动画。
                                $box.data('status','hidden').trigger('hidden');
                        })
                })

                //初始化
                init($box);
        </script>
</body>
</html>