Javascript常见数据结构二——队列

Javascript常见数据结构二——队列

三、队列

1.创建队列结构

队列是一种遵循先进先出(FIFO)原则的有序数据集

先进先出,只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作

类似场景举例…

入队 、 出队

功能需求分析:

入队 (enqueue) 队列尾部进行入队	
出队 (dequeue) 取出队列首端数据 并且删除
返回队列首端的数据,但不删除(first)
清空队列 (clear)
返回队列长度 (size)

自定义队列

class Queue{
    constructor() {
        this.items = []
    }
    // 入队
    enqueue(...r){
        this.items.push(...r)
    }
    // 出队 返回队首数据 并删除
    dequeue(){
        if(!this.size()) console.warn('队列为空 默认返回undefined')
        return this.items.shift()
    }
    // 返回队首数据 但是不删除
    first(){
        if(!this.size()) console.warn('队列为空 默认返回undefined')
        return this.items[0]
    }
    // 清空队列
    clear(){
        this.items =  []
    }
    // 队列长度
    size(){
        return this.items.length
    }
}
此时 外界是可以访问到我们的items 的 ,并且可以对items进行增删改查,items不应该暴露给外界,所以在这里 需要进行一下改进
// 使用 IIFE 将 class 进行包裹 
let Queue = (function (){
    // 向外界暴露symbol  但是外界无法访问 
    const symbol = Symbol()
    // 函数的类型 是由函数的返回值决定
    return class{
        constructor() {
            this[symbol] = []
        }
        // 入队
        enqueue(...r){
            this[symbol].push(...r)
        }
        // 出队 返回队首数据 并删除
        dequeue(){
            if(!this.size()) console.warn('队列为空 默认返回undefined')
            return this[symbol].shift()
        }
        // 返回队首数据 但是不删除
        first(){
            if(!this.size()) console.warn('队列为空 默认返回undefined')
            return this[symbol][0]
        }
        // 清空队列
        clear(){
            this[symbol] =  []
        }
        // 队列长度
        size(){
            return this[symbol].length
        }
    }
})()
此时通过IIFE 可以有效的阻止外界获取我们内部的items,在IIFE内的变量都是局部变量,执行完毕之后 就会被销毁,不会占用多余的内存。此时 虽然向外界暴露了symbol 数组 ,但是外界是无法对该数组进行操作的,并且新创建的实例对象属性也不会相互干扰,因为Symbol是独一无二的。

2.实现jquery动画队列效果

先看一下index文件:

// index.html
<style>
    .wrap {
        position: relative;
        top: 0;
        left: 0;
        width: 50px;
        height: 50px;
        background: pink;
        margin-bottom: 10px;
    }
</style>
<body>
<div class="wrap"></div>
<div class="wrap"></div>
<div class="wrap"></div>
<script src="./fly.js"></script>
<script>
    let $wrap = $('.wrap') // 成功获取到DOM Init {0: div.wrap, 1: div.wrap, 2: div.wrap, length: 3}
    
    // 设置 wrap 的 宽度为100px
    $wrap.css('width', 100)
    
    // 链式操作 设置wrap 动画队列 
    $wrap.animate({
        width: 150,
        height: 150
    }, 2000).animate({
        left: 100
    }).animate({
        top: 200
    })
</script>
</body>

下面是 j s 文件 :

// fly.js
(function () {
    // 纯数字补px
    const add_px = function (str) {
        str += ''
        if (/^\d+$/.test(str)) {
            str += 'px'
        }
        return str
    }

    //队列
    const Queue = (function () {
        const symbol = Symbol()
        return class {
            constructor() {
                this[symbol] = []
            }

            // 入队
            enqueue(...r) {
                this[symbol].push(...r)
            }

            // 出队 返回队首数据 并删除
            dequeue() {
                if (!this.size()) console.warn('队列为空 默认返回undefined')
                return this[symbol].shift()
            }

            // 返回队首数据 但是不删除
            first() {
                if (!this.size()) console.warn('队列为空 默认返回undefined')
                return this[symbol][0]
            }

            // 清空队列
            clear() {
                this[symbol] = []
            }

            // 队列长度
            size() {
                return this[symbol].length
            }
        }
    })()

    // 继承出一个能够满足 animate 需求的 Queue
    const AnimateQueue = class extends Queue {
        constructor() {
            super()
            this.ifRun = false
        }

        enqueue(...r) {
            super.enqueue(...r);
            this.run()
        }

        run() {
            if (this.ifRun) {
                return
            }
            this.ifRun = true
            new Promise(this.dequeue())
                .then(() => {
                    // 执行下一个队列
                    this.ifRun = false
                    this.size() && this.run()
                })
        }
    }

    //继承动画队列
    // 动画 Map  用来存储所有dom 对应的队列
    let animate_map = new Map()

    // 类
    class Init {
        constructor(selector) {
            let type = typeof selector;
            let dom = null;
            if (type === 'string') {
                // 选择器 获取dom 并且添加到this实例
                dom = document.querySelectorAll(selector)
            } else if (type === 'object') {
                // 节点对象
                dom = [selector]
            }
            dom.forEach((item, index) => {
                this[index] = item
            })
            // 设置this.length
            this.length = dom.length
        }

        // 遍历
        each(fn) {
            for (let i = 0; i < this.length; i++) {
                // 绑定回调函数的 this 为
                fn.call(this[i], i)
            }
            // 方便链式操作
            return this
        }

        // css 操作
        css(a, b) {
            // 根据参数不同的情况 来进行不同的操作
            let typeA = typeof a,
                typeB = typeof b;
            //设置 参数是对象
            if (typeA === 'object') {
                this.each(function () {
                    // 实例中的每个dom元素 都会执行该回调函数
                    for (const key in a) {
                        // 判断是不是 纯数字
                        this.style[key] = add_px(a[key])
                    }
                })
            }
            //设置 第一个参数是字符串 第二个参数是数字或者是字符串
            if (typeA === 'string' && (typeB === 'number' || typeB === 'string')) {
                this.each(function () {
                    this.style[a] = add_px(b)
                })
            }
            //取值 第一个参数是字符串 第二个参数是undefined
            if (typeA === 'string' && typeB === 'undefined') {
                if (this.length) {
                    return getComputedStyle(this[0])[a]
                } else {
                    return ''
                }
            }
            // 方便链式操作
            return this
        }

        // 实现动画 默认动画时间为300毫秒
        animate(options, time = 300) {
            this.each(function () {
                // 得到实例化队列
                let queue = animate_map.has(this) ? animate_map.get(this) : new AnimateQueue()
                // 让 dom 和其 动画任务队列 进行映射
                animate_map.set(this, queue);
                // 动画任务入队
                queue.enqueue((res) => {
                    let $this = $(this)
                    // 记录初始 transition
                    let startTransition = $this.css('transition')
                    // 设置transition
                    $this.css('transition', time + 'ms')
                    // 触发dom更新
                    this.offsetLeft;
                    // 更改css
                    $this.css(options)

                    setTimeout(() => {
                        $this.css('transition', startTransition)
                        res()
                    }, time)
                })
            })
            // 方便链式操作
            return this
        }
    }

    // 对外接口函数 定义 $ 和 Fly 为全局对象
    window.$ = window.Fly = function (selector) {// 入口函数 接受一个选择器
        return new Init(selector)
    }
})()
以上方法 主要是实现了jquery动画队列的底层原理 ,jquery动画队列的执行顺序是先入先出方式,和队列特别的相似,定义的动画会在之前定义的动画执行完毕之后依次执行,所以在这里,我们继承出了一个能够满足 animate 需求的 Queue,通过run方法 依次跑完所有的动画。

3.优先级队列

let Queue = (function () {
    const symbol = Symbol()
    return class {
        constructor() {
            this[symbol] = []
        }

        // 入队 需要考虑优先级别
        // 第一个参数为 入队数据 第二个参数为 优先级
        enqueue(data, priority = -Infinity) {
            // 对比优先级别 需要将优先级高的放到前面
            let i = 0
            for (; i < this.size(); i++) {
                if (priority > this[symbol][i].priority) {
                    break
                }
            }
            this[symbol].splice(i, 0, {data, priority})
        }

        // 出队 返回队首数据 并删除
        dequeue() {
            if (!this.size()) console.warn('队列为空 默认返回undefined')
            let thisData = this[symbol].shift()
            return thisData ? thisData.data : undefined
        }

        // 返回队首数据 但是不删除
        first() {
            if (!this.size()) console.warn('队列为空 默认返回undefined')
            return this[symbol][0] ? this[symbol].data : undefined
        }

        // 清空队列
        clear() {
            this[symbol] = []
        }

        // 队列长度
        size() {
            return this[symbol].length
        }
    }
})()
优先级队列和普通队列的唯一区别就是 入队的时候,可以传一个优先级,用来区分每个元素的优先级,优先级高的就排在前面。
还是比较常用的,比如说 我们机场登机的顺序,肯定是遵循先去先登记规则,但是,贵宾vip 的优先级会高一些,登机的顺序自然靠前,这就是金钱的魅力,哈哈哈......

4.双向队列

let Queue = (function () {
    const symbol = Symbol()
    return class {
        constructor() {
            this[symbol] = []
        }

        // 从前面入队
        unshift(data){
            this[symbol].unshift(data)
        }
        // 从后面入队
        push(data){
            this[symbol].push(data)
        }
        // 从前面取值 并删除
        shift(){
            return this[symbol].shift()
        }
        // 从后面取值 并删除
        pop(){
            return this[symbol].pop()
        }
        // 去队列首个元素
        first(){
            return this[symbol][0]
        }
        // 取队列最后元素
        last(){
            return this[symbol][this[symbol].length-1]
        }
        // 清空队列
        clear(){
            this[symbol] =  []
        }
        // 队列长度
        size(){
            return this[symbol].length
        }
    }
})()
双向队列我们平时用的不是很多 ,可以发现和数组数据类型非常相似,所以我们可以直接使用数组。

5.实现击鼓传花的效果

击鼓传花的规则:

几个朋友一起玩一个游戏, 围成一圈, 开始数数, 数到某个数字的人自动淘汰。最后剩下的这个人会获得胜利, 请问最后剩下的是原来在哪一个位置上的人?

function playGames(players,number){
    // 首先创建一个队列
    let queue = new Queue()
    // 将所有玩家 入队
    for (let i=0;i<players.length;i++){
        queue.enqueue(players[i])
    }
    // 将被num选中的人都pass
    while(queue.size()>1){
        // 将数过数的人进行出队操作 然后再入队放到队列的最后
        for(let i=0;i<number-1;i++){
            queue.enqueue(queue.dequeue())
        }
        // 此时 队列的第一个人 就是数到number的人
        // 这个时候只需要将他出队就好了
        queue.dequeue()
    }
    return queue.dequeue()
}
// test
let players = ['张三','李四','王二','麻子']
console.log(playGames(players, 8)); // 王二
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

貂蝉的腿毛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值