es6学习记录之对象的扩展

127 阅读9分钟

es6学习记录之对象的扩展

属性的简洁表示法

    //属性简写
    let ad = 'bar'
    let ca = {ad}  
    console.log(ca);//ad:"bar"
    // 相当于
    let ad = 'bar'
    let ca = {ad:ad}  
    console.log(ca);//ad:"bar"

说明:当对象的key跟属性值为变量的名称一样的时候可以直接写key,这种写法vue组件注册的时候用的多

        const a = {
            add() {
                console.log(111);
            }
        }
        a.add() //111
        //相当于
        const a = {
            add: function () {
                console.log(111);
            }
        }
        a.add() //111       

说明:方法也可以简写

属性名表达式

对象属性的添加有两种方式,方法二可以放表达式

        const obj = {

        }
        // 方法一
        obj.foo = true;
        // 方法二
        obj['a' + 'bc'] = 123;
        console.log(obj);//{abc: 123,foo: true}

ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。

        let asd = "ddd"
        let obj = {
            [asd]:564,
            asd
        }
        console.log(obj);//{ddd: 564,asd: "ddd"}

说明:中括号内的变量值成为了属性名,这种写法感觉作用不大...

属性的可枚举性和遍历

可枚举性

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。这个感觉作用不大没有详细看略过

属性的遍历

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

(1)for…in

for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

(2)Object.keys(obj)

Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

(3)Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

(4)Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

(5)Reflect.ownKeys(obj)

Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

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

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

super 关键字

我们知道,this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。

        const proto = {
            foo: 'hello'
        };
        const obj = {
            foo: 'world',
            find() {
                return super.foo;
            }
        };
     console.log(Object.setPrototypeOf(obj, proto));
     console.log(obj.find() );// "hello"

链判断运算符

编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取message.body.user.firstName,安全的写法是写成下面这样。

        const firstName = (message
            && message.body
            && message.body.user
            && message.body.user.firstName) || 'default';

或者使用三元运算符?:,判断一个对象是否存在。

        const fooInput = myForm.querySelector('input[name=foo]')
        const fooValue = fooInput ? fooInput.value : undefined

这样的层层判断非常麻烦,因此 ES2020 引入了“链判断运算符”(optional chaining operator)?.,简化上面的写法。

        const firstName = message?.body?.user?.firstName || 'default';
        const fooValue = myForm.querySelector('input[name=foo]')?.value
        //我自己写的例子
                const fooInput = {
            name: {
                age: 14
            }
        }
        const cad = fooInput?.name?.age || 188
        console.log(cad);//14

链判断运算符有三种用法。

  • obj?.prop // 对象属性
  • obj?.[expr] // 同上
  • func?.(...args) // 函数或对象方法的调用

下面是判断对象方法是否存在,如果存在就立即执行的例子。

       let iterator={
            acc(){
                console.log(111);
            }
        }
       let cd =  iterator.return?.()
       console.log(cd);//undefined
       //上面代码中,`iterator.return`如果有定义,就会调用该方法,否则直接返回`undefined`。
             let iterator={
            return(){
                console.log(111);
            }
        }
       let cd = iterator.return?.() //111
       console.log(cd);//undefined
       //还是会返回undefined但是方法执行了
        

下面是这个运算符常见的使用形式,以及不使用该运算符时的等价形式。

        a?.b
        // 等同于
        a == null ? undefined : a.b
        a?.[x]
        // 等同于
        a == null ? undefined : a[x]
        a?.b()
        // 等同于
        a == null ? undefined : a.b()
        a?.()
        // 等同于
        a == null ? undefined : a()

上面代码中,特别注意后两种形式,如果a?.b()里面的a.b不是函数,不可调用,那么a?.b()是会报错的。a?.()也是如此,如果a不是nullundefined,但也不是函数,那么a?.()会报错。

使用这个运算符,有几个注意点。

image.png

image.png

image.png

image.png

Null 判断运算符

读取对象属性的时候,如果某个属性的值是nullundefined,有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。

        let ac = 0
        let ad = ac||18
        console.log(ad);//18

上面的代码通过||运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为nullundefined,默认值就会生效,但是属性的值如果为空字符串或false0,默认值也会生效。

为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符??。它的行为类似||,但是只有运算符左侧的值为nullundefined时,才会返回右侧的值。

    let ac = 0
    let ad = ac??18
    console.log(ad);//0

上面代码中,默认值只有在属性值为nullundefined时,才会生效。 这个运算符的一个目的,就是跟链判断运算符?.配合使用,为nullundefined的值设置默认值。

        const animationDuration = response.settings?.animationDuration ?? 300;

??有一个运算优先级问题,它与&&||的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。

        // 报错
lhs && middle ?? rhs
lhs ?? middle && rhs
lhs || middle ?? rhs
lhs ?? middle || rhs

上面四个表达式都会报错,必须加入表明优先级的括号。

        (lhs && middle) ?? rhs;
        lhs && (middle ?? rhs);
        (lhs ?? middle) && rhs;
        lhs ?? (middle && rhs);
        (lhs || middle) ?? rhs;
        lhs || (middle ?? rhs);
        (lhs ?? middle) || rhs;
        lhs ?? (middle || rhs);

对象的新增方法

Object.is()

ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。

ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致

    //===
   let cd = (NaN===NaN)
   console.log(cd);//false   
   //Object.is()
   let  ad =  Object.is(NaN,NaN)
   console.log(ad);//true

跟===区别不大只是对-0===+0 还有NaN===NaN的情况做了处理

Object.assign()

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

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

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

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

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

如果只有一个参数,Object.assign会直接返回该参数

        const obj = { a: 1 };
        Object.assign(obj) === obj // true
        console.log(Object.assign(obj));//{a:1}

如果该参数不是对象,则会先转成对象,然后返回。

 console.log(Object.assign(true))

image.png

如果是不能转成对象的数据,比如undefinednull无法转成对象,所以如果它们作为参数,就会报错。

注意点

Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

        const obj1 = { a: { b: 1 } };
        const obj2 = Object.assign({}, obj1);
        obj1.a.b = 2;
        obj2.a.b // 2

上面代码中,源对象obj1a属性的值是一个对象,Object.assign拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。 并且返回值跟合并对象地址相同

        const obj3 = { c: 1 };
        const obj1 = { a: { b: 1 } };
        const obj2 = Object.assign(obj3, obj1);
        obj1.a.b = 2;
        obj2.a.b // 2
        console.log(obj2===obj3);//true

同名属性的替换 对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。

        const target = { a: { b: 'c', d: 'e' } }
        const source = { a: { b: 'hello' } }
        Object.assign(target, source)
        // { a: { b: 'hello' } }

上面代码中,target对象的a属性被source对象的a属性整个替换掉了,而不会得到{ a: { b: 'hello', d: 'e' } }的结果。这通常不是开发者想要的,需要特别小心。

一些函数库提供Object.assign的定制版本(比如 Lodash 的_.defaultsDeep方法),可以得到深拷贝的合并。

数组的处理 Object.assign 可以处理数组但是会把数组当成对象

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

这里我的理解就是将索引当key进行了合并,重复的索引会被直接覆盖

取值函数的处理 Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。

       const source = {
            get foo() { return 1 }
        };
        const target = {};
        Object.assign(target, source)
        console.log(target);
        // { foo: 1 }

可读性不高感觉,应该不会使用

常见用途 为对象添加属性

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

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

为对象添加方法

     Object.assign(SomeClass.prototype, {
            someMethod(arg1, arg2) {
                    ···
            },
            anotherMethod() {
                    ···
            }
        });
        // 等同于下面的写法
        SomeClass.prototype.someMethod = function (arg1, arg2) {
                  ···
        };
        SomeClass.prototype.anotherMethod = function () {
                  ···
        };

上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,再使用assign方法添加到SomeClass.prototype之中。

克隆对象

合并多个对象

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

Object.keys()

返回对象的key的数组

    let ad = {
        name:'站手感',
        age:13,
        sex:1,
        possword:"13123",
        username:"张大仙"
    }
    console.log(Object.keys(ad));//['name', 'age', 'sex', 'possword', 'username']

ES2017 引入了跟Object.keys配套的Object.valuesObject.entries,作为遍历一个对象的补充手段,供for...of循环使用。

        let { keys, values, entries } = Object;
        let obj = { a: 1, b: 2, c: 3 };
        for (let key of keys(obj)) {
            console.log(key); // 'a', 'b', 'c'
        }
        for (let value of values(obj)) {
            console.log(value); // 1, 2, 3
        }
        for (let [key, value] of entries(obj)) {
            console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
        }

Object.values()

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

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

Object.entries()

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

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

除了返回值不一样,该方法的行为与Object.values基本一致。

扩展自己实现Object.entries()方法(我直接复制的)

 function* entries(obj) {
            for (let key of Object.keys(obj)) {
                yield [key, obj[key]];
            }
        }
        // 非Generator函数的版本
        function entries(obj) {
            let arr = [];
            for (let key of Object.keys(obj)) {
                arr.push([key, obj[key]]);
            }
            return arr;
        }

image.png

参考资料 阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版