共计 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 函数的语句