2021-04-08
对象的扩展
1,属性的简洁表示法
ES6 允许在大括号里面直接写入变量和函数,作为对象的属性和方法
let o = 100
let obj = {o}
等同于
let o = 100
let obj = {o: o}
上面代码中,变量foo直接写在大括号里,这时属性名就是变量名,属性值就是变量值
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()
// {x:1, y:10}
2,属性名表达式
js定义对象的属性有两种方法
// 方法1: 直接用标识符作为属性名
obj.foo = true
// 方法2: 直接用表达式作为属性名,这时将表达式放在方括号内
obj['a' + 'bc'] = true
但是,如果使用字面量方式定义对象(使用大括号),在 ES5 中只能使用方法一(标识符)定义属性。
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
3, 方法的name属性
函数的name属性,返回函数名,对象方法也是函数,因此也有name属性
const person = {
sayName() {
console.log('hello!');
},
};
person.sayName.name // "sayName"
方法的name属性返回函数名(即方法名)
4, 属性的可枚举性和遍历
1,可枚举性
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象
let obj = {
foo: 123
};
console.log(Object.getOwnPropertyDescriptor(obj, 'foo'))

描述对象的enumerable属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。
5,属性的遍历
ES6有5种方法可以遍历对象的属性
- 1,for...in
- 2,Object.keys() 返回一个包含对象的键名的数组
- 3Object.getOwnPropertyNames() 返回一个包含对象的所有属性的键名的数组
- 4,Object.getOwnPropertySymbols() 返回一个包含对象的所有Symbol属性的键名的数组
- 5,Reflect.ownKeys(obj) 返回一个包含对象的所有属性的键名的数组
6,super关键字
我们知道this关键字总是指向函数所在的当前对象,ES6新增了super指向当前对象的原型对象
~
更多内容可以参考:https://es6.ruanyifeng.com/#docs/object
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"}
如果扩展运算符后面是一个空对象,则没有任何效果。
{...{}, a: 1}
// { a: 1 }
如果扩展运算符后面不是对象,则会自动将其转为对象。
// 等同于 {...Object(1)}
{...1} // {}
上面代码中,扩展运算符后面是整数1,会自动转为数值的包装对象Number{1}。由于该对象没有自身属性,所以返回一个空对象。
如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
对象的扩展运算符等同于使用Object.assign()方法
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
扩展运算符可以用于合并两个对象。
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
8,解构赋值
对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和他们的值都会拷贝到新对象上面
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefined或null,就会报错,因为它们无法转为对象
let { ...z } = null; // 运行时错误
let { ...z } = undefined; // 运行时错误
解构赋值必须是最后一个参数,否则会报错。
let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误
上面代码中,解构赋值不是最后一个参数,所以会报错。
注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2
上面代码中,x是解构赋值所在的对象,拷贝了对象obj的a属性。a属性引用了一个对象,修改这个对象的值,会影响到解构赋值对它的引用。
另外,扩展运算符的解构赋值,不能复制继承自原型对象的属性
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined
上面代码中,对象o3复制了o2,但是只复制了o2自身的属性,没有复制它的原型对象o1的属性。
下面是另一个例子
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o;
let { y, z } = newObj;
x // 1
y // undefined
z // 3
上面代码中,变量x是单纯的解构赋值,所以可以读取对象o继承的属性;变量y和z是扩展运算符的解构赋值,只能读取对象o自身的属性,所以变量z可以赋值成功,变量y取不到值。ES6 规定,变量声明语句之中,如果使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式,所以上面代码引入了中间变量newObj,如果写成下面这样会报错。
let { x, ...{ y, z } } = o;
// SyntaxError: ... must be followed by an identifier in declaration contexts
9, Null 判断运算符
读取对象属性的时候,如果某个属性的值是null或undefined,有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。
let arr = res.data || []
let time = res.time || 3000
let checked = res.checked || true
上面的三行代码都通过||运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为null或undefined,默认值就会生效,但是属性的值如果为空字符串或false或0,默认值也会生效。
为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符??。它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
let arr = res.data.list ?? []
let time = res.data.time ?? 3000
let checked = res.data.checked ?? true
上面代码中,默认值只有在左侧属性值为null或undefined时,才会生效。
这个运算符的一个目的,就是跟链判断运算符?.配合使用,为null或undefined的值设置默认值。
const time = res.data?.time ?? 300
上面代码中,res.data是null或undefined,或者res.data.time是null或undefined,就会返回默认值300。
这个运算符很适合判断函数参数是否赋值。
function Component(props) {
const enable = props.enabled ?? true;
// …
}
上面代码判断props参数的enabled属性是否赋值,基本等同于下面的写法。
function Component(props) {
const {
enabled: enable = true,
} = props;
// …
}
??有一个运算优先级问题,它与&&和||的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。
// 报错
lhs && middle ?? rhs
lhs ?? middle && rhs
lhs || middle ?? rhs
lhs ?? middle || rhs
上面四个表达式都会报错,必须加入表明优先级的括号。
(lhs && middle) ?? rhs;
lhs && (middle ?? rhs);
(lhs ?? middle) && rhs;
lhs ?? (middle && rhs);
(lhs || middle) ?? rhs;
lhs || (middle ?? rhs);
(lhs ?? middle) || rhs;
lhs ?? (middle || rhs);