ES6对象的扩展
1. 属性的简洁表示法
ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
const foo = 'foo';
const baz = {foo}
baz // {foo: 'foo'}
上面代码表明,ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。
function f (x, y) {
return {x, y};
}
f(1, 2) // {x: 1, y: 2}
es6中对象的方法可以这样写
const o = {
method() {
return "Hello!";
}
};
o.method() // 'Hello!'
function getPoint() {
const x = 1;
const y = 10;
return {x, y};
}
getPoint() // {x: 1, y: 10}
简洁写法的属性名总是字符串,这会导致一些看上去比较奇怪的结果。
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world!'
}
a['first word'] // "hello"
a['last word'] // "world!"
a[lastWord] // "world!"
属性名表达式与简洁表示法,不能同时使用,会报错。
属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]
。
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // {[object Object]: "valueB"}
2. 方法的name属性
函数的name
属性,返回函数名。对象方法也是函数,因此也有name
属性。方法的name
属性返回函数名(即方法名)。
const person = {
sayName() {
console.log('hello!');
}
}
person.sayName.name // "sayName"
3. Object.is()
比较两个值是否严格相等
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true
4. Object.assign()
用于对象的合并,并把源对象的可枚举属性复制到目标对象中
const target = {a: 1};
const source1 = {b: 2};
const source2 = {c: 3};
Object.assign(target, source1, source2);
target // {a: 1, b: 2, c: 3}
参数一,是目标对象,后面的参数都是源对象。
如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
const target = {
a: 1,
b: 2
}
const source1 = {
b: 2,
c: 2
};
const source2 = {
c: 3
}
Object.assign(target, source1, source2) // {a: 1, b: 2, c: 3}
如果只有一个参数,Object.assign
会直接返回该参数。
const obj = {
a: 1
};
Object.assign(obj) // {a: 1}
如果该参数不是对象,则会先转成对象,然后返回。
typeof Object.assign(2)
由于undefined
和null
无法转成对象,所以如果它们作为参数,就会报错。
如果undefined, null不是Object.assign()方法的首个参数, 则不会报错, 直接跳过。
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
obj // {0: "a", 1: "b", 2: "c"}
只有字符串的包装对象,会产生可枚举属性。
1)浅拷贝
Object.assign
方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
2)同名属性的替换
一旦遇到同名属性,Object.assign
的处理方法是替换,而不是添加。
const target = {a: {b: 'c', d: 'e'}};
const source = {a: {b: 'hello'}}
Object.assign(target, source) // {a: {b: 'hello'}} 对象a被整体替换掉
3)数据的处理
Object.assign()可以用来处理数组,但是会把数组视为对象
Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]
Object.assign
把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4
覆盖了目标数组的 0 号属性1
。
4)取值函数的处理
Object.assign
只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
const source = {
get foo() {
return 1;
}
}
const target = {}
Object.assign({}, source) // {foo: 1}
source
对象的foo
属性是一个取值函数,Object.assign
不会复制这个取值函数,只会拿到值以后,将这个值复制过去。
5)常见用途
①为对象添加属性
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
通过Object.assign
方法,将x
属性和y
属性添加到Point
类的对象实例。
②为对象添加方法
Object.assign(Point.prototype, {
someMethod(arg1, arg2) {
},
anotherMethod() {
}
})
// {someMethod: ƒ, anotherMethod: ƒ, constructor: ƒ}
③克隆对象
function clone (obj) {
Object.assign({}, obj);
}
let origin = {
name: 'xhk',
age: 36
}
clone(origin) // {name: "xhk", age: 36}
将原始对象拷贝到一个空对象,就得到了原始对象的克隆。
采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。
function clone (origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto, origin));
}
④合并多个对象
将多个对象合并到某个对象。
5. 属性的可枚举性和遍历
有四个操作会忽略enumerable
为false
的属性。
for...in
循环:只遍历对象自身的和继承的可枚举的属性。Object.keys()
:返回对象自身的所有可枚举的属性的键名。JSON.stringify()
:只串行化对象自身的可枚举的属性。Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
6. 属性的遍历
ES6 一共有 5 种方法可以遍历对象的属性。
1) for ... in
for...in
循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
2)Object.keys(obj)
Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
const obj = {
name: 'xhk',
age: 36
}
Object.keys(obj) // ["name", "age"]
3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
4)Object.getOwnPropertySymbols(obj) Object.getOwnPropertySymbols
返回一个数组,包含对象自身的所有 Symbol 属性的键名。
5)Reflect.ownKeys(obj)
Reflect.ownKeys
返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
- 首先遍历所有数值键,按照数值升序排列。
- 其次遍历所有字符串键,按照加入时间升序排列。
- 最后遍历所有 Symbol 键,按照加入时间升序排列。
7. Object.getOwnPropertyDescriptors()
对象的属性是赋值方法或取值的方法时, 在复制的时候回出现错误, Object.getOwnPropertyDescriptors
方法配合Object.defineProperties
方法,就可以实现正确拷贝。
Object.getOwnPropertyDescriptors
方法的另一个用处,是配合Object.create
方法,将对象属性克隆到一个新对象。这属于浅拷贝。
let obj = {
set foo(val) {
console.log(val);
}
}
const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
Object.create()
方法会使用指定的原型对象及其属性去创建一个新的对象。
Object.getOwnPropertyDescriptors
方法可以实现一个对象继承另一个对象。以前,继承另一个对象,常常写成下面这样。
const obj = {
__proto__: prot,
foo: '456'
}
// { foo:"456"
__proto__: {
foo:123
__proto__:Object
}
}
8. __proto__ 属性(最好不要使用)
用来读取或设置当前对象的prototype
对象。目前,所有浏览器(包括 IE11)都部署了这个属性。
Object.setPrototypeOf()
(写操作)
Object.getPrototypeOf()
(读操作)
Object.create()
(生成操作)
Object.setPrototypeOf() 用来设置一个对象的__proto__对象, 返回参数对象本身。
如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。
Object.setPrototypeOf(1, {}) // 1
Object.setPrototypeOf('foo', {}) // 'foo'
Object.setPrototypeOf(true, {}) // true
Object.setPrototypeOf(undefined, {}) // 报错
Object.setPrototypeOf(null, {}) // 报错
Object.getPrototypeOf() 用来读取一个对象的原型对象
function A() {}
const a = new A();
Object.getPrototypeOf(a) === A.prototype // true
9. super关键字
this
关键字总是指向函数所在的当前对象, super关键字
,指向当前对象的原型对象。
只有下面的写法可以让Javascript引擎确认:
const obj3 = {
foo() {
return super.foo
}
}
const obj = {
foo: super.foo
} // 报错
const obj1 = {
foo: () => super.foo
} // 报错
const obj2 = {
foo: function () {
return super.foo;
}
} // 报错
const proto = {
x: 'hello',
foo() {
console.log(this.x);
}
}
const obj = {
x: 'world',
foo() {
super.foo();
}
}
Object.setPrototypeOf(obj, proto);
obj.foo() // 'world'
扩展运算符的解构赋值,不能复制继承自原型对象的属性。
let o1 = {a: 1}
let o2 = {b: 2}
o2.__proto__ = o1;
let {...o3} = o2
o3.b // 2
o3.a // undefined
扩展运算符(...
)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z = {a: 3, b: 4}
let n = {...z}
n // {a: 3, b: 4}
上面的例子只是拷贝了对象实例的属性,如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法。
let obj = {
find() {},
foo: 'foo'
}
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
)
扩展运算符可以用于合并两个对象
const a = {
name: 'xhk'
}
const b = {
wife: 'coco'
}
let ab = {...a, ...b}
ab // {name: 'xhk', wife: 'coco'}
let cd = Object.assign({}, a, b);
cd // {name: 'xhk', wife: 'coco'}
let x = 1;
let y = 2;
const z = {
x = 0;
}
const a = {...z, x, y}
const obj = {
name: 'xhk',
age: 36,
wife: 'coco'
}
let b = {
...obj,
age: 24
}
b // {name: "xhk", age: 24, wife: "coco"}
10. Null传导运算符(?.)
如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。
const firstName = message?.body?.user?.firstName || 'default'
等同于
const firstName = (message && message.body&& message.body.user && message.body.user.firstName) || 'default'
上面代码有三个?.
运算符,只要其中一个返回null
或undefined
,就不再往下运算,而是返回undefined
。