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 }; // 不报错
  • 相关阅读:
    python subprocess.Popen 非阻塞
    linux错误码
    python中logging
    python多线程和多进程对比
    python多进程提高cpu利用率
    django orm 操作
    linux故障判断
    linux中软链接打包、计算以及同步
    小程序收集formid跳转后收集不到
    Git Base 操作(二)
  • 原文地址:https://www.cnblogs.com/hustshu/p/14629953.html
Copyright © 2011-2022 走看看