(1)属性的简洁表示法
ES6允许直接写入变量和函数作为对象的属性和方法。
在对象之中直接写变量,这时,属性名为变量名,属性值为变量的值
例子:
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};
例子:
const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
注意:简洁写法的属性名是字符串,所以下面的写法不会报错(但是看起来很奇怪)
const obj = {
class () {}
};
// 等同于
var obj = {
'class': function() {}
};
如果某个方法的值是一个Generator函数,前面需要加上星号。
例子:
const obj = {
* m() {
yield 'hello world';
}
};
(2)属性名表达式
JavaScript定义对象的属性,有两种方法
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
方法一直接用标识符作为属性名,方法二用表达式作为属性名,这时要将表达式放在方括号之内。
但是如果采用字面量方式定义对象(使用大括号),ES5中只能使用方法一(标识符)定义属性。
ES6允许字面量定义对象时,使用方法二(表达式)作为对象的属性名。
例如:let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
表达式还可以用于定义方法名
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
注意:1.属性名表达式与简洁表示法,不能同时使用,会报错。
例子:
// 报错
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };
// 正确
const foo = 'bar';
const baz = { [foo]: 'abc'};
2.属性表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]
例子:
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}
说明:[keyA]和[keyB]得到的都是[object Object],所以[keyB]会把[keyA]覆盖掉,而myObject最后只有一个[object Object]属性。
(3)方法的name属性
函数的name属性返回函数名,对象方法也是函数,因此也有name属性。
const person = {
sayName() {
console.log('hello!');
},
};
person.sayName.name // "sayName"
注意:1.如果对象的方法使用了取值函数(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"
2.bind方法创造的函数,name属性返回bound加上原函数的名字
var doSomething = function() {
// ...
};
doSomething.bind().name // "bound doSomething"
3.Function构造函数创造的函数,name属性返回anonymous
(new Function()).name // "anonymous"
4.如果对象的方法是一个Symbol值,name属性返回的是这个Symbol值的描述。
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1]() {},
[key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""
(4)属性的可枚举性和遍历
1.描述对象:对象的每个属性都有一个描述对象,用来控制该属性的行为。
Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
2.描述对象的属性,例如value,writable,enumerable,configurable等
其中enumerable属性,称为可枚举性,如果为false,则表示某些操作会忽略当前操作。
例如目前这4个操作会忽略enumerable为false的属性:
for...in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
注:1.Object.assign()是ES6新增的。
2.ES6规定所有的Class的原型的方法都是不可枚举的。
3.大多时候,我们只关心对象自身的属性,继承的属性会让问题复杂化,所以,尽量不要使用
for...in循环,而是用Object.keys()代替。
(5)属性的遍历
ES6共有5中方法
1.for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
2.Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
3.Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
4.Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
5.Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
这5种方法遍历对象的键名,都遵循以下次序规则:
首先遍历所有数值键,按照数值升序排列。
其次遍历所有字符串键,按照加入时间升序排列。
最后遍历所有 Symbol 键,按照加入时间升序排列。
例子:Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
(6)super关键字
this关键字:指向函数所在的当前对象
super关键字:指向当前对象的原型对象
例子:
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {
return super.foo;
}
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
注:super关键字表示原型对象时,只能用在对象的方法中,用在其他地方会报错。
例子:
// 报错
const obj = {
foo: super.foo//用在属性里
}
// 报错
const obj = {
foo: () => super.foo//用在函数中,然后将函数赋值给foo属性
}
// 报错
const obj = {
foo: function () {
return super.foo//用在函数中,然后将函数赋值给foo属性
}
}
(7)对象的扩展运算符
数组有扩展运算符,ES2018将这个运算符引入了对象
1.解构赋值
对象的解构赋值:从一个对象取值,相当于将目标对象自身的所有可遍历的,但尚未
被读取的属性,分配到指定的对象上,所有的键和它们的值,都会拷贝到新的对象上。
例子:
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
注意:a.由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefined或null,会报错【因为它们无法转为对象】
例子:
let { ...z } = null; // 运行时错误
let { ...z } = undefined; // 运行时错误
b.解构赋值必须是最后一个参数,否则报错
例子:let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误
c.解构赋值的拷贝是浅拷贝,如果一个键的值是复合类型的值(数组、对象、函数),那么,解构赋值拷贝的是这个值的引用,
而不是这个值的副本。
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2
d.扩展运算符的解构赋值,不能继承自原型对象的属性。
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined
2.扩展运算符
对象的扩展运算符:用于取出参数对象的所有可遍历属性,拷贝到当前对象中。
注:a.对象的扩展运算符可以用于数组【数组是特殊的对象】
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}
b.如果扩展运算符后是一个空对象,则没有任何效果
{...{}, a: 1}
// { a: 1 }
c.如果扩展运算符后不是一个对象(例如数字,布尔值,undefined,null),则会自动将其转化为对象。
// 等同于 {...Object(1)}
{...1} // {}
// 等同于 {...Object(true)}
{...true} // {}
// 等同于 {...Object(undefined)}
{...undefined} // {}
// 等同于 {...Object(null)}
{...null} // {}
d.如果扩展运算符后不是对象,是字符串,则它会自动转成一个类似数组的对象,所有返回的不是空对象。
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
e.对象的扩展运算符等同于使用Object.assign()方法
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
3.扩展运算符的作用
a.合并两个对象
例子:
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
b.修改现有对象的部分属性
【用户自定义属性在扩展运算符之后,扩展运算符内部同名属性被自定义属性覆盖】
let aWithOverrides = { ...a, x: 1, y: 2 };
// 等同于
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
// 等同于
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
// 等同于
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
c.设置新对象的默认属性值
【用户自定义属性在扩展运算符之前】
let aWithDefaults = { x: 1, y: 2, ...a };
// 等同于
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
// 等同于
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);