美文网首页
一把锁引发的思考

一把锁引发的思考

作者: 小丸子啦啦啦呀 | 来源:发表于2018-05-05 19:08 被阅读0次

    大概半个多月前就有用户反馈:同时打开文件A和文件B,在A的某个单元格中写几行执行起来非常耗时的代码,在等待A执行完成的时候,我再切到文件B,去执行B中的代码,发现无法执行,刷新整个页面之后才OK。
    听到这个反馈,我第一反应是,有可能是执行某些耗时长,占用资源大的代码时,内核挂掉了,于是我立马重启一下内核,发现内核能够重启成功,重启之后照样不能执行代码。
    排掉这个因素,我想,是不是socket受不住断掉了,于是试了一下代码提示功能,发现代码提示功能正常,说明socket连接也没问题。
    在看socket的时候我顺便看了一下Frames,点击执行按钮的时候,事件并没有触发,那这意味着run方法没有执行:

    // 触发socket 执行代码
      run(focusLine) {
        this.setState({ runLine: focusLine, kernelStatus: 'busy' });
        const { byId, socket } = this.props;
        console.log('待执行代码:', byId[focusLine].source.join('\n'), focusLine);
       // 如果这一句执行了,那么肯定可以看到对应Frame
        socket.emit('codeExe', byId[focusLine].source.join('\n'), focusLine);
      }
    

    run方法其实并不是点击按钮时的回调,单步执行的回调是:

    // 单步运行
      runStep = needGoNext => {
        this.needGoNext = needGoNext;
        this.msgObj = {};
        const {  focusLine } = this.props;
        console.log('runstep', finishFlag);
        if (!this.finishFlag) return;
        this.finishFlag = false;
        this.run(focusLine);
      };
    

    那难道是runStep没有执行?可是console.log('runstep', finishFlag);能看到打印结果,说明点击事件绝对是生效了,那么就只有一个可能了:finishFlag这把锁造成的。

    这把锁干什么用的?

    finishFlag就是为了保证上一次执行完成才能触发下一次的执行。
    如果不加锁,会造成代码执行结果的重复输出。
    这把锁的逻辑很简单:

    // 初始化锁为已完成状态
    let finishFlag = true;
    // notebook组件
    export default class Notebook extends Component {
         // 单步运行
        runStep = needGoNext => {
          this.needGoNext = needGoNext;
          this.msgObj = {};
          const {  focusLine } = this.props;
          console.log('runstep', finishFlag);
          // 上一次执行未完成,直接返回
          if (!this.finishFlag) return;
          // 上锁
          this.finishFlag = false;
          this.run(focusLine);
          // 不在这儿解锁,解锁的时机是收到idel片段时
        };
         return (<div>...notebook...<div>)
    }
    
    // 父组件
    import Notebook from '../...'
    export default class EditorTabs extends Component {
       return
    ( <div>
          <Notebook />
          <Notebook />
    </div>)
    }
    

    再想想错误现象,一个notebook无法执行的时候,另一个也没法执行了,那说明两个Notebook组件共享了这一个finishFlag,也就是说所有的Notebook都是是用的同一把锁!
    为什么会这样呢?会不会有别的原因??
    来做个实验好了,用create-react-app建一个空的项目,然后自己写一个这样的测试组件:

    import React, { Component } from 'react';
    
    let flag = true;
    class Test extends Component {
        changeFlag = () => {
            console.log('----before', flag);
            flag = !flag;
            console.log('----after', flag);
        }
        render() {
            return <button onClick={this.changeFlag}>Test</button>
        }
    }
    
    export default Test;
    
    import React, { Component } from 'react';
    import Test from './Test';
    class App extennds Component{
       return (
          <div>
              <Test />
              <Test />
              <Test />
              <Test /> 
          </div>
        );
    }
    

    如果每次点击输出都为

    ----before true
    ----after false

    那说明是flag独立不受影响的。
    但实验结果为:


    image.png

    再次验证了我的想法。
    接下来把flag从组件外挪到组件内:

    class Test extends Component {
        constructor(props) {
            super(props);
            this.flag = true;
        }
        changeFlag = () => {
            console.log('----before', flag);
            this.flag = !this.flag;
            console.log('----after', flag);
        }
        render() {
            return <button onClick={this.changeFlag}>Test</button>
        }
    }
    

    这样就没毛病了。
    照葫芦画瓢我把finishFlag放到组件里,经过测试没有再出现用户反应的问题。

    疑问

    到这里,问题虽然解决了,但是我还是有些疑惑,这个flag到底在哪个作用域里?为什么没有被组件单独拥有呢?
    看看Test模块的输出输入,然后在网上看了一些js模块化开发的博客,我终于懂了:

    其实,flag属于这个Test.js模块,但是不属于这个App组件

    export default Test;
    import Test from '../..'
    

    引申

    1. js模块化开发
      js不像java有package把作用域隔开,但是开发中我们又经常需要分隔作用域,人为制造模块。
      最古老的方式有:用function制造独立作用域,用对象集中管理不同作用域,立即执行函数制造独立作用域等;这样写起来很麻烦,需要开发人员去跟踪管理所有依赖关系;
      随后出现了CommonJS, 个人人为CommonJS最大的改进是:规定一个单独的文件就是一个模块,每一个模块都是一个单独的作用域。CommonJS使用require和module.exports函数来导入导出模块,但是使用require获取依赖模块的时候,由于同步的原因产生阻塞, 这样的缺点显而易见。
      随后出现了AMD和CMD,AMD使用requireJS,主要解决异步加载和前置引用的问题,CMD使用seaJs, 可以用到的时候再去require。但说实话这个时候JS都还没有原声支持“模块”,都要借助第三方库去做,直到ES6出来。
      从ES6出来后,有了新的模块化开发方式,即ES6模块化。也就是我现在用得最多的这种。ES6模块化其实很多引擎还不支持,需要用babel转成CommonJS规范的那种。那ES6模块化和CommonJS有什么区别呢?
      1.CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
      2.CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

    总结

    以后不要轻易在组件外写变量!写之前想清楚能不能这样写。

    参考

    [前端模块化]http://www.cnblogs.com/dolphinX/p/4381855.html
    [Javascript模块化编程(一):模块的写法
    ]http://www.ruanyifeng.com/blog/2012/10/javascript_module.html
    [js模块化进程]https://blog.csdn.net/u014168594/article/details/77099315
    [Module 的加载实现]http://es6.ruanyifeng.com/#docs/module-loader
    [深入 JSX]http://www.css88.com/react/docs/jsx-in-depth.html

    相关文章

      网友评论

          本文标题:一把锁引发的思考

          本文链接:https://www.haomeiwen.com/subject/zererftx.html