分类: js

有关 JavaScript 的技术文章和行业分析文章。

  • CodeMirror 笔记

    CodeMirror 笔记

    这篇文章用来记录使用 CodeMirror 时的一些心得。

    (更多…)
  • 使用 Proxy 添加魔术属性/方法

    使用 Proxy 添加魔术属性/方法

    最近在开发我厂的 QA 工具时,遇到一个问题。我需要模拟 Puppeteer 的所有方法,以便兼容原先的 JS 文件。Puppeteer 提供一个 .asElement() 方法,可以把函数执行结果转换成一个伪 DOM Element(如果函数返回的就是 DOM Element 的话),然后我们就可以在 Node.js 里调用原本属于 DOM 的方法,比如 .focus()。Pupputeer 会替我们完成映射和函数调用,并且返回结果。

    对于大部分对象来说,我只要模拟对应的属性、方法,然后用自己的函数实现功能即可。但是 DOM Element 有上百个属性和方法,手工实现一遍实在太低效了。必须寻找其它途径。

    好在我之前看过 Proxy 的介绍,赶紧翻出文档和书又复习两遍,就大概知道怎么做了。

    Proxy 类如其名,可以“代理”对某个对象的访问。你可以把他理解成明星的经纪人。明星成名之前都是自己处理一切事务,有了经纪人之后,大部分事务就由经纪人负责,但仍然有一些事情需要明星自己处理。

    Proxy 的用法很简单,实例化时,把要代理的对象传进去,定义一下代理方法就好。

    const obj = { name: 'meathill' };
    new Proxy(obj, {
      get(target, property) {
        // 如果对象中有要求的属性或方法,则返回
        if (property in target) {
          return target[property];
        }
        // 没有的话,进行其它处理
        return 'hello';
      }
    });
    
    obj.name // 'meathill'
    obj.age // 'hello'
    obj.sex // 'hello'

    接下来,比如我们访问 obj.foo,那么代理就会生效,它会先检查 obj,如果这个对象上本来就有 foo 属性,就会返回;如果没有,则会调用我们定义的方法来处理。

    如此一来,我们可以定义一个 VElement 类,这个类可以实现一些特殊方法,比如 .type(str) 输入,.click() 点击等;然后用 Proxy 代理其它方法和属性,让对象进入插件 Context 执行。


    关于如何创建具有魔法属性/方法的类,请移步阅读 使用 Proxy 创建有魔术属性/方法的类


    Proxy 还有其它方法也很有用,尤其是 get 对应的 set ,以后再介绍。大家可以自己抽空研究下。

    参考

    • 阮一峰的 ES6
    • 《深入理解 ES6》

  • Chat idea:Vue 进阶之路

    Chat idea:Vue 进阶之路

    本文记录一下 Vue 里常见的坑,为下一次 GitChat 做准备。

    (更多…)
  • WebSocket.onerror 没有错误描述

    WebSocket.onerror 没有错误描述

    用 WebSocket 时遇到一个问题:有时候连接出错,我希望把错误描述报告给用户,方便他们排除。但是尝试了好几种方法,都无法获得错误描述。

    于是只有 Google 之,发现了这个答案:https://stackoverflow.com/questions/18803971/websocket-onerror-how-to-read-error-description。原来是为了防止开发者利用 WebSocket 搞破坏,扫描特定条件下的网络,WebSocket 的 ErrorEvent 只包含一个 error,没有更进一步的描述。oncloseCloseEvent.code也只有 1006——非正常退出,这样毫无价值的信息。 

    所以我的处理方式是:建议用户按 F12 打开开发者工具看错误信息。

  • 在 WebSocket 中处理二进制文本

    在 WebSocket 中处理二进制文本

    我厂产品中有个需求,要用 WebSocket 接收很长的一段文本。生产环境中发现,有些内容发送时会失败,经查,是服务器端进行文本转换时,特殊字符处理存在一些问题。于是决定改为直接发送二进制流,我这边也要修改。

    开始以为要处理 ArrayBuffer,人工转换,后来经过一些摸索找到方法,记录一下。

    const socket = new WebSocket('wss://mydomain.com/path/to/api');
    socket.binaryType = 'arraybuffer';
    socket.onmessage = event => {
      const decodedString = new TextDecoder('utf-8').decode(event.data);
      // go on
    }

    其实很简单,主要用到原生类 TextDecoder,并且配置 WebSocket 以二进制文档流的方式来处理返回的数据。 

  • Promise 改造 child_process.exec

    Promise 改造 child_process.exec

    child_process 是 Node.js 的一个内建模块,用于分裂出(spawn)一个子进程,执行一些特定操作。.exec() 是它的方法,接受一个参数,即要执行的 shell 命令,然后通过回调返回结果。.exec().spawn() 的不同之处在于,前者重在返回结果,后者则重在返回内容。所以当你需要执行一个命令,你并不关心执行过程中发生了什么,只要看到结果就好,那么就用 .exec();反之,假如执行过程中产生的信息对你特别有价值,你并不是特别在意结果,就应该用 .spawn()

    另外,我之前在《Node.js 8 中的 util.promisify》中介绍过,Node.js 8 引入了一个新函数,位于 util 模块,叫做 promisify(),用于将回调风格的 Node.js 函数改造成 Promise 规范的函数。

    OK,背景知识介绍结束。近期开发中,我需要执行一个命令,并且取得它的 stdoutstderrexit code,使用 promisify() 之后发现没有 exit code,于是只好重新写了一下,代码如下:

    import {exec as BaseExec} from 'util';
    
    function exec(command, options) {
      return new Promise((resolve, reject) => {
        let result = {};
        const cp = baseExec(command, options, (err, stdout, stderr) => {
          if (err) {
            err.stdout = stdout;
            err.stderr = stderr;
            reject(err);
            return;
          }
    
          result.stdout = stdout;
          result.stderr = stderr;
          if ('code' in result) {
            resolve(result);
          }
        });
    
        cp.on('exit', (code, signal) => {
          result.code = code;
          result.signal = signal;
          if ('stdout' in result) {
            resolve(result);
          }
        });
      });
    }
    

    希望对大家有用。

    新键盘到了,FC660C,静电容,试用一下,效果还不错。略硬,段落感不强,声音不大。

  • 表单元素 disabled 的判定

    表单元素 disabled 的判定

    测试需求,判定表单元素是否 disabled。

    看起来很简单,直接在浏览器控制台里用 $('input[type="text"]) 选中一个 <input>,验证一下它的 disabled 属性,没问题,于是就直接写成:

    function isDisabled(element) {
      return element.disabled;
    }
    

    后来做到一个用户权限的功能,某一类用户,可以看到某些设置,但不能改。为了省事,就直接 禁用这部分表单。同时,因为我们用 <fieldset> 包装表单元素,于是我就把 disabled 加在 <fieldset> 上,这样可以不需要修改每个元素。

    结果测试的时候就失败了,JS 认为这些元素不是 disabled,但视觉上和操作上它们的确被禁用了。于是调试,发现这些元素的 disabled 的确为 false,但是它们可以 .matches(':disabled'),Google 之,StackOverflow 的几个问答也是同样的结果。

    于是将前面的函数改成

    function isDisabled(element) {
      return element.matches(':disabled');
    }
    

    进一步的,我检查了其它几个属性,包括 readonlyrequired,发现 <fieldset> 只支持 disabled

  • 记一个正则问题

    记一个正则问题

    前几天写代码,遇到一个需求:

    1. 解析 sleep NUMBER 这样的命令
    2. 能够识别缺参数或者参数错误的情况

    这个正则并不复杂,初步写出来大概是这样:sleep\s+(.*)。这样,$1 就是参数,然后就可以检验。但是 .* 匹配“任意字符出现零次或多次”,所以实际测试发现它根本不匹配任何参数。

    然后我就改成了 sleep(?:\s+(.*))?,然后在下一步 trim。这样,sleep 后面整个都是可选参数,就能解决上面的问题。

    然后就被老板骂了……老板的答案是 sleep\s+(.*?)\s*$。重点在于后面的 $,要求正则必须匹配行尾,这样一来,懒惰模式的 .*? 就需要一直匹配到行尾,并且尽量少匹配内容,所以诸如 a b 之类的情况也可以正常跑匹配了。


    从这里,我学到:

    1. $\b 虽然并不能匹配到一个确定的字符,但它们同样意义重大
    2. 不特定长度匹配,包括 *+,甚至 {n, m},在懒惰模式下,后面都要尽量跟上明确的结束条件,以便让前面尽快结束。
  • Puppeteer 笔记

    Puppeteer 笔记

    记录使用 Puppeteer 的一些经验。

    安装使用

    puppeteer 是一个“库”,没有自带的命令行功能。所以要使用的话必须写一个文件,然后实现对应的功能。

    npm i puppeteer
    

    在墙内安装

    puppeteer 里面包含完成的浏览器程序,少说也是 100MB,所以需要下载比较长的时间,在墙内则经常会失败。所以建议国内开发者用淘宝的源:

    npm config set puppeteer_download_host=https://npm.taobao.org/mirrors

    Could not find browser revision xxxxx

    如果安装依赖时,默认的浏览器下载不成功,使用时可能会报这个错误。里面的 xxxxx 是某个版本号。此时可以使用 npm i puppeteer --force 重新安装。如果是墙内用户,那么按照上一小节设置源之后再安装,多半就可以解决问题了。

    在 WSL 下使用

    关于 WSL 使用,请参考这篇博文:在 Windows 10 WSL 中使用 Puppeteer

    我的测试仓库和工具

    参见 GitHub puppeteer-tool

  • Vue  小贴士

    Vue 小贴士

    书说简短:

    1. 使用 Vue + Webpack 开发
    2. 使用 CDN 加载依赖
    3. 在开发阶段尽量不要使用压缩的文件,一边取得尽量全面的错误信息

    (更多…)