关于前端:ES中对象的扩展

3次阅读

共计 5096 个字符,预计需要花费 13 分钟才能阅读完成。

属性的可枚举性

可枚举性

对象的每个属性都有一个形容对象(Desciprtor), 用来管制该属性的行为。Object.getOwnPropertyDescriptor 办法能够获取该属性的形容对象

    let obj = {foo: 'foo'}
    Object.getOwnPropertyDescriptor(obj, 'foo')
    /*
        {
            value: 'foo',
            writable: true,
            enumerable: true,
            configurable: true
        }
    */ 

形容对象的 enumerable 属性,称为’可枚举性’, 如果该属性为 false,示意某些操作会疏忽以后属性

目前,有四个操作会疏忽 enumerable 为 false 的属性

  • for in 只遍历对象本身和继承的可枚举属性
  • object.keys 只返回可遍历的属性名
  • Json.stringify 只串行化对象本身的可枚举的属性
  • Object.assign 疏忽 enumerable 为 false 的属性,只拷贝对象本身的可枚举属性

引入可枚举性这个概念的最后目标,就是让某些属性能够防止被 for…in,否则外部的办法,属性都会被遍历到。比方对象的 tostring,数组的 length

    Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumable
    // false

    Object.getOwnPropertyDescriptor([], 'length').enumable
    // false

下面代码对象的 toString 和数组的 length 属性的 enumable 都是 false,所以不会被 for…in 遍历到

ES6 规定所有 class 的继承属性都是不可枚举的

    Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
    // false

因为 for…in 总是引入继承的属性,所以尽量应用 Object.keys

属性的遍历

ES6 一共有五种办法,能够遍历对象的属性

(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 键,依照退出工夫升序排列

    let obj = {[Symbol()]: 0,
        b: 0,
        3: 9,
        10: 20,
        a: 9
    }
    Reflect.ownKeys(obj)
    // [3, 10, b, a, Symbol()]

Reflect.ownKeys 办法会返回一个数组,这个数组的返回秩序是这样的,

首先是数值 3 和 10,其次是字符串 b 和 a,最初是 symbol

super 关键字

this 总是指向函数所在的以后对象,ES6 新增了 super 关键字,它指代的是以后对象的原型对象

this –> 以后对象
super –> 以后对象的原型对象

    let person = {name: 'person'}

    let son = {
        name: 'sun',
        printPerson () {return super.name}
    }
    
    Object.setPrototypeOf(son, person)
    son.printPerson()
    // 'person'

下面通过设置 son 的原型,调用 son 办法来找到原型的属性

super 示意原型对象时,只能用在对象的办法之中,用在其余中央会报错

    let obj = {foo: super.foo}
    // 用在了属性上

    let obj1 = {foo: () => super.foo
    }
    // 用在了办法

    let obj2 = {foo: function () {return super.foo}
    }

下面三种都会报错,因为对于 js 引擎而言,基本没有用到 super。

第一种属于用在属性中
第二种和第三种属于用在函数中,但又赋值给了 foo 属性

当初只有简写的对象办法能力让 js 引擎确认,定义的是对象的办法

js 引擎外部,super.foo 等同于
Object.getPrototypeOf(this).foo 属性 或 Object.getPrototypeOf(this).foo.call(this) 办法。

    let father = {
        name: 'person',
        sayHello () {return this.name}
    }

    let son = {
        name: 'son',
        sayHi () {return super.sayHello();
        }
    }
    
    Object.setPrototypeOf(son, father);
    son.sayHi();
    // 'son'

下面代码分为三步

  • 第一步调用 son.sayHi 函数返回了原型的 sayHello 办法
  • 第二步 father 本身调用 sayHello 办法,返回了 this.name
  • 第三部 this.name 此时的 this,因为在 son 的环境执行的所以,this 指向 son,所以打印后果为’son’

如果改为 father.sayHello() 就不一样了

对象的扩大运算符

数组中的扩大运算符(…)以及得心应手了,对象的写法与之性能根本相似

解构赋值

对象解构用于将一个对象的值全副取出来(可遍历的),并且没有被读取的属性,赋值到另一个对象身上,所有他们的键和值,最初造成一个新对象

    let {x, y, ...z} = {x: 1, y: 2, z: 3, u: 10, n: 20}
    x // 1
    y // 2
    z // {z: 3, u: 10, n: 20}

下面代码只有 z 是解构胜利的对象,x 和 y 属于被读取过后的值了,z 会把 x y 没有读取到的键和值拷贝过去,返回一个新数组

解构赋值等号左边必须是一个对象,如果不是就会报错

    let {...y} = undefined // error
    let {...n} = null        // error 

解构赋值必须是最初一个参数,否则会报错

    let {...x, y} = obj; // error
    let {x, ...y, z} = obj; // error

解构赋值是浅拷贝,即如果拷贝的值是一个数组,对象,函数,那么拷贝的理论是援用,而不是正本

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

下面代码拷贝的是一个对象,因为解构只是浅拷贝,所以指向了同一个指针地址,导致新地址的数据扭转,原地址指向的数据也要产生扭转,因为他们的指向同一个房间。

解构赋值无奈拿到原型的属性

    let a1 = {bar: 'bar'}
    let a2 = {foo: 'foo'}
    Object.setPrototypeOf(a1, a2)
    let {...a3} = a2
    a3
    // {foo: 'foo'}
    a3.bar
    // undefined

下面代码 a2 继承 a1,a3 又拷贝了 a2,然而解构赋值无奈拿到原型属性,导致 a3 没有获取到 a1 这个先人的属性

    // 创立一个 obj 的原型对象
    let obj = Object.create({x: 1, y: 2})
    // 本身增加一个 z 属性
    obj.z = 3
    
    let {x, ...newObj} = obj
    // x 是单纯的解构赋值,所以能够读取继承属性。// y 和 z 是扩大运算符解构赋值,只能读取对象本身的属性
    let {y, z} = newObj
    // y 是继承属性,所以 newObj 中获取不到。x // 1
    y // undefined
    z // 3

可能有人感觉能够省事,会这么写

    let {x, ...{y, z}} = obj
    // error

ES6 规定,变量申明语句中,如果应用解构赋值,扩大运算符前面必须是一个变量名,而不能是一个解构赋值表达式。

解构赋值的一个用途,是扩大某个函数的参数,传递给另一个函数或者引入其余操作

    function test (a, b) {return a + b}
    
    function bar (x, y, ...anther) {
        // 应用 x y 进行外部操作
        // 把扩大运算符导出进来,提供给另一个函数,或者做别的操作
        console.log(x * y);
        return test(anther);
        
    }

    bar(1, 3, 9, 9)

扩大运算符

对象的扩大运算符,用于取出参数对象所有可遍历属性,拷贝到以后对象之中

    let z = {x: 1, y: 2}
    let n = {...z}
    n // {x: 1, y: 2}

因为数组是非凡的对象,所以对象扩大运算符也能够用于数组

    let foo = {...['x', 'y', 'z']}
    foo 
    // {0: 'x', 1: 'y', 2: 'z'}

如果扩大运算符前面是一个空对象,则没有任何成果

    let obj = {...{}, a: 1}
    obj
    // {a: 1}

如果不是个对象,会把他转换为对象

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

下面会调用包装类,转换为 Number(1) 但该对象本身本就没有任何属性,所以返回空

还有一些类型

    {...true} // {}
    {...undefined} // {}
    {...null} // {}
    {...'es6'} // {0: 'e', 1: 's', 2: '6'}

前三个都会返回空对象,最初一个字符串,因为字符串会转换为类数组,所以返回的就是一组带索引的对象

对象的扩大运算符等同于应用 Object.assign() 办法

    let aClone = {...a};

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

下面只是拷贝对象实例属性,想要拷贝原型属性,须要这样写

    // fn1 
    let clone1 = {
        // 拿到 obj 的原型,并赋给本人的 proto, 本人的原型指向了 obj 的原型 
        __proto__ : Object.getPrototypeOf(obj),
        // obj 的实例属性赋给本人原型,拷贝 obj 的实例属性和原型属性
        ...obj
    }

    // fn2
    let clone2 = Object.assign(
        // 创立一个对象,该对象的原型是 obj 原型对象
        Object.create(Object.getPrototypeOf(obj)),
        // 把 obj 放到下面这个对象里
        obj
    )
    
    // fn3
    let clone3 = Object.create(
        // 创立参照对象,该对象为 obj 的原型对象
        Object.getPrototypeOf(obj),
        // 把 obj 的所有可枚举属性传递给参照对象
        Object.getOwnPropertyDescriptors(obj)
    )

扩大运算符,能够合并对象

    let obj1 = {...a, ...b}
    let obj2 = Object.assign({}, a, b)

如果扩大运算符前面还有用户自定义的属性,那么扩大运算符外部的同名属性会被笼罩掉

    let obj = {...a, x: 1, y: 2}
    // 等同于
    let obj1 = {...a, ...{x: 1, y: 2}}
    // 等同于
    let x = 1, y = 2, obj2 = {...a, x, y}
    // 等同于
    let obj3 = Object.assign({}, a, {x: 1, y: 2})

下面代码中,a 对象中的 x,y 属性都会被 a 前面的 x y 都会在拷贝到新对象时笼罩掉

这样一来更改局部现有属性,很不便

    let newVersion = {
        ...oldVersion,
        name: 'new Version'
    }

下面的旧版本 name 会被替换掉

如果自定义属性放在扩大运算符前,就会变成设置新对象的默认属性值

    let obj = {x: 1, y: 2, ...a}
    ==
    let obj1 = Object.assign({}, {x: 1, y: 2}, a
    == 
    let obj2 = Object.assign({x: 1, y: 2}, a)

对象扩大运算符前面也能够跟表达式

    let obj = {...(x > 1 : {name: 'wei'} ? {}),
        bar: 'foo'
    }

如果扩大运算符的参数对象之中,有取值函数 get,它会主动执行的

    let obj = {get foo () {console.log(1)
        }
    }
    
    let newObj = {...obj}
    // 1

下面代码,在扩大完结后就会执行 get 函数的语句

正文完
 0