这里的状态机通常指有限状态机。
所谓的有限状态机,是一个模型,通俗的来说:
这个模型有x个状态(x可确定),在任一时刻只能处于一个状态下,并且可以从一个状态切换到另外一个状态。
举个例子:
如代码:
<html lang="en">
<head>
<meta charset="UTF-8">
al-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>状态机与Generator函数title>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js">script>
head>
<body>
<button id="login-btn">登陆button>
<script>
// 封装函数
function btn(node, canClickText, doingText) {
function *g() {
while (true) {
if (this.node.hasClass('submiting')) {
this.node.removeClass('submiting')
this.node.text(canClickText ? canClickText : '提交')
debugger
iter.onBeDone()
yield true // 表示true当前可点击
} else {
this.node.addClass('submiting')
this.node.text(doingText ? doingText : '提交中')
iter.onBeDoing()
yield false // 返回false表示当前不可点击
}
}
}
// 是否可点击
g.prototype.canClick = function () {
if (this.node.hasClass('submiting')) {
iter.onDoing()
return false
} else {
return true
}
}
/* 以下是默认事件 */
// 可点击时点击,触发事件
g.prototype.onBeDoing = function () {
}
// 从不可点击变为点击时,触发的事件
g.prototype.onBeDone = function () {
}
// 当不可点击时点击,触发的事件
g.prototype.onDoing = function () {
}
// 设置node
g.prototype.node = node
let iter = g.call(g.prototype, canClickText, doingText)
return iter
}
// 绑定之
let loginBtn = btn($("#login-btn"), '登陆', '登录中')
loginBtn.onDoing = function () {
console.log('提交中,不可继续点击')
}
loginBtn.onBeDoing = function () {
console.log('开始提交')
}
loginBtn.onBeDone = function () {
console.log('提交完毕')
}
// 点击事件
$("#login-btn").click(function () {
if (!loginBtn.canClick()) {
return
}
loginBtn.next()
setTimeout(function () {
loginBtn.next()
}, 1500)
})
script>
body>
html>
代码解释:
next()
方法,异步完成之后再执行一次next()
方法,具体内部如何实现,在点击事件里无需关心;onBeDoing
、onBeDone
、onDoing
,分别表示当切换为【不可点击】、【可点击】状态,以及当前是【不可点击时】进行触发时,所执行的响应函数。这个是比较复杂状态的,因为要考虑到不可转换的情况;
但是可以通过给next()添加参数,然后通过参数来判断当前状态是什么,从而抛弃canClick方法,但缺点在于,这种处理方法的前提是从【点击】行为到发起异步行为这个过程中,处理步骤比较少而且简单,如果过程比较复杂的话,那么就比较麻烦了。
推荐一篇阮一峰的【JavaScript与有限状态机】,讲的比较通俗易懂。
Generator函数的主要目的就是用于解决异步编程,可以极大的减轻异步编程的撰写难度。
具体来说,我们之前写ajax时,通常是采用回调函数的方法来完成。在只有一个ajax请求的时候,这是可以接受的。
但假如我们有5个AJAX请求,并且后一个需要根据前一个ajax请求的结果来完成请求,那么代码的复杂度将急剧上升。
如示例:
function delay(msg, callback) {
setTimeout(function () {
console.log(msg)
callback()
}, 1000)
}
delay('1', function () {
delay('2', function () {
delay('3', function () {
delay('4', function () {
delay('5', function () {
console.log('done')
})
})
})
})
})
哇,简直可怕。
es6新增的promise,在解决异步编程时,降低了一定难度,但主要是降低了对于单个ajax请求的难度,看起来简单轻松。也增强了对错误处理的能力,但对于以上情况的解决,并没有质的提升。
如代码:
function delay(msg) {
return new Promise((resolve, reject) => {
setTimeout(function () {
console.log(msg)
resolve()
}, 1000)
})
}
delay('1')
.then(() => delay('2'))
.then(() => delay('3'))
.then(() => delay('4'))
.then(() => delay('5'))
.then(() => {
console.log('done')
})
看起来似乎好看了一些,但有以下几个问题:
于是,Generator函数出现了,是解决此类场景的极佳办法;
function delay(msg) {
setTimeout(function () {
iter.next(msg)
}, 1000)
}
function*g() {
let result1 = yield delay('1')
console.log(result1)
let result2 = yield delay('2')
console.log(result2)
let result3 = yield delay('3')
console.log(result3)
let result4 = yield delay('4')
console.log(result4)
let result5 = yield delay('5')
console.log(result5)
console.log('done')
}
let iter = g()
iter.next()
这个方法具备以下特点:
iter.next()
方法,用于将状态机推到下一个状态,但却不必关心下一个状态是哪个异步请求;iter.next()
的参数,可以将上一次请求的数据传给下一次请求的数据,简单暴力;之前提过,Generator函数返回遍历器,而遍历器接口[Symbol.iterator]
函数返回的是也是遍历器,所以简直完美搭配,啊~
show the code:
let obj = {
a: 1,
b: 2,
c: 3,
*[Symbol.iterator]() {
yield this.c
yield this.b
yield this.a
}
}
let arr = [...obj];
arr; // [3, 2, 1]