zoukankan      html  css  js  c++  java
  • ES6读书笔记(二)

    前言

    前段时间整理了ES6的读书笔记:《ES6读书笔记(一)》,现在为第二篇,本篇内容包括:

    • 一、数组扩展
    • 二、对象扩展
    • 三、函数扩展
    • 四、Set和Map数据结构
    • 五、Reflect

    本文笔记也主要是根据阮一峰老师的《ECMAScript 6 入门》和平时的理解进行整理的,希望对你有所帮助,喜欢的就点个赞吧!

    一、数组扩展

    1. 扩展运算符

    ①复制数组:

    const a1 = [1, 2];
    // 写法一
    const a2 = [...a1];
    // 写法二
    const [...a2] = a1;
    

    ②求最大值:

    Math.max(...[14, 3, 77])
    Math.max.apply(null, [14, 3, 77])
    

    ③合并数组:

    let arr1 = ['a', 'b'];
    let arr2 = ['c'];
    let arr3 = ['d', 'e'];
    
    // ES5:
    arr1.concat(arr2, arr3);   // [ 'a', 'b', 'c', 'd', 'e' ] 为浅拷贝,slice、Object.assign()也为浅拷贝
    
    // ES6:
    [...arr1, ...arr2, ...arr3]    // [ 'a', 'b', 'c', 'd', 'e' ] 浅拷贝
    
    // push:
    Array.prototype.push.apply(arr1, arr2);   // 因为push参数不能为数组
    arr1.push(...arr2);
    

    ④数组克隆:

    // ES5:
    const a1 = [1, 2];
    const a2 = a1.concat();
    
    // ES6:
    const a1 = [1, 2];
    // 写法一
    const a2 = [...a1];
    // 写法二
    const [...a2] = a1;
    

    ⑤与解构赋值结合:

    // ES5
    a = list[0], rest = list.slice(1)
    // ES6
    [a, ...rest] = list
    
    const [first, ...rest] = [1, 2, 3, 4, 5];  
    first  // 1
    rest  // [2, 3, 4, 5]
    
    // 赋值时即在左边时只能放在参数最后一位
    const [...butLast, last] = [1, 2, 3, 4, 5];  // 报错
    const [first, ...middle, last] = [1, 2, 3, 4, 5];  // 报错
    
    var arr1 = ['a', 'b'];
    var a2 = [...arr1,2];   // 这样是没问题的,因为是扩展运算
    console.log(a2)   // ['a', 'b', 2] 
    

    ⑥将字符串转为真正的数组:

    [...'hello']   // [ "h", "e", "l", "l", "o" ]   
    Array.from('hello')   // [ "h", "e", "l", "l", "o" ] 
    
    //扩展运算符内部调用的是数据结构的Iterator接口,因此只有Iterator接口的对象才可以用扩展运算符转为真正的数组,Array.from也是如此,同时Array.from还支持转化类似数组的对象,即带有length属性的对象,可不含遍历器接口(Symbol.iterator):
    let arrayLike = {
      '0': 'a',
      '1': 'b',
      '2': 'c',
      length: 3
    };
    
    let arr = [...arrayLike];  // TypeError: Cannot spread non-iterable object.
    // 这个类似数组的对象没有Iterator接口,所以不能转化,可以使用Array.from()
    
    Array.from({ length: 3 });  // [ undefined, undefined, undefined ]
    Array.from({ length: 2 }, () => 'jack')   // ['jack', 'jack']
    

    ⑦扩展运算符后面是一个空数组,则不产生任何效果:

    [...[], 1]  // [1]
    

    2. Array上的方法:

    ①Array.from(数组,回调函数(val,index),this绑定)
    第二个参数,类似于map方法,第三参数,this指向:

    Array.from(arrayLike, x => x * x);
    // 等同于
    Array.from(arrayLike).map(x => x * x);
    
    Array.from([1, 2, 3], (x) => x * x)
    // [1, 4, 9]
    

    应用:
    (1)可用于转化/拷贝数组:

    //原方式:
    Array.prototype.slice.call(arr);
    
    let arr1 = ['a', 'b', 'c'];
    let arr2 = Array.from(arr1); // ['a', 'b', 'c']
    

    (2)处理空位:

    var arrLike = { 
        length: 4, 
        2: "foo" 
    }; 
    Array.from( arrLike ); 
    // [ undefined, undefined, "foo", undefined ]  // 和Array(3)产生空槽位不一样,这里是有undefined值的,会将空位转为undefined
    

    ②Array.of():用于将一组值,转换为数组:
    与Array()不同,Array()的单参数会变为长度:

    Array() // []
    Array(3) // [, , ,]
    Array(3, 11, 8) // [3, 11, 8]
    
    Array.of(3, 11, 8) // [3,11,8]
    Array.of(3) // [3]
    Array.of(3).length // 1
    
    //模拟:
    function ArrayOf(){
      return [].slice.call(arguments);
    }
    

    3. Array实例的方法:

    ①find((val, index, arr)=>{}, this) :用于找出【第一个】符合条件的【数组成员】,它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined,第二参数为this绑定。

    [1, 4, -5, 10].find((n) => n < 0);
    // -5  不是返回数组
    
    [1, 5, 10, 15].find((value, index, arr) => value > 9); // 10
    
    var a = [1,2,3,4,5];
    (a.indexOf("2") != -1);   // false  indexOf是严格匹配===的,所以不会转化字符串
    
    a.find(v => v == "2");    // 2  返回这个匹配的值,而不是返回布尔值
    a.find(v => v == 7);      // undefined 
    
    let person = {name: 'John', age: 20};
    [10, 12, 26, 15].find((v) => { v > this.age;}, person);    // 26
    

    ②findIndex((val, index, arr)=>{}, this) :返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1,第二参数为this绑定。

    find()和findIndex()都弥补了indexOf方法对NaN的不足:

    [NaN].indexOf(NaN)   // -1
    [NaN].findIndex(y => Object.is(NaN, y))   // 0
    

    ③copyWithin(target, start = 0, end = this.length)
    target(必需):从该位置开始替换数据。如果为负值,表示倒数。
    start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
    end(可选):到该位置 前 (这个位置前,所以不包含这个位置)停止读取数据,默认等于数组长度。如果为负值,表示倒数。

    [1, 2, 3, 4, 5].copyWithin(0, 3)   // [4, 5, 3, 4, 5]  4、5替换1、2
    [1, 2, 3, 4, 5].copyWithin(0, 2)   // [3, 4, 5, 4, 5]  3、4、5替换1、2、3
    [1, 2, 3, 4, 5].copyWithin(0, 3, 4)  // [4, 2, 3, 4, 5]  4替换1
    

    ④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"
    
    var a = [1,2,3]; 
    [...a.values()];              // [1,2,3] 
    [...a.keys()];                // [0,1,2] 
    [...a.entries()];             // [ [0,1], [1,2], [2,3] ] 
    [...a[Symbol.iterator]()];    // [1,2,3]
    
    //也可手动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(包含项,起始位置): Array.prototype.includes方法返回一个布尔值(而find返回的是值),表示某个数组是否包含给定的值,与字符串的includes方法类似:

    [1, 2, 3].includes(2)     // true
    [1, 2, 3].includes(4)     // false
    [1, 2, NaN].includes(NaN) // true  弥补了indexOf(使用===)对NaN的不足
    

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

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

    ⑥fill(填充项,起始位置,结束位置):使用给定值填充数组

    var a = Array(4).fill(undefined); 
    a;   // [undefined,undefined,undefined,undefined]
    
    var a = [null, null, null, null].fill(42, 1, 3); 
    a;   // [null,42,42,null]  不包含结束位置
    
    //如果被赋值的是引用类型,是浅拷贝:
    let arr = new Array(3).fill({name: "Mike"});
    arr[0].name = "Ben";
    arr    // [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
    

    ⑦flat():用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响:

    [1, 2, [3, 4]].flat()   // [1, 2, 3, 4]
    

    参数可设置拉平几层:

    [1, 2, [3, [4, 5]]].flat(2)   // [1, 2, 3, 4, 5]
    

    不管几层使用Infinity:

    [1, [2, [3]]].flat(Infinity)   // [1, 2, 3]
    

    会跳过空位:

    [1, 2, , 4, 5].flat()   // [1, 2, 4, 5]
    

    flatMap():只能展开一层,相当于map方法,第二参为this

    [2, 3, 4].flatMap((x) => [x, x * 2])     // [2, 4, 3, 6, 4, 8]
    
    //相当于:
    [2, 3, 4].map((x) => [x, x * 2 ]).flat()
    
    [1, 2, 3, 4].flatMap(x => [[x * 2]]) // 只能展开一层 [[2], [4], [6], [8]]
    

    4. 数组空位:空位不是undefined,是指没有值

    var a = Array(3);
    console.log(a)     // [empty × 3]
    console.log(a[0])  // undefined 为空,找不到值,所以返回undefined
    
    0 in [undefined, undefined, undefined] // true  说明0号位是有值的
    0 in [, , ,]   // false
    

    二、对象扩展

    1. 对象属性

    ①简写:{x, y}

    ②属性名表达式:

    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]:
    const keyA = {a: 1};
    const keyB = {b: 2};
    
    const myObject = {
      [keyA]: 'valueA',
      [keyB]: 'valueB'
    };
    
    myObject // Object {[object Object]: "valueB"}  'valueA'被覆盖了
    

    ③属性的可枚举性:设置为false,规避了for in操作,防止遍历到不可枚举属性。

    Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
    // false
    
    Object.getOwnPropertyDescriptor([], 'length').enumerable
    // false
    

    2.对象的解构赋值+扩展运算符:只能用在最后。

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

    为浅拷贝:

    let obj = { a: { b: 1 } };
    let { ...x } = obj;
    obj.a.b = 2;
    x.a.b // 2
    

    扩展运算符:用于取出参数对象的所有可遍历属性(应该就只是自身可枚举属性),拷贝到当前对象之中。

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

    数组是特殊的对象,所以对象的扩展运算符也可以用于数组:

    {...['a', 'b', 'c']}  // {0: "a", 1: "b", 2: "c"}
    

    如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象:

    {...'hello'}   // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
    

    如果扩展运算符后面是一个空对象,则没有任何效果:

    {...{}, a: 1}  // { a: 1 }
    

    如果扩展运算符后面不是对象,则会自动将其转为对象:

    {...1}  // {} 等同于 {...Object(1)}
    

    对象的扩展运算符等同于使用Object.assign()方法:

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

    上面的例子只是拷贝了对象实例的属性,如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法:

    // 写法一
    const clone1 = {
      __proto__: Object.getPrototypeOf(obj),
      ...obj
    };
    
    // 写法二
    const clone2 = Object.assign(
      Object.create(Object.getPrototypeOf(obj)),
      obj
    );
    

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

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

    3.super对象:指向当前对象的原型对象,super.foo等同于Object.getPrototypeOf(this).foo,只能用在对象的简写形式的方法之中

    var o1 = { 
        foo() { 
            console.log("o1:foo"); 
        } 
    }; 
    var o2 = { 
        foo() { 
            super.foo();   // super相当于Object.getPrototypeOf(o2),指向o1,所以得到"o1:foo"的结果
            console.log("o2:foo"); 
        } 
    }; 
    Object.setPrototypeOf(o2, o1); 
    o2.foo();      // o1:foo 
                   // o2:foo
    

    4. Object上新增的方法:

    (1)Object.is():比较两个值是否相等,与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0不等于-0,二是NaN等于自身:

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

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

    // 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性:

    const target = { a: 1, b: 1 };
    
    const source1 = { b: 2, c: 2 };
    const source2 = { c: 3 };
    
    Object.assign(target, source1, source2);
    target // {a:1, b:2, c:3}
    
    // 如果只有一个参数,Object.assign会直接返回该参数:
    const obj = {a: 1};
    Object.assign(obj) === obj // true
    
    // 如果该参数不是对象,则会先转成对象,然后返回:
    typeof Object.assign(2) // "object"
    
    // undefined和null无法转成对象,所以如果它们作为参数,就会报错:
    Object.assign(undefined) // 报错
    Object.assign(null) // 报错
    
    // 如果非对象不是首参数,则会跳过(字符串会转为数字键对象),不会报错:
    let obj = {a: 1};
    Object.assign(obj, undefined) === obj   // true
    Object.assign(obj, null) === obj   // true
    
    // 其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果:
    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" }
    
    // 只拷贝源对象的自身可枚举属性,包括属性名为 Symbol 值的属性:
    Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
    // { a: 'b', Symbol(c): 'd' }
    
    // 且是浅拷贝:
    const obj1 = {a: {b: 1}};
    const obj2 = Object.assign({}, obj1);
    
    obj1.a.b = 2;
    obj2.a.b // 2
    
    // 可以用来处理数组,但是会把数组视为对象:
    Object.assign([1, 2, 3], [4, 5])   // [4, 5, 3]
    

    上面代码中,Object.assign把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。

    应用:
    (1)为对象添加属性和方法:

    class Point {
      constructor(x, y) {
        Object.assign(this, {x, y});
      }}
    

    上面方法通过Object.assign方法,将x属性和y属性添加到Point类的对象实例。

    (2)克隆或合并多个对象:

    function clone(origin) {
       return Object.assign({}, origin);
    }
    

    上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。
    不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。

    function clone(origin) {
       let originProto = Object.getPrototypeOf(origin);
       return Object.assign(Object.create(originProto), origin);
    }
    

    (3)为属性指定默认值:

    const DEFAULTS = {
       logLevel: 0,
       outputFormat: 'html'
       };
    
    function processContent(options) {
       options = Object.assign({}, DEFAULTS, options);
       console.log(options);
       // ...
    }
    

    上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。

    (3)Object.getOwnPropertyDescriptor():返回某个对象属性的描述对象。

    const obj = {
      foo: 123,
      get bar() { return 'abc' }
    };
    
    Object.getOwnPropertyDescriptors(obj);
    /*{ foo:
       { value: 123,
          writable: true,
          enumerable: true,
          configurable: true 
        },
       bar:
        { get: [Function: get bar],
          set: undefined,
          enumerable: true,
          configurable: true 
        } 
    }
    */
    

    继承:

    const obj = Object.create(prot);
    obj.foo = 123;
    // 或者
    const obj = Object.assign(
      Object.create(prot),
      {
        foo: 123,
      });
    

    有了Object.getOwnPropertyDescriptors(),我们就有了另一种写法:

    const obj = Object.create(
      prot,
      Object.getOwnPropertyDescriptors({
        foo: 123,
      }));
    

    (4)Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替__proto__

    ①Object.setPrototypeOf(obj, prototype)

    ②Object.getPrototypeOf(obj)
    如果参数不是对象,会被自动转为对象

    (5)Object.keys()、Object.values()、Object.entries(),返回的都是数组,数组也有这些方法,但是返回的是遍历器对象。

    ①Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名:

    var obj = { foo: 'bar', baz: 42 };
    Object.keys(obj);
    // ["foo", "baz"]
    

    ②Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值:

    const obj = {100: 'a', 2: 'b', 7: 'c'};
    Object.values(obj);
    // ["b", "c", "a"]
    // 属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是b、c、a
    

    ③Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组:

    const obj = {foo: 'bar', baz: 42};
    Object.entries(obj);
    // [ ["foo", "bar"], ["baz", 42] ]
    

    (6)Object.fromEntries():是Object.entries()的逆操作,用于将一个键值对数组转为对象。

    Object.fromEntries([
      ['foo', 'bar'],
      ['baz', 42]])
    // { foo: "bar", baz: 42 }
    

    特别适合将Map结构转为对象:

    const entries = new Map([
      ['foo', 'bar'],
      ['baz', 42]]);
    
    Object.fromEntries(entries)
    // { foo: "bar", baz: 42 }
    

    三、函数扩展

    1. 函数参数

    (1)默认参数:

    ①不能重复声明:

    function foo(x = 5) {
      let x = 1;   // error
      const x = 2; // error
    }
    

    ②默认参数的位置应该是写在参数的末尾,这样才是自适应省略传参,否则如果默认参数放前面,如果不给默认传参,想给其它参数传参,则还是得向默认参数传递一个undefined,否则所传参数会覆盖默认参数,造成混淆。

    ③指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真:

    (function (a) {}).length         // 1
    (function (a = 5) {}).length     // 0
    (function (a, b, c = 5) {}).length // 2
    
    (function(...args) {}).length     // 0
    

    如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了:

    (function (a = 0, b, c) {}).length   // 0
    (function (a, b = 1, c) {}).length   // 1
    

    ④产生作用域:
    一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的:

    var x = 1;
    
    function f(x, y = x) {   // 不会找到外部的x,因为参数作用域中第一个参数已经声明有了x,所以会y=x的x会指向它,而不会指向外部的x
      console.log(y);
    }
    
    f(2); // 2
    f();  // undefined
    
    //----------------------------------
    
    let x = 1;
    
    function f(y = x) {  // 会找到外部的x,因为参数中未声明x,所以会往上层作用域查找x
      let x = 2;
      console.log(y);
    }
    
    f(3); // 3
    f();  // 1
    

    ⑤默认值表达式是惰性求值的,这意味着它们只在需要的时候运行——即在参数的值省略或者为undefined的时候:

    function bar(val) { 
        console.log("bar called!"); 
        return y + val; 
    } 
    function foo(x = y + 3, z = bar(x)) {  // 形参作用域是在函数声明包裹的作用域,而不是在函数体
        console.log(x, z); 
    } 
    var y = 5; 
    foo();                  // "bar called"
                            // 8 13
    foo(10);                // "bar called"
                            // 10 15
    y = 6; 
    foo(undefined, 10);     // 9 10
    
    //--------------------------------------------
    
    var w = 1, z = 2; 
    function foo(x = w + 1, y = x + 1, z = z + 1) { 
        console.log(x, y, z); 
    } 
    foo();    // ReferenceError 问题出在z+1中的z发现z是一个未初始化的参数变量,所以不会往上层作用域查找z
    

    ⑥undefined会触发默认值,null不会。

    (2)参数的解构赋值:

    function foo({x, y = 5}) {
      console.log(x, y);
    }
    
    foo({}) // undefined 5
    foo({x: 1}) // 1 5
    foo({x: 1, y: 2}) // 1 2
    foo()   // TypeError: Cannot read property 'x' of undefined 如果函数foo调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况:
    function foo({x, y = 5} = {}) {
      console.log(x, y);
    }
    
    foo() // undefined 5
    

    区别以下:

    // 写法一
    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]
    
    // x 和 y 都有值的情况
    m1({x: 3, y: 8}) // [3, 8]
    m2({x: 3, y: 8}) // [3, 8]
    
    // x 有值,y 无值的情况
    m1({x: 3}) // [3, 0]
    m2({x: 3}) // [3, undefined]
    
    // x 和 y 都无值的情况
    m1({}) // [0, 0];
    m2({}) // [undefined, undefined]
    
    m1({z: 3}) // [0, 0]
    m2({z: 3}) // [undefined, undefined]
    

    (3)参数使用...运算符:

    ①扩展运算符...之后不能再有其他参数:

    // 报错
    function f(a, ...b, c) {
      // ...
    }
    

    ②函数的length属性,不包括rest参数:

    (function(a) {}).length  // 1
    (function(...a) {}).length  // 0
    (function(a, ...b) {}).length  // 1
    

    2. 箭头函数

    ①函数体内的this对象,就是定义时所在的对象(找离它最近的一个执行环境作为执行上下文,如对象方法中的箭头函数的this是指向window的,所以对象方法不要使用箭头函数),而不是使用时所在的对象。由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向:

    function foo() {
      // setTimeout中的函数默认由全局环境执行,所以此时this指向window
      setTimeout(() => {
        console.log('id:', this.id);
      }, 100);
    }
    
    var id = 21;
    
    foo()  // id: 21
    foo.call({id: 42});  // id: 42  call改变了this指向
    

    ②不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

    ③不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用rest参数代替:

    function foo() {
      setTimeout(() => {
        console.log('args:', arguments);
      }, 100);
    }
    
    foo(2, 4, 6, 8)
    // args: [2, 4, 6, 8]   得到的是外部函数的参数
    

    ④不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

    不适用场合:

    const cat = {
      lives: 9,
      jumps: () => {
        this.lives--;
        console.log(this.lives);
      }
    }
    cat.jumps();  // NaN
    

    this指向:普通函数的 this 是动态的,所以要在运行时找拥有当前上下文的对象。

    而箭头函数的 this 是静态的,也就是说,只需要看箭头函数在什么函数作用域下声明的,那么这个 this 就会绑定到这个函数的上下文中。即“穿透”箭头函数。

    例子里的箭头函数并没有在哪个函数里声明,所以 thisfallback 到全局,全局的lives未声明,为undefined,运算后得到NaN。

    (3)函数的name属性:

    var f = function() {};
    // ES5
    f.name    // ""
    // ES6
    f.name    // "f"
    
    const bar = function baz() {};
    // ES5
    bar.name   // "baz"
    // ES6
    bar.name   // "baz"
    

    (4)双冒号运算符:

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

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

    function f(x){
      return g(x);
    }
    

    四、Set和Map数据结构

    4.1 Set

    1. Set类似于数组,但是成员的值都是唯一的,没有重复的值,它是构造函数,用于构造Set数据结构:

    const s = new Set();
    
    [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
    
    for (let i of s) {
      console.log(i);
    }
    // 2 3 5 4  不会添加重复值
    

    2. Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化:

    const set = new Set([1, 2, 3, 4, 4]);  // 可为数组去重
    set       // Set结构: {1, 2, 3, 4}
    [...set]  // [1, 2, 3, 4]
    
    

    数组或类数组去重:

    Array.from(new Set(array))
    

    也可去除字符串里的重复字符:

    [...new Set('ababbc')].join('')
    // "abc"
    

    3. Set内部认为NaN是相等的,所以只能添加一个;两个对象也总是不相等的。

    4. Array.from方法可以将 Set 结构转为数组:

    const items = new Set([1, 2, 3, 4, 5]);
    const array = Array.from(items);
    

    5. Set实例属性:

    // 构造函数,默认就是Set函数
    Set.prototype.constructor
    
    // 返回Set实例的成员总数
    Set.prototype.size
    

    6. Set实例方法:

    四个操作方法:

    • ①add(value):添加某个值,返回 Set 结构本身。
    • ②delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
    • ③has(value):返回一个布尔值,表示该值是否为Set的成员。
    • ④clear():清除所有成员,没有返回值。
    let s = new Set();
    
    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
    

    四个遍历方法:

    • ①keys():返回键名的遍历器
    • ②values():返回键值的遍历器
    • ③entries():返回键值对的遍历器
    • ④forEach():使用回调函数遍历每个成员

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

    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"]
    

    Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法:

    Set.prototype[Symbol.iterator] === Set.prototype.values    // true
    let set = new Set(['red', 'green', 'blue']);
    
    for (let x of set) {
      console.log(x);
    }
    // red
    // green
    // blue
    
    //方便实现并集、交集、差集:
    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}
    

    4.2 WeakSet

    WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别:

    ①WeakSet 的成员只能是对象,而不能是其他类型的值:

    const ws = new WeakSet();
    ws.add(1)
    // TypeError: Invalid value used in weak set
    ws.add(Symbol())
    // TypeError: invalid value used in weak set
    

    ②WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,且不可遍历。

    作为构造函数,WeakSet 可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有 Iterable 接口的对象,都可以作为 WeakSet 的参数。)

    const a = [[1, 2], [3, 4]];
    const ws = new WeakSet(a);
    // WeakSet {[1, 2], [3, 4]} 是数组的成员成为WeakSet 实例对象的成员,而不是a,所以成员必须是对象
    const b = [3, 4];
    const ws = new WeakSet(b);   // 成员不是对象,所以会报错
    // Uncaught TypeError: Invalid value used in weak set(…)
    

    三个方法:

    • WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
    • WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
    • WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。

    没有size属性,无法遍历

    4.3 Map

    Map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键:

    const m = new Map();
    const o = {p: 'Hello World'};
    
    m.set(o, 'content')  // 将对象o作为键
    m           // {{…} => "content"}
    m.get(o)    // "content"
    
    m.has(o)    // true
    m.delete(o) // true
    m.has(o)    // false
    

    可接受数组作为参数,数组成员是一个个表示键值对的数组:

    const map = new Map([
      ['name', '张三'],
      ['title', 'Author']
    ]);
    map       // {"name" => "张三", "title" => "Author"}
    map.size  // 2
    map.has('name')  // true
    map.get('name')  // "张三"
    map.has('title') // true
    map.get('title') // "Author"
    

    不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map:

    const set = new Set([
      ['foo', 1],
      ['bar', 2]
    ]);
    const m1 = new Map(set);
    m1.get('foo') // 1
    
    const m2 = new Map([['baz', 3]]);
    const m3 = new Map(m2);
    m3.get('baz') // 3
    

    只有相同的简单类型的键才会被视为同一个键,否则如数组、对象等作为键时,不是同一个引用时就算是同名,也是不同的键:

    const map = new Map();
    
    map.set(['a'], 555);   // 数组对象键
    map.get(['a']) // undefined 不是同一个地址
    
    // ----------------------------------------------------
    
    const map = new Map();
    
    const k1 = ['a'];
    const k2 = ['a'];
    
    map
    .set(k1, 111)
    .set(k2, 222);
    
    map.get(k1)  // 111 引用地址不同,所以是不同的键
    map.get(k2)  // 222
    

    属性方法:

    • ①size 属性
    • ②set(key, value)
    • ③get(key)
    • ④has(key)
    • ⑤delete(key)
    • ⑥clear()

    三个遍历器生成函数和一个遍历方法:

    • 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"
    
    
    map[Symbol.iterator] === map.entries   // true
    

    可用扩展运算符转为数组:

    const map = new Map([
      [1, 'one'],
      [2, 'two'],
      [3, 'three'],
    ]);
    
    [...map.keys()]
    // [1, 2, 3]
    
    [...map.values()]
    // ['one', 'two', 'three']
    
    [...map.entries()]
    // [[1,'one'], [2, 'two'], [3, 'three']]
    
    [...map]
    // [[1,'one'], [2, 'two'], [3, 'three']]
    

    数组转为Map:

    new Map([
      [true, 7],
      [{foo: 3}, ['abc']]
    ])
    // Map {
    //   true => 7,
    //   Object {foo: 3} => ['abc']
    // }
    

    4.4 WeakMap

    ①WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名:

    const map = new WeakMap();
    map.set(1, 2)
    // TypeError: 1 is not an object!
    map.set(Symbol(), 2)
    // TypeError: Invalid value used as weak map key
    map.set(null, 2)
    // TypeError: Invalid value used as weak map key
    

    ②WeakMap的键名所指向的对象,不计入垃圾回收机制。
    不能遍历且无size属性

    五、Reflect

    类似于proxy,reflect也是为了操作对象,reflect对象上可以拿到语言内部的方法:

    // 旧写法
    try {
      Object.defineProperty(target, property, attributes);
      // success
    } catch (e) {
      // failure
    }
    
    // 新写法
    if (Reflect.defineProperty(target, property, attributes)) {
      // success
    } else {
      // failure
    }
    
    // 旧写法
    'assign' in Object // true
    
    // 新写法
    Reflect.has(Object, 'assign') // true
    

    1. Reflect.get(target, name, receiver)

    Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined:

    var myObject = {
      foo: 1,
      bar: 2,
      get baz() {
        return this.foo + this.bar;
      },
    };
    
    var myReceiverObject = {
      foo: 4,
      bar: 4,
    };
    
    Reflect.get(myObject, 'baz', myReceiverObject)  // 8
    

    2. Reflect.set(target, name, value, receiver)

    Reflect.set方法设置target对象的name属性等于value:

    var myObject = {
      foo: 1,
      set bar(value) {
        return this.foo = value;
      },
    }
    
    myObject.foo // 1
    
    Reflect.set(myObject, 'foo', 2);
    myObject.foo // 2
    
    Reflect.set(myObject, 'bar', 3)
    myObject.foo // 3
    

    3. Reflect.has(obj, name)

    Reflect.has方法对应name in obj里面的in运算符:

    var myObject = {
      foo: 1,
    };
    
    // 旧写法
    'foo' in myObject // true
    
    // 新写法
    Reflect.has(myObject, 'foo') // true
    // 如果第一个参数不是对象,Reflect.has和in运算符都会报错。
    

    4. Reflect.deleteProperty(obj, name)

    Reflect.deleteProperty方法等同于delete obj[name],用于删除对象的属性:

    const myObj = { foo: 'bar' };
    
    // 旧写法
    delete myObj.foo;
    
    // 新写法
    Reflect.deleteProperty(myObj, 'foo');
    
    //该方法返回一个布尔值。如果删除成功,或者被删除的属性不存在,返回true;删除失败,被删除的属性依然存在,返回false。
    

    5. Reflect.construct(target, args)

    Reflect.construct方法等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法:

    function Greeting(name) {
      this.name = name;
    }
    
    // new 的写法
    const instance = new Greeting('张三');
    
    // Reflect.construct 的写法
    const instance = Reflect.construct(Greeting, ['张三']);
    

    6. Reflect.getPrototypeOf(obj)

    Reflect.getPrototypeOf方法用于读取对象的__proto__属性,对应Object.getPrototypeOf(obj):

    const myObj = new FancyThing();
    
    // 旧写法
    Object.getPrototypeOf(myObj) === FancyThing.prototype;
    
    // 新写法
    Reflect.getPrototypeOf(myObj) === FancyThing.prototype;
    

    Reflect.getPrototypeOf和Object.getPrototypeOf的一个区别是,如果参数不是对象,Object.getPrototypeOf会将这个参数转为对象,然后再运行,而Reflect.getPrototypeOf会报错。

    Object.getPrototypeOf(1)  // Number {[[PrimitiveValue]]: 0}
    Reflect.getPrototypeOf(1) // 报错
    

    7. Reflect.setPrototypeOf(obj, newProto)

    Reflect.setPrototypeOf方法用于设置目标对象的原型(prototype),对应Object.setPrototypeOf(obj, newProto)方法。它返回一个布尔值,表示是否设置成功:

    const myObj = {};
    
    // 旧写法
    Object.setPrototypeOf(myObj, Array.prototype);
    
    // 新写法
    Reflect.setPrototypeOf(myObj, Array.prototype);
    
    myObj.length // 0
    

    8. Reflect.apply(func, thisArg, args)

    Reflect.apply方法等同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数:

    const ages = [11, 33, 12, 54, 18, 96];
    
    // 旧写法
    const youngest = Math.min.apply(Math, ages);
    const oldest = Math.max.apply(Math, ages);
    const type = Object.prototype.toString.call(youngest);
    
    // 新写法
    const youngest = Reflect.apply(Math.min, Math, ages);
    const oldest = Reflect.apply(Math.max, Math, ages);
    const type = Reflect.apply(Object.prototype.toString, youngest, []);
    

    9. Reflect.defineProperty(target, propertyKey, attributes)

    Reflect.defineProperty方法基本等同于Object.defineProperty,用来为对象定义属性。未来,后者会被逐渐废除,所以最好现在就开始使用Reflect.defineProperty代替它:

    function MyDate() {
      /*…*/
    }
    
    // 旧写法
    Object.defineProperty(MyDate, 'now', {
      value: () => Date.now()
    });
    
    // 新写法
    Reflect.defineProperty(MyDate, 'now', {
      value: () => Date.now()
    });
    

    10. Reflect.getOwnPropertyDescriptor(target, propertyKey)

    Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象,将来会替代掉后者:

    var myObject = {};
    Object.defineProperty(myObject, 'hidden', {
      value: true,
      enumerable: false,
    });
    
    // 旧写法
    var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');
    
    // 新写法
    var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');
    

    11. Reflect.isExtensible(target)

    Reflect.isExtensible方法对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展:

    const myObject = {};
    
    // 旧写法
    Object.isExtensible(myObject) // true
    
    // 新写法
    Reflect.isExtensible(myObject) // true
    

    12. Reflect.preventExtensions(target)

    Reflect.preventExtensions对应Object.preventExtensions方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功:

    var myObject = {};
    
    // 旧写法
    Object.preventExtensions(myObject)  // Object {}
    
    // 新写法
    Reflect.preventExtensions(myObject) // true
    

    13. Reflect.ownKeys(target)

    Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和:

    var myObject = {
      foo: 1,
      bar: 2,
      [Symbol.for('baz')]: 3,
      [Symbol.for('bing')]: 4,
    };
    
    // 旧写法
    Object.getOwnPropertyNames(myObject)
    // ['foo', 'bar']
    
    Object.getOwnPropertySymbols(myObject)
    //[Symbol(baz), Symbol(bing)]
    
    // 新写法
    Reflect.ownKeys(myObject)
    // ['foo', 'bar', Symbol(baz), Symbol(bing)]
    

    以上静态方法,如果传入的不是对象,基本都会报错

    最后

    好了,本篇就到这里,主要都是摘抄常用的知识点和备注自己的理解,希望对你有所帮助,后面会持续更新,也感谢你能看到这里!

    GitHub传送门
    博客园传送门

  • 相关阅读:
    java-ApiDemo
    java编译器特性
    java
    java
    java
    java
    java
    java
    hdoj 3549 Flow Problem(最大网络流)
    hdoj 1269 迷宫城堡(强连通分量)
  • 原文地址:https://www.cnblogs.com/BetterMan-/p/10390931.html
Copyright © 2011-2022 走看看