JavaScript代码的简洁写法

aki发表于:2020年09月08日 12:29:19更新于:2020年10月12日 08:53:45

Index

概要

基本上发布在Developer Network上的文章,都会注意使用ECMA Script5版本来写JavaScript代码,以确保在Internet Explorer也能够运行。

但是,最新版本ECMA Script 2017等比起旧版更加好用很多,因此本次就向大家介绍几个最新的语法和kintone的JavaScript自定义中可能用到的地方。

※对于Internet Explorer上无法运行的语法,会备注【不支持IE】。
如果也希望在IE上可以使用,可以使用Webpack 或Polyfill,详情请参考
 webpack入门 ~Babel,Polyfill为你带来美好的ES6体验~ 

用const / let定义变量让初始化更加安全

一直以来定义变量都是使用var abc = 123; 等,今后推荐使用const和let。

  • var的作用域是函数,const / let 的作用域是块
    块是决定变量名称或者函数等可访问的范围。在块内定义的变量只能在块内使用,出了块就访问不到。
    var的作用域比较广,可以到函数作用域。但是const / let 是块作用域,因此只有在if块等用 {} 括住的范围内有效。

    // 在函数内声明
    function someFunc() {
      var var_func_number = 1000;
      const const_func_number = 2000;
      let let_func_number = 3000;
    }
    
    // 使用typeof确认是否有声明过(已声明过的情况返回true)
    // 下面因为在函数外面不可以访问,因此都返回false
    console.log('var: ', typeof var_func_number !== 'undefined');
    console.log('const: ', typeof const_func_number !== 'undefined');
    console.log('let: ', typeof let_func_number !== 'undefined');
    
    
    // 在if块内声明变量
    if (true) {
      var var_block_number = 1000;
      const const_block_price = 2000;
      let let_block_price = 3000;
    }
    
    // 使用typeof确认是否有声明过(已声明过的情况返回true)
    // 用var声明时,在块之外也可以访问
    console.log('var: ', typeof var_block_number !== 'undefined');
    // 用const / let声明时,块外面不可以访问
    console.log('const: ', typeof const_block_number !== 'undefined');
    console.log('let: ', typeof let_block_number !== 'undefined');
  • const不可以重复声明或更改值
    const不可以重复声明也不可以更改变量的值。如果要声明的是常量,就使用const。
    代码写长后,可能会不小心重复声明同一变量名,或更改变量的值,为了防止类似的错误,推荐大家积极使用const。
    ※ 但请注意:可改写Object或者数组内的内容。

    var var_price = 1000;
    var_price = 2000; // 用var声明的变量的值被改了也不会报错
    var var_price = 3000; // 可以重复声明
    
    const const_price = 1000;
    const_price = 2000; // 用const声明的变量的值被改时会报错
    const const_price = 3000; // const不可以重复声明
  • let可更改变量的值(和const一样不可重复声明)
    let和const一样不可以重复声明,但是可以更改变量的值。如果事先知道代码执行过程中需要更改变量的值,请使用let声明。
    简单地说,基本上使用const,除非明确需要再更改值的情况才使用let。

    var var_price = 1000;
    var_price = 2000; // 用var声明的变量的值被改时不会报错
    var var_price = 3000; // 可以重复声明
    
    let let_price = 1000;
    let_price = 2000; // 用let声明的变量的值可以更改
    let let_price = 3000; // let不可以重复声明

尽量避免使用or语句

比如,经常会碰到类似kintone的记录列表的数据等需要循环处理的情况,如果使用for语句的话,很容易不小心陷入无限循环,而且代码也会很长。
因此,在对数组进行处理时,尽量使用下述的forEach() / filter() / map() / reduce() 。

forEach()

单纯只是想访问所有记录里的每天数据时,适合用forEach。

Array.prototype.forEach()

例)要在console.log里输出公司名称列表

  • 用for写时
    缺点是如果记录的条数指定错误时,可能陷入无限循环,而且要像records[i]这样写,所有需要记住[ i ]是指什么。

    const records = [
      {
        $id: {
          value: 1,
          type: 'RECORD_NUMBER',
        },
        '公司名称': {
          value: 'ABC公司',
          type: 'SINGLE_LINE_TEXT',
        },
      },
      {
        $id: {
          value: 2,
          type: 'RECORD_NUMBER',
        },
        '公司名称': {
          value: '示例公司',
          type: 'SINGLE_LINE_TEXT',
        },
      },
      {
        $id: {
          value: 3,
          type: 'RECORD_NUMBER',
        },
        '公司名称': {
          value: '公司XYZ',
          type: 'SINGLE_LINE_TEXT',
        },
      },
    ];
    
    
    for (let i = 0; i < records.length; i++) {
      console.log(records[i]['公司名称'].value); // 显示结果
    }
  • 用forEach写的时
    不用特意记住哪一行,records中的数据逐一代入 record变量中,执行循环处理。这样写,既可提高可读性,写起来也方便。
    以往用for进行的处理基本上可以按照这样写,所以建议在写之前先考虑一下是否可以用forEach来写。

    const records = [
      {
        $id: {
          value: 1,
          type: 'RECORD_NUMBER',
        },
        '公司名称': {
          value: 'ABC公司',
          type: 'SINGLE_LINE_TEXT',
        },
      },
      {
        $id: {
          value: 2,
          type: 'RECORD_NUMBER',
        },
        '公司名称': {
          value: '示例公司',
          type: 'SINGLE_LINE_TEXT',
        },
      },
      {
        $id: {
          value: 3,
          type: 'RECORD_NUMBER',
        },
        '公司名称': {
          value: '公司XYZ',
          type: 'SINGLE_LINE_TEXT',
        },
      },
    ];
    
    
    records.forEach(function(record) {
      console.log(record['公司名称'].value); // 显示结果
    });

filter()

使用filter的话,可以获取条件一致的数组。
比如,只想要销售额达到n元以上的数据,可以用非常简单的代码实现。

Array.prototype.filter()

例)获取合计达10,000元以上的记录

使用filter的话可获取和条件一致的数组。
下面就是获取销售额达n元以上的数据的例子。

  • 用for写时

    const records = [
      {
        $id: {
          value: 1,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '1000',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 2,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '20000',
          type: 'NUMBER',
    
        },
      },
      {
        $id: {
          value: 3,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '30000',
          type: 'NUMBER',
        },
      },
    ];
    
    let results = [];
    
    for (let i = 0; i  records.length; i++) {
      if (records[i]['合计'].value > 10000) {
        results.push(records[i]);
      }
    }
    console.log(results); // 显示结果
  • 用filter写时
    不需要用if语句也可以获取任意数据,代码看上去也更加清晰。
    实际上,只是这种单纯处理的话可以在执行kintone API的时候直接指定条件,但是如果条件更加复杂,这个方法就非常好用了。

    const records = [
      {
        $id: {
          value: 1,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '1000',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 2,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '20000',
          type: 'NUMBER',
    
        },
      },
      {
        $id: {
          value: 3,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '30000',
          type: 'NUMBER',
        },
      },
    ];
    
    const results = records.filter(function(record) {
      return (record['合计'].value > 10000);
    });
    console.log(results); // 显示结果

map

使用map的话,可对整个数组进行处理。(返回新数组)
Array.prototype.map()

例)求合计(含消费税)

  • 用for写时

    const records = [
      {
        $id: {
          value: 1,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '0',
          type: 'NUMBER',
        },
        '单价': {
          value: '1000',
          type: 'NUMBER',
        },
        '用户数': {
          value: '2',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 2,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '0',
          type: 'NUMBER',
        },
        '单价': {
          value: '3000',
          type: 'NUMBER',
        },
        '用户数': {
          value: '4',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 3,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '0',
          type: 'NUMBER',
        },
        '单价': {
          value: '20000',
          type: 'NUMBER',
        },
        '用户数': {
          value: '8',
          type: 'NUMBER',
        },
      },
    ];
    
    let results = [];
    for (let i = 0; i  records.length; i++) {
      results.push(records[i]['合计'].value = records[i]['单价'].value * records[i]['用户数'].value * 1.08);
    }
  • 用map写时
    用这个可以大大减少代码行数。
    从records[i]...开始的代码可以大大简短,而且还可以降低缺陷的风险。
    实际上,如果只是单纯计算合计,那么用kintone标准功能的计算字段就可以了,但是如果遇到更加复杂的计算,无法使用计算字段时这个写法就可以派上用场了。

    const records = [
      {
        $id: {
          value: 1,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '0',
          type: 'NUMBER',
        },
        '单价': {
          value: '1000',
          type: 'NUMBER',
        },
        '用户数': {
          value: '2',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 2,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '0',
          type: 'NUMBER',
        },
        '单价': {
          value: '3000',
          type: 'NUMBER',
        },
        '用户数': {
          value: '4',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 3,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '0',
          type: 'NUMBER',
        },
        '单价': {
          value: '20000',
          type: 'NUMBER',
        },
        '用户数': {
          value: '8',
          type: 'NUMBER',
        },
      },
    ];
    
    const results = records.map(function(record) {
      record['合计'].value = record['单价'].value * record['用户数'].value * 1.08;
      return record;
    });
    console.log(results); // 输出结果

reduce

使用reduce可以计算records数组内的合计的总和。
使用reduce写的代码跟上面比可能会显得更加复杂一点,但是如果习惯了,在求数据的合计等情况特别的方便。
reduce一般是从左到右处理数组的,也可以用 reduceRight反向处理。
Array.prototype.reduce()

例)求所有合计的总和

  • 用for写时

    const records = [
      {
        $id: {
          value: 1,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '10000',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 2,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '20000',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 3,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '30000',
          type: 'NUMBER',
        },
      },
    ];
    
    var results = 0;
    for (var i = 0; i  records.length; i++) {
      results += Number(records[i]['合计'].value);
    }
    console.log(results); // 输出结果
  • 用reduce写时
    代码行数基本上没有什么差别,所以看不出有什么不一样,但是不使用index变量[i]可以减少缺陷的概率。
    也可以用forEach写,但是用reduce不需要初始化,处理可以全部写在reduce内。
    像下面例子一样,在求合计时,或者循环records数组计算合计等的情况下,非常推荐使用的一个函数。

    const records = [
      {
        $id: {
          value: 1,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '10000',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 2,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '20000',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 3,
          type: 'RECORD_NUMBER',
        },
        '合计': {
          value: '30000',
          type: 'NUMBER',
        },
      },
    ];
    
    var results = records.reduce(function(prev, record) {
      return prev + Number(record['合计'].value);
    }, 0);
    console.log(results); // 输出结果

Arrow函数让函数代码更加简洁【不支持IE】

声明函数的方法有如下几种。

  • 以往的函数声明方法(一般的function句式)

    function someFunc() {
      // 处理内容
    };
    
    // 或者
    
    var someFunc = function() {
      // 处理内容
    }
  • Arrow函数

    const someFunc = () => {
      // 处理内容
    };

从ES6起新增了如上Arrow函数。 (使用=> 这种形状的箭头,所以称为Arrow函数。 )

Arrow函数的优点

Arrow函数有几个优点。

  1. 更加简洁
    如上只是声明函数的话,其实没有什么差别,但是如果是Callback函数等,函数多样化时就显得特别的简洁。
    特别是比如使用Array.map()或者filter()等获取记录列表中必要的数据,然后按照条件分支处理时。

    以下例子是使用Array.prototype.filter()函数判断是否含税的数据,然后再用Array.prototype.map()获取含税的金额,生成新数组。

    const records = [
      {
        $id: {
          value: 1,
          type: 'RECORD_NUMBER',
        },
        '税种': {
          value: '含税',
          type: 'RADIO_BUTTON',
        },
        '合计': {
          value: '10000',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 2,
          type: 'RECORD_NUMBER',
        },
        '税种': {
          value: '不含税',
          type: 'RADIO_BUTTON',
        },
        '合计': {
          value: '20000',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 3,
          type: 'RECORD_NUMBER',
        },
        '税种': {
          value: '含税',
          type: 'RADIO_BUTTON',
        },
        '合计': {
          value: '30000',
          type: 'NUMBER',
        },
      },
    ];
    
    // 税种字段的值为“含税”或者“不含税”
    // 合计字段的值为金额。
    
    // 以往的函数声明方法
    const includedTaxPriceArray = records.filter(function(record) {
      return record['税种'].value === '含税';
    }).map(function(record) {
      return record['合计'].value * 1.1;
    });
    
    // 用Arrow函数的方法
    const includedTaxPriceArrayWithArrowFunc = records.filter((record) => {
      return record['税种'].value === '含税';
    }).map((record) => {
      return record['合计'].value * 1.1;
    });
    
    console.log(includedTaxPriceArray);
    console.log(includedTaxPriceArrayWithArrowFunc)

    像这样没有了 function看过去简洁多了。除此之外,还有其他的使用规则。
    比如,如果参数只有一个,可以省略参数的括号;如果处理只有一行,那么可以省略Return等,如果把这些规则也用上,又可以省更多。

    // Arrow函数(省略括号以及Return的版本)
    const includedTaxPriceArrayWithArrowFunc = records.filter(record => record['税种'].value === '含税').map(record => record.price.value * 1.1);
    const records = [
      {
        $id: {
          value: 1,
          type: 'RECORD_NUMBER',
        },
        '税种': {
          value: '含税',
          type: 'RADIO_BUTTON',
        },
        '合计': {
          value: '10000',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 2,
          type: 'RECORD_NUMBER',
        },
        '税种': {
          value: '不含税',
          type: 'RADIO_BUTTON',
        },
        '合计': {
          value: '20000',
          type: 'NUMBER',
        },
      },
      {
        $id: {
          value: 3,
          type: 'RECORD_NUMBER',
        },
        '税种': {
          value: '含税',
          type: 'RADIO_BUTTON',
        },
        '合计': {
          value: '30000',
          type: 'NUMBER',
        },
      },
    ];
    
    // 税种字段的值为[含税]或者[不含税]
    // 合计字段的值为金额。
    
    
    // 用Arrow函数的方法
    const includedTaxPriceArrayWithArrowFunc = records.filter(record => record['税种'].value === '含税').map(record => record['合计'].value * 1.1);
    
    console.log(includedTaxPriceArrayWithArrowFunc);

     不用return的那部分,可很方便地定义可返回新函数的函数(柯里化)。 ... spread opereator等请看后续 

    const contracts = [
      {
        '合同名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '合同C'
        },
        '费用': {
          'type': 'NUMBER',
          'value': '50000'
        },
        '商品ID': {
          'type': 'NUMBER',
          'value': '1'
        },
        '客户ID': {
          'type': 'NUMBER',
          'value': '3'
        },
        '日期': {
          'type': 'DATE',
          'value': '2019-08-31'
        },
        '$id': {
          'type': '__ID__',
          'value': '3'
        }
      },
      {
        '合同名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '合同B'
        },
        '费用': {
          'type': 'NUMBER',
          'value': '150000'
        },
        '商品ID': {
          'type': 'NUMBER',
          'value': '2'
        },
        '客户ID': {
          'type': 'NUMBER',
          'value': '4'
        },
        '日期': {
          'type': 'DATE',
          'value': '2019-08-30'
        },
        '$id': {
          'type': '__ID__',
          'value': '2'
        }
      },
      {
        '记录编号': {
          'type': 'RECORD_NUMBER',
          'value': '1'
        },
        '合同名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '合同A'
        },
        '费用': {
          'type': 'NUMBER',
          'value': '100000'
        },
        '商品ID': {
          'type': 'NUMBER',
          'value': '3'
        },
        '客户ID': {
          'type': 'NUMBER',
          'value': '1'
        },
        '日期': {
          'type': 'DATE',
          'value': '2019-08-29'
        },
        '$id': {
          'type': '__ID__',
          'value': '1'
        }
      }
    ];
    
    const customers = [
      {
        '客户名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '样品公司'
        },
        '$id': {
          'type': '__ID__',
          'value': '5'
        }
      },
      {
        '客户名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '金枪鱼水产'
        },
        '$id': {
          'type': '__ID__',
          'value': '4'
        }
      },
      {
        '客户名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '样品株式会社'
        },
        '$id': {
          'type': '__ID__',
          'value': '3'
        }
      },
      {
        '客户名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '街田商社'
        },
        '$id': {
          'type': '__ID__',
          'value': '2'
        }
      },
      {
        '客户名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': 'AA物流公司'
        },
        '$id': {
          'type': '__ID__',
          'value': '1'
        }
      }
    ];
    
    const products = [
      {
        '商品名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '电脑'
        },
        '$id': {
          'type': '__ID__',
          'value': '3'
        }
      },
      {
        '商品名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '商用冰箱'
        },
        '$id': {
          'type': '__ID__',
          'value': '2'
        }
      },
      {
        '商品名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '大型电视'
        },
        '$id': {
          'type': '__ID__',
          'value': '1'
        }
      }
    ];
    
    // 假设分别有合同应用的记录、与该应用关联起来的客户应用的记录和商品应用的记录。
    
    // 创建一个函数用于返回将多条记录结合起来的函数
    const joinRecordsCreator =
      (leftRecords) =>
      (rightRecords, joinKey) =>
      leftRecords.map(leftRecord => ({...rightRecords.find((rightRecord) => rightRecord[joinKey].value === leftRecord.$id.value), ...leftRecord}))
    
    // 上面是将Contracts和其他应用的记录结合起来的函数
    const joinContracts = joinRecordsCreator(contracts);
    
    // 获取使用joinContracts函数将Contracts和Customers结合起来的数组
    const contractsJoinedWithCustomers = joinContracts(customers, '$id');
    
    // 获取使用joinContracts函数将Contracts和Products结合起来的数组
    const contractsJoinedWithProducts = joinContracts(products, '$id');
    
    console.log(contractsJoinedWithCustomers);
    console.log(contractsJoinedWithProducts);
  2. 可固定this
    在kintone的JS自定义过程中,应该会碰到在操作DOM元素时需要访问this的情况。
    一般的函数定义需要注意“this”是指什么,但是arrow函数的话,this是固定的,因此不会引起混乱。
    取而代之,使用currentTarget等可以获取特定的元素本身。

    // 一般情况下的函数,this是指按钮本身
    $('button').click(function() {  
      console.dir(this); // 打印button元素。
    });
    
    // 用箭头函数时,因为this是固定的,因此不是指button元素。
    // 要获取按钮元素,需要使用回调的参数的currentTarget
    $('button').click((e) => {  
      console.dir(e.currentTarget); // 打印button元素。
    });

Async/Await来替代Promise【不支持IE】

要从多个应用中获取数据等情况,需要使用Promise进行同步处理。
Promise是在.then()内写要执行的处理,对于不太习惯层层嵌套的人来说可能不太直观。
下面例子是从某个应用中获取4条数据,然后求合计。

  • 例) 获取4条数据并求和(Promise)
    ※ 一般情况下使用批量获取记录的API比较合理,这里作为Promise的范例,特意用一条条获取的方法。

    (function() {
      'use strict';
      // 获取4条数据并求和
      // 省略params1-4。
    
      // kintone通过返回Promise,可让保存前处理等处理待机
      kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], function(event) {
        return new kintone.Promise(function(resolve, reject) {
          return kintone.api('/k/v1/record', 'GET', params1);
        }).then(function(resp1) { // 应答内容放入resp1。等待处理结束后再执行后续处理
          return kintone.api('/k/v1/record', 'GET', params2);
        }).then(function(resp2) { // 应答内容放入resp2。等待处理结束后再执行后续处理
          return kintone.api('/k/v1/record', 'GET', params3);
        }).then(function(resp3) { // 应答内容放入resp3。等待处理结束后再执行后续处理
          return kintone.api('/k/v1/record', 'GET', params4);
        }).then(function(resp4) { // 应答内容放入resp4。等待处理结束后再执行后续处理
    
          // 到这里获取数据的处理已经全部结束了
          // 对resp1 - 4求和并将和合计值输出到“合计字段”
          event.record.总计.value = resp1.record.合计.value + resp2.record.合计.value + resp3.record.合计.value + resp4.record.合计.value;
          resolve(event); // resolve返回promise处理完成
        });
      });
    })();

下面是替代上述的Promise写法,而使用Async/Await来写,使用了通常的函数

并不是说 哪个对哪个错,而是跟用多个Promise或用复杂的语法相比,使用Async/Await的写法会比较直观。

  • 例) 获取4条数据并求和(Async/Await) 

    (() => {
      'use strict';
      // 获取4条数据并求和
      // 省略params1-4。
    
      kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], async function(event) { // 加上async
        // 需要等待的处理要加上await
        const resp1 = await kintone.api('/k/v1/record', 'GET', params1); // 应答内容放入resp1。等待处理结束后再执行后续处理
        const resp2 = await kintone.api('/k/v1/record', 'GET', params2); // 应答内容放入resp2。等待处理结束后再执行后续处理
        const resp3 = await kintone.api('/k/v1/record', 'GET', params3); // 应答内容放入resp3。等待处理结束后再执行后续处理
        const resp4 = await kintone.api('/k/v1/record', 'GET', params4); // 应答内容放入resp4。等待处理结束后再执行后续处理
    
        // 到这里获取数据的处理已经全部结束了
        // 对resp1 - 4求和并将和合计值输出到“合计字段”
        event.record.总计.value = resp1.record.合计.value + resp2.record.合计.value + resp3.record.合计.value + resp4.record.合计.value;
        return event;
      });
    })();

但是,要注意的是:加了async后会返回PromiseObject,不支持Promise的事件句柄里,不用立即执行函数包住会报错。

通过解构赋值实现智能的变量提取方法【不支持IE】

这个功能也特别的方便。一般情况下,要取出kintone.events.on()中的record变量,需要这样:

  • 在kintone.events.on()中用event.record取出record变量(通常情况) 

    kintone.events.on('app.record.create.submit', (event) => {
      const record = event.record;
      console.log(record);
    });

    用解构赋值的方法不需要写两遍“record”。

  • 在kintone.events.on()中从event.record里取出record变量(解构赋值)

    kintone.events.on('app.record.create.submit', (event) => {
      const {record} = event;
      console.log(record);
    });

    像这样可以节省了写两边“record”的麻烦。
    甚至还可以如下获取record下的price和customer字段。

    kintone.events.on('app.record.create.submit', (event) => {
      const {record} = event;
      const {price, customer} = record;
      console.log(price, customer);
    });

    还可以先取出price和customer。

    kintone.events.on('app.record.create.submit', (event) => {
      const {record:{price, customer}} = event;
      console.log(price, customer);
    });

并不是要求一定要这样写,但是这样写可以减少代码量,看起来更加清晰。如果像让你的代码变得更加优雅,不妨可以试一试。

使用展开语法展开【不支持IE】

如果习惯了展开语法也特别的方便。展开语法用3个点[ ... ]来表示,可以展开数组、函数的参数、对象。

展开语法用于数组时

  • 对kintone的记录数组(records数组)进行展开并合并

    const records1 = [
      {
        '合同名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '合同C'
        },
        '费用': {
          'type': 'NUMBER',
          'value': '50000'
        },
        '日期': {
          'type': 'DATE',
          'value': '2019-08-31'
        },
        '$id': {
          'type': '__ID__',
          'value': '3'
        }
      },
      {
        '合同名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '合同B'
        },
        '费用': {
          'type': 'NUMBER',
          'value': '150000'
        },
        '日期': {
          'type': 'DATE',
          'value': '2019-08-30'
        },
        '$id': {
          'type': '__ID__',
          'value': '2'
        }
      },
    ];
    
    const records2 = [
      {
        '合同名称': {
          'type': 'SINGLE_LINE_TEXT',
          'value': '合同A'
        },
        '费用': {
          'type': 'NUMBER',
          'value': '100000'
        },
        '日期': {
          'type': 'DATE',
          'value': '2019-08-29'
        },
        '$id': {
          'type': '__ID__',
          'value': '1'
        }
      }
    ];
    
    
    // 将记录数records1和records2合并成一个数组
    const resultRecords = [...records1, ...records2];
    
    console.log(resultRecords);

也可将其与Array.prototype.concat()结合使用,但这更加直观!
如果records1、records2内分别有三个元素,那么[...records1, ...records2]这样写和以下写法基本同一个意思:

[records1-1, records1-2, records1-3, records2-1, records2-2, records2-3]

如果理解为用...将数组展开的话,应该就好懂了。

展开语法用于函数的参数时

用于函数的参数时,可支持变量的参数。

  • 以下是对多条合同记录的合计金额求和的函数

    const record1 = {
      '合同名称': {
        'type': 'SINGLE_LINE_TEXT',
        'value': '合同C'
      },
      '费用': {
        'type': 'NUMBER',
        'value': '50000'
      },
      '日期': {
        'type': 'DATE',
        'value': '2019-08-31'
      },
      '$id': {
        'type': '__ID__',
        'value': '3'
      }
    };
    
    const record2 = {
      '合同名称': {
        'type': 'SINGLE_LINE_TEXT',
        'value': '合同B'
      },
      '费用': {
        'type': 'NUMBER',
        'value': '150000'
      },
      '日期': {
        'type': 'DATE',
        'value': '2019-08-30'
      },
      '$id': {
        'type': '__ID__',
        'value': '2'
      }
    };
    
    
    const record3 = {
      '合同名称': {
        'type': 'SINGLE_LINE_TEXT',
        'value': '合同A'
      },
      '费用': {
        'type': 'NUMBER',
        'value': '100000'
      },
      '日期': {
        'type': 'DATE',
        'value': '2019-08-29'
      },
      '$id': {
        'type': '__ID__',
        'value': '1'
      }
    };
    
    
    const sumContractPriceRecords = (...records) => {
      // 参数records为数组
      return records.reduce((sum, record) => sum + Number(record.费用.value), 0);
    };
    
    // 求record1,2,3的合计的总和
    const sum = sumContractPriceRecords(record1, record2, record3);
    
    console.log(sum);

展开语法用于对象时

可以像数组一样展开对象,因此可以如下结合对象。

const contract = {
  '合同名称': {
    'type': 'SINGLE_LINE_TEXT',
    'value': '合同C'
  },
  '费用': {
    'type': 'NUMBER',
    'value': '50000'
  },
  '日期': {
    'type': 'DATE',
    'value': '2019-08-31'
  },
  '$id': {
    'type': '__ID__',
    'value': '3'
  }
};


const customer = {
  '客户名称': {
    'type': 'SINGLE_LINE_TEXT',
    'value': '样品公司'
  },
  '$id': {
    'type': '__ID__',
    'value': '5'
  }
};

// 将客户信息的customerRecord和合同信息的contractRecord结合
// 此时,如果发生$id等冲突时,以后者为优先
const joinedRecord = {...contract, ...customer};


console.log(joinedRecord);

这个也是跟数组一样,类似于将contractRecord和customerRecord展开。

安全无副作用的代码写法

编程中,状态被改变称作为副作用。代码尽量减少副作用可以避免缺陷。用语言比较难表达,我们来看实际例子。

 // 假设有条kintone的记录含有price字段
const record = {
  $id: {
    type: 'RECORD_NUMBER',
    value: 1,
  },
  price: {
    type: 'NUMBER',
    value: 1000,
  },
};

// 以下函数用于计算含消费税(10%)
const addTax = (record) => {
  record.price.value = record.price.value * 1.1;
  return record;
};

// 保存已经计算了消费税的record
const addedTaxRecord = addTax(record);

// 对比计算了消费税的record和计算前的record的值,看结果如何?
console.log('含税金额', addedTaxRecord.price.value);
console.log('税前金额', record.price.value);

一眼看上去,感觉上述代码会在console中显示含税金额(1100)和税前金额(1000)。但是实际如下显示。

0015f7bf5196aa0454e96eab0e4da9f

竟然两者都变成含税金额(1100)了。这个就是“对原始变量产生副作用”的状态。像这样,如果不随时注意是否可能产生副作用,就会发生意想不到的缺陷。

这个是JavaScript的变量总是去访问值而引起的现象。要回避这个有几个方法。

IE之外的浏览器时

使用SpreadOperator可以如下改写addTax函数。创建新的Object并返回。

// 假设有条kintone的记录含有price字段
const record = {
  $id: {
    type: 'RECORD_NUMBER',
    value: 1,
  },
  price: {
    type: 'NUMBER',
    value: 1000,
  },
};

// 以下函数用于计算含消费税(10%)(不起副作用的写法)
const addTax = (record) => {
  return {
    ...record, // 展开record数组
    price: {   // 覆盖price
      ...record.price,  // 要保留type等,因此也要展开price内容
      value: record.price.value * 1.1, // 仅覆盖value
    },
  };
};
// 保存已经计算了消费税的record
const addedTaxRecord = addTax(record);

// 对比计算了消费税的record和计算前的record的值,看结果如何?
console.log('含税金额', addedTaxRecord.price.value);
console.log('税前金额', record.price.value);

IE时

可以采用先复制Objec的方法,或者使用immer以及immutable.js等库来保证旧数据不改变的情况下对数据进行操作的方法。

不使用jQuery单纯用JS操作DOM

要对DOM元素进行操作时,当然可以使用jQuery,但是假设你只想对一个DOM元素进行操作,特意导入jQuery的话就太累赘了。下面介绍单靠JS对DOM进行操作的方法。

  • 获取元素
    使用document.querySelector() / querySelectorAll() 可以获取元素。

    /* 要获取元素id = foo的元素时 */
    
    // jQuery
    const elementJquery = $('#foo');
    
    // JavaScript
    const element = document.querySelector('#foo');
    
    // 要获取Class等多个元素时使用querySelectorAll
    const elements = document.querySelectorAll('.foo');
    
    
    console.log('获取到的元素的text: ', elementJquery.text());
    
    console.log('获取到的元素的text: ', element.innerText);
    
    console.log('获取到的元素的text: ', [...elements].map(e => e.innerText));

     ※ 当然也可以使用document.getElementById(),但是使用上述的方法不用管是class还是id,使用querySelector都可以取。


  • 更改获取到的元素的Style
    用元素.style.属性 = "值"; 的写法可以更改元素的style。
    可使用CSS可设置的style。

    // 将id: foo的元素的文字改为红字粗体
    const element = document.querySelector('#foo');
    element.style.color = "red";
    element.style.fontWeight = "bold";

    当然,也可以对用kintone.app.record.getFieldElement获取的元素进行同样更改。
    0015f7ab5a1906ccd64595c69b54496

导入TypeScript

使用TypeScript来进行kintone自定义开发(日语)中也介绍了,kintone表格的Record结构相对比较复杂,
因此使用TypeScript的话可以减少弄错对象的key以及类型。
使用Visual Studio Code等的IDE,会自动补全代码。如果今后从事JavaScript自定义的机会比较多,推荐您导入。

最后

综上所述,JavaScript在不断演变,使编程越来越轻松。
将const/let、Spread语法以及Arrow函数等组合使用,或积极使用一些其他新功能,可以大大减少缺陷,使你的代码无副作用更加优秀。请务必尝试一下!