属性的可枚举性

可枚举性

对象的每个属性都有一个形容对象(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函数的语句