1.属性的简洁表示法
1.1.表示方法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。更加简洁
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};
上面代码中,变量foo直接写在大括号里面。这时,属性名就是变量名, 属性值就是变量值。下面是另一个例子。
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}
除了属性简写,方法也可以简写。
const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
1.2.应用
这种写法用于函数的返回值,将会非常方便。
function getPoint() {
const x = 1;
const y = 10;
return {x, y};
}
getPoint()
// {x:1, y:10}
简洁写法在打印对象时也很有用。
let user = {
name: 'test'
};
let foo = {
bar: 'baz'
};
console.log(user, foo)
// {name: "test"} {bar: "baz"}
console.log({user, foo})
// {user: {name: "test"}, foo: {bar: "baz"}}
上面代码中,console.log直接输出user和foo两个对象时,就是两组键值对,可能会混淆。把它们放在大括号里面输出,就变成了对象的简洁表示法,每组键值对前面会打印对象名,这样就比较清晰了。
2.属性名表达式
2.1.基本概述
顾名思义,就是对象定义时和调用时,它的属性名称可以用表达式来定义
var obj = {}
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
console.log(obj.abc) // "123"
console.log(obj[abc]) // "123"
上面的代码,表述了属性名可以用表达式来定义以及使用
也可以直接把用了表达式的属性放在对象内
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
还可以定义方法
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
注意:属性名表达式与简洁表示法,不能同时使用,会报错。
注意:属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[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 属性
与 ES6函数新属性类似 ,详情见 阮一峰《ECMAScript 6 入门》 阮一峰《ECMAScript 6 入门》
const person = {
sayName() {
console.log('hello!');
},
};
person.sayName.name // "sayName"
4.属性的可枚举性和遍历
4.1可枚举性
对象的每一个属性都有一个描述对象,用来控制属性的行为。
Object.getOwnPropertyDescriptor
,此方法可以获取属性的描述对象。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
描述对象的enumerable
属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。
目前,有四个操作会忽略enumerable为false的属性。
-
for...in
循环:只遍历对象自身的和继承的可枚举的属性。 -
Object.keys()
:返回对象自身的所有可枚举的属性的键名。 -
JSON.stringify()
:只串行化对象自身的可枚举的属性。(将值转换为相应的JSON格式) -
Object.assign()
: 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
注:
- 只有for…in会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。
- 实际上,引入“可枚举”(enumerable)这个概念的最初目的,就是让某些属性可以规避掉for…in操作,不然所有内部属性和方法都会被遍历到。
- 比如,对象原型的toString方法,以及数组的length属性,就通过“可枚举性”,从而避免被for…in遍历到。
- 总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for…in循环,而用Object.keys()代替。
- ES6 规定,所有 Class 的原型的方法都是不可枚举的。
4.2属性的遍历
-
for...in
循环遍历对象自身的和继承的可枚举属性 -
Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。 -
Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。 -
Object.getOwnPropertySymbols
返回一个数组,包含对象自身的所有 Symbol 属性的键名。 -
Reflect.ownKey
s返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的方法都遵守同样的属性遍历的次序规则。
- 首先遍历所有数值键,按照数值升序排列。
- 其次遍历所有字符串键,按照加入时间升序排列。
- 最后遍历所有 Symbol 键,按照加入时间升序排列。
5.super 关键字
ES6 新增了关键字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
}
// 报错
const obj = {
foo: function () {
return super.foo
}
}
上面三种super的用法都会报错,因为对于 JavaScript 引擎来说,这里的super都没有用在对象的方法之中。第一种写法是super用在属性里面,第二种和第三种写法是super用在一个函数里面,然后赋值给foo属性。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。
JavaScript 引擎内部,super.foo
等同于Object.getPrototypeOf(this).foo
(属性)或Object.getPrototypeOf(this).foo.call(this)
(方法)。
const proto = {
x: 'hello',
foo() {
console.log(this.x);
},
};
const obj = {
x: 'world',
foo() {
super.foo();
}
}
Object.setPrototypeOf(obj, proto);
obj.foo() // "world"
6.解构赋值
对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
之前有学习过结构赋值,这里只举个例子
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
- 解构赋值必须是最后一个参数,否则会报错。
- 解构赋值要求等号右边是一个对象,所以如果等号右边是undefined或null,就会报错,因为它们无法转为对象。
- 扩展运算符的解构赋值,不能复制继承自原型对象的属性。
- … 实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。
注意:解构赋值的拷贝是浅拷贝,即如果 一个键的值 是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
应用
能够扩展某个函数的参数,引入其他操作。
function baseFunction({ a, b }) {
// ...
}
function wrapperFunction({ x, y, ...restConfig }) {
// 使用 x 和 y 参数进行操作
// 其余参数传给原始函数
return baseFunction(restConfig);
}
7.扩展运算符
对象的扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
- 由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}
- 如果扩展运算符后面是一个空对象,则没有任何效果。
- 如果扩展运算符后面不是对象,则会自动将其转为对象。
// 等同于 {...Object(1)}
{...1} // {}
上面代码中,扩展运算符后面是整数1,会自动转为数值的包装对象Number{1}。由于该对象没有自身属性,所以返回一个空对象。
// 等同于 {...Object(true)}
{...true} // {}
// 等同于 {...Object(undefined)}
{...undefined} // {}
// 等同于 {...Object(null)}
{...null} // {}
//对象中字符串的扩展
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
- 对象的扩展运算符等同于使用Object.assign()方法。
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
Object.assign()
: 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
扩展运算符可以用于合并两个对象。
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉如果用户自定义的属性放在前面,则后面扩展运算符的同名属性也会覆盖用户自定义的属性。
var obj = {x: 10, y : 1}
var newobj = {x:1, ...obj}
console.log(newobj,obj) //{x: 10, y: 1} {x: 10, y: 1}
var obj1 = {x: 1, y: 1}
var nobj = {...obj1,x: 10}
console.log(nobj,obj1) //{x: 10, y: 1} {x: 1, y: 1}
与数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式。
8.新增方法
8.1.Object.is()
ES5 比较两个值是否相等,只有两个运算符:相等运算符 == 和严格相等运算符 ===。它们都有缺点,前者会自动转换数据类型,后者的 NaN 不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
ES6 提出“Same-value equality”(同值相等)算法。只要两个值是一样的,它们就应该相等。Object.is
用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
上面的吗两个对象不相等,是因为对象是引用的比较。即使两个对象包含同样的属性和值,它们也是不相等的。同理,只要是比较引用类型的数据,都会不相等
不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
8.2.Object.assign()
Object.assign
方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
Object.assign
方法的第一个参数是目标对象
,后面的参数都是源对象
。
第一个参数(目标参数):
- 如果只有一个参数(目标参数),方法会直接返回该参数
- 如果该参数不是对象,则会先转成对象,然后返回。若是 undefind 或者 null ,就会报错,因为它俩无法转为对象
const obj = {a: 1};
Object.assign(obj) === obj // true
typeof Object.assign(2) // "object"
Object.assign(undefined) // 报错
Object.assign(null) // 报错
其他参数(源参数):
- 如果原参数为非对象,则都会转成对象,如果无法转成对象,就会跳过(undefind,null,Boolean,Number)。
- 所以说除了字符串会以数组形式,这是因为只有字符串的包装对象,会产生可枚举属性。,拷贝入目标对象,其他值都不会产生效果。
注:
- 此方法只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。属性名为 Symbol 值的属性,也会被Object.assign拷贝。
-
Object.assign
方法实行的是浅拷贝 - 同名属性的替换
const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }
-
Object.assign
可以用来处理数组,但是会把数组视为对象。
Object.assign([1, 2, 3], [4, 5]) //{0:1,1:2,2:3},{0:4,1:5}
// [4, 5, 3]
-
Object.assign
只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
const source = {
get foo() { return 1 }
};
const target = {};
Object.assign(target, source)
// { foo: 1 }
上面代码中,source对象的foo属性是一个取值函数,Object.assign不会复制这个取值函数,只会拿到值以后,将这个值复制过去。
常见的用途
(1)为对象添加属性
(2)为对象添加方法
(3)克隆对象
(4)合并多个对象
(5)为属性指定默认值
8.3.Object.getOwnPropertyDescriptors()
ES5 的Object.getOwnPropertyDescriptor()
方法会返回某个对象属性的描述对象(descriptor)
ES7引入了Object.getOwnPropertyDescriptors()
返回指定对象所有自身属性(非继承属性)的描述对象。
const obj = {
foo: 123,
get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: get bar],
// set: undefined,
// enumerable: true,
// configurable: true } }
上面代码中,Object.getOwnPropertyDescriptors()方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。
该方法的引入目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。
8.4.Object.keys ( )
ES5 引入的Object.keys
方法,返回一个数组,参数是自身(不含继承)所有的可遍历属性的键名
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]
ES2017 引入了跟Object.keys
配套的Object.values
和Object.entries
,作为遍历一个对象的补充手段,供for...of
循环使用。
8.5.Object.values()
Object.values ( )
返回一个数组,成员是参数对象自身的所有可遍历属性(不含继承)
const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]
当属性名为数值时,会按照数值大小,从小到大排列
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)
// ["b", "c", "a"]
如果Object.values
方法的参数是一个字符串,会返回各个字符组成的一个数组。
Object.values('foo')
// ['f', 'o', 'o']
注:如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,当传入参数为数值和布尔值时,Object.values会会返回空数组。
传入null 和 undefined会报错
8.6.Object.entries()
Object.entries()
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
作用:
-
Object.entries
的基本用途是遍历对象的属性。
let obj = { one: 1, two: 2 };
for (let [keys, values] of Object.entries(obj)) {
console.log(
`${JSON.stringify(keys)}: ${JSON.stringify(values)}`
);
}
// "one": 1
// "two": 2
- 另一个用处是,将对象转为真正的Map结构。
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }
- 自己实现
Object.entries
方法
// Generator函数的版本
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
// 非Generator函数的版本
function entries(obj) {
let arr = [];
for (let key of Object.keys(obj)) {
arr.push([key, obj[key]]);
}
return arr;
}