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传送门
    博客园传送门

  • 相关阅读:
    086 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 03 面向对象基础总结 01 面向对象基础(类和对象)总结
    085 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 04 构造方法调用
    jQuery UI组件库Kendo UI使用技巧小分享
    Kendo UI ListView模板功能,让Web开发更轻松
    UI组件套包DevExpress ASP.NET Core v20.2新版亮点:全新的查询生成器
    Devexpress WinForms最新版开发.NET环境配置Visual Studo和SQL Server对应版本
    全新的桌面应用数据可视化呈现方式,Sankey Diagram控件你了解多少?
    java中的递归方法
    连接数据库查询 将查询结果写入exce文件中
    java连接mysql数据查询数据
  • 原文地址:https://www.cnblogs.com/BetterMan-/p/10390931.html
Copyright © 2011-2022 走看看