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