9.1 属性的简洁表示法
ES6 允许直接写入变量和函数,作为对象的属性和方法,这样的书写更加简洁。
const foo = 'bar';
const baz = {foo};
baz // {foo: 'bar'}
//等同于
const baz = {foo:foo};
上面代码表明,ES6允许在对象之中,直接写变量。这时,属性名为变量名,属性值为变量的值。
比如:
function f(x,y){
return {x,y}
}
//等同于
function f(x,y){
return {
x: x,
y: y
}
}
f(1, 2) //{ x: 1, y: 2}
除了属性简写,方法也可以简写
const o = {
method() {
return 'Hello!'
}
}
//等同于
const o = {
method: function(){
return 'Hello!'
}
}
我们来看一个实际的例子:
let birth = '2000/01/01';
const Person = {
name: '张三',
//等同于birth: birth
birth,
//等同于 hello: function()...
hello() { console.log('我的名字是:', this.name); }
}
这种写法用于函数的返回值,将会非常方便。
function getPoint(){
const x = 1;
const y = 10;
return {x, y}
}
getPoint();
9.2 属性名表达式
JS中定义对象的方法
// 直接使用标识符作为属性名
obj.foo = true;
//使用表达式作为属性名
obj['a' + 'bc'] = 123;
//使用字面量方式定义对象
var obj = {
foo: true,
abc: 123
}
ES6允许字面量定义对象时,使用表达式作为对象的属性名
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
}
来看另一个例子:
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
表达式还可以用于定义方法名。
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
注意,属性名表达式与简洁表达式法,不能同时使用,会报错。
//报错
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };
//正确
const foo = 'bar';
const baz = { [foo]: 'abc' };
注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]
,这一点要特别小心。
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}
9.3 方法的 name 属性
const person = {
sayName() {
console.log('hello!')
}
}
person.sayName.name // 'sayName'
9.4 Object.is()
ES5 比较两个值是否相等:== 和 ===
他们都有缺点:== 会自动转换数据类型,=== 中 NaN不等于自身,以及 +0 等于 -0。
JS缺乏一种运算,在所有环境中,只要两个值是一样的,他们就应该相等。
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
ES5中,可以通过下面的代码部署 Object.is()
Object.defineProperty(Object, 'is', {
value: function(x, y){
if ( x === y ){
// 针对 +0 不等于 -0 的情况
return x !== 0 || 1/x === 1/y
}
//针对NaN的情况
return x !== x && y !== y;
},
configurable: true,
enumerable: false,
writable: true
})
9.5 Object.assign()
Object.assign方法用于对象的合并,将源对象(source)
的所有可枚举属性,复制到目标对象(target)
const target = { a: 1};
const source1 = { b: 2};
const source2 = { c: 3};
Object.assign(target, source1, source2); // { a: 1,b: 2,c: 3}
如果该参数不是对象,则会先转为对象,然后返回
Object.assign(2) // Number(2)
由于 undefined和null无法转为对象,所以如果他们作为参数,就会报错。
Object.assign(undefined) //报错
Object.assign(null) // 报错
如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)
注意点:
(1) 浅拷贝
Object.assign方法实行的是浅拷贝
const obj1 = {
a: {
b: 1
}
}
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
(2) 同名属性的替换
const target = { a: { b: 'c', d: 'e'} }
const source = { a: { b: 'hello' }}
Object.assign(target,source) // { a: { b: 'hello' }}
(3) 数组的处理
Object.assign
可以用来处理数组,但是会把数组视为对象
Object.assign([1,2,3],[4,5]) // [4,5,3]
上面代码中,Object.assign把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。
用途:
(1) 为对象添加属性
class Point{
constructor(x,y) {
Object.assign(this, {x, y});
}
}
(2) 为对象添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2){
...
},
anotherMethod() {
···
}
})
等同于下面的方法
SomeClass.prototype.someMethod = function(arg1, arg2){
}
(3) 克隆对象
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
(4) 合并多个对象
const merge =
(...sources) => Object.assign({}, ...sources);
9.6 属性的可枚举性和遍历
(1) 可枚举性
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。
Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
-
for...in
循环:只遍历对象自身的和继承的可枚举的属性。 -
Object.keys()
:返回对象自身的所有可枚举的属性的键名。
-JSON.stringify()
:只串行化对象自身的可枚举的属性。 -
Object.assign()
: 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
(2) 属性的遍历
9.7 Object.getOwnPropertyDescriptors()
9.8 proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()
JS语言的对象继承是通过原型链实现的,ES6提供了更多原型对象的操作方法
(1) proto属性
用来读取或设置当前对象的 prototype 对象。
//ES6的写法
const obj = {
method: function(){
}
}
obj._proto_ = someOtherObj;
// es5的写法
var obj = Object.create(someOtherObj);
obj.method = function(){ ... }
具体的实现原理:
Object.defineProperty(Object.prototype, '_proto_', {
get() {
let _thisObj = Object(this);
return Object.getPrototypeOf(__thisObj);
},
set(proto){
if ( this === undefined || this === null ){
throw new TypeError();
}
if ( !isObject(this) ){
return undefined;
}
if ( !isObject(proto) ){
return undefined;
}
let status = Reflect.setPrototypeOf(this, proto);
if (!status) {
throw new TypeError();
}
}
})
function isObject(value) {
return Object(value) === value;
}
(2) Object.setPrototypeOf()
用来设置一个对象的prototype对象,返回参数对象本身。它是ES6正式推荐的设置原型对象的方法
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);
proto.y = 20;
proto.z = 40;
obj.x // 10
obj.y // 20
obj.z // 40
(3) Object.getPrototypeOf()
用于读取一个对象的原型对象
Object.getPrototypeOf(obj);
function Rectangle() {
// ...
}
const rec = new Rectangle();
Object.getPrototypeOf(rec) === Rectangle.prototype
// true
Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false
9.9 super关键字
我们知道,this
关键字总是指向函数所在的当前对象,ES6又新增了另一个类似的关键字super
,指向当前对象的原型对象。
const proto = {
foo: 'hello'
}
const obj = {
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
}
}
9.10 Object.keys(), Object.values(), Object.entries()
(1) Object.keys():成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
var obj = {
foo: 'bar',
baz: 42
}
Object.keys(obj)
(2) Object.values()
Object.values
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
(3) Object.entries()
Object.entries
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
自己实现Object.entries方法,非常简单:
function entries(obj){
let arr = [];
for ( let key of Object.keys(obj) ){
arr.push([key, obj[key]])
}
return arr;
}
9.11 对象的扩展运算符
(1) 解构赋值
对象的解构赋值用于从一个对象取值,相当于将所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }
x: // 1
y: // 2
z: // { a: 3, b: 4}
需要注意两点:
(一) 解构赋值右边必须是一个对象,要不会报错
let { x, y, ..z } = null //报错
let { x, y, ..z } = undefined //报错
(二) 解构赋值必须是最后一个参数
let { ...x, y, z } = obj //句法错误
let { x, ..y, ..z } = obj //句法错误
(2) 扩展运算符
let z = { a: 3, b: 4 };
let n = { ...z }
n // { a: 3, b: 4 }
9.12 Null传导运算符
如果要读取对象内部的某个属性,往往需要判断该对象是否存在。比如,要读取message.body.user.firstName
,安全的写法:
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default'
先在,有了一个提案,引入了 'Null 传导运算符'
const firstName = message?.body?.user?.firstName || 'default';