Skip to content

属性的简洁表示法

const obj = {
    foo,
    class() {}, // class是字符串,所以不会因为它属于关键字而报错
    *m() {},    // Generator函数前面需要加上*
}

属性名表达式

// ES5
obj.foo = true; // 标识符作为属性名
obj['a' + 'bc'] = 123   // 表达式作为属性名

// ES6
let obj = {
    ['a' + 'bc']: 123
}

注意:如果表达式是一个对象默认情况下会被自动转为字符串[Object Object]

Object.is()

比较两个值是否严格相等与 === 行为一致但是可以判断+0 与-0 和 NaN

Object.is({}, {})       // false
Object.is(+0, -0)       // false
Object.is(NaN, NaN)     // true

Object.assign()

将源对象所有的可枚举属性复制到目标对象,第一个参数为目标对象,后面的都是源对象。

  • 后面的同名属性会覆盖前边的
  • 如果只有一个参数会直接返回该参数
var obj = {a: 1};
Object.assign(obj) === obj // true,存储地址相同
  • 参数不是对象会先转换为对象,null 和 undefined 无法转为对象,在首位会报错,在其他位置会跳过。其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了 字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
typeof Object.assign(2) // "Object"

var v1 = 'abc';
var v2 = true;
var v3 = 10;
var obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
  • 只会拷贝可枚举的实义属性和原对象的自身属性(不拷贝继承属性)
  • 实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
  • 可以处理数组,但会把数组视为对象
Object.assign([1, 2, 3], [4, 5]) // [4,5,3]

用途

  • 为对象添加属性
class Point {
    constructor(x, y) {
        Object.assign(this, {x, y});
    }
}
  • 为对象添加方法
  • 拷贝对象,但只能克隆原始对象自身的值,不能克隆它继承的值,要保持继承链可以采用下面代码
function clone(origin) {
    let originProto = Object.getPrototypeOf(origin);
    return Object.assign(Object.create(originProto), origin);
}
  • 合并对象
  • 为属性指定默认值
Object.assign({}, DEFAULTS, options);

属性的可枚举性和遍历

可枚举性

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行 为。 Object.getOwnPropertyDescriptor 方法可以获取该属性的描述对象。

let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
//   value: 123,
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

描述对象的 enumerable 属性,称为”可枚举性“,如果该属性为 false 以下操作会忽略当前属性:

  • for...in 循环:只遍历对象自身的和继承的可枚举的属性。
  • Object.keys() :返回对象自身的所有可枚举的属性的键名。
  • JSON.stringify() :只串行化对象自身的可枚举的属性。
  • Object.assign() : 忽略 enumerable 为 false 的属性,只拷贝对象自身的可枚举的属性。

属性的遍历

  • for...in: 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
  • Object.keys: 返回一个数组,包含对象自身的可枚举属性(不含 Symbol 属性)
  • Object.getOwnPropertyNames(obj): 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)。
  • Object.getOwnPropertySymbols: 返回一个数组,包含对象自身的所有 Symbol 属性。
  • Reflect.ownKeys: 返回一个数组,包含对象自身的所有属性,不管属性名是 Symbol 或字符串,也不管是否可枚举。

    以上 5 种方法遍历对象属性都遵循以下次序规则:

  • 首先遍历所有属性名为数值的属性,按照数字排序。
  • 其次遍历所有属性名为字符串的属性,按照生成时间排序。
  • 最后遍历所有属性名为 Symbol 值的属性,按照生成时间排序。

Object.getOwnPropertyDescriptors()

返回某个对象所有的自生属性的描述对象

使用场景

  • 解决 Object.assign()无法正确拷贝 get 和 set 属性的问题
const shallowMerge = (target, source) => Object.defineProperties(target,Object.getOwnPropertyDescriptors(source));
  • 配合 Object.create 方法,将对象属性克隆到一个新对象。这属于浅拷贝。
const clone = Object.create(Object.getPrototypeOf(obj),Object.getOwnPropertyDescriptors(obj));
  • 实现一个对象继承另一个对象
const obj = Object.create(
    prot,
    Object.getOwnPropertyDescriptors({
        foo: 123,
    })
);

Object.setPrototypeOf()

ES6 推荐的设置原型对象的方法,用来设置一个对象的 prototype,如果第一个参数不是对象会转为对象,但返回的还是第一个参数,所以不会产生任何效果

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

Object.getPrototypeOf()

该方法与 Object.setPrototypeOf 方法配套,用于读取一个对象的原型对象。参数不是对象会被转为对象

Object.keys()

返回一个对象自身(不含继承)所有可枚举属性的键名数组

Object.values()

返回一个对象自身(不含继承)所有可枚举属性的键值数组,会过滤属性名为 Symbol 值的属性。

Object.entries()

返回一个对象自身(不含继承)所有可枚举属性的键值对数组, 会过滤属性名为 Symbol 值的属性。

var obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

Object.fromEntries()

Object.entries()的反转

Object.fromEntries(new Map([ ['foo', 'bar'], ['baz', 42] ]))
// { foo: 'bar', baz: 42 }

Object.fromEntries([['foo', 'bar'], ['baz', 42]])
// { foo: 'bar', baz: 42 }

对象扩展运算符(...)

  • 解构赋值,将所有的可遍历但尚未被读取的属性分配到制定的对象
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
  • 扩展运算符,取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let newObj = { ...obj }
  • 合并两个对象
{ ...obj1, ...obj2 }

Object.assign 与展开运算符的区别

相同点
  • 行为一致
  • 执行的都是浅拷贝
  • 都是将已有对象的所有可枚举属性拷贝到新构造的对象中
不同点
  • Object.assign() 函数会触发 setters,而展开语法则不会

Null 传导运算符(提案)

const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
// 简化如下:
const firstName = message?.body?.user?.firstName || 'default';

四种用法:

  • obj?.prop // 读取对象属性
  • obj?.[expr] // 同上
  • func?.(...args) // 函数或对象方法的调用
  • new C?.(...args) // 构造函数的调用