zoukankan      html  css  js  c++  java
  • 《ECMAScript6入门》读书笔记(二)

    《ECMAScript6入门》读书笔记

    ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefinednull、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

    字符、数值、正则的扩展

    第4章 字符串的扩展:http://es6.ruanyifeng.com/#docs/string
    第5章 数值的扩展:http://es6.ruanyifeng.com/#docs/array
    第6章 正则的扩展:http://es6.ruanyifeng.com/#docs/regex

    第7章 数组的扩展

    扩展运算符

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

    console.log(1, ...[2, 3, 4], 5); // 1 2 3 4 5
    

    function push(array, ...items) {
    array.push(...items);
    }

    function add(x, y) {
    return x + y;
    }
    const numbers = [4, 38];
    add(...numbers); // 42

    Array.from()

    Array.from 方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和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']

    Array.of()

    Array.of 方法用于将一组值,转换为数组。

    Array.of(3, 11, 8) // [3,11,8]

    copyWithin()

    数组实例的 copyWithin() 将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。

    Array.prototype.copyWithin(target, start = 0, end = this.length)

    target(必需):从该位置开始替换数据。
    start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
    end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。

    [1, 2, 3, 4, 5].copyWithin(0, 3);
    // [4, 5, 3, 4, 5]

    find() & findIndex()

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

    [1, 5, 10, 15].find(function(value, index, arr) {
      return value > 9;
    }) // 10

    find() 方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。

    [1, 5, 10, 15].findIndex(function(value, index, arr) {
      return value > 9;
    }) // 2

    findIndex() 方法的用法与 find() 方法非常类似,返回第一个符合条件的数组成员的位置

    fill()

    数组实例的 fill() 方法使用给定值,填充一个数组。

    ['a', 'b', 'c'].fill(7); // [7, 7, 7]
    new Array(3).fill(7); // [7, 7, 7]
    // 第二个和第三个参数,用于指定填充的起始位置和结束位置。
    ['a', 'b', 'c'].fill(7, 1, 2); // ['a', 7, 'c']

    entries(),keys() 和 values()

    ES6 提供三个新的方法 -- 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"

    includes()

    Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。

    [1, 2, 3].includes(2)     // true
    [1, 2, 3].includes(4)     // false
    [1, 2, NaN].includes(NaN) // true
    // 第二个参数表示搜索的起始位置,默认为0
    [1, 2, 3].includes(3, 3);  // false

    数组的空位

    数组的空位指,数组的某一个位置没有任何值。

    Array(3) // [, , ,]

    ES6 明确将空位转为 undefined。

    Array.from(['a',,'b']); // [ "a", undefined, "b" ]

    由于空位的处理规则非常不统一,所以建议避免出现空位。

    第8章 函数的扩展

    函数参数的默认值

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

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

    参数默认值是惰性求值的。

    与解构赋值默认值结合使用

    函数默认值生效:当前参数没有传值或传值为 undefined ;
    解构赋值默认值生效:当前参数传值或没有传值而默认值与 undefined 严格相等。

    // 写法一
    function m1({x = 0, y = 0} = {}) {
      return [x, y];
    }
    // 写法二
    function m2({x, y} = { x: 0, y: 0 }) {
      return [x, y];
    }
    

    m1() // [0, 0]
    m2() // [0, 0]

    m1( {x: 3, y: 8} ) // [3, 8]
    m2( {x: 3, y: 8} ) // [3, 8]

    m1( {x: 3} ) // [3, 0]
    m2( {x: 3} ) // [3, undefined]

    m1( {} ) // [0, 0];
    m2( {} ) // [undefined, undefined]

    m1( {z: 3} ) // [0, 0]
    m2( {z: 3} ) // [undefined, undefined]

    区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。

    参数默认值的位置

    定义了默认值的参数应该是函数的尾参数,否则这个参数不能省略。

    // 例一:默认值的参数在前
    function f(x = 1, y) {
      console.log([x, y]);
    }
    f();             // [1, undefined]
    f(2);            // [2, undefined]
    f(, 1);          // 报错
    f(undefined, 1); // [1, 1]
    // 例二:默认值的参数在后
    function f(x , y= 1) {
      console.log([x, y]);
    }
    f();             // [undefined, 1]
    f(2);            // [2, 1]
    f(2, );          // [2, 1]
    f(2, undefined); // [2, 1]

    undefined 能触发默认值,null 不能。

    function foo(x = 5, y = 6) {
      console.log(x, y);
    }
    foo(undefined, null); // 5 null

    函数的 length 属性:没有指定默认值的参数个数。

    作用域:一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域。等到初始化结束,这个作用域就会消失。

    var x = 1;
    function f(x, y = x) {
      console.log(y);
    }
    f();  // undefined
    f(2); // 2
    // x 没有定义,所以指向外部全局变量 x
    let x = 1;
    function f(y = x) {
      let x = 2;
      console.log(y);
    }
    f();  // 1
    f(3); // 3

    更复杂的例子:

    var x = 1; // 全局变量
    function foo(x, y = function() { x = 2; }) { //函数参数作用域
      var x = 3; // 函数内部变量
      y();
      console.log(x);
    }
    foo();          // 3 - 第5行
    console.log(x); // 1

    rest 参数

    ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数。

    function add(...values) {
      let sum = 0;
      for (var val of values) {
        sum += val;
      }
      return sum;
    }
    add(2, 5, 3); // 10

    rest 参数搭配的变量为数组,arguments 为类似数组的对象。
    rest 参数之后不能再有其他参数;函数的 length 属性,也不包括 rest 参数。

    严格模式

    从 ES5 开始,函数内部可以设定为严格模式。ES2016 规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

    name 属性

    函数的 name 属性,返回该函数的函数名。

    function foo() {}
    foo.name // "foo"
    var f = function () {};
    f.name // "" - ES5
    f.name // "f" - ES6

    箭头函数

    ES6 允许使用“箭头”(=>)定义函数。

    var sum = (num1, num2) => num1 + num2;
    // 等同于
    var sum = function(num1, num2) {
      return num1 + num2;
    };

    箭头函数有几个使用注意点:
    (1)函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
    (2)不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
    (3)不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
    (4)不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

    function foo() {
      setTimeout(() => {
        console.log('id:', this.id);
      }, 100);
    }
    var id = 21;
    foo.call({ id: 42 }); // id: 42

    箭头函数 this 指向定义时所在的对象。

    function Timer() {
      this.s1 = 0;
      this.s2 = 0;
      setInterval(() => this.s1++, 1000); // 箭头函数
      setInterval(function () {           // 普通函数
        this.s2++;
      }, 1000);
    }
    var timer = new Timer();
    setTimeout(() => console.log('s1: ', timer.s1), 3100); // s1: 3
    setTimeout(() => console.log('s2: ', timer.s2), 3100); // s2: 0

    前者的 this 绑定定义时所在的作用域(即Timer函数),后者的 this 指向运行时所在的作用域(即全局对象)。

    this 指向的固定化,并不是因为箭头函数内部有绑定 this 的机制,实际原因是箭头函数根本没有自己的 this,导致内部的 this 就是外层代码块的 this。

    // ES6
    function foo() {
      setTimeout(() => {
        console.log('id:', this.id);
      }, 100);
    }
    // ES5
    function foo() {
      var _this = this;
      setTimeout(function () {
        console.log('id:', _this.id);
      }, 100);
    }

    由于没有 this,因而也不存在通过 bind()、call()、apply() 来改变 this 的指向。

    嵌套的箭头函数:箭头函数内部,还可以再使用箭头函数。

    let insert = (value) => ({into: (array) => ({after: (afterValue) => {
      array.splice(array.indexOf(afterValue) + 1, 0, value);
      return array;
    }})});
    // 等价于
    function insert(value) {
      return {into: function (array) {
        return {after: function (afterValue) {
          array.splice(array.indexOf(afterValue) + 1, 0, value);
          return array;
        }};
      }};
    }
    // 结果相同
    insert(2).into([1, 3]).after(1); //[1, 2, 3]

    双冒号运算符

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

    foo::bar;
    // 等同于
    bar.bind(foo);

    如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

    var method = obj::obj.foo;
    // 等同于
    var method = ::obj.foo;

    双冒号运算符的运算结果,还是一个对象,因此可以采用链式写法。

    let { find, html } = jake;
    document.querySelectorAll("div.myClass")
    ::find("p")
    ::html("hahaha");

    尾调用优化

    尾调用(Tail Call)是指某个函数的最后一步是调用另一个函数。

    function f(x) {
      if (x > 0) {
        return m(x)
      }
      return n(x);
    }

    尾调用优化:即只保留内层函数的调用帧。

    函数调用会在内存形成一个调用记录即“调用帧”,如果函数 A 调用函数 B ,还会形成一个 B 的调用帧,依次类推形成一个调用栈。

    如果函数存在尾调用,只保留最后的函数的调用帧即可,这将大大节省内存。

    function f() {
      let m = 1;
      let n = 2;
      return g(m + n);
    }
    f();
    // 等同于
    function f() {
      return g(3);
    }
    f();
    // 等同于
    g(3);

    执行到最后一步,完全可以删除f(x)的调用帧,只保留g(3)的调用帧。

    注意,只有不再用到外层函数的内部变量才能进行“尾调用优化”。
    下面函数就不会进行“尾调用优化”。

    function addOne(a){
      var one = 1;
      function inner(b){
        return b + one;
      }
      return inner(a);
    }

    尾递归:函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

    function factorial(n) {
      if (n === 1) return 1;
      return n * factorial(n - 1);
    }
    factorial(5) // 120
    // 改写成尾递归
    function factorial(n, total) {
      if (n === 1) return total;
      return factorial(n - 1, n * total);
    }
    facorial(5, 1) // 120

    函数参数的尾逗号

    ES2017 允许函数的最后一个参数有尾逗号。

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

    第9章 对象的扩展

    属性的简洁表示法

    ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。

    const foo = 'bar';
    const baz = {foo};
    // 等同于
    const baz = {foo: foo};

    除了属性简写,方法也可以简写。

    const o = {
      method() {
        return "Hello!";
      }
    };
    // 等同于
    const o = {
      method: function() {
        return "Hello!";
      }
    };

    属性名表达式

    JavaScript 定义对象的属性,有两种方法。

    // 方法一
    obj.foo = true;
    // 方法二
    obj['a' + 'bc'] = 123;

    如果使用字面量方式定义对象(使用大括号),在 ES5 中只能使用方法一(标识符)定义属性。
    ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名。

    let lastWord = 'last word';
    const a = {
      'first word': 'hello',
      [lastWord]: 'world'
    };
    a['first word'] // "hello"
    a[lastWord] // "world"
    a['last word'] // "world"

    表达式还可以用于定义方法名。

    let obj = {
      ['h' + 'ello']() {
        return 'hi';
      }
    };
    obj.hello(); // hi

    注意:属性名表达式与简洁表示法不能同时使用;属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串 [object Object]

    方法的 name 属性

    函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。

    const person = {
      sayName() {
        console.log('hello!');
      },
    };
    person.sayName.name   // "sayName"

    Object.is()

    ES6 提出“Same-value equality”(同值相等)算法,用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。。

    Object.is('foo', 'foo'); // true
    Object.is({}, {});       // false

    不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

    +0 === -0           // true
    NaN === NaN         // false
    Object.is(+0, -0)   // false
    Object.is(NaN, NaN) // true

    Object.assign()

    Object.assign() 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

    const target = { a: 1 };
    const source1 = { b: 2 };
    const source2 = { c: 3 };
    Object.assign(target, source1, source2);
    target // {a:1, b:2, c:3}

    Object.assign() 方法的第一个参数是目标对象,后面的参数都是源对象。

    如果 undefined 或 null 作为第一个参数会报错,在之后则会跳过;
    数值、布尔值可以被转换成包装对象,但依然会被忽略;
    字符串转对象会被拆分成数组,除了原始值会被拷贝到[[PrimitiveValue]]属性中,每个字符都是可枚举的实义属性,因此是有效的。

    const v1 = 'abc';
    const v2 = true;
    const v3 = 10;
    const obj = Object.assign({}, v1, v2, v3);
    console.log(obj); // { "0": "a", "1": "b", "2": "c" }

    Object.assign() 只拷贝对象的自有属性,属性名为 Symbol 的属性也会被拷贝,继承属性和不可枚举的属性不会被拷贝。
    Object.assign() 执行的是浅拷贝,对于同名属性会直接覆盖而非合并。
    Object.assign() 可以用于处理数组,但会把数组转换成属性名为 0、1、2…… 的对象,并依次替换。

    Object.getOwnPropertyDescriptors()

    ES2017 引入了 Object.getOwnPropertyDescriptors() ,返回指定对象所有自身属性(非继承属性)的描述对象。

    Object.setPrototypeOf(),Object.getPrototypeOf()

    __proto__属性:(前后各两个下划线),用来读取或设置当前对象的prototype对象。

    Object.setPrototypeOf(): 作用与proto相同,用来设置一个对象的prototype对象,返回参数对象本身。
    Object.getPrototypeOf(): 读取一个对象的原型对象。

    Object.keys(),Object.values(),Object.entries()

    ES5 引入了 Object.keys() 来返回一个数组,其成员是参数对象的可枚举的自有属性的键名。
    ES2017 跟着引入了 Object.values() 和 Object.entries() 用于返回属性对应的值,以及以键值对数组的形式返回。
    可以配合 for...of 循环使用。

    属性的可枚举性和遍历

    对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。

    可枚举性:描述对象的 enumerable 属性,如果该属性为false,就表示某些操作会忽略当前属性。

    for...in 循环、Object.keys()、JSON.stringify()、Object.assign() 忽略 enumerable 为 false 的属性。

    ES6 一共有5种方法可以遍历对象的属性。

    1. for...in:循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
    2. Object.keys(obj):返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
    3. Object.getOwnPropertyNames(obj):返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
    4. Object.getOwnPropertySymbols(obj):返回一个数组,包含对象自身的所有 Symbol 属性的键名。
    5. Reflect.ownKeys(obj):返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

    以上的5种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。

    • 首先遍历所有数值键,按照数值升序排列。
    • 其次遍历所有字符串键,按照加入时间升序排列。
    • 最后遍历所有 Symbol 键,按照加入时间升序排列。

    super 关键字

    ES6 又新增了另一个类似的关键字 super,指向当前对象的原型对象。

    对象的扩展运算符

    ES2017 将这个运算符引入了对象。

    解构赋值:对象的解构赋值用于从一个对象取值,相当于将所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。

    let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
    x // 1
    y // 2
    z // { a: 3, b: 4 }

    解构赋值必须是最后一个参数,否则会报错。

    注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
    另外,扩展运算符的解构赋值,不能复制继承自原型对象的属性。

    扩展运算符:

    扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

    let z = { a: 3, b: 4 };
    let n = { ...z }; // { a: 3, b: 4 }

    扩展运算符可以用于合并两个对象。

    let ab = { ...a, ...b };
    // 等同于
    let ab = Object.assign({}, a, b);

    如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。

    let newVersion = {
      ...previousVersion,
      name: 'New Name' // Override the name property
    };

    如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。

    let aWithDefaults = { x: 1, y: 2, ...a };
    // 等同于
    let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
    let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);

    与数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式。

    // 空对象没有任何效果
    {...{}, a: 1} // { a: 1 }
    // 忽略 null 和 undefined
    let emptyObject = { ...null, ...undefined }; // 不报错
  • 相关阅读:
    第八章 多线程编程
    Linked List Cycle II
    Swap Nodes in Pairs
    Container With Most Water
    Best Time to Buy and Sell Stock III
    Best Time to Buy and Sell Stock II
    Linked List Cycle
    4Sum
    3Sum
    Integer to Roman
  • 原文地址:https://www.cnblogs.com/hustshu/p/14629953.html
Copyright © 2011-2022 走看看