属性的简洁写法
传统的js中,对象采用{key: value}的写法,但在es6中允许在大括号里面直接写入变量。
const name = 'Kimmy';
const age = 20;
const person = { name, age }; // { name: 'Kimmy', age: 20 }
// 等同于
const person = {
name: name,
age: age
};
上面例子中,在定义person对象时,变量名name作为了对象的属性名,它的值作为了属性值,因此只需要写一个{name}就可以表示{name: name}的含义。
除了属性可以简写,函数也可以简写,即省略关键字function
const person = {
sayHello: function() {
return 'hello';
}
};
// 等同于
const person = {
sayHello() {
return 'hello';
}
};
按照CommonJS写法,当需要输出一组模块变量,就非常合适使用对象简写的方法。
let person = {};
// 获取元素
function getItem(key) {
return key in Person ? person[key] : null;
}
// 添加元素
function setItem(key, value) {
person[key] = value;
}
// 清空元素
function clear() {
person = {};
}
module.exports = { getItem, setItem, clear };
// 等同于
module.exports = {
getItem: getItem,
setItem: setItem,
clear: clear
};
链判断运算符
在js实际写法中,如果需要读取对象内部的某个属性,往往需要判断一下该对象是否存在(防止空对象取值报错的情况)
// 错误的写法
const firstName = message.body.user.firstName;
// 正确的写法
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
上面例子中,firstName属性在对象的第四层,所以要判断四次,每一层都需要判断是否空对象,这样的层层判断写法非常麻烦,代码不直观,es6中新增了一种新的写法?.用于判断对象是否存在。
const firstName = message?.body?.user?.firstName;
上面例子中,先判断?左侧的对象是否为null或undefined,如果是,就不再往下运算,返回undefined, 否则层层计算,即先判断message是否存在,然后判断message.body是否存在,再然后判断message.body.user是否存在,最后取值message.body.user.firstName。
下面是判断对象方法是否存在,如果存在立即执行
person.sayHello?.();
上面代码中,person.sayHello如果有定义,就会调用该方法,否则person.sayHello直接返回undefined,不再执行?.后面的部分。
链判断运算符有三种用法:
-obj?.prop // 对象属性
-ojb?.[expr] // 对象属性
-function.(...args) // 函数或对象方法的调用
使用?.注意点:
(1)短路机制
?.相当于一种短路机制,如果不存在则不再往下继续执行。
```js
person?.name
// 相当于
person ? person.name : undefined
```
(2)delete运算符
```js
delete person?.name;
// 相当于
person ? delete person.name : undefined
```
(3)括号的影响
```js
(person?.name).firstName
// 相当于
(person ? person.name : undefined).firstName
```
`?.`只对括号内生效,括号外面的不受任何影响。所以在使用`?.`时,尽量不要与圆括号一起使用。
(4)报错场合
```js
// 构造函数
new person?.();
new person?.name();
// 链式运算符右侧是模板字符
person?.`${name}`;
person?.`f${name}`;
// 链式运算符左侧有super关键字
super?.();
super?.name;
// 链式运算符用于赋值
person?.name = 'Kimmy';
```
(5)右侧不得为十进制数值
若果`?.`后面紧跟十进制,会被当成是三元运算符计算
```js
true?.3:0 // 0.3
```
Null判断运算符
读取对象属性的时候,如果某个属性的值是null或undefined,有时需要指定默认值,常见的做法是通过||运算符指定默认值。
person.name || 'Kimmy'
上面例子中,person.name为null或undefined时,默认值Kimmy就会生效,但是person.name的值为''或false或0时,默认值也会生效。
为了避免这种情况,ES2020引入了一个新的Null判断运算符??,它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
??运算符的目的是跟链判断运算符?.配合使用,为null或undefined的值设置默认值。
person?.name ?? 'Kimmy'
上面例子中person是null或undefined,或者person.name是null或undefined,就会返回默认值Kimmy。
??非常适合用来参数解构
function fn(props) {
const fname = props.name ?? 'Kimmy';
}
// 等同于
function fn(props) {
const {
name: fname = 'Kimmy'
} = props;
}
??与||和&&一起使用时,为了表明优先级需要使用括号,否则会报错。
// 报错
person && person.name ?? 'Kimmy'
// 正确写法
(person && person.name) ?? 'Kimmy'
属性的遍历
对象的属性遍历一共有5中方法可实现,分别是
- for...in
- Object.keys(obj)
- Object.getOwnPropertyNames(obj)
- Object.getOwnPropertySymbols(obj)
- Reflect.ownKeys(obj)
它们的区别请看下面的栗子:
// 定义父类
function Animal(name, type) {
Object.assign(this, { name, type });
}
// 定义子类
function Cat(age, weight) {
Object.assign(this, {
age,
weight,
[Symbol('one')]: 'one'
});
}
// 子类继承父类
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// 生成子类的实例
let cat = new Cat(2, '2kg');
// 实例增加可枚举属性
Object.defineProperty(cat, 'color', {
configurable: true,
enumerable: true,
value: 'orange',
writable: true
});
// 实例增加不可枚举属性
Object.defineProperty(cat, 'height', {
configurable: true,
enumerable: true,
value: '15cm',
writable: true
});
实例cat的属性:
| 属性类型 | 属性值 |
|---|---|
| 实例属性 | age、weight、Symbol('one')、color |
| 继承属性 | name、type |
| 可枚举属性 | age、weight、color |
| 不可枚举属性 | height |
| Symbol属性 | Symbol('one') |
(1)for...in
for...in用于遍历对象自身和继承的可枚举属性(不包含Symbol属性)
for (let key in cat) {
console.log(key); // age, weight, color, name, type
}
(2)Object.keys()
Object.keys()返回一个数组,包含对象自身所有可枚举属性,不包含继承属性和Symbol属性
Object.keys(cat); // ["age", "weight", "color"]
(3)Object.getOwnPropertyNames()
Object.getOwnPropertyNames()返回一个数组,包含对象自身所有可枚举属性和不可枚举属性,不包含继承属性和Symbol属性
Object.getOwnPropertyNames(cat); // ["age", "weight", "color", "height"]
(4)Object.getOwnPropertySymbols()
Object.getOwnPropertySymbols返回一个数组,包含对象自身所有Symbol属性,不包含其他属性
Object.getOwnPropertySymbols(cat); // [Symbol(one)]
(5)Reflect.ownKeys()
Reflect.ownKeys()返回一个数组,包含可枚举属性、不可枚举属性及Symbol属性,不包含继承属性
Reflect.ownKeys(cat); // ["age", "weight", "color", "height", Symbol(one)]
结论:
| 遍历方法 | 可继承 | 自身可枚举 | 自身不可枚举 | Symbol属性 |
|---|---|---|---|---|
| for...in | Yes | Yes | No | No |
| Object.keys() | No | Yes | No | No |
| Object.getOwnPropertyNames() | No | Yes | Yes | No |
| Object.getOwnPerpertySymbols() | No | No | No | Yes |
| Reflect.ownKeys() | No | Yes | Yes | Yes |
Object.assign()
Object.assign()用于将一个或者多个对象的可枚举属性复制到目标对象,然后返回目标对象。
当多个源对象具有相同属性时,后面的属性会覆盖前面的属性。
先看个简单的栗子:
const target = {a: 1}; // 目标对象
const target1 = {b: 2}; // 源对象1
const target2 = {c: 3}; // 源对象2
const target3 = {c: 4}; // 源对象3,和源对象2有共同的属性名c
Object.assign(target, targe1, target2, target3); // {a: 1, b: 2, c: 4}
target2和target3对象中同时出现了属性c,target3对象覆盖了target2对象的属性,所以c的属性值为“4”。
注:Object.assign()函数无法复制对象的不可枚举属性和继承属性,但是可复制可枚举的Symbol属性。
让我们来看个例子: 首先创建一个同时拥有可枚举属性、不可枚举属性、继承属性、Symbol属性的对象。
const obj = Object.create({
a: 1 // 继承属性
}, {
b: {
value: 2 // 不可枚举属性
},
c: {
value: 3,
enumerable: true // 可枚举属性
},
[Symbol('one')]: {
value: 'one',
enumerable: true // Symbol属性
}
});
调用Object.assign()将obj对象属性复制到一个空对象,并输出结果
Object.assign({}, obj); // {c: 3, Symbol(one): "one"}
从结果可以看出被复制的属性中包含了可枚举属性c和Symbol属性,不包含继承属性a和不可枚举属性b。如果想要保持继承链,可以这样处理:
function clone(origin) {
let originProto = Object.getPrototypeOf(origin); // 对象的原型链
return Object.assign(Object.create(originProto), origin);
}
Object.assign()常见的用途:
(1)克隆对象
Object.assign()可以复制源对象的属性到目标对象中,所以使用Object.assign()可以实现对象克隆。
function clone(origin) {
return Object.assign({}, origin);
}
let origin = {
name: 'Kimmy',
age: 20
};
clone(origin); // {name: "Kimmy", age: 20}
需要注意的是,使用Object.assign()进行克隆时,进行的是浅克隆。如果属性时基本数据类型,则会复制它的值;如果属性时引用数据类型,则会复制它的引用。
let target = {};
let source1 = {a: 1, b: {c: 2}};
let result = Object.assign(target, source1);
result.a; // 1,
reuslt.b.c; // 2
source1.b.c = 3;
result.b.c; // 3
从上面的栗子中可以看出,将source1对象复制得到result,既可访问到基本数据类型a,又可访问到引用数据类型c。因为Object.assign()使用的是浅克隆,对源对象属性值进行的修改会影响到目标对象的属性值,两者实际共享同一个对象的引用。因此,在涉及对象深克隆的问题时,不能使用Object.assign()。
(2)给对象添加属性
当我们采用传统的方法为对象添加实例属性时,我们会将属性添加到this上。当属性非常多时,这样的写法比较繁琐,但是通过Object.assign()可以省略很多繁琐的代码。
// 传统的写法
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
// Object.assign()写法
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
上面的方法通过Object.assign(),将x属性和y属性添加到Point类的对象实例。
(3)给对象添加方法
当我们采用传统的写法为对象添加共同方法时,会扩展其prototype属性,使用Object.assign()可以简化代码编写方式。
// 传统写法
Point.prototype.getWidth = function() {
return this.x;
}
Point.prototype.getHeight = function() {
return this.y;
}
// Object.assign()写法
Object.assign(Point.prototype, {
getWidth() {
return this.x;
},
getHeight() {
return this.y;
}
});
(4)合并对象
使用Object.assign()可以将多个对象合并到某个对象中,也可以将多个对象合并到一个新对象,只需要将target设置为空对象{}即可。
// 多个对象合并到某个对象
const merge = (...target, ...sources) => Object.assign(target, ...sources);
// 多个对象合并为一个新对象并返回
const merge = (...sources) => Object.assign({}, ...sources);
参考文档