题目一 LazyMan

  • 描述

实现一个LazyMan,可以按照以下方式调用:

LazyMan(“Hank”)输出:

Hi! This is Hank!

LazyMan(“Hank”).sleep(10).eat(“dinner”)输出

Hi! This is Hank!

//等待10秒..

Wake up after 10

Eat dinner~

LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出

Hi This is Hank!

Eat dinner~

Eat supper~

LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出

//等待5秒

Wake up after 5

Hi This is Hank!

Eat supper


以此类推。

  • 思路
  1. 借助队列的先进先出特性实现异步流的控制
  2. 利用闭包将事件保存至队列中
  3. 创建一个中间件next()用来触发事件
  4. 通过在每次事件结束之后返回对象本身,来实现链式调用。
  • 实现

创建一个_LazyMan类,构造函数里面创建一个数组当做队列使用,存储排队执行的事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class _LazyMan {
constructor(name) {
this.task = [] //事件存储队列
var self = this //绑定this指向 可用箭头函数代替
var fn = (function(name) {
//借助闭包绑定作用域
return function() {
console.log(`Hi! This is ${name} !`)
self.next()
}
})(name)
this.task.push(fn)
//使用setTimeout方法,初始化任务,将执行函数放到下一次事件循环当中,从而达到先注册事件,后执行的目的
setTimeout(function() {
self.next()
}, 0)
}
}

加下来添加next方法,每次触发next方法,从队列头部读取事件执行。

1
2
3
4
next() {
var fn = this.task.shift()
if(fn) fn()
}

添加eat方法,函数执行结束之后,调用next进入下一个事件,同时通过返回this实现链式调用。

1
2
3
4
5
6
7
8
9
10
11
eat(food) {
var self = this
var fn = (function(food) {
return function() {
console.log(`Eat ${name} '~'`)
self.next()
}
})(food)
this.task.push(fn)
return this
}

添加sleep方法,借助setTimeout实现延迟的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
sleep(time) {
var self = this
var fn = (function(name) {
return function() {
setTimeout(function() {
console.log("Wake up after " + time + "s!");
self.next()
}, time * 1000)
}
})(name)
this.task.push(fn)
return this
}

添加sleepFirst方法,sleepFirst函数需要最先执行,所以我们需要在任务队列前面放入,然后再执行后面的任务

1
2
3
4
5
6
7
8
9
10
11
12
13
sleepFirst(time) {
var self = this
var fn = (function(name) {
return function() {
setTimeout(function() {
console.log("Wake up after " + time + "s!");
self.next()
}, time * 1000)
}
})(name)
this.task.unshift(fn)
return this
}

调用Lazyman的时候不需要用到new关键字,这意味着我们需要使用工厂函数返回Layman实例

1
2
3
function LazyMan(name){
return new _LazyMan(name);
}

这样,Lazyman这一题就实现了。

  • 使用Promise

使用Promise的解法,本质上也是事先构造一个事件队列,在下一次事件循环开始时,开始执行。

与上一个解法不同的是,先前的解法,是在每次的事件结束之后手动调用 next() 方法,去推动事件的执行流程。

而Promise的解法,是借助了Promise的状态resolved之后,触发then方法进行回调。(手动返回Promise也行,下面会演示)

  • Promise版的实现

创建一个_LazyMan类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class _LazyMan {
constructor(name) {
this.task = [] //事件存储队列
var start = Promise.resolve() //构造第一个Promise,借助其then回调触发事件流程,亦可用第一个task任务来生成该Promise。

var fn = (function(name) {
return new Promise(function(resolve, reject) {
console.log(`Hi! This is ${name} !`)
resolve()
})
})(name)

//或者手动返回一个Promise,后面的eat方法同理
// var fn = (function(name) {
// return function() {
// console.log(`Hi! This is ${name} !`)
// return Promise.resolve()
// }
// })(name)

this.task.push(fn)

setTimeout(() => {
this.task.forEach(task => {
start = start.then(task)
})
}, 0)
}
}

eat方法

1
2
3
4
5
6
7
8
9
10
eat(food) {
var fn = function() {
return new Promise(function(resolve, reject) {
console.log(`Eat ${food} '~'`)
resolve()
})
}
this.task.push(fn)
return this
}

其余方法同理,不赘述,具体实现见源码。

题目二 AJAX按钮

  • 描述

某个应用模块由文本框 input,以及按钮 A,按钮 B 组成。点击按钮 A,会向地址 urlA 发出一个 ajax 请求,并将返回的字符串填充到 input 中(覆盖 input 中原有的数据),点击按钮 B,会向地址 urlB 发出一个 ajax 请求,并将返回的字符串填充到 input 中(覆盖 > input 中原有的数据)。

当用户依次点击按钮 A、B 的时候,预期的效果是 input 依次被 urlA、urlB 返回的数据填充,但是由于到 urlA 的请求返回比较慢,导致 urlB 返回的数据被 urlA 返回的数据覆盖了,与用户预期的顺序不一致。

请问如何设计代码,解决这个问题?

  • 思路

借助队列,点击事件触发时,将将请求塞进压进队列,通过进出队列控制两次请求的触发的顺序。

  • 实现

html结构

1
2
3
4
5
<body>
<input type="text"/>
<button id="btnA">A</button>
<button id="btnB">B</button>
</body>

借助Promise和setTimeout模拟接口返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let btnA = document.querySelector('#btnA')
let btnB = document.querySelector('#btnB')
let input = document.querySelector('input');
//模拟ajax
let ajaxA = () => new Promise((resolve, reject) => {
setTimeout(() => {
input.value = 'ajaxA response';
resolve()
}, 4000)
})
let ajaxB = () => new Promise((resolve, reject) => {
setTimeout(() => {
input.value = 'ajaxB response';
resolve()
}, 1000)
})

构造一个队列类以及队列操作方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Queue {
constructor() {
this.list = []
}
enqueue(ele) {
this.list.push(ele)
}
dequeue(ele) {
return this.list.shift(ele)
}
isEmpty() {
return this.list.length === 0
}
}

构建队列实例,点击时将事件压入队列,然后推动队列执行流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let eventQueue = new Queue()
let flag = false

let run = async function() {
flag = true
while(!eventQueue.isEmpty()) {
await eventQueue.dequeue()()
}
flag = false
}

btnA.addEventListener('click', () => {
eventQueue.enqueue(ajaxA);
if (!flag) { run() }
})
btnB.addEventListener('click', () => {
eventQueue.enqueue(ajaxB);
if (!flag) { run() }
})

以上就是这道题的解法,但是这里的是把整个请求方法塞进队列,一个个取出来执行,相当于是同步的方法。

下面可以改成异步的,修改成将请求方法执行完之后返回的Promise塞进队列,这个时候不会阻塞的执行,读取队列的时候,借助async判断Promise的状态已经变成resolved即可。

  • 实现

修改按钮的请求方法,Promise状态变为resolved时,将响应返回。

1
2
3
4
5
let ajaxA = () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ajaxA response')
}, 4000)
})

修改按钮点击事件,点击时执行请求方法,将Promise压入队列。

1
2
3
4
btnA.addEventListener('click', () => {
eventQueue.enqueue(ajaxA());
if (!flag) { run() }
})

run方法修改为借助await控制异步队列。

1
2
3
4
5
6
7
8
9
let run = async function() {
flag = true
while(!eventQueue.isEmpty()) {
let res = await eventQueue.dequeue()
input.value = res
console.log('inputValue:',res)
}
flag = false
}

源码

  • Lazyman

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    class _LazyMan {
    constructor(name) {
    this.task = []
    var self = this
    var fn = (function(name) {
    return function() {
    console.log(`Hi! This is ${name} !`)
    self.next()
    }
    })(name)
    this.task.push(fn)

    setTimeout(function() {
    self.next()
    }, 0)
    }

    next() {
    var fn = this.task.shift()
    fn && fn()
    }

    eat(food) {
    var self = this
    var fn = (function(food) {
    return function() {
    console.log(`Eat ${food} '~'`)
    self.next()
    }
    })(food)
    this.task.push(fn)
    return this
    }


    sleep(time) {
    var self = this
    var fn = (function(name) {
    return function() {
    setTimeout(function() {
    console.log("Wake up after " + time + "s!");
    self.next()
    }, time * 1000)
    }
    })(name)
    this.task.push(fn)
    return this
    }

    sleepFirst(time) {
    var self = this
    var fn = (function(name) {
    return function() {
    setTimeout(function() {
    console.log("Wake up after " + time + "s!");
    self.next()
    }, time * 1000)
    }
    })(name)
    this.task.unshift(fn)
    return this
    }
    }

    function LazyMan(name){
    return new _LazyMan(name);
    }
  • Pormise版Lazyman

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    class _LazyMan {
    constructor(name) {
    this.task = []
    var start = Promise.resolve()

    var fn = (function(name) {
    return new Promise(function(resolve, reject) {
    console.log(`Hi! This is ${name} !`)
    resolve()
    })
    })(name)

    this.task.push(fn)

    setTimeout(() => {
    this.task.forEach(task => {
    start = start.then(task)
    })
    }, 0)
    }

    eat(food) {
    var fn = function() {
    return new Promise(function(resolve, reject) {
    console.log(`Eat ${food} '~'`)
    resolve()
    })
    }
    this.task.push(fn)
    return this
    }

    sleep(time) {
    var fn = function() {
    return new Promise(function(resolve, reject) {
    setTimeout(function(){
    console.log("Wake up after " + time + "s!");
    resolve()
    }, time * 1000)
    })
    }

    this.task.push(fn)
    return this
    }

    sleepFirst(time) {
    var fn = function() {
    return new Promise(function(resolve, reject) {
    setTimeout(function(){
    console.log("Wake up after " + time + "s!");
    resolve()
    }, time * 1000)
    })
    }

    this.task.unshift(fn)
    return this
    }

    }

    function LazyMan(name) {
    return new _LazyMan(name)
    }
  • AJAX按钮 同步的解法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    let btnA = document.querySelector('#btnA')
    let btnB = document.querySelector('#btnB')
    let input = document.querySelector('input');

    let ajaxA = () => new Promise((resolve, reject) => {
    setTimeout(() => {
    console.log('ajaxA end')
    input.value = 'ajaxA end';
    resolve()
    }, 4000)
    })
    let ajaxB = () => new Promise((resolve, reject) => {
    setTimeout(() => {
    console.log('ajaxB end')
    input.value = 'ajaxB end';
    resolve()
    }, 1000)
    })

    class Queue {
    constructor() {
    this.list = []
    }
    enqueue(ele) {
    this.list.push(ele)
    }
    dequeue(ele) {
    return this.list.shift(ele)
    }
    isEmpty() {
    return this.list.length === 0
    }
    }

    let eventQueue = new Queue()
    let flag = false

    let run = async function() {
    flag = true
    while(!eventQueue.isEmpty()) {
    await eventQueue.dequeue()()
    }
    flag = false
    }

    btnA.addEventListener('click', () => {
    eventQueue.enqueue(ajaxA);
    if (!flag) { run() }
    })
    btnB.addEventListener('click', () => {
    eventQueue.enqueue(ajaxB);
    if (!flag) { run() }
    })
  • AJAX按钮 异步的解法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    let btnA = document.querySelector('#btnA')
    let btnB = document.querySelector('#btnB')
    let input = document.querySelector('input');

    let ajaxA = () => new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('ajaxA response')
    }, 1500)
    })
    let ajaxB = () => new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('ajaxB response')
    }, 2000)
    })

    class Queue {
    constructor() {
    this.list = []
    }
    enqueue(ele) {
    this.list.push(ele)
    }
    dequeue(ele) {
    return this.list.shift(ele)
    }
    isEmpty() {
    return this.list.length === 0
    }
    }

    let eventQueue = new Queue()
    let flag = false

    let run = async function() {
    flag = true
    while(!eventQueue.isEmpty()) {
    let res = await eventQueue.dequeue()
    input.value = res
    console.log('inputValue:',res)
    }
    flag = false
    }

    btnA.addEventListener('click', () => {
    eventQueue.enqueue(ajaxA());
    if (!flag) { run() }
    })
    btnB.addEventListener('click', () => {
    eventQueue.enqueue(ajaxB());
    if (!flag) { run() }
    })