0
点赞
收藏
分享

微信扫一扫

ES6基础3

前程有光 03-15 23:00 阅读 2
es6前端
  1. 函数的扩展
    1. 基本用法
      1. 函数参数的默认值
        ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
      2. 参数变量是默认声明的,所以不能用letconst再次声明。下面代码中,参数变量x是默认声明的,在函数体中,不能用letconst再次声明,否则会报错。
        function foo(x = 5) {
          let x = 1; // error
          const x = 2; // error
        }
    2. 与解构赋值默认值结合使用
      1. 参数默认值可以与解构赋值的默认值,结合起来使用。下面代码使用了对象的解构赋值默认值,而没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量xy才会通过解构赋值而生成。如果函数foo调用时参数不是对象,变量xy就不会生成,从而报错。如果参数对象没有y属性,y的默认值5才会生效。
        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

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

        // 写法一
        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]

        个人理解当m1没有传入参数时,参数设置为空对象,使用对象结构值为{x = 0, y = 0};当m2没有传入参数时,使用设置的默认参数

    3. 参数默认值的位置
      1. 通常情况下,定义默认值参数应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部参数设置默认值,实际上这个参数是没法省略的。
        // 例一
        function f(x = 1, y) {
          return [x, y];
        }
        
        f() // [1, undefined]
        f(2) // [2, undefined])
        f(, 1) // 报错
        f(undefined, 1) // [1, 1]
        
        // 例二
        function f(x, y = 5, z) {
          return [x, y, z];
        }
        
        f() // [undefined, 5, undefined]
        f(1) // [1, 5, undefined]
        f(1, ,2) // 报错
        f(1, undefined, 2) // [1, 5, 2]
      2. 如果传入undefined,将触发该参数等于默认值null没有这个效果
        function foo(x = 5, y = 6) {
          console.log(x, y);
        }
        
        foo(undefined, null)
        // 5 null
    4. 函数的length属性
      1. 指定默认值以后,函数的length属性,将返回没有指定默认值参数个数。也就是说,指定了默认值后,length属性将失真。
        (function (a) {}).length // 1
        (function (a = 5) {}).length // 0
        (function (a, b, c = 5) {}).length // 2
      2. 因为length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest参数不会计入length属性
        (function(...args) {}).length // 0
      3. 如果设置了默认值参数不是尾参数,那么length属性不再计入后面的参数了。
        (function (a = 0, b, c) {}).length // 0
        (function (a, b = 1, c) {}).length // 1
    5. 作用域
      1. 下面代码中,函数foo的参数x的默认值也是x。这时,默认值x的作用域是函数作用域,而不是全局作用域。由于在函数作用域中,存在变量x,但是默认值在x赋值之前先执行了,所以这时属于暂时性死区(参见《let和const命令》一章),任何对x的操作都会报错。
        var x = 1;
        
        function foo(x = x) {
          // ...
        }
        
        foo() // ReferenceError: x is not defined

    6. 应用
      1. 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
        function throwIfMissing() {
          throw new Error('Missing parameter');
        }
        
        function foo(mustBeProvided = throwIfMissing()) {
          return mustBeProvided;
        }
        
        foo()
        // Error: Missing parameter

        上面代码的foo函数,如果调用的时候没有参数,就会调用默认值throwIfMissing函数,从而抛出一个错误。

        从上面代码还可以看到,参数mustBeProvided的默认值等于throwIfMissing函数的运行结果(即函数名之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行),这与python语言不一样。

        另外,可以将参数默认值设为undefined,表明这个参数是可以省略的。

        function foo(optional = undefined) { ··· }
    7. rest参数
      1. ES6引入rest参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中
        function add(...values) {
          let sum = 0;
        
          for (var val of values) {
            sum += val;
          }
        
          return sum;
        }
        
        add(2, 5, 3) // 10
      2. 注意,rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
        // 报错
        function f(a, ...b, c) {
          // ...
        }
      3. 函数的length属性不包括rest参数
        (function(a) {}).length  // 1
        (function(...a) {}).length  // 0
        (function(a, ...b) {}).length  // 1
    8. 扩展运算符
      1. 扩展运算符(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>]
      2. 该运算符主要用于函数调用。
        function push(array, ...items) {
          array.push(...items);
        }
        
        function add(x, y) {
          return x + y;
        }
        
        var numbers = [4, 38];
        add(...numbers) // 42

        上面代码中,array.push(...items)add(...numbers)这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。

      3. 扩展运算符与正常的函数参数可以结合使用,非常灵活。
        function f(v, w, x, y, z) { }
        var args = [0, 1];
        f(-1, ...args, 2, ...[3]);
    9. 扩展运算符的应用
      1. 合并数组:扩展运算符提供了数组合并的新写法。
        // ES5
        [1, 2].concat(more)
        // ES6
        [1, 2, ...more]
        
        var arr1 = ['a', 'b'];
        var arr2 = ['c'];
        var arr3 = ['d', 'e'];
        
        // ES5的合并数组
        arr1.concat(arr2, arr3);
        // [ 'a', 'b', 'c', 'd', 'e' ]
        
        // ES6的合并数组
        [...arr1, ...arr2, ...arr3]
        // [ 'a', 'b', 'c', 'd', 'e' ]

      2. 与解构赋值结合:扩展运算符可以与解构赋值结合起来,用于生成数组
        // 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 [first, ...rest] = [];
        first // undefined
        rest  // []:
        
        const [first, ...rest] = ["foo"];
        first  // "foo"
        rest   // []

        如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

        const [...butLast, last] = [1, 2, 3, 4, 5];
        // 报错
        
        const [first, ...middle, last] = [1, 2, 3, 4, 5];
        // 报错
      3. 字符串

        1. 扩展运算符还可以将字符串转为真正的数组。

          [...'hello']
          // [ "h", "e", "l", "l", "o" ]
        2. 正确返回字符串长度的函数,可以像下面这样写。

          function length(str) {
            return [...str].length;
          }
          
      4. 实现了Iterator接口的对象
        1. 任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。下面是一个最特殊的例子。它不是数组,而一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了Iterator接口
          var nodeList = document.querySelectorAll('div');
          var array = [...nodeList];
        2. 对于那些没有部署Iterator接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。下面代码中,arrayLike是一个类似数组的对象,但是没有部署Iterator接口,扩展运算符就会报错。这时,可以改为使用Array.from方法将arrayLike转为真正的数组。
          let arrayLike = {
            '0': 'a',
            '1': 'b',
            '2': 'c',
            length: 3
          };
          
          // TypeError: Cannot spread non-iterable object.
          let arr = [...arrayLike];
    10. Map和Set结构,Generator函数
      1. 扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。
        let map = new Map([
          [1, 'one'],
          [2, 'two'],
          [3, 'three'],
        ]);
        
        let arr = [...map.keys()]; // [1, 2, 3]
      2. Generator函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。
        var go = function*(){
          yield 1;
          yield 2;
          yield 3;
        };
        
        [...go()] // [1, 2, 3]

        下面代码中,变量go是一个Generator函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。

        如果对没有iterator接口的对象,使用扩展运算符,将会报错。

        var obj = {a: 1, b: 2};
        let arr = [...obj]; // TypeError: Cannot spread non-iterable object
    11. name属性
      1. 函数的name属性,返回该函数的函数名。
        function foo() {}
        foo.name // "foo"
        var func1 = function () {};
        
        // ES5
        func1.name // ""
        
        // ES6
        func1.name // "func1"
        const bar = function baz() {};
        
        // ES5
        bar.name // "baz"
        
        // ES6
        bar.name // "baz"

    12. 箭头函数
      1. 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
  2. 对象的扩展
    1. 属性的简洁表示法
      1. ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。下面代码表明,ES6允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。下面是另一个例子。
    2. 属性名表达式
      1. ES6 允许字面量定义对象时,用(表达式)作为对象的属性名,即把表达式放在方括号内。
        let propKey = 'foo';
        
        let obj = {
          [propKey]: true,
          ['a' + 'bc']: 123
        };
      2. 注意,属性名表达式简洁表示法不能同时使用,会报错。
        // 报错
        var foo = 'bar';
        var bar = 'abc';
        var baz = { [foo] };
        
        // 正确
        var foo = 'bar';
        var baz = { [foo]: 'abc'};
      3. 注意,属性名表达式如果一个对象默认情况下会自动将对象转为字符串[object Object],这一点要特别小心。下面代码中,[keyA][keyB]得到的都是[object Object],所以[keyB]会把[keyA]覆盖掉,而myObject最后只有一个[object Object]属性。
        const keyA = {a: 1};
        const keyB = {b: 2};
        
        const myObject = {
          [keyA]: 'valueA',
          [keyB]: 'valueB'
        };
        
        myObject // Object {[object Object]: "valueB"}
    3. Object.is()
      1. ES5比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。ES6提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
        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
    4. Object.assign()
      1. Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性复制到目标对象(target)。Object.assign方法的第一个参数是目标对象后面的参数都是源对象。注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性覆盖前面属性。如果只有一个参数,Object.assign会直接返回该参数。如果该参数不是对象,则会先转成对象然后返回。由于undefinednull无法转成对象,所以如果它们作为参数(仅限第一个参数),就会报错(其它参数无法转成对象,会直接跳过)。
        var target = { a: 1 };
        
        var source1 = { b: 2 };
        var source2 = { c: 3 };
        
        Object.assign(target, source1, source2);
        target // {a:1, b:2, c:3}
      2. 其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
        var v1 = 'abc';
        var v2 = true;
        var v3 = 10;
        
        var obj = Object.assign({}, v1, v2, v3);
        console.log(obj); // { "0": "a", "1": "b", "2": "c" }
      3. 注意点:
        1. Object.assign方法实行的是浅拷贝,而不是深拷贝。深拷贝实现方式如下
           Object.assign(obj2,JSON.parse(JSON.stringify(obj1)))
        2. 常见用途
          1. 为对象添加属性
            class Point {
              constructor(x, y) {
                Object.assign(this, {x, y});
              }
            }
          2. 为对象添加方法
            Object.assign(SomeClass.prototype, {
              someMethod(arg1, arg2) {
                ···
              },
              anotherMethod() {
                ···
              }
            });
            
            // 等同于下面的写法
            SomeClass.prototype.someMethod = function (arg1, arg2) {
              ···
            };
            SomeClass.prototype.anotherMethod = function () {
              ···
            };
          3. 克隆对象
            function clone(origin) {
              return Object.assign({}, origin);
            }
          4. 合并多个对象
            const merge =
              (target, ...sources) => Object.assign(target, ...sources);
          5. 为属性指定默认值
            const DEFAULTS = {
              logLevel: 0,
              outputFormat: 'html'
            };
            
            function processContent(options) {
              options = Object.assign({}, DEFAULTS, options);
            }
    5. 属性的可枚举性
      1. 对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取属性描述对象
        let obj = { foo: 123 };
        Object.getOwnPropertyDescriptor(obj, 'foo')
        //  {
        //    value: 123,
        //    writable: true,
        //    enumerable: true,
        //    configurable: true
        //  }
      2. 描述对象的enumerable属性,称为”可枚举性“,如果该属性为false,就表示某些操作忽略当前属性。ES5有三个操作会忽略enumerable为false的属性。

        for...in循环:只遍历对象自身的和继承的可枚举的属性
        Object.keys():返回对象自身的所有可枚举的属性的键名
        JSON.stringify():只串行化对象自身的可枚举的属性
        ES6新增了一个操作Object.assign(),会忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

        这四个操作之中,只有for...in会返回继承的属性。实际上,引入enumerable的最初目的,就是让某些属性可以规避掉for...in操作比如,对象原型的toString方法,以及数组的length属性,就通过这种手段,不会被for...in遍历到。

        Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
        // false
        
        Object.getOwnPropertyDescriptor([], 'length').enumerable
        // false
      3. 上面代码中,toStringlength属性的enumerable都是false,因此for...in不会遍历到这两个继承自原型的属性。另外,ES6规定,所有Class的原型的方法都是不可枚举的。

        Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
        // false
      4. 总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for...in循环,而使用Object.keys()代替

    6. __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
      1. __proto__属性
        1. __proto__属性(前后各两个下划线),用来读取或设置当前对象的prototype对象。目前,所有浏览器(包括IE11)都部署了这个属性。
          // es6的写法
          var obj = {
            method: function() { ... }
          };
          obj.__proto__ = someOtherObj;
          
          // es5的写法
          var obj = Object.create(someOtherObj);
          obj.method = function() { ... };
        2. 该属性没有写入ES6的正文,而是写入了附录,原因__proto__前后的双下划线,说明它本质上是一个内部属性,而不是一个正式的对外的API,只是由于浏览器广泛支持,才被加入了ES6。标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
      2. Object.setPrototypeOf()
        1. Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象。它是ES6正式推荐的设置原型对象的方法。
          // 格式
          Object.setPrototypeOf(object, prototype)
          
          // 用法
          var o = Object.setPrototypeOf({}, null);
        2. 该方法等同于下面的函数。
          function (obj, proto) {
            obj.__proto__ = proto;
            return obj;
          }
        3. 下面是一个例子。
          let proto = {};
          let obj = { x: 10 };
          Object.setPrototypeOf(obj, proto);
          
          proto.y = 20;
          proto.z = 40;
          
          obj.x // 10
          obj.y // 20
          obj.z // 40

          上面代码将proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性。

      3. Object.getPrototypeOf()
        1. 该方法与setPrototypeOf方法配套,用于读取一个对象的prototype对象
          Object.getPrototypeOf(obj);
        2. 下面是一个例子。
          function Rectangle() {
          }
          
          var rec = new Rectangle();
          
          Object.getPrototypeOf(rec) === Rectangle.prototype
          // true
          
          Object.setPrototypeOf(rec, Object.prototype);
          Object.getPrototypeOf(rec) === Rectangle.prototype
          // false
    7. Object.values()
      1. Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
        var obj = { foo: "bar", baz: 42 };
        Object.values(obj)
        // ["bar", 42]
      2. 返回数组的成员顺序,与《属性的遍历》介绍的排列规则一致。(首先遍历所有属性名为数值的属性,按照数字排序其次遍历所有属性名为字符串的属性,按照生成时间排序最后遍历所有属性名为Symbol值的属性,按照生成时间排序。
        var obj = { 100: 'a', 2: 'b', 7: 'c' };
        Object.values(obj)
        // ["b", "c", "a"]
      3. 上面代码中,属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是bcaObject.values只返回对象自身的可遍历属性。

        var obj = Object.create({}, {p: {value: 42}});
        Object.values(obj) // []
      4. 上面代码中,Object.create方法的第二个参数添加的对象属性(属性p),如果不显式声明,默认是不可遍历的。Object.values不会返回这个属性。Object.values会过滤属性名为Symbol值的属性。

        Object.values({ [Symbol()]: 123, foo: 'abc' });
        // ['abc']
      5. 如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。

        Object.values('foo')
        // ['f', 'o', 'o']
      6. 上面代码中,字符串会先转成一个类似数组的对象。字符串的每个字符,就是该对象的一个属性。因此,Object.values返回每个属性的键值,就是各个字符组成的一个数组。

        如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values会返回空数组。

        Object.values(42) // []
        Object.values(true) // []
    8. Object.entries()
      1. Object.entries方法返回一个数组成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组
        var obj = { foo: 'bar', baz: 42 };
        Object.entries(obj)
        // [ ["foo", "bar"], ["baz", 42] ]
      2. 除了返回值不一样,该方法的行为与Object.values基本一致。如果原对象的属性名是一个Symbol值,该属性会被省略。

    9. 对象的扩展运算符
      1. 解构赋值
        1. 对象的解构赋值用于从一个对象取值,相当于将所有可遍历的、但尚未被读取的属性分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
          let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
          x // 1
          y // 2
          z // { a: 3, b: 4 }

        2. 扩展运算符
          1. 扩展运算符(...)用于取出参数对象所有可遍历属性拷贝到当前对象之中
            let z = { a: 3, b: 4 };
            let n = { ...z };
            n // { a: 3, b: 4 }
          2. 扩展运算符可以用于合并两个对象。
            let ab = { ...a, ...b };
            // 等同于
            let ab = Object.assign({}, a, b);
    10. Object.getOwnPropertyDescriptors()
      1. ES7有一个提案,提出了Object.getOwnPropertyDescriptors方法,返回指定对象所有自身属性(非继承属性)的描述对象。
        const obj = {
          foo: 123,
          get bar() { return 'abc' }
        };
        
        Object.getOwnPropertyDescriptors(obj)
        // { foo:
        //    { value: 123,
        //      writable: true,
        //      enumerable: true,
        //      configurable: true },
        //   bar:
        //    { get: [Function: bar],
        //      set: undefined,
        //      enumerable: true,
        //      configurable: true } }

        Object.getOwnPropertyDescriptors方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。

举报

相关推荐

es6基础语法

ES6 ------ 基础(五)

ES6基础语法

ES6 ------ 基础(四)

ES6 基础入门学习

es6基础理论

ES6

0 条评论