JavaScript 生成器的用例
在我对 JavaScript 进行多次深入研究的过程中,我遇到了生成器。它们看起来很有趣。
然后,我寻找了一些生成器的用例。然后,我又寻找了一遍又一遍。
最终,我找到了一个简单的发电机节流阀示例。经过一番研究,我决定看看如何使用它们。由于我正在准备一个异步 JavaScript 演讲(JavaScript Enjoys Your Tears),我编写了一个状态机,以便在幻灯片中定位并在演示文稿端管理字体大小。
我的发现记录在这里......
生成器是可以退出并稍后重新进入的函数。它们的上下文(变量绑定)将在重新进入时保存。—— MDN。
函数可以暂停并再次恢复。生成器返回一个迭代器。创建时,生成器内部的代码不会被执行。
- 解决“推理”问题。
- 允许非“运行至完成”行为。仅限局部阻塞。
- 状态机的句法形式。
- 合作并发与抢占并发。
发电机的优点
惰性求值
这是一种延迟表达式求值的求值模型,直到需要该值时才进行求值。也就是说,如果不需要该值,它就不会存在。它是按需计算的。
内存效率
惰性求值的直接结果是生成器内存效率高。它只生成需要的值。普通函数必须预先生成所有值并保存起来,以备后续使用。然而,生成器的计算过程被推迟了。
用例
以下是一些生成器用例...
无限重复数组
这篇文章(作者是 Shawn Reisner)首先引起了我对这个话题的兴趣。
生成唯一标识符
这来自一篇帖子(作者:Nick Scialli @nas5w):TWEET。
javascript 生成器的一个有趣用例:生成无限数量的唯一标识符!
function * idCreator() {
let i = 0;
while (true) yield i++;
}
const ids = idCreator();
console.log(ids.next().value); // 0
console.log(ids.next().value); // 1
console.log(ids.next().value); // 2
// etc ...
油门发电机
该生成器将在一定时间内(以毫秒为单位)限制某个函数的运行。
export function * throttle(func, time) {
let timerID = null;
function throttled(arg) {
clearTimeout(timerID);
timerID = setTimeout(func.bind(window, arg), time);
}
while(true) throttled(yield);
}
export class GeneratorThrottle {
constuctor() {};
start = () => {
thr = throttle(console.log, 3000);
thr.next('');
};
toString = () => {
console.log(throttle);
console.log('start =', this.start);
};
};
内容状态机
export class ContentStateMachine {
_content;
_default;
_statePatterns;
_returnState;
_changeAlgorithm;
_machine;
constructor(settings) {
this._content = settings.content;
this._default = settings.defaultIndex;
this._statePatterns = settings.statePatterns;
this._returnState = settings.returnState;
this._changeAlgorithm = settings.changeAlgorithm;
const machineSettings = {
'content': this._content,
'defaultIndex': this._default,
'statePatterns': this._statePatterns,
'returnState': this._returnState
};
this._machine = this.stateMachine(machineSettings);
return this._machine;
};
stateMachine = function * stateMachine(settings) {
const content = settings.content;
const defaultIndex = settings.defaultIndex;
const statePatterns = settings.statePatterns;
const returnState = settings.returnState;
let currentIndex = defaultIndex;
while (currentIndex >= 0 && currentIndex < content.length) {
if (this._changeAlgorithm) {
const states = returnState(content, currentIndex);
this._changeAlgorithm(states, currentIndex);
}
const changeType = yield returnState(content, currentIndex);
currentIndex = statePatterns[changeType](content, currentIndex);
}
};
}
用作字体状态机...
import { ContentStateMachine } from '/scripts/presentation/_content-state-machine.js';
$(document).ready(() => {
const main = $('.main');
const upButton = $('.up');
const downButton = $('.down');
const resetButton = $('.reset');
const channel = new BroadcastChannel('le-slides-font-size');
const actions = {
init: () => {
upButton.hide();
downButton.hide();
resetButton.hide();
},
'trigger-up': () => {
fontStateMachine.next('up');
},
'trigger-reset': () => {
fontStateMachine.next('reset');
},
'trigger-down': () => {
fontStateMachine.next('down');
},
'report-states': () => {
channel.postMessage({
upDisabled: upButton.hasClass('disabled'),
downDisabled: downButton.hasClass('disabled')
});
}
};
channel.onmessage = (triggerAction) => {
actions[triggerAction.data]();
};
const sizes = [
'fsm05', 'fsm04', 'fsm03', 'fsm02', 'fsm01',
'fs00',
'fsp01', 'fsp02', 'fsp03', 'fsp04', 'fsp05'
];
const defaultIndex = Math.floor(sizes.length / 2);
const changeFont = (classes, currentIndex) => {
for (var i = 0, len = classes.length; i < len; i++) {
if (i === currentIndex) {
main.addClass(classes[i]);
} else {
main.removeClass(classes[i]);
}
}
if (currentIndex === 0) {
downButton.addClass('disabled');
} else {
downButton.removeClass('disabled');
}
if (currentIndex === classes.length - 1) {
upButton.addClass('disabled');
} else {
upButton.removeClass('disabled');
}
actions['report-states']();
};
const statePatterns = {
'up': (content, index) => {
const max = content.length - 1;
return (index + 1 <= max) ? index + 1 : index;
},
'down': (content, index) => {
return (index - 1 > 0) ? index - 1 : 0;
},
'reset': (content, index) => {
return defaultIndex;
}
};
const returnState = (content, currentIndex) => {
return content;
};
const settings = {
'content': sizes,
'defaultIndex': defaultIndex,
'statePatterns': statePatterns,
'returnState': returnState,
'changeAlgorithm': changeFont
};
const fontStateMachine = new ContentStateMachine(settings);
fontStateMachine.next('reset');
upButton.on('click', () => {
actions['trigger-up']();
});
resetButton.on('click', () => {
actions['trigger-reset']();
});
downButton.on('click', () => {
actions['trigger-down']();
});
});
用作导航状态机...
import { ContentStateMachine } from '/scripts/presentation/_content-state-machine.js';
$(document).ready(() => {
$('.notes').load('/templates/cards.html', function() {
let slideStateMachine;
const nextButton = $('.next');
const previousButton = $('.previous');
const channel = new BroadcastChannel('le-slides-position');
const actions = {
init: () => {
nextButton.hide();
previousButton.hide();
},
'trigger-previous': () => {
slideStateMachine.next('previous');
},
'trigger-next': () => {
slideStateMachine.next('next');
},
'report-states': (index) => {
channel.postMessage({
currentIndex: index,
previousDisabled: previousButton.hasClass('disabled'),
nextDisabled: nextButton.hasClass('disabled')
});
}
};
channel.onmessage = (triggerAction) => {
actions[triggerAction.data]();
};
let cardData = [];
let cardTitles = [];
$.getJSON('/data/card-data.json')
.done((data) => {
cardData = data;
})
.fail((data) => {
console.log('fail', data);
if (data.status!==200) {
const error = $('<div/>').text('Error loading JSON file');
content.append(error);
}
})
.always(() => {
if (cardData.length > 0) {
initTitles();
}
});
function initTitles() {
for (let i = 0, len = cardData.length; i < len; i++) {
cardTitles.push(cardData[i].id);
}
init();
}
function init() {
const changeCurrentCard = (cards, currentIndex) => {
const title = cards[currentIndex];
const currentCard = $(`.note[card="${title}"]`);
const previousTitle = (currentIndex - 1 < 0)
? '' : cardTitles[currentIndex - 1];
const nextTitle = (currentIndex + 1 > maxCards - 1)
? '' : cardTitles[currentIndex + 1];
const keep = [title];
currentCard.addClass('slide');
currentCard.attr('style', 'left:0;');
if (previousTitle.length > 0) {
keep.push(previousTitle);
previousButton.removeClass('disabled');
$(`[card="${previousTitle}"]`)
.attr('style', 'left:-100%;')
.removeClass('slide');
} else {
previousButton.addClass('disabled');
}
if (nextTitle.length > 0) {
keep.push(nextTitle);
nextButton.removeClass('disabled');
$(`[card="${nextTitle}"]`)
.attr('style', 'left:100%;')
.removeClass('slide');
} else {
nextButton.addClass('disabled');
}
$('.n').text(currentIndex + 1);
actions['report-states'](currentIndex);
for (let i = 0, len = cards.length; i < len; i++) {
const element = $(`[card="${cards[i]}"`);
if (!keep.includes(cards[i])) {
element.attr('style', 'display:none;');
}
}
};
const statePatterns = {
'previous': (content, index) => {
return (index - 1 > 0) ? index - 1 : 0;
},
'next': (content, index) => {
const max = content.length - 1;
return (index + 1 <= max) ? index + 1 : index;
},
'reset': (content, index) => {
return 0;
}
};
const returnState = (content, currentIndex) => {
return content;
};
const settings = {
'content': cardTitles,
'defaultIndex': 0,
'statePatterns': statePatterns,
'returnState': returnState,
'changeAlgorithm': changeCurrentCard
};
const maxCards = cardTitles.length;
$('.max').text(maxCards);
slideStateMachine = new ContentStateMachine(settings);
slideStateMachine.next('reset');
nextButton.on('click', (event) => {
actions['trigger-next']();
});
previousButton.on('click', (event) => {
actions['trigger-previous']();
});
}
});
});
结论
经过大量的研究,我发现 JavaScript 生成器的实际例子很少。我想找到使用它们的方法。在一次关于异步 JavaScript 的演讲(JavaScript Enjoys Your Tears)中,我发现一个状态机是一个很好的例子,它可以方便地在幻灯片中定位,并在演示文稿端管理字体大小。
我可以用其他方式管理状态吗?当然可以。但是,我学到的东西远不如上面的代码那么多。
文章来源:https://dev.to/rfornal/use-cases-for-javascript-generators-1npc