JS(AS)中的原子操作
原子操作这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
当然JS是单线程的,所以不存在线程打断这么一说,我只是从Java中借引了这么一个概念。如果一段JS代码在执行过程中没有未知操作被引入,那么这段代码就是100%可控和安全的,这就是原子操作。反之非原子操作可能会因为外界操作的引入导致代码变得难以控制而产生隐晦的bug。
下面举例说明非原子操作可能会带来的问题
function start() { player = new Player(); player.start(); fireEvent('start'); player.resume(); fireEvent('play'); } function stop() { player.pause(); fireEvent('pause'); player.stop(); player = null; fireEvent('stop'); }
这段代码中定义两个方法,start表示开始播放视频,里面分别有两段原子操作,在每个原子操作结束之后都向外发送了事件;stop方法类似。代码看起来简单而完美,但由于这两个方法都不是原子操作,所以可能会存在隐患。
下面我们用同样简单的方式使用这两个方法就会产生混乱的结果。
on('start', function(){ stop(); }); start();
这段代码试图让播放器一开始播放就停止,意图明确。但是它却会让实际执行结果变成下面这样
player = new Player(); player.start(); fireEvent('start'); //监听start事件后引入的操作 player.pause(); fireEvent('pause'); player.stop(); player = null; fireEvent('stop'); //end player.resume(); fireEvent('play');
这段代码对外界来说居然在stop事件发生之后还会发生一次play事件,堪称诡异。
究其原因是因为触发play事件后引入外部操作导致下一个原子操作所依赖的前提改变。这就是我说的非原子操作的隐患。
那么如何避免这种问题呢,把代码改成这样就行
function start() { if (!started) { player = new Player(); player.start(); started = true; fireEvent('start'); } if (started && !played) { player.resume(); played = true; fireEvent('play'); } } function stop() { if (started && played) { player.pause(); played = false; fireEvent('pause'); } if (started) { player.stop(); player = null; started = false; fireEvent('stop'); } }
只需要给每个原子操作加上足够的前提判断就可以避免上述问题。
有时候我们无法避免非原子操作,但是我们要认清哪些是原子操作,不要想当然得认为上一个原子操作产生的结果必然会是下一个原子操作的环境。在每个原子操作前加上足够的判断。