美文网首页
JavaScript:ES6的一些习惯

JavaScript:ES6的一些习惯

作者: 勇往直前888 | 来源:发表于2017-05-22 17:17 被阅读73次

    ECMAScript 6 入门
    这篇文章不错,下面的只是随手记。

    块级作用域

    • 能用let的地方,就不要用var,可以解决i异常的问题。

    • 少用立即执行函数表达式(IIFE)

    • 考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

    • 允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。

    • const的作用域与let命令相同:只在声明所在的块级作用域内有效。

    • const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。提倡多用const,安全性高。

    • const一般用于基本类型,对象类型要慎用,可能达不到预期的效果。因为本质是地址不变,地址指向的内容不能保证不变。

    • 对象的冻结,应该用Object.freeze方法,采用递归的方法,将对象和属性都冻结。

    let constantize = (obj) => {
      Object.freeze(obj);
      Object.keys(obj).forEach( (key, i) => {
        if ( typeof obj[key] === 'object' ) {
          constantize( obj[key] );
        }
      });
    };
    
    • let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。多用这些关键字来声明变量,减少对window顶层对象的影响。

    解构赋值

    主要用在数组和对象上,比较方便。以下几个方面推荐使用:

    • 输入模块的指定方法
    const { SourceMapConsumer, SourceNode } = require("source-map");
    
    • 遍历Map结构
    var map = new Map();
    map.set('first', 'hello');
    map.set('second', 'world');
    
    for (let [key, value] of map) {
      console.log(key + " is " + value);
    }
    // first is hello
    // second is world
    
    // 获取键名
    for (let [key] of map) {
      // ...
    }
    
    // 获取键值
    for (let [,value] of map) {
      // ...
    }
    
    • 提取JSON数据
    let jsonData = {
      id: 42,
      status: "OK",
      data: [867, 5309]
    };
    
    let { id, status, data: number } = jsonData;  // data是模式匹配,number是变量
    
    console.log(id, status, number);
    // 42, "OK", [867, 5309]
    
    • 从函数返回多个值
    // 返回一个数组
    
    function example() {
      return [1, 2, 3];
    }
    let [a, b, c] = example();
    
    // 返回一个对象
    
    function example() {
      return {
        foo: 1,
        bar: 2
      };
    }
    let { foo, bar } = example();
    
    • 函数参数的默认值
    jQuery.ajax = function (url, {
      async = true,
      beforeSend = function () {},
      cache = true,
      complete = function () {},
      crossDomain = false,
      global = true,
      // ... more config
    }) {
      // ... do stuff
    };
    
    • 交换变量的值
    let x = 1;
    let y = 2;
    
    [x, y] = [y, x];
    

    Unicode 表示法

    • 将将码点放入大括号,可以表示4字节的Unicode字符
    '\z' === 'z'     // true
    '\172' === 'z'   // true
    '\x7A' === 'z'   // true
    '\u007A' === 'z' // true
    '\u{7A}' === 'z' // true
    
    '\u{1F680}' === '\uD83D\uDE80'  // true 这是老的表示方法,现状可以不用了,直接用{}方便
    
    • String.fromCharCode方法,用于从码点返回对应字符;(类方法)codePointAt方法会正确返回32位的UTF-16字符的码点(实例方法)。这两个方法是一对的,支持4字节的Unicode字符。
    var s = '𠮷a';
    s.codePointAt(0).toString(16) // "20bb7";𠮷
    s.codePointAt(2).toString(16) // "61" ; a;参数序号仍然不对,这是缺点
    
    String.fromCodePoint(0x20BB7)    // "𠮷"
    String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'    // true
    
    • 字符串增加了遍历器接口,可以被for...of循环遍历,并且可以识别大于0xFFFF的码点(4字节),传统的for循环无法识别这样的码点。
    var text = String.fromCodePoint(0x20BB7);
    
    for (let i = 0; i < text.length; i++) {
      console.log(text[i]);
    }
    // " "
    // " "
    
    for (let i of text) {
      console.log(i);
    }
    // "𠮷"
    

    字符串新增方法

    • 判断包含,开头,结尾。以前只有indexOf,现在有新方法
      includes():返回布尔值,表示是否找到了参数字符串。
      startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
      endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
    var s = 'Hello world!';
    
    s.startsWith('Hello') // true
    s.endsWith('!')       // true
    s.includes('o')       // true
    
    • repeat方法返回一个新字符串,表示将原字符串重复n次。
    'x'.repeat(3)     // "xxx"
    'hello'.repeat(2) // "hellohello"
    'na'.repeat(0)     // ""
    
    • 模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。这个在格式化输出的时候很有用,推荐使用。
    // 普通字符串
    `In JavaScript '\n' is a line-feed.`
    
    // 多行字符串
    `In JavaScript this is
     not legal.`
    
    console.log(`string text line 1
    string text line 2`);
    
    // 字符串中嵌入变量
    var name = "Bob", time = "today";
    `Hello ${name}, how are you ${time}?`
    
    • String.raw()转义字符串,可以少写一个\;也可以理解为输出原来的样子,用在模板字符串,format格式化输出中。
    String.raw `Hi\n!`;                 
    // "Hi\\n!",这里得到的不是 Hi 后面跟个换行符,而是跟着 \ 和 n 两个字符
    
    String.raw `Hi\u000A!`;             
    // "Hi\\u000A!",同上,这里得到的会是 \、u、0、0、0、A 6个字符,
    // 任何类型的转义形式都会失效,保留原样输出,不信你试试.length
    
    let name = "Bob";
    String.raw `Hi\n${name}!`;             
    // "Hi\\nBob!",内插表达式还可以正常运行
    
    String.raw({raw: "test"}, 0, 1, 2); 
    // "t0e1s2t",我认为你通常不需要把它当成普通函数来调用
    // String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);
    // String.raw({raw: ['Hi ', '!']}, 5);  // "Hi 5!"
    // 这个比较难理解,不要这样用
    

    Number新增特性

    • ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)0o(或0O)表示。
    0b111110111 === 503 // true
    0o767 === 503 // true
    
    • 如果要将0b和0o前缀的字符串数值转为十进制,要使用Number方法。
    Number('0b111');  // 7
    Number('0o10');   // 8
    
    • Number.isFinite()用来检查一个数值是否为有限的(finite)
    Number.isFinite(15);           // true
    Number.isFinite(0.8);          // true
    Number.isFinite(NaN);          // false
    Number.isFinite(Infinity);     // false
    Number.isFinite(-Infinity);    // false
    Number.isFinite('foo');        // false
    Number.isFinite('15');         // false
    Number.isFinite(true);         // false
    
    • Number.isNaN()用来检查一个值是否为NaN
    Number.isNaN(NaN)             // true
    Number.isNaN(15)              // false
    Number.isNaN('15')            // false
    Number.isNaN(true)            // false
    Number.isNaN(9/NaN)           // true
    Number.isNaN('true'/0)        // true
    Number.isNaN('true'/'true')   // true
    
    • ES6将全局方法parseInt()parseFloat(),移植到Number对象上面,行为完全保持不变。这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。
    // ES5的写法
    parseInt('12.34') // 12
    parseFloat('123.45#') // 123.45
    
    // ES6的写法
    Number.parseInt('12.34') // 12
    Number.parseFloat('123.45#') // 123.45
    
    • Number.isInteger()用来判断一个值是否为整数。需要注意的是,在JavaScript内部,整数和浮点数是同样的储存方法,所以3和3.0被视为同一个值。
    Number.isInteger(25)   // true
    Number.isInteger(25.0) // true;这个要注意
    Number.isInteger(25.1) // false
    Number.isInteger("15") // false
    Number.isInteger(true) // false
    
    • ES6Number对象上面,新增一个极小的常量Number.EPSILON。引入一个这么小的量的目的,在于为浮点数计算,设置一个误差范围。我们知道浮点数计算是不精确的。但是如果这个误差能够小于Number.EPSILON,我们就可以认为得到了正确结果。
    0.1 + 0.2
    // 0.30000000000000004
    
    0.1 + 0.2 - 0.3
    // 5.551115123125783e-17
    
    5.551115123125783e-17.toFixed(20)
    // '0.00000000000000005551'
    
    • ES6引入了Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。
    Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
    // true
    Number.MAX_SAFE_INTEGER === 9007199254740991
    // true
    
    Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER
    // true
    Number.MIN_SAFE_INTEGER === -9007199254740991
    // true
    
    Number.isSafeInteger('a')       // false
    Number.isSafeInteger(null)      // false
    Number.isSafeInteger(NaN)       // false
    Number.isSafeInteger(Infinity)  // false
    Number.isSafeInteger(-Infinity) // false
    
    Number.isSafeInteger(3)    // true
    Number.isSafeInteger(1.2)  // false
    Number.isSafeInteger(9007199254740990) // true
    Number.isSafeInteger(9007199254740992) // false
    
    Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false
    Number.isSafeInteger(Number.MIN_SAFE_INTEGER)     // true
    Number.isSafeInteger(Number.MAX_SAFE_INTEGER)     // true
    Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false
    
    • Math.trunc方法用于去除一个数的小数部分,返回整数部分。
    Math.trunc(4.1)      // 4
    Math.trunc(4.9)      // 4
    Math.trunc(-4.1)     // -4
    Math.trunc(-4.9)     // -4
    Math.trunc(-0.1234)  // -0
    
    // 对于非数值,Math.trunc内部使用Number方法将其先转为数值。
    Math.trunc('123.456')
    // 123
    
    // 对于空值和无法截取整数的值,返回NaN。
    Math.trunc(NaN);      // NaN
    Math.trunc('foo');    // NaN
    Math.trunc();         // NaN
    
    • Math.sign方法用来判断一个数到底是正数、负数、还是零。
    Math.sign(-5)        // -1;负数
    Math.sign(5)         // +1;正数
    Math.sign(0)         // +0
    Math.sign(-0)        // -0
    Math.sign(NaN)       // NaN
    Math.sign('foo');    // NaN
    Math.sign();         // NaN
    
    • Math.cbrt方法用于计算一个数的立方根。
    Math.cbrt(-1) // -1
    Math.cbrt(0)  // 0
    Math.cbrt(1)  // 1
    Math.cbrt(2)  // 1.2599210498948734
    
    // 对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。
    Math.cbrt('8')     // 2
    Math.cbrt('hello') // NaN
    
    • Math.hypot方法返回所有参数的平方和的平方根。
    Math.hypot(3, 4);        // 5;勾三股四玄五
    Math.hypot(3, 4, 5);     // 7.0710678118654755
    Math.hypot();            // 0
    Math.hypot(NaN);         // NaN
    Math.hypot(3, 4, 'foo'); // NaN
    Math.hypot(3, 4, '5');   // 7.0710678118654755
    Math.hypot(-3);          // 3
    
    • ES2016 新增了一个指数运算符(**)。指数运算符可以与等号结合,形成一个新的赋值运算符(**=)
    2 ** 2 // 4
    2 ** 3 // 8
    
    let a = 1.5;
    a **= 2;
    // 2.25;等同于 a = a * a; 
    
    let b = 4;
    b **= 3;
    // 64; 等同于 b = b * b * b;
    

    Array新增特性

    • Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。这个方法很有用,数组有map方法,这个可是函数式编程的重点内容啊。
    let arrayLike = {
        '0': 'a',
        '1': 'b',
        '2': 'c',
        length: 3
    };
    
    // ES5的写法
    var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
    
    // ES6的写法
    let arr2 = Array.from(arrayLike);    // ['a', 'b', 'c']
    
    // 实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。
    
    // NodeList对象
    let ps = document.querySelectorAll('p');
    Array.from(ps).forEach(function (p) {
      console.log(p);
    });
    
    // arguments对象
    function foo() {
      var args = Array.from(arguments);
      // ...
    }
    
    // 只要是部署了Iterator接口的数据结构,Array.from都能将其转为数组。
    Array.from('hello')
    // ['h', 'e', 'l', 'l', 'o']
    
    let namesSet = new Set(['a', 'b'])
    Array.from(namesSet) // ['a', 'b']
    
    // 所谓类似数组的对象,本质特征只有一点,即必须有length属性。
    Array.from({ length: 3 });
    // [ undefined, undefined, undefined ]
    
    // Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
    Array.from(arrayLike, x => x * x);
    // 等同于
    Array.from(arrayLike).map(x => x * x);
    
    Array.from([1, 2, 3], (x) => x * x)
    // [1, 4, 9]
    
    // Array.from()的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种Unicode字符,可以避免JavaScript将大于\uFFFF的Unicode字符,算作两个字符的bug。
    function countSymbols(string) {
      return Array.from(string).length;
    }
    
    • 扩展运算符(...)也可以将某些数据结构转为数组。这个也是比较方便的,可以多用。比如,函数定义直接写成function foo(...args) {}
    // arguments对象
    function foo() {
      var args = [...arguments];
    }
    
    // NodeList对象
    [...document.querySelectorAll('div')]
    
    • Array.of方法用于将一组值,转换为数组。基本上可以用来替代Array()new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一。这个函数可以多用用。
    Array.of()          // []
    Array.of(undefined) // [undefined]
    Array.of(1)         // [1]
    Array.of(1, 2)      // [1, 2]
    
    • 数组实例的find()和findIndex(),用来查找元素,可以和原先的indexOf()按照使用场景,灵活使用。
    // find 返回元素
    [1, 5, 10, 15].find(function(value, index, arr) {
      return value > 9;
    }) // 10
    
    // findeIndex返回下标
    [1, 5, 10, 15].findIndex(function(value, index, arr) {
      return value > 9;
    }) // 2
    
    // indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。
    [NaN].indexOf(NaN)
    // -1
    
    [NaN].findIndex(y => Object.is(NaN, y))
    // 0
    
    • fill方法使用给定值,填充一个数组。在初始化数组的时候需要用,减少一些for循环
    ['a', 'b', 'c'].fill(7)
    // [7, 7, 7]
    
    new Array(3).fill(7)
    // [7, 7, 7]
    
    • entries(),keys()和values()——用于遍历数组,结合for...of一起使用
    for (let index of ['a', 'b'].keys()) {
      console.log(index);
    }
    // 0
    // 1
    
    for (let elem of ['a', 'b'].values()) {
      console.log(elem);
    }
    // 'a'
    // 'b'
    
    for (let [index, elem] of ['a', 'b'].entries()) {
      console.log(index, elem);
    }
    // 0 "a"
    // 1 "b"
    
    • Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。该方法属于ES7,但Babel转码器已经支持。chrom浏览器可以使用。
      有这个之后,检查元素的存在与否,语义就更直观了,推荐使用。
    [1, 2, 3].includes(2);     // true
    [1, 2, 3].includes(4);     // false
    [1, 2, NaN].includes(NaN); // true
    
    • 数组的空位指,数组的某一个位置没有任何值。注意,空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。
    0 in [undefined, undefined, undefined] // true
    0 in [, , ,]                           // false
    
    • ES6则是明确将空位转为undefined。由于空位的处理规则非常不统一,所以建议避免出现空位。统一用undefined代替空位是个好习惯。
    // Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。
    Array.from(['a',,'b'])
    // [ "a", undefined, "b" ]
    
    // 扩展运算符(...)也会将空位转为undefined。
    [...['a',,'b']]
    // [ "a", undefined, "b" ]
    
    // entries()、keys()、values()、find()和findIndex()会将空位处理成undefined。
    // entries()
    [...[,'a'].entries()] // [[0,undefined], [1,"a"]]
    
    // keys()
    [...[,'a'].keys()] // [0,1]
    
    // values()
    [...[,'a'].values()] // [undefined,"a"]
    
    // find()
    [,'a'].find(x => true) // undefined
    
    // findIndex()
    [,'a'].findIndex(x => true) // 0
    

    函数新增特性

    • ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
    function log(x, y = 'World') {
      console.log(x, y);
    }
    
    log('Hello') // Hello World
    log('Hello', 'China') // Hello China
    log('Hello', '') // Hello
    
    • 参数默认值可以与解构赋值的默认值,结合起来使用。
    function fetch(url, { body = '', method = 'GET', headers = {} }) {
      console.log(method);
    }
    
    fetch('http://example.com', {})
    // "GET"
    
    fetch('http://example.com')
    // 报错
    

    上面的写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。

    function fetch(url, { method = 'GET' } = {}) {
      console.log(method);
    }
    
    fetch('http://example.com')
    // "GET"
    
    • 通常情况下,定义了默认值的参数,应该是函数的尾参数。

    • ES6 引入rest 参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。这种写法更简洁明确,推荐使用。

    // arguments变量的写法
    function sortNumbers() {
      return Array.prototype.slice.call(arguments).sort();
    }
    
    // rest参数的写法
    const sortNumbers = (...numbers) => numbers.sort();
    
    • 注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。函数的length属性,不包括rest参数。
    // 报错
    function f(a, ...b, c) {
      // ...
    }
    
    (function(a) {}).length  // 1
    (function(...a) {}).length  // 0
    (function(a, ...b) {}).length  // 1
    
    • 扩展运算符(spread)是三个点(...)。它好比rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。该运算符主要用于函数调用。
    function push(array, ...items) {
      array.push(...items);
    }
    
    function add(x, y) {
      return x + y;
    }
    
    var numbers = [4, 38];
    add(...numbers) // 42
    
    // 扩展运算符与正常的函数参数可以结合使用,非常灵活。
    function f(v, w, x, y, z) { }
    var args = [0, 1];
    f(-1, ...args, 2, ...[3]);
    
    • 由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。
    // ES5的写法
    Math.max.apply(null, [14, 3, 77])
    
    // ES6的写法
    Math.max(...[14, 3, 77])
    
    // 等同于
    Math.max(14, 3, 77);
    
    // ES5的写法
    var arr1 = [0, 1, 2];
    var arr2 = [3, 4, 5];
    Array.prototype.push.apply(arr1, arr2);
    
    // ES6的写法
    var arr1 = [0, 1, 2];
    var arr2 = [3, 4, 5];
    arr1.push(...arr2);
    
    • 函数的name属性,返回该函数的函数名。
    function foo() {}
    foo.name // "foo"
    
    • ES6允许使用“箭头”(=>)定义函数。
    // 正常函数写法
    var result = values.sort(function (a, b) {
      return a - b;
    });
    
    // 箭头函数写法
    var result = values.sort((a, b) => a - b);
    
    • 函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
      递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
    // 一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。
    function factorial(n) {
      if (n === 1) return 1;
      return n * factorial(n - 1);
    }
    
    factorial(5) // 120
    
    // 如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。
    function factorial(n, total = 1) {
      if (n === 1) return total;
      return factorial(n - 1, n * total);
    }
    
    factorial(5) // 120
    

    ES6 中只要使用尾递归,就不会发生栈溢出,相对节省内存。

    • ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

    • ES2017允许函数的最后一个参数有尾逗号(trailing comma)。这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。

    function clownsEverywhere(
      param1,
      param2,
    ) { /* ... */ }
    
    clownsEverywhere(
      'foo',
      'bar',
    );
    

    对象扩展

    • 属性、函数简洁表示法:keyvalue一样的话,只写一遍
    var ms = {};
    
    function getItem (key) {
      return key in ms ? ms[key] : null;
    }
    
    function setItem (key, value) {
      ms[key] = value;
    }
    
    function clear () {
      ms = {};
    }
    
    module.exports = { getItem, setItem, clear };
    // 等同于
    module.exports = {
      getItem: getItem,
      setItem: setItem,
      clear: clear
    };
    
    • 属性名表达式,用[]包起来
    var lastWord = 'last word';
    
    var a = {
      'first word': 'hello',
      [lastWord]: 'world'
    };
    
    a['first word'] // "hello"
    a[lastWord] // "world"
    a['last word'] // "world"
    
    • Object.is用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
    +0 === -0 //true
    NaN === NaN // false
    
    Object.is(+0, -0) // false
    Object.is(NaN, NaN) // true
    
    • Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。第一个参数是目标对象,后面的参数都是源对象。
    var target = { a: 1 };
    
    var source1 = { b: 2 };
    var source2 = { c: 3 };
    
    Object.assign(target, source1, source2);
    target // {a:1, b:2, c:3}
    
    • 扩展运算符可以用于合并两个对象。
    let ab = { ...a, ...b };
    // 等同于
    let ab = Object.assign({}, a, b);
    

    Module 的加载实现

    浏览器加载

    • 要加入type="module"属性,效果等同于打开了<script>标签的defer属性。
    <script type="module" src="foo.js"></script>
    <!-- 等同于 -->
    <script type="module" src="foo.js" defer></script>
    

    ES6 模块与 CommonJS 模块的差异

    • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

    • CommonJS 模块是运行时加载,ES6模块是编译时输出接口。

    ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
    ES6的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";
    ES6模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。

    export 命令

    一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。

    • 推荐的做法是在文件最后集中导出,使用者查看更方便
    // profile.js
    var firstName = 'Michael';
    var lastName = 'Jackson';
    var year = 1958;
    
    export {firstName, lastName, year};
    
    • 也可以直接在导出变量的前面加export关键字,这样做比较省事
    // profile.js
    export var firstName = 'Michael';
    export var lastName = 'Jackson';
    export var year = 1958;
    
    • export命令除了输出变量,还可以输出函数或类(class)。通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。
    function v1() { ... }
    function v2() { ... }
    
    export {
      v1 as streamV1,
      v2 as streamV2,
      v2 as streamLatestVersion
    };
    

    import 命令

    • 通常的做法是用一个{}导出具体的变量名或者函数名。只需要导出要用的函数或者变量,用不到的就不用导出了。
    // main.js
    import {firstName, lastName, year} from './profile';
    
    function setName(element) {
      element.textContent = firstName + ' ' + lastName;
    }
    
    • 如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。
    import { lastName as surname } from './profile';
    
    • 除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。这种写法不推荐。
    // circle.js
    
    export function area(radius) {
      return Math.PI * radius * radius;
    }
    
    export function circumference(radius) {
      return 2 * Math.PI * radius;
    }
    
    // main.js
    import * as circle from './circle';
    
    console.log('圆面积:' + circle.area(4));
    console.log('圆周长:' + circle.circumference(14));
    
    • 为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。需要注意的是,这时import命令后面,不使用大括号。默认导出的函数名或者类名都不起效果,名字由使用者在外面自己起,相当于匿名的函数或者类。这个有点像ES5的习惯,在有限的场景使用,不推荐。
    // export-default.js
    export default function () {
      console.log('foo');
    }
    
    // import-default.js
    import customName from './export-default';
    customName(); // 'foo'
    
    • 如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。
    export { foo, bar } from 'my_module';
    
    // 等同于
    import { foo, bar } from 'my_module';
    export { foo, bar };
    

    跨模块常量

    • 如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。
    // constants.js 模块
    export const A = 1;
    export const B = 3;
    export const C = 4;
    
    // test1.js 模块
    import * as constants from './constants';
    console.log(constants.A); // 1
    console.log(constants.B); // 3
    
    // test2.js 模块
    import {A, B} from './constants';
    console.log(A); // 1
    console.log(B); // 3
    
    • 如果要使用的常量非常多,可以建一个专门的constants目录,将各种常量写在不同的文件里面,保存在该目录下。
    // constants/db.js
    export const db = {
      url: 'http://my.couchdbserver.local:5984',
      admin_username: 'admin',
      admin_password: 'admin password'
    };
    
    // constants/user.js
    export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];
    
    // constants/index.js
    export {db} from './db';
    export {users} from './users';
    
    // main.js使用的时候,直接加载index.js就可以了。
    import {db, users} from './index';
    

    Set

    ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

    • Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
    // 例一
    const set = new Set([1, 2, 3, 4, 4]);
    [...set]
    // [1, 2, 3, 4]
    
    // 例二
    const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
    items.size // 5
    
    • Set 结构的实例有以下属性。
    Set.prototype.constructor:构造函数,默认就是Set函数。
    Set.prototype.size:返回Set实例的成员总数。
    
    • Set 实例的方法
    add(value):添加某个值,返回Set结构本身。
    delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
    has(value):返回一个布尔值,表示该值是否为Set的成员。
    clear():清除所有成员,没有返回值。
    
    • 下面是一个对比,看看在判断是否包括一个键上面,Object结构和Set结构的写法不同。
    // 对象的写法
    const properties = {
      'width': 1,
      'height': 1
    };
    
    if (properties[someName]) {
      // do something
    }
    
    // Set的写法
    const properties = new Set();
    
    properties.add('width');
    properties.add('height');
    
    if (properties.has(someName)) {
      // do something
    }
    
    • Array.from方法可以将 Set 结构转为数组。这就提供了去除数组重复成员的另一种方法。
    function dedupe(array) {
      return Array.from(new Set(array));
    }
    
    dedupe([1, 1, 2, 3]) // [1, 2, 3]
    
    • 如果想在遍历操作中,同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;另一种是利用Array.from方法。
    // 方法一
    let set = new Set([1, 2, 3]);
    set = new Set([...set].map(val => val * 2));
    // set的值是2, 4, 6
    
    // 方法二
    let set = new Set([1, 2, 3]);
    set = new Set(Array.from(set, val => val * 2));
    // set的值是2, 4, 6
    
    • 扩展运算符(...)可以展开set为数组,也展示了一种去除数组重复成员的方法。
    // 去除数组的重复成员
    [...new Set(array)]
    

    Map

    JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
    为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

    • size属性返回 Map 结构的成员总数。
    const map = new Map();
    map.set('foo', true);
    map.set('bar', false);
    
    map.size // 2
    
    • set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
    const m = new Map();
    
    m.set('edition', 6)        // 键是字符串
    m.set(262, 'standard')     // 键是数值
    m.set(undefined, 'nah')    // 键是 undefined
    

    set方法返回的是当前的Map对象,因此可以采用链式写法。

    let map = new Map()
      .set(1, 'a')
      .set(2, 'b')
      .set(3, 'c');
    
    • get方法读取key对应的键值,如果找不到key,返回undefined。
    const m = new Map();
    
    const hello = function() {console.log('hello');};
    m.set(hello, 'Hello ES6!') // 键是函数
    
    m.get(hello)  // Hello ES6!
    
    • has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
    const m = new Map();
    
    m.set('edition', 6);
    m.set(262, 'standard');
    m.set(undefined, 'nah');
    
    m.has('edition')     // true
    m.has('years')       // false
    m.has(262)           // true
    m.has(undefined)     // true
    
    • delete方法删除某个键,返回true。如果删除失败,返回false。
    const m = new Map();
    m.set(undefined, 'nah');
    m.has(undefined)     // true
    
    m.delete(undefined)
    m.has(undefined)       // false
    
    • clear方法清除所有成员,没有返回值。
    let map = new Map();
    map.set('foo', true);
    map.set('bar', false);
    
    map.size // 2
    map.clear()
    map.size // 0
    
    • Map 结构原生提供三个遍历器生成函数和一个遍历方法。
      keys():返回键名的遍历器。
      values():返回键值的遍历器。
      entries():返回所有成员的遍历器。
      forEach():遍历 Map 的所有成员。
    const map = new Map([
      ['F', 'no'],
      ['T',  'yes'],
    ]);
    
    for (let key of map.keys()) {
      console.log(key);
    }
    // "F"
    // "T"
    
    for (let value of map.values()) {
      console.log(value);
    }
    // "no"
    // "yes"
    
    for (let item of map.entries()) {
      console.log(item[0], item[1]);
    }
    // "F" "no"
    // "T" "yes"
    
    // 或者
    for (let [key, value] of map.entries()) {
      console.log(key, value);
    }
    // "F" "no"
    // "T" "yes"
    
    // 等同于使用map.entries()
    for (let [key, value] of map) {
      console.log(key, value);
    }
    // "F" "no"
    // "T" "yes"
    

    Class 的基本语法

    ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

    //定义类
    class Point {
    
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    
      toString() {
        return '(' + this.x + ', ' + this.y + ')';
      }
    
    }
    
    var point = new Point(2, 3);
    
    point.toString() // (2, 3)
    
    • 与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
    class MyClass {
      constructor() {
        // ...
      }
      get prop() {
        return 'getter';
      }
      set prop(value) {
        console.log('setter: '+value);
      }
    }
    
    let inst = new MyClass();
    
    inst.prop = 123;
    // setter: 123
    
    inst.prop
    // 'getter'
    
    • 如果某个方法之前加上星号(*),就表示该方法是一个 Generator 函数。
    class Foo {
      constructor(...args) {
        this.args = args;
      }
      * [Symbol.iterator]() {
        for (let arg of this.args) {
          yield arg;
        }
      }
    }
    
    for (let x of new Foo('hello', 'world')) {
      console.log(x);
    }
    // hello
    // world
    
    • 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    Foo.classMethod() // 'hello'
    
    var foo = new Foo();
    foo.classMethod()
    // TypeError: foo.classMethod is not a function
    
    • 类的实例属性可以用等式,写入类的定义之中。
    class MyClass {
      myProp = 42;
    
      constructor() {
        console.log(this.myProp); // 42
      }
    }
    
    • 类的静态属性只要在上面的实例属性写法前面,加上static关键字就可以了。
    class MyClass {
      static myStaticProp = 42;
    
      constructor() {
        console.log(MyClass.myStaticProp); // 42
      }
    }
    
    • Class 可以通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
    class Point {
    }
    
    class ColorPoint extends Point {
      constructor(x, y, color) {
        super(x, y); // 调用父类的constructor(x, y)
        this.color = color;
      }
    
      toString() {
        return this.color + ' ' + super.toString(); // 调用父类的toString()
      }
    }
    

    编程风格

    • let 取代 var;
      在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。
    // bad
    var a = 1, b = 2, c = 3;
    
    // good
    const a = 1;
    const b = 2;
    const c = 3;
    
    // best
    const [a, b, c] = [1, 2, 3];
    
    • 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
    // bad
    const a = "foobar";
    const b = 'foo' + a + 'bar';
    
    // acceptable
    const c = `foobar`;
    
    // good
    const a = 'foobar';
    const b = `foo${a}bar`;
    const c = 'foobar';
    

    解构赋值

    • 使用数组成员对变量赋值时,优先使用解构赋值。
    const arr = [1, 2, 3, 4];
    
    // bad
    const first = arr[0];
    const second = arr[1];
    
    // good
    const [first, second] = arr;
    
    • 函数的参数如果是对象的成员,优先使用解构赋值。
    // bad
    function getFullName(user) {
      const firstName = user.firstName;
      const lastName = user.lastName;
    }
    
    // good
    function getFullName(obj) {
      const { firstName, lastName } = obj;
    }
    
    // best
    function getFullName({ firstName, lastName }) {
    }
    
    • 如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。
    // bad
    function processInput(input) {
      return [left, right, top, bottom];
    }
    
    // good
    function processInput(input) {
      return { left, right, top, bottom };
    }
    
    const { left, right } = processInput(input);
    

    对象

    • 单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。
    // bad
    const a = { k1: v1, k2: v2, };
    const b = {
      k1: v1,
      k2: v2
    };
    
    // good
    const a = { k1: v1, k2: v2 };
    const b = {
      k1: v1,
      k2: v2,
    };
    
    • 对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。
    // bad
    const a = {};
    a.x = 3;
    
    // if reshape unavoidable
    const a = {};
    Object.assign(a, { x: 3 });
    
    // good
    const a = { x: null };
    a.x = 3;
    
    • 如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。
    // bad
    const obj = {
      id: 5,
      name: 'San Francisco',
    };
    obj[getKey('enabled')] = true;
    
    // good
    const obj = {
      id: 5,
      name: 'San Francisco',
      [getKey('enabled')]: true,
    };
    
    • 对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。
    var ref = 'some value';
    
    // bad
    const atom = {
      ref: ref,
    
      value: 1,
    
      addValue: function (value) {
        return atom.value + value;
      },
    };
    
    // good
    const atom = {
      ref,
    
      value: 1,
    
      addValue(value) {
        return atom.value + value;
      },
    };
    

    数组

    • 使用扩展运算符(...)拷贝数组。
    // bad
    const len = items.length;
    const itemsCopy = [];
    let i;
    
    for (i = 0; i < len; i++) {
      itemsCopy[i] = items[i];
    }
    
    // good
    const itemsCopy = [...items];
    
    • 使用Array.from方法,将类似数组的对象转为数组。
    const foo = document.querySelectorAll('.foo');
    const nodes = Array.from(foo);
    

    函数

    • 立即执行函数可以写成箭头函数的形式。
    (() => {
      console.log('Welcome to the Internet.');
    })();
    
    • 那些需要使用函数表达式的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了this。
    // bad
    [1, 2, 3].map(function (x) {
      return x * x;
    });
    
    // good
    [1, 2, 3].map((x) => {
      return x * x;
    });
    
    // best
    [1, 2, 3].map(x => x * x);
    
    • 箭头函数取代Function.prototype.bind,不应再用self/_this/that绑定 this。
    // bad
    const self = this;
    const boundMethod = function(...params) {
      return method.apply(self, params);
    }
    
    // acceptable
    const boundMethod = method.bind(this);
    
    // best
    const boundMethod = (...params) => method.apply(this, params);
    
    • 所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。
    // bad
    function divide(a, b, option = false ) {
    }
    
    // good
    function divide(a, b, { option = false } = {}) {
    }
    
    • 不要在函数体内使用arguments变量,使用rest运算符(...)代替。因为rest运算符显式表明你想要获取参数,而且arguments是一个类似数组的对象,而rest运算符可以提供一个真正的数组。
    // bad
    function concatenateAll() {
      const args = Array.prototype.slice.call(arguments);
      return args.join('');
    }
    
    // good
    function concatenateAll(...args) {
      return args.join('');
    }
    
    • 使用默认值语法设置函数参数的默认值。
    // bad
    function handleThings(opts) {
      opts = opts || {};
    }
    
    // good
    function handleThings(opts = {}) {
      // ...
    }
    

    Map结构

    注意区分Object和Map,只有模拟现实世界的实体对象时,才使用Object。如果只是需要key: value的数据结构,使用Map结构。因为Map有内建的遍历机制。

    let map = new Map(arr);
    
    for (let key of map.keys()) {
      console.log(key);
    }
    
    for (let value of map.values()) {
      console.log(value);
    }
    
    for (let item of map.entries()) {
      console.log(item[0], item[1]);
    }
    

    Class

    • 总是用Class,取代需要prototype的操作。因为Class的写法更简洁,更易于理解。
    // bad
    function Queue(contents = []) {
      this._queue = [...contents];
    }
    Queue.prototype.pop = function() {
      const value = this._queue[0];
      this._queue.splice(0, 1);
      return value;
    }
    
    // good
    class Queue {
      constructor(contents = []) {
        this._queue = [...contents];
      }
      pop() {
        const value = this._queue[0];
        this._queue.splice(0, 1);
        return value;
      }
    }
    
    • 使用extends实现继承,因为这样更简单,不会有破坏instanceof运算的危险。
    // bad
    const inherits = require('inherits');
    function PeekableQueue(contents) {
      Queue.apply(this, contents);
    }
    inherits(PeekableQueue, Queue);
    PeekableQueue.prototype.peek = function() {
      return this._queue[0];
    }
    
    // good
    class PeekableQueue extends Queue {
      peek() {
        return this._queue[0];
      }
    }
    

    模块

    • 使用import取代require。
    // bad
    const moduleA = require('moduleA');
    const func1 = moduleA.func1;
    const func2 = moduleA.func2;
    
    // good
    import { func1, func2 } from 'moduleA';
    
    • 使用export取代module.exports
      如果模块只有一个输出值,就使用export default,如果模块有多个输出值,就不使用export default,export default与普通的export不要同时使用。
    // commonJS的写法
    var React = require('react');
    
    var Breadcrumbs = React.createClass({
      render() {
        return <nav />;
      }
    });
    
    module.exports = Breadcrumbs;
    
    // ES6的写法
    import React from 'react';
    
    class Breadcrumbs extends React.Component {
      render() {
        return <nav />;
      }
    };
    
    export default Breadcrumbs;
    
    • 不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。
    // bad
    import * as myObject './importModule';
    
    // good
    import myObject from './importModule';
    
    • 如果模块默认输出一个函数,函数名的首字母应该小写。
    function makeStyleGuide() {
    }
    
    export default makeStyleGuide;
    
    • 如果模块默认输出一个对象,对象名的首字母应该大写。
    const StyleGuide = {
      es6: {
      }
    };
    
    export default StyleGuide;
    

    ESLint的使用

    • 安装ESLint。
    $ npm i -g eslint
    
    • 安装Airbnb语法规则。
    $ npm i -g eslint-config-airbnb
    
    • 在项目的根目录下新建一个.eslintrc文件,配置ESLint。
    {
      "extends": "eslint-config-airbnb"
    }
    
    • 使用ESLint检查文件。
    $ eslint xxx.js
    

    相关文章

      网友评论

          本文标题:JavaScript:ES6的一些习惯

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