美文网首页
es6学习笔记

es6学习笔记

作者: EarthChen | 来源:发表于2017-08-06 18:11 被阅读32次

    ECMAScript 6.0( 以下简称ES6) 是JavaScript语言的下一代标准。

    ECMAScript和JavaScript的关系是, 前者是后者的规格, 后者是前者的一种实现( 另外的ECMAScript方言还有Jscript和ActionScript) 。 日常场合, 这两个词是可以互换的。

    在前端工程化的现在,学习es6还是有必要的。
    本文为个人根据阮老师的es6标准入门学习笔记。

    ES6

    let和const命令

    let

    1. let用来声明变量。 它的用法类似于var, 但是所声明的变量, 只在let命令所在的代码块内有效
    2. 在循环中,如果变量i是var声明的, 在全局范围内都有效。 所以每一次循环,新的i值都会覆盖旧值,如果变量i是let声明的, 当前的i只在本轮循环有效, 所以每一次循环的i其实都是一个新的变量。
    3. let不像var那样会发生“变量提升”现象。 所以, 变量一定要在声明后使用, 否则报错
    4. let不允许在相同作用域内, 重复声明同一个变量
    //所声明的变量, 只在let命令所在的代码块内有效
    {
        let a = 10;
        var b = 1;
        console.log('a=' + a + '\nb=' + b);
    
    }
    
    console.log('let代码块外b=' + b);
    // console.log('let代码块外b=' + a);
    
    arr = [1, 2, 3, 4, 5, 6, 4];
    for (let i = 0; i < arr.length; i++) {
        console.log(i);
    }
    
    /**
     * 变量i是var声明的, 在全局范围内都有效。 所以每一次循环,
     * 新的i值都会覆盖旧值, 导致最后输出的是最后一轮的i的值
     */
    var a = [];
    for (var i = 0; i < 10; i++) {
        a[i] = i;
    }
    console.log(i);
    
    /**
     * 变量i是let声明的, 当前的i只在本轮循环有效, 所以每一次循环的i其实都是一个新的变量, 所以最后输出的是6
     * @type {Array}
     */
    var a = [];
    for (let i = 0; i < 10; i++) {
        a[i] = i;
    }
    console.log(a[6]);
    
    /**
     * 只要块级作用域内存在let命令, 它所声明的变量就“绑定”( binding) 这个区域, 不再受外部的影响
     *存在全局变量tmp, 但是块级作用域内let又声明了一个局部变量tmp, 导致后者绑定这个块级作用域, 所以在let声明变量前, 对tmp赋
     值会报错。
     如果区块中存在let和const命令, 这个区块对这些命令声明的变量, 从一开始就形成了封闭作用域。 凡是在声明之前就使用这些变
     量, 就会报错。
     总之, 在代码块内, 使用let命令声明变量之前, 该变量都是不可用的
     暂时性死区”也意味着typeof不再是一个百分之百安全的操作
     * @type {number}
     */
    var tmp = 123;
    if (true) {
        //tmp = 'abc'; // ReferenceError
        let tmp;
    }
    
    /**
     *调用bar函数之所以报错
     *参数x默认值等于另一个参数y, 而此时y还没有声明
     * @param x
     * @param y
     * @returns {[null,null]}
     */
    function bar(x = y, y = 2) {
        return [x, y];
    }
    
    //bar();  //报错
    
    function bar(x = 2, y = x) {
        return [x, y];
    }
    bar();
    
    /**
     * let不允许在相同作用域内, 重复声明同一个变量,都会报错
     */
    // function () {
    //     let a = 10;
    //     var a = 1;
    // }
    //
    // function () {
    //     let a = 10;
    //     let a = 1;
    // }
    

    结果为:


    let结果

    const

    1. const声明一个只读的常量。 一旦声明, 常量的值就不能改变。
    2. const声明的变量不得改变值, 这意味着, const一旦声明变量, 就必须立即初始化, 不能留到以后赋值。
    3. onst的作用域与let命令相同: 只在声明所在的块级作用域内有效。
    4. const命令声明的常量也是不提升, 同样存在暂时性死区, 只能在声明的位置后面使用
    5. const声明的常量, 也与let一样不可重复声明
    const PI=3.1415;
    
    console.log(PI);
    /**
     * 量a是一个数组, 这个数组本身是可写的, 但是如果将另一个数组赋值给a, 就会报错
     * @type {Array}
     */
    const a=[];
    a.push('hello');
    //a = ['Dave'];
    

    全局对象的属性

    1. var命令和function命令声明的全局变量, 依旧是全局对象的属性
    2. let命令、 const命令、 class命令声明的全局变量, 不属于全局对象的属性
    var a = 1;
    // 如果在Node的REPL环境, 可以写成global.a
    // 或者采用通用方法, 写成this.a
    this.a // 1
    let b = 1;
    //window.b // undefined
    

    变量的解构赋值

    数组的解构赋值

    1. ES6允许按照一定模式, 从数组和对象中提取值, 对变量进行赋值, 这被称为解构( Destructuring)
    /**
     * 同时给abc赋值可以用一下方式
     */
    var [a, b, c] = [1, 2, 3];
    console.log('a='+a+'  b='+b+'  c='+c);
    
    /**
     * 这种写法属于“模式匹配”, 只要等号两边的模式相同, 左边的变量就会被赋予对应的值
     */
    let [foo, [[bar], baz]] = [1, [[2], 3]];
    console.log('foo='+foo+'  bar='+bar+'  baz='+baz);
    
    let [ , , third] = ["foo", "bar", "baz"];
    console.log('third='+third);
    
    let [x, , y] = [1, 2, 3];
    console.log('x='+x+'  y='+y);
    
    let [head, ...tail] = [1, 2, 3, 4];
    console.log('head='+head+' tail='+tail);
    
    /**
     *另一种情况是不完全解构, 即等号左边的模式, 只匹配一部分的等号右边的数组
     */
    let [x2, y2] = [1, 2, 3];
    console.log('x2='+x2+'  y2='+y2);
    
    let [a2, [b2], c2] = [1, [2, 3], 4];
    console.log('a2='+a2+'  b='+b2+'  c2='+c2);
    

    结果为:


    数组的解构赋值

    注:

    • 只要某种数据结构具有Iterator接口, 都可以采用数组形式的解构赋值
    • 解构赋值允许指定默认值,ES6内部使用严格相等运算符( ===) , 判断一个位置是否有值。 所以, 如果一个数组成员不严格等于undefined, 默认值是不会生效的

    对象的解构赋值

    解构不仅可以用于数组, 还可以用于对象

    1. 对象的解构与数组有一个重要的不同。 数组的元素是按次序排列的, 变量的取值由它的位置决定; 而对象的属性没有次序, 变量必须与属性同名, 才
      能取到正确的值。
    2. 对象的解构也可以指定默认值。默认值生效的条件是, 对象的属性值严格等于undefined。
    var {foo, bar} = {foo: "aaa", bar: "bbb"};
    console.log('foo=' + foo + ' bar=' + bar);
    
    var {foo2: foo2, bar2: bar2} = {foo2: "aaa", bar2: "bbb"};
    console.log('foo2=' + foo2 + ' bar2=' + bar2);
    /**
     * 真正被赋值的是变量baz, 而不是模式foo。
     */
    var {foo: baz} = {foo: "aaa", bar: "bbb"};
    console.log(baz);
    
    /**
     * 和数组一样, 解构也可以用于嵌套结构的对象
     * 这时p是模式, 不是变量, 因此不会被赋值
     * @type {{p: [string,null]}}
     */
    var obj = {
        p: [
            'Hello',
            {y: 'World'}
        ]
    };
    var {p: [x, {y}]} = obj;
    console.log('x=' + x + ' y=' + y);
    /**
     * line和column是变量, loc和start都是模式, 不会被赋值
     * @type {{loc: {start: {line: number, column: number}}}}
     */
    var node = {
        loc: {
            start: {
                line: 1,
                column: 5
            }
        }
    };
    var {loc: {start: {line, column}}} = node;
    console.log('line=' + line + ' column=' + column);
    
    /**
     * 嵌套赋值
     *  let命令下面一行的圆括号是必须的, 否则会报错。 因为解析器会将起首的大括号, 理解成一个代码块, 而不是赋值语句。
     * @type {{}}
     */
    let obj2 = {};
    let arr = [];
    ({foo: obj2.prop, bar: arr[0]} = {foo: 123, bar: true});
    console.log('obj2.prop='+obj2.prop+' arr='+arr);
    
    /**
     * 对象的解构赋值, 可以很方便地将现有对象的方法, 赋值到某个变量
     * 将Math对象的对数、 正弦、 余弦三个方法, 赋值到对应的变量上
     */
    let { log, sin, cos } = Math;
    

    结果为:


    对象解析赋值

    字符串的解构赋值

    字符串也可以解构赋值。 这是因为此时, 字符串被转换成了一个类似数组的对象

    注:类似数组的对象都有一个length属性, 因此还可以对这个属性解构赋值

    let [a, b, c, d, e] = 'hello';
    let {length : len} = 'hello';
    console.log(a+b+c+d+e+'  length='+len);
    

    结果:

    hello  length=5
    

    函数参数的解构赋值

    函数的参数也可以使用解构赋值。

    函数add的参数表面上是一个数组, 但在传入参数的那一刻, 数组参数就被解构成变量x和y

    function add([x, y]) {
        return x + y;
    }
    
    console.log(add([1, 2]));
    
    /**
     * 使用默认值
     * @param x
     * @param y
     * @returns {[null,null]}
     */
    function move({x = 0, y = 0} = {}) {
        return [x, y];
    }
    

    圆括号问题

    只要有可能导致解构的歧义, 就不得使用圆括号

    不能使用圆括号的情况

    1. 变量声明语句中, 不能带有圆括号
    2. 函数参数中, 模式不能带有圆括号。
    3. 赋值语句中, 不能将整个模式, 或嵌套模式中的一层, 放在圆括号之中。
    // 全部报错
    var [(a)] = [1];
    var {x: (c)} = {};
    var ({x: c}) = {};
    var {(x: c)} = {};
    var {(x): c} = {};
    var { o: ({ p: p }) } = { o: { p: 2 } };
    
    // 报错
    function f([(z)]) { return z; }
    
    // 全部报错
    ({ p: a }) = { p: 42 };
    ([a]) = [5];
    

    可以使用圆括号的情况

    赋值语句的非模式部分, 可以使用圆括号。

    [(b)] = [3]; // 正确
    ({ p: (d) } = {}); // 正确
    [(parseInt.prop)] = [3]; // 正确
    

    变量的解构赋值用途

    交换变量的值

    [x, y] = [y, x];
    

    从函数返回多个值

    // 返回一个数组
    function example() {
        return [1, 2, 3];
    }
    
    var [a, b, c] = example();
    
    // 返回一个对象
    function example() {
        return {
            foo: 1,
            bar: 2
        };
    }
    
    var {foo, bar} = example();
    

    提取JSON数据

    var jsonData = {
        id: 42,
        status: "OK",
        data: [867, 5309]
    };
    let {id, status, data: number} = jsonData;
    

    遍历Map结构

    任何部署了Iterator接口的对象, 都可以用for...of循环遍历。 Map结构原生支持Iterator接口, 配合变量的解构赋值, 获取键名和键值就非常方便。

    var map = new Map();
    map.set('first', 'hello');
    map.set('second', 'world');
    for (let [key, value] of map) {
        console.log(key + " is " + value);
    }
    

    如果只想获取键名, 或者只想获取键值, 可以写成下面这样

    // 获取键名
    for (let [key] of map) {
        // ...
    } 
    // 获取键值
    for (let [,value] of map) {
        // ...
    }
    

    字符串

    字符串的遍历器接口

    字符串可以被for...of循环遍历

    for (let codePoint of 'hello') {
        console.log(codePoint)
    }
    

    常用的新方法

    includes(), startsWith(), endsWith()

    • includes(): 返回布尔值, 表示是否找到了参数字符串
    • startsWith(): 返回布尔值, 表示参数字符串是否在源字符串的头部
    • endsWith(): 返回布尔值, 表示参数字符串是否在源字符串的尾部。
    var s = 'Hello world!';
    s.startsWith('Hello') // true
    s.endsWith('!') // true
    s.includes('o') // true
    
    • 三个方法都支持第二个参数, 表示开始搜索的位置
    var s = 'Hello world!';
    s.startsWith('world', 6) // true 
    s.endsWith('Hello', 5) // true
    s.includes('Hello', 6) // false
    

    repeat()

    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}?`
    
    //可以放入任意的JavaScript表达式, 可以进行运算
    var x = 1;
    var y = 2;
    `${x} + ${y} = ${x + y}`
    // "1 + 2 = 3"
    
    //调用函数
    function fn() {
        return "Hello World";
    } 
    `foo ${fn()} bar`
    

    注:

    • 如果在模板字符串中需要使用反引号, 则前面要用反斜杠转义
    • 如果使用模板字符串表示多行字符串, 所有的空格和缩进都会被保留在输出之中
    • 模板字符串中嵌入变量, 需要将变量名写在${}之中
    • 大括号内部可以放入任意的JavaScript表达式, 可以进行运算, 以及引用对象属性
    • 模板字符串之中还能调用函数

    数值扩展

    Math对象的扩展

    Math.trunc()

    Math.trunc方法用于去除一个数的小数部分, 返回整数部分

    Math.sign()

    Math.sign方法用来判断一个数到底是正数、 负数、 还是零

    Math.cbrt()

    Math.cbrt方法用于计算一个数的立方根

    数组的扩展

    Array.from()

    Array.from方法用于将两类对象转为真正的数组: 类似数组的对象( array-like object) 和可遍历( iterable) 的对象( 包括ES6新增的数据结构Set和
    Map) 。

    //常见的类似数组的对象是DOM操作返回的NodeList集合
    let ps = document.querySelectorAll('p');
    Array.from(ps).forEach(function (p) {
        console.log(p);
    });
    
    //只要是部署了Iterator接口的数据结构, Array.from都能将其转为数组
    Array.from('hello')
    let namesSet = new Set(['a', 'b'])
    Array.from(namesSet) // ['a', 'b']
    

    数组实例的find()和findIndex()

    数组实例的find方法, 用于找出第一个符合条件的数组成员。 它的参数是一个回调函数, 所有数组成员依次执行该回调函数, 直到找出第一个返回值
    为true的成员, 然后返回该成员。 如果没有符合条件的成员, 则返回undefined。

    数组实例的fill()

    fill方法使用给定值, 填充一个数组

    • fill方法还可以接受第二个和第三个参数, 用于指定填充的起始位置和结束位置

    数组实例的entries(), keys()和values()

    可以用for...of循环进行遍历, 唯一的区别是keys()是对键名的遍历、 values()是对键值的遍历, entries()是对键值对的遍历。

    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"
    
    //如果不使用for...of循环, 可以手动调用遍历器对象的next方法, 进行遍历
    let letter = ['a', 'b', 'c'];
    let entries = letter.entries();
    console.log(entries.next().value); // [0, 'a']
    console.log(entries.next().value); // [1, 'b']
    console.log(entries.next().value); // [2, 'c']
    

    数组实例的includes()

    表示某个数组是否包含给定的值, 与字符串的includes方法类似

    [1, 2, 3].includes(2); // true
    [1, 2, 3].includes(4); // false
    [1, 2, NaN].includes(NaN); // true
    

    该方法的第二个参数表示搜索的起始位置, 默认为0。 如果第二个参数为负数, 则表示倒数的位置, 如果这时它大于数组长度( 比如第二个参数为-4,
    但数组长度为3) , 则会重置为从0开始

    [1, 2, 3].includes(3, 3); // false
    [1, 2, 3].includes(3, -1); // true
    

    函数的扩展

    函数参数的默认值

    ES6允许为函数的参数设置默认值, 即直接写在参数定义的后面

    function log(x, y = 'World') {
        console.log(x, y);
    } 
    log('Hello') // Hello World
    log('Hello', 'China') // Hello China
    log('Hello', '') // Hello
    

    rest参数

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

    //利用rest参数, 可以向该函数传入任意数目的参数
    function add(...values) {
        let sum = 0;
        for (var val of values) {
            sum += val;
        } 
        return sum;
    } 
    
    add(2, 5, 3) // 10
    
    //rest参数中的变量代表一个数组, 所以数组特有的方法都可以用于这个变量
    function push(array, ...items) {
        items.forEach(function(item) {
            array.push(item);
            console.log(item);
        });
    }
    var a = [];
    push(a, 1, 2, 3)
    

    注:rest参数之后不能再有其他参数( 即只能是最后一个参数)

    扩展运算符

    扩展运算符( spread) 是三个点( ...) 。 它好比rest参数的逆运算, 将一个数组转为用逗号分隔的参数序列。

    console.log(...[1, 2, 3])
    // 1 2 3
    console.log(1, ...[2, 3, 4], 5)
    // 1 2 3 4 5
    [...document.querySelectorAll('div')]
    // [<div>, <div>, <div>]
    
    //合并数组
    [1, 2, ...more]
    [...arr1, ...arr2, ...arr3]
    
    //转为真正的数组。
    var nodeList = document.querySelectorAll('div');
    var array = [...nodeList];
    
    //map转数组
    let map = new Map([
        [1, 'one'],
        [2, 'two'],
        [3, 'three'],
    ]);
    let arr = [...map.keys()]; // [1, 2, 3]
    

    箭头函数

    使用“箭头”( =>) 定义函数

    var f = v => v;
    //等同于
    // var f = function (v) {
    //     return v;
    // };
    
    //如果箭头函数不需要参数或需要多个参数, 就使用一个圆括号代表参数部分
    var f2 = () => 5;
    // 等同于
    // var f = function () { return 5 };
    
    var sum = (num1, num2) => num1 + num2;
    // 等同于
    // var sum = function(num1, num2) {
    //     return num1 + num2;
    // };
    
    //如果箭头函数的代码块部分多于一条语句, 就要使用大括号将它们括起来, 并且使用return语句返回
    var sum = (num1, num2) => {
        return num1 + num2;
    }
    
    //由于大括号被解释为代码块, 所以如果箭头函数直接返回一个对象, 必须在对象外面加上括号
    var getTempItem = id => ({id: id, name: "Temp"});
    console.log(getTempItem(5));
    
    const full = ({ first, last }) => first + ' ' + last;
    // 等同于
    // function full(person) {
    //     return person.first + ' ' + person.last;
    // }
    
    console.log([1,2,3].map(x => x * x));
    

    注:

    • 函数体内的this对象, 就是定义时所在的对象, 而不是使用时所在的对象。
    • 不可以当作构造函数, 也就是说, 不可以使用new命令, 否则会抛出一个错误

    函数绑定

    箭头函数可以绑定this对象, 大大减少了显式绑定this对象的写法( call、 apply、 bind) 。

    函数绑定运算符是并排的两个双冒号( ::) , 双冒号左边是一个对象, 右边是一个函数。 该运算符会自动将左边的对象, 作为上下文环境( 即this对象) , 绑定到右边的函数上面。

    foo::bar;
    // 等同于
    bar.bind(foo);
    foo::bar(...arguments);
    // 等同于
    bar.apply(foo, arguments);
    
    //如果双冒号左边为空, 右边是一个对象的方法, 则等于将该方法绑定在该对象上面。
    var method = obj::obj.foo;
    // 等同于
    var method = ::obj.foo;
    let log = ::console.log;
    // 等同于
    var log = console.log.bind(console);
    

    尾调用优化

    尾调用

    某个函数的最后一步是调用另一个函数

    function f(x){
        return g(x);
    }
    //函数f的最后一步是调用函数g, 这就叫尾调用
    
    function f(x) {
        if (x > 0) {
            return m(x)
        } 
        return n(x);
    }
    //函数m和n都属于尾调用, 因为它们都是函数f的最后一步操作
    

    Set和Map数据结构

    Set

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

    //Set本身是一个构造函数, 用来生成Set数据结构
    let s = new Set();
    [2, 3, 5, 4, 5, 2, 2].map(x => s.add(x));
    for (let i of s) {
        console.log(i);
    }
    
    //Set函数接受数组作为参数
    //Set函数可以接受一个数组( 或类似数组的对象) 作为参数, 用来初始化
    var set = new Set([1, 2, 3, 4, 4]);
    
    // 例二
    var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
    console.log(items.size) // 5
    
    //接受类似数组的对象作为参数
    // 例三
    function divs() {
        return [...document.querySelectorAll('div')];
    }
    
    var set2 = new Set(divs());
    console.log(set2.size); // 56
    // 类似于
    divs().forEach(div => set.add(div));
    console.log(set2.size); // 56
    

    Set实例的属性和方法

    属性
    • Set.prototype.constructor: 构造函数, 默认就是Set函数
    • Set.prototype.size: 返回Set实例的成员总数。
    方法
    1. 操作方法:
    • add(value): 添加某个值, 返回Set结构本身
    • delete(value): 删除某个值, 返回一个布尔值, 表示删除是否成功
    • has(value): 返回一个布尔值, 表示该值是否为Set的成员
    • clear(): 清除所有成员, 没有返回值
    s.add(1).add(2).add(2);
    // 注意2被加入了两次
    s.size // 2
    s.has(1) // true
    s.has(2) // true
    s.has(3) // false
    s.delete(2);
    s.has(2) // false
    
    1. 遍历方法
    • keys(): 返回键名的遍历器
    • values(): 返回键值的遍历器
    • entries(): 返回键值对的遍历器
    • forEach(): 使用回调函数遍历每个成员
    let set = new Set(['red', 'green', 'blue']);
    
    for (let item of set.keys()) {
        console.log(item);
    } 
    // red
    // green
    // blue
    
    for (let item of set.values()) {
        console.log(item);
    } 
    // red
    // green
    // blue
    
    for (let item of set.entries()) {
        console.log(item);
    } 
    // ["red", "red"]
    // ["green", "green"]
    // ["blue", "blue"]
    
    let set = new Set([1, 2, 3]);
    set.forEach((value, key) => console.log(value * 2) )
    // 2
    // 4
    // 6
    

    注:由于Set结构没有键名, 只有键值( 或者说键名和键值是同一个值) , 所以key方法和value方法的行为完全一致。

    1. 应用
    //扩展运算符( ...) 内部使用for...of循环, 所以也可以用于Set结构
    let set = new Set(['red', 'green', 'blue']);
    let arr = [...set];
    // ['red', 'green', 'blue']
    
    //去除数组的重复成员。
    let arr = [3, 5, 2, 2, 5, 5];
    let unique = [...new Set(arr)];
    // [3, 5, 2]
    
    //数组的map和filter方法也可以用于Set
    let set = new Set([1, 2, 3]);
    set = new Set([...set].map(x => x * 2));
    // 返回Set结构: {2, 4, 6}
    let set = new Set([1, 2, 3, 4, 5]);
    set = new Set([...set].filter(x => (x % 2) == 0));
    // 返回Set结构: {2, 4}
    
    //并集( Union) 、 交集( Intersect) 和差集( Difference)
    let a = new Set([1, 2, 3]);
    let b = new Set([4, 3, 2]);
    // 并集
    let union = new Set([...a, ...b]);
    // Set {1, 2, 3, 4}
    // 交集
    let intersect = new Set([...a].filter(x => b.has(x)));
    // set {2, 3}
    // 差集
    let difference = new Set([...a].filter(x => !b.has(x)));
    // Set {1}
    

    Map

    Map结构提供了“值—值”的对应, 是一种更完善的Hash结构实现。 如果你需要“键值对”的
    数据结构,请使用Map

    var m = new Map();
    var o = {p: 'Hello World'};
    m.set(o, 'content')
    m.get(o) // "content"
    m.has(o) // true
    m.delete(o) // true
    m.has(o) // false
    
    // Map也可以接受一个数组作为参数。 该数组的成员是一个个表示键值对的数组
    var map = new Map([
        ['name', '张三'],
        ['title', 'Author']
    ]);
    
    console.log(map); //Map { 'name' => '张三', 'title' => 'Author' }
    map.size // 2
    map.has('name') // true
    map.get('name') // "张三"
    map.has('title') // true
    map.get('title') // "Author"
    
    //如果对同一个键多次赋值, 后面的值将覆盖前面的值
    let map = new Map();
    map
        .set(1, 'aaa')
        .set(1, 'bbb');
    map.get(1) // "bbb"
    

    属性和操作方法

    size属性

    size属性返回Map结构的成员总数。

    let map = new Map();
    map.set('foo', true);
    map.set('bar', false);
    map.size // 2
    
    set(key, value)

    set方法设置key所对应的键值, 然后返回整个Map结构。 如果key已经有值, 则键值会被更新, 否则就新生成该键

    var m = new Map();
    m.set("edition", 6) // 键是字符串
    m.set(262, "standard") // 键是数值
    m.set(undefined, "nah") // 键是undefined
    
    get(key)

    get方法读取key对应的键值, 如果找不到key, 返回undefined

    var m = new Map();
    var hello = function() {console.log("hello");}
    m.set(hello, "Hello ES6!") // 键是函数
    m.get(hello) // Hello ES6!
    
    has(key)

    has方法返回一个布尔值, 表示某个键是否在Map数据结构中

    var 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(key)

    delete方法删除某个键, 返回true。 如果删除失败, 返回false。

    var m = new Map();
    m.set(undefined, "nah");
    m.has(undefined) // true
    m.delete(undefined)
    m.has(undefined) // false
    
    clear()

    clear方法清除所有成员, 没有返回值

    let map = new Map();
    map.set('foo', true);
    map.set('bar', false);
    map.size // 2
    map.clear()
    map.size // 0
    

    遍历

    • keys(): 返回键名的遍历器。
    • values(): 返回键值的遍历器
    • entries(): 返回所有成员的遍历器。
    • forEach(): 遍历Map的所有成员
    let 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);
    } 
    // 等同于使用map.entries()
    for (let [key, value] of map) {
        console.log(key, value);
    }
    
    map.forEach(function(value, key, map) {
    console.log("Key: %s, Value: %s", key, value);
    });
    

    结合数组的map方法、 filter方法, 可以实现Map的遍历和过滤( Map本身没有map和filter方法)

    let map0 = new Map()
        .set(1, 'a')
        .set(2, 'b')
        .set(3, 'c');
    let map1 = new Map(
        [...map0].filter(([k, v]) => k < 3)
    );
    // 产生Map结构 {1 => 'a', 2 => 'b'}
    let map2 = new Map(
        [...map0].map(([k, v]) => [k * 2, '_' + v])
    );
    // 产生Map结构 {2 => '_a', 4 => '_b', 6 => '_c'}
    

    map与其他数据结构的互相转换:

    1. Map转为数组
      Map转为数组最方便的方法, 就是使用扩展运算符( ...) 。
    let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
    [...myMap]
    // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
    
    1. 数组转为Map
      将数组转入Map构造函数, 就可以转为Map
    new Map([[true, 7], [{foo: 3}, ['abc']]])
    // Map {true => 7, Object {foo: 3} => ['abc']}
    
    1. Map转为JSON
      Map转为JSON要区分两种情况:
    • Map的键名都是字符串, 这时可以选择转为对象JSON
    function strMapToJson(strMap) {
    return JSON.stringify(strMapToObj(strMap));
    } 
    let myMap = new Map().set('yes', true).set('no', false);
    strMapToJson(myMap)
    // '{"yes":true,"no":false}'
    
    • Map的键名有非字符串, 这时可以选择转为数组JSON
    function mapToArrayJson(map) {
    return JSON.stringify([...map]);
    } 
    let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
    mapToArrayJson(myMap)
    // '[[true,7],[{"foo":3},["abc"]]]'
    
    1. JSON转为Map
    function jsonToStrMap(jsonStr) {
    return objToStrMap(JSON.parse(jsonStr));
    } 
    jsonToStrMap('{"yes":true,"no":false}')
    // Map {'yes' => true, 'no' => false}
    

    Generator 函数

    Generator函数是ES6提供的一种异步编程解决方案, 语法行为与传统函数完全不同。

    执行Generator函数会返回一个遍历器对象, 也就是说, Generator函数除了状态机,还是一个遍历器对象生成函数。 返回的遍历器对象, 可以依次遍历Generator函数内部的每一个状态。

    形式上, Generator函数是一个普通函数, 但是有两个特征。

    • function关键字与函数名之间有一个星号;
    • 函数体内部使用yield语句, 定义不同的内部状态( yield语句在英语里的意思就是“产出”) 。

    Generator函数的调用方法与普通函数一样, 也是在函数名后面加上一对圆括号。 不同的是, 调用Generator函数后, 该函数并不执行, 返回的也不是函数运行结果, 而是一个指向内部状态的指针对象, 也就是上一章介绍的遍历器对象( Iterator Object)

    必须调用遍历器对象的next方法, 使得指针移向下一个状态。 也就是说, 每次调用next方法, 内部指针就从函数头部或上一次停下来的地方开始执行, 直到遇到下一个yield语句( 或return语句) 为止。 换言之, Generator函数是分段执行的,yield语句是暂停执行的标记, 而next方法可
    以恢复执行。

    function* helloWorldGenerator() {
        yield 'hello';
        yield 'world';
        return 'ending';
    } 
    var hw = helloWorldGenerator();
    
    hw.next()
    // { value: 'hello', done: false }
    hw.next()
    // { value: 'world', done: false }
    hw.next()
    // { value: 'ending', done: true }
    hw.next()
    // { value: undefined, done: true }
    
    

    yield语句

    遍历器对象的next方法的运行逻辑如下

    • 遇到yield语句, 就暂停执行后面的操作, 并将紧跟在yield后面的那个表达式的值, 作为返回的对象的value属性值
    • 下一次调用next方法时, 再继续往下执行, 直到遇到下一个yield语句。
    • 如果没有再遇到新的yield语句, 就一直运行到函数结束, 直到return语句为止, 并将return语句后面的表达式的值, 作为返回的对象的value属性值。
    • 如果该函数没有return语句, 则返回的对象的value属性值为undefined

    注: yield语句不能用在普通函数中, 否则会报错

    Promise对象

    所谓Promise, 简单说就是一个容器, 里面保存着某个未来才会结束的事件( 通常是一个异步操作) 的结果。 从语法上说, Promise是一个对象, 从它可以获取异步操作的消息

    Promise对象有以下两个特点:

    • 对象的状态不受外界影响。 Promise对象代表一个异步操作, 有三种状态: Pending( 进行中) 、 Resolved( 已完成, 又称Fulfilled)和Rejected( 已失败) 。 只有异步操作的结果, 可以决定当前是哪一种状态, 任何其他操作都无法改变这个状态。 这也是Promise这个名字的由来, 它的英语意思就是“承诺”, 表示其他手段无法改变
    • 一旦状态改变, 就不会再变, 任何时候都可以得到这个结果。 Promise对象的状态改变, 只有两种可能: 从Pending变为Resolved和从Pending变为Rejected。 只要这两种情况发生, 状态就凝固了, 不会再变了, 会一直保持这个结果。 就算改变已经发生了, 你再对Promise对象添加回调函数, 也会立即得到这个结果。 这与事件( Event) 完全不同, 事件的特点是, 如果你错过了它, 再去监听, 是得不到结果的。
    //创造了一个Promise实例。
    var promise = new Promise(function(resolve, reject) {
        // ... some code
        if (/* 异步操作成功 */){
            resolve(value);
        } else {
            reject(error);
        }
    });
    
    //可以用then方法分别指定Resolved状态和Reject状态的回调函数
    promise.then(function(value) {
        // success
    }, function(error) {
        // failure
    });
    
    //用Promise对象实现的Ajax操作的例子
    var getJSON = function (url) {
        var promise = new Promise(function (resolve, reject) {
            var client = new XMLHttpRequest();
            client.open("GET", url);
            client.onreadystatechange = handler;
            client.responseType = "json";
            client.setRequestHeader("Accept", "application/json");
            client.send();
    
            function handler() {
                if (this.readyState !== 4) {
                    return;
                }
                if(this.status === 200)
                {
                    resolve(this.response);
                }
            else
                {
                    reject(new Error(this.statusText));
                }
            };
        });
        return promise;
    };
    getJSON("/posts.json").then(function (json) {
        console.log('Contents: ' + json);
    }, function (error) {
        console.error('出错了', error);
    });
    

    async函数

    async函数就是Generator函数的语法糖。

    async函数就是将Generator函数的星号( *) 替换成async, 将yield替换成await, 仅此而已

    //async函数返回一个Promise对象, 可以使用then方法添加回调函数。 当函数执行的时候, 一旦遇到await就会先返回, 等到触发的异步操作完成, 再接着执行函数体内后面的语句。
    async function getStockPriceByName(name) {
        var symbol = await getStockSymbol(name);
        var stockPrice = await getStockPrice(symbol);
        return stockPrice;
    }
    
    getStockPriceByName('goog').then(function (result) {
        console.log(result);
    });
    
    //指定多少毫秒后输出一个值
    function timeout(ms) {
        return new Promise((resolve) => {
            setTimeout(resolve, ms);
        });
    }
    
    async function asyncPrint(value, ms) {
        await timeout(ms);
        console.log(value)
    }
    
    asyncPrint('hello world', 50);
    
    //Async函数有多种使用形式。
    // 函数声明
    async function foo() {}
    // 函数表达式
    const foo = async function () {};
    // 对象的方法
    let obj = { async foo() {} };
    // 箭头函数
    const foo = async () => {};
    

    Class

    class Point {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
    
        toString() {
            return `x=${this.x}    y=${this.y}`;
        }
    }
    
    let point = new Point(1, 2);
    console.log(point.toString()); //x=1    y=2
    

    注:定义“类”的方法的时候, 前面不需要加上function这个关键字, 直接把函数定义放进去了就可以了。 另外, 方法之间不需要逗号分隔, 加了会报错。

    constructor方法

    constructor方法是类的默认方法, 通过new命令生成对象实例时, 自动调用该方法。 一个类必须有constructor方法, 如果没有显式定义, 一个空的constructor方法会被默认添加

    class Foo {
        constructor() {
            return Object.create(null);
        }
    } 
    new Foo() instanceof Foo
    

    Class的继承

    class ColorPoint extends Point {
        constructor(x, y, color) {
            super(x, y); // 调用父类的constructor(x, y)
            this.color = color;
        }
    
        toString() {
            return this.color + ' ' + super.toString(); // 调用父类的toString()
        }
    }
    

    注:

    • 子类必须在constructor方法中调用super方法, 否则新建实例时会报错。 这是因为子类没有自己的this对象, 而是继承父类的this对象, 然后对其进行加工
    • 在子类的构造函数中, 只有调用super之后, 才可以使用this关键字, 否则会报错

    Module

    模块功能主要由两个命令构成: export和import。 export命令用于规定模块的对外接口, import命令用于输入其他模块提供的功能。
    一个模块就是一个独立的文件。 该文件内部的所有变量, 外部无法获取。 如果你希望外部能够读取模块内部的某个变量, 就必须使用export关键字输出该变量

    export

    //用export命令对外部输出了三个变量
    // profile.js
    export var firstName = 'Michael';
    export var lastName = 'Jackson';
    export var year = 1958;
    
    //另外一种写法
    var firstName = 'Michael';
    var lastName = 'Jackson';
    var year = 1958;
    export {firstName, lastName, year};
    
    //export命令除了输出变量, 还可以输出函数或类( class) 。
    export function multiply(x, y) {
        return x * y;
    };
    
    //export输出的变量就是本来的名字, 但是可以使用as关键字重命名。
    function v1() { ... }
    function v2() { ... }
    export {
        v1 as streamV1,
        v2 as streamV2,
        v2 as streamLatestVersion
    };
    
    //export命令规定的是对外的接口, 必须与模块内部的变量建立一一对应关系
    // 写法一
    export var m = 1;
    // 写法二
    var m = 1;
    export {m};
    // 写法三
    var n = 1;
    export {n as m};
    

    import

    使用export命令定义了模块的对外接口以后, 其他JS文件就可以通过import命令加载这个模块( 文件)
    import命令接受一个对象( 用大括号表示) , 里面指定要从其他模块导入的变量名。 大括号里面的变量名, 必须与被导入模块( profile.js) 对外接口的名称相同。

    //从profile中导入firstName, lastName, year
    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;
    }
    
    //整体加载
    import * as circle from './circle';
    console.log('圆面积: ' + circle.area(4));
    console.log('圆周长: ' + circle.circumference(14));
    

    export default命令

    为模块指定默认输出

    // export-default.js
    //默认输出是一个函数。
    export default function () {
        console.log('foo');
    }
    
    // import-default.js
    //import命令可以为该匿名函数指定任意名字。
    import customName from './export-default';
    customName(); // 'foo'
    

    注:一个模块只能有一个默认输出, 因此export deault命令只能使用一次。 所以, import命令后面才不用加大括号, 因为只可能对应一个方法。

    跨模块常量

    const声明的常量只在当前代码块有效。 如果想设置跨模块的常量( 即跨多个文件) , 可以采用下面的写法

    // 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
    

    相关文章

      网友评论

          本文标题:es6学习笔记

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