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)); // 王二