ECMAScript 6的一些注意点 第四部分(对象扩展)
对象的扩展
1.属性简洁表示法 -- 允许在对象中写入变量和函数:
let name = 'jack';
let person = {name};
person //{name:"jack"}
let Person = {name:name};
Person //{name:"jack"}
属性名为变量名, 属性值为变量的值
2.简写方法(method):
let obj = {
method(){
return 1;
}
}
3.用于返回值:
function getPoint(){
let x = 1;
let y = 10;
return {x,y};
}
getPoint(); //{x:1,y:10}
4.简洁写法的属性名总是字符串,所以不会因为它属于关键字,而导致语法解析报错。
5.如果某个方法的值是一个 Generator 函数,前面需要加上星号。
属性名表达式
1.允许字面量定义对象时,用表达式作为对象的属性名,即把表达式放在方括号内。
let pername = 'name';
let obj = {
[pername]: "jack",
['a' + 'ge']: 20
}
obj // {name:"jack",age:20}
obj[pername] // jack
obj[name] //jack
2.表达式还可以用于定义方法名。
let halo = 'hello';
let obj = {
[halo](){
return 'hello';
}
}
obj[helo]() //hello
obj.hello() //hello
3.属性名表达式与简洁表示法,不能同时使用,会报错。
4.属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]。
方法的name属性
1.方法的name
属性返回函数名(即方法名)。
2.如果对象的方法使用了取值函数(getter
)和存值函数(setter
),则name
属性不是在该方法上面,而是该方法的属性的描述对象的get
和set
属性上面,返回值是方法名前加上get
和set
。
const obj = {
get foo() {},
set foo(x) {}
};
obj.foo.name
// TypeError: Cannot read property 'name' of undefined
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
Object.getOwnPropertyDescriptor():
Object.getOwnPropertyDescriptor()
方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
3.两种特殊情况:
-
bind
方法创造的函数,name
属性返回bound
加上原函数的名字 -
Function
构造函数创造的函数,name
属性返回anonymous
4.如果对象的方法是一个 Symbol 值,那么name
属性返回的是这个 Symbol 值的描述。
属性的可枚举性和遍历
1.可枚举性:对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor
方法可以获取该属性的描述对象。
2.描述对象的enumerable
属性,称为“可枚举性”,如果该属性为false
,就表示某些操作会忽略当前属性。
3.目前,有四个操作会忽略enumerable
为false
的属性。
-
for...in
循环:只遍历对象自身的和继承的可枚举的属性。 -
Object.keys()
:返回对象自身的所有可枚举的属性的键名。 -
JSON.stringify()
:只串行化对象自身的可枚举的属性。 -
Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
4.引入“可枚举”(enumerable
)这个概念的最初目的,就是让某些属性可以规避掉for...in
操作,不然所有内部属性和方法都会被遍历到。
5.ES6 规定,所有 Class 的原型的方法都是不可枚举的。
6.尽量不要用for...in
循环,而用Object.keys()
代替。
7.属性的遍历:
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 键,按照加入时间升序排列。
let {
log: a
} = console;
// 属性的遍历
let objname = 'name'
let objmethod = 'hello';
let obj = {
[objname]: "jack",
age: 20,
[objmethod]() {
return objmethod;
},
[Symbol()]:0
}
for (let i in obj) {
a(obj[i]) // 20 hello ƒ [objmethod]() {return objmethod;}
}
let objMsg = Object.keys(obj)
a(objMsg); //["name", "age", "hello"]
let objgetNames = Object.getOwnPropertyNames(obj)
a(objgetNames); //["name", "age", "hello"]
let objgetSymbols = Object.getOwnPropertySymbols(obj);
a(objgetSymbols); //[Symbol()]
let objReflect = Reflect.ownKeys(obj)
a(objReflect); //["name", "age", "hello", Symbol()]
super关键字
1.指向当前对象的原型对象。
let proto = {
foo: 'hello'
}
let obj = {
foo:"world",
find(){
return super.foo;
}
}
Object.setPrototypeOf(obj,proto);
obj.find(); //hello
setPrototypeOf():
Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或
null
。
语法:
Object.setPrototypeOf(obj, prototype)
上面例子将obj的原型指向了proto,然后通过super引用原型对象proto的属性。
2.super
关键字表示原型对象时,只能用在对象的方法之中(目前:只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法),用在其他地方都会报错。
// 报错 super用在属性里面
const obj = {
foo: super.foo
}
// 报错 super用在一个函数里面,然后赋值给foo属性
const obj = {
foo: () => super.foo
}
// 报错 super用在一个函数里面,然后赋值给foo属性
const obj = {
foo: function () {
return super.foo
}
}
3.引擎内部,super.foo
等同于Object.getPrototypeOf(this).foo
(属性)或Object.getPrototypeOf(this).foo.call(this)
(方法)。
解构赋值
1.相当于将目标对象自身的所有可遍历,但尚未被读取的属性分配到指定的对象上面,键和值都会被拷贝到新的对象。
let {x , y , ...z} = { x:1 , y:2 , a:3 , b:4 };
x //1
y //2
z //{ a : 3 , b : 4 }
z
是解构赋值所在的对象。它获取等号右边的所有尚未读取的键(a
和b
),将它们连同值一起拷贝过来。
2.如果等号右边是undefined
或null
,就会报错,因为它们无法转为对象。
3.解构赋值必须是最后一个参数,否则会报错。
let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误
4.解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2
5.扩展运算符的解构赋值,不能复制继承自原型对象的属性。
let o1 = {
a: 1
};
let o2 = {
b: 2
};
o2.__proto__ = o1;
let {...o3} = o2;
console.log(o2.__proto__); //{a: 1}
console.log(o3.__proto__); //{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ,
//hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
只复制了o2
自身的属性,没有复制它的原型对象o1
的属性。
6.扩展某个函数的参数,引入其他操作。
// 扩展函数引入操作
function baseFunction({a, b }) {
return a + b
}
console.log(baseFunction({
a: 10,
b: 20
})) //30
function wrapperFunction({x,y, ...restConfig}) {
// 使用 x 和 y 参数进行操作
// 其余参数传给原始函数
let sum = x + y
console.log(sum);
return baseFunction(restConfig);
}
console.log(wrapperFunction({
x: 20,
y: 50,
a: 10,
b: 20
})) // 70 30
拓展运算符
1.取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let a = { x:10 , y:20 };
let b = {...a};
b // { x:10 , y:20 }
2.数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
let arr = ['a','b'];
let foo = {...arr};
foo //{0:"a",1:"b"}
3.扩展运算符后面是一个空对象,则没有任何效果。
4.扩展运算符后面不是对象,则会自动将其转为对象。
// 等同于 {...Object(1)}
{...1} // {}
扩展运算符后面是整数1,会自动转为数值的包装对象Number{1}。由于该对象没有自身属性,所以返回一个空对象。
5.扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
6.对象的扩展运算符等同于使用Object.assign()
方法。
7.想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法:
// 写法一
const clone1 = {
__proto__: Object.getPrototypeOf(obj),
...obj
};
//示例:
let obj = {
a:"jack",
__proto__:{
b:1
}
}
const clone1 = {
__proto__ : Object.getPrototypeOf(obj),
...obj
}
console.log(clone1);
/*
{a: "jack"}
a: "jack"
__proto__: b:1
__proto__: Object
*/
// 写法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
// 写法三
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
写法一的__proto__
属性在非浏览器的环境不一定部署,因此推荐使用写法二和写法三。
8.如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
9.用来修改现有对象部分的属性就很方便:
let previousVersion = {
name:"jack",
age:20,
work:"It"
}
let newVersion = {
...previousVersion,
name:"New Name"
}
console.log(newVersion); //{name: "New Name", age: 20, work: "It"}
10.把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。
11.与数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式。
const obj = {
...(x > 1 ? {a: 1} : {}),
b: 2,
};
12.扩展运算符的参数对象之中,如果有取值函数get
,这个函数是会执行的。
// 并不会抛出错误,因为 x 属性只是被定义,但没执行
let aWithXGetter = {
...a,
get x() {
throw new Error('not throw yet');
}
};
// 会抛出错误,因为 x 属性被执行了
let runtimeError = {
...a,
...{
get x() {
throw new Error('throw now');
}
}
};