es6学习记录之对象的扩展
属性的简洁表示法
//属性简写
let ad = 'bar'
let ca = {ad}
console.log(ca);//ad:"bar"
// 相当于
let ad = 'bar'
let ca = {ad:ad}
console.log(ca);//ad:"bar"
说明:当对象的key跟属性值为变量的名称一样的时候可以直接写key,这种写法vue组件注册的时候用的多
const a = {
add() {
console.log(111);
}
}
a.add() //111
//相当于
const a = {
add: function () {
console.log(111);
}
}
a.add() //111
说明:方法也可以简写
属性名表达式
对象属性的添加有两种方式,方法二可以放表达式
const obj = {
}
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
console.log(obj);//{abc: 123,foo: true}
ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。
let asd = "ddd"
let obj = {
[asd]:564,
asd
}
console.log(obj);//{ddd: 564,asd: "ddd"}
说明:中括号内的变量值成为了属性名,这种写法感觉作用不大...
属性的可枚举性和遍历
可枚举性
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。这个感觉作用不大没有详细看略过
属性的遍历
ES6 一共有 5 种方法可以遍历对象的属性。
(1)for…in
for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
(2)Object.keys(obj)
Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
- 首先遍历所有数值键,按照数值升序排列。
- 其次遍历所有字符串键,按照加入时间升序排列。
- 最后遍历所有 Symbol 键,按照加入时间升序排列。
super 关键字
我们知道,this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {
return super.foo;
}
};
console.log(Object.setPrototypeOf(obj, proto));
console.log(obj.find() );// "hello"
链判断运算符
编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取message.body.user.firstName,安全的写法是写成下面这样。
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
或者使用三元运算符?:,判断一个对象是否存在。
const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined
这样的层层判断非常麻烦,因此 ES2020 引入了“链判断运算符”(optional chaining operator)?.,简化上面的写法。
const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value
//我自己写的例子
const fooInput = {
name: {
age: 14
}
}
const cad = fooInput?.name?.age || 188
console.log(cad);//14
链判断运算符有三种用法。
obj?.prop// 对象属性obj?.[expr]// 同上func?.(...args)// 函数或对象方法的调用
下面是判断对象方法是否存在,如果存在就立即执行的例子。
let iterator={
acc(){
console.log(111);
}
}
let cd = iterator.return?.()
console.log(cd);//undefined
//上面代码中,`iterator.return`如果有定义,就会调用该方法,否则直接返回`undefined`。
let iterator={
return(){
console.log(111);
}
}
let cd = iterator.return?.() //111
console.log(cd);//undefined
//还是会返回undefined但是方法执行了
下面是这个运算符常见的使用形式,以及不使用该运算符时的等价形式。
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
上面代码中,特别注意后两种形式,如果a?.b()里面的a.b不是函数,不可调用,那么a?.b()是会报错的。a?.()也是如此,如果a不是null或undefined,但也不是函数,那么a?.()会报错。
使用这个运算符,有几个注意点。
Null 判断运算符
读取对象属性的时候,如果某个属性的值是null或undefined,有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。
let ac = 0
let ad = ac||18
console.log(ad);//18
上面的代码通过||运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为null或undefined,默认值就会生效,但是属性的值如果为空字符串或false或0,默认值也会生效。
为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符??。它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
let ac = 0
let ad = ac??18
console.log(ad);//0
上面代码中,默认值只有在属性值为null或undefined时,才会生效。
这个运算符的一个目的,就是跟链判断运算符?.配合使用,为null或undefined的值设置默认值。
const animationDuration = response.settings?.animationDuration ?? 300;
??有一个运算优先级问题,它与&&和||的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。
// 报错
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);
对象的新增方法
Object.is()
ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致
//===
let cd = (NaN===NaN)
console.log(cd);//false
//Object.is()
let ad = Object.is(NaN,NaN)
console.log(ad);//true
跟===区别不大只是对-0===+0 还有NaN===NaN的情况做了处理
Object.assign()
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
source1.b = 15
console.log(target);
// {a:1, b:2, c:3}
Object.assign方法的第一个参数是目标对象,后面的参数都是被合并对象(源对象)
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
const target = { a: 1 };
const source1 = { a: 2 };
const source2 = { a: 3 };
Object.assign(target, source1, source2);
console.log(target);
// {a:3}
如果只有一个参数,Object.assign会直接返回该参数
const obj = { a: 1 };
Object.assign(obj) === obj // true
console.log(Object.assign(obj));//{a:1}
如果该参数不是对象,则会先转成对象,然后返回。
console.log(Object.assign(true))
如果是不能转成对象的数据,比如undefined和null无法转成对象,所以如果它们作为参数,就会报错。
注意点
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
const obj1 = { a: { b: 1 } };
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
上面代码中,源对象obj1的a属性的值是一个对象,Object.assign拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
并且返回值跟合并对象地址相同
const obj3 = { c: 1 };
const obj1 = { a: { b: 1 } };
const obj2 = Object.assign(obj3, obj1);
obj1.a.b = 2;
obj2.a.b // 2
console.log(obj2===obj3);//true
同名属性的替换
对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。
const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }
上面代码中,target对象的a属性被source对象的a属性整个替换掉了,而不会得到{ a: { b: 'hello', d: 'e' } }的结果。这通常不是开发者想要的,需要特别小心。
一些函数库提供Object.assign的定制版本(比如 Lodash 的_.defaultsDeep方法),可以得到深拷贝的合并。
数组的处理 Object.assign 可以处理数组但是会把数组当成对象
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
这里我的理解就是将索引当key进行了合并,重复的索引会被直接覆盖
取值函数的处理
Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
const source = {
get foo() { return 1 }
};
const target = {};
Object.assign(target, source)
console.log(target);
// { foo: 1 }
可读性不高感觉,应该不会使用
常见用途 为对象添加属性
class Point {
constructor(x, y) {
Object.assign(this, { x, y });
}
}
上面方法通过Object.assign方法,将x属性和y属性添加到Point类的对象实例。
为对象添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};
上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,再使用assign方法添加到SomeClass.prototype之中。
克隆对象
合并多个对象
Object.keys(),Object.values(),Object.entries()
Object.keys()
返回对象的key的数组
let ad = {
name:'站手感',
age:13,
sex:1,
possword:"13123",
username:"张大仙"
}
console.log(Object.keys(ad));//['name', 'age', 'sex', 'possword', 'username']
ES2017 引入了跟Object.keys配套的Object.values和Object.entries,作为遍历一个对象的补充手段,供for...of循环使用。
let { keys, values, entries } = Object;
let obj = { a: 1, b: 2, c: 3 };
for (let key of keys(obj)) {
console.log(key); // 'a', 'b', 'c'
}
for (let value of values(obj)) {
console.log(value); // 1, 2, 3
}
for (let [key, value] of entries(obj)) {
console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
Object.values()
Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]
Object.entries()
Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
除了返回值不一样,该方法的行为与Object.values基本一致。
扩展自己实现Object.entries()方法(我直接复制的)
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;
}