对象的扩展

属性的简洁表示法

es6 允许直接写入变量和函数,作为对象的属性和方法。

const a = 1;
const b = { a };
log(b); // {a: 1}
// 相当于 const b = { a: a};

方法简写:

const obj = {
  hello() {
    return 'Hello';
  },
};

属性名表达式

JS 定义属性的方法:

// 用标识符作为属性名
obj.a = 'a';
// 用表达式作为属性名
obj['a' + 'b'] = 'ab';

使用字面量方式定义对象时,es5 只能使用标识符作为属性名。

const obj = {
  a: 'a',
};

es6 可以使用表达式作为属性名。

const a = 'aaa';
const obj = {
  [a]: 'aaa',
};
log(obj); // {aaa: "aaa"}

属性名表达式和简洁表示法不能同时使用。

const a = 'aaa';
const obj = { [a] }; // Uncaught SyntaxError: Unexpected token }

如果属性名是一个对象,默认情况下会转为字符串 "[object Object]"。

const objA = {};
const objB = {};
const objC = {
  [objA]: 'aaa',
  [objB]: 'bbb',
};
log(objC); // {[object Object]: "bbb"}

方法的 name 属性

略...

属性的可枚举性和遍历

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。

Object.getOwnPropertyDescriptor 可以获取属性的描述对象。

const obj = {
  a: 1,
};

log(Object.getOwnPropertyDescriptor(obj, 'a'));
// {
//  value: 1,
//  writable: true,
//  enumerable: true, <-- 可枚举性
//  configurable: true
//}

四个操作会忽略 enumerable: false 的属性。

  • for...in -- 只遍历自身和继承的 可枚举的 属性
  • Object.keys() -- 返回自身的所有可枚举属性的键名
  • JSON.stringify() -- 只序列化对象自身的可枚举属性
  • Object.assign() -- 只拷贝对象自身的可枚举属性

引入可枚举的概念,最初是为了让某些属性规避 for...in ,不然所有的内部属性和方法都会被遍历到。

比如对象原型的 toString() 方法,数组的 length 属性。

Object.getOwnPropertyDescriptor(Object.prototype, 'toString');
// {
//  value: ƒ toString(),
//  writable: true,
//  enumerable: false, <-- 可枚举属性为 false
//  configurable: true
//}

Object.getOwnPropertyDescriptor((class {foo() {}}).prototype, 'foo');
// {
//  value: ƒ foo(),
//  writable: true,
//  enumerable: false, <-- 可枚举属性为 false
//  configurable: true
//}

属性的遍历

方法 说明 举例
for...in 遍历对象自身和继承可枚举属性(不含 Symbol 属性)。
Object.keys() 返回数组,包含对象自身(不含继承的)所有可枚举属性(不含 Symbol)的键名。 Object.keys([]); // []
Object.getOwnPropertyNames() 返回数组,包含对象自身的所有属性(不含 Symbol,但是包含不可枚举属性)的键名。 Object.getOwnPropertyNames([]); // ["length"]
Object.getOwnPropertySymbols() 返回数组,包含对象自身的所有 Symbol 属性的键名 const symbolA = Symbol('A'); const obj = {[symbolA]: 'a'}; Object.getOwnPropertySymbols(obj); // [Symbol(A)]
Reflect.ownKeys() 返回数组,包含对象自身的所有键名——包含字符串、Symbol、可枚举不可枚举。
function People() {
  this.country = 'China';
}

People.prototype.name = 'Tom';
Object.defineProperty(People.prototype, 'language', {
  value: 'Chinese',
  enumerable: false
});

let people1 = new People();
people1.age = 10;
people1[Symbol()] = 'symbol';
Object.defineProperty(people1, 'gender', {
  value: 'male',
  enumerable: false
});

for (let key in people1) {
  console.log(key);
}
// country
// age
// name

console.log(Object.keys(people1)); // ["country", "age"]

console.log(Object.getOwnPropertyNames(people1)); // ["country", "age", "gender"]

console.log(Object.getOwnPropertySymbols(people1)); // [Symbol()]

console.log(Reflect.ownKeys(people1)); // ["country", "age", "gender", Symbol()]

以上五种都遵循属性遍历的次序规则:

  • 先遍历所有数值键,按数值升序排列
  • 再遍历字符串键,按加入时间升序
  • 遍历 Symbol 键,按加入时间升序

super 关键字

super 关键字指向当前对象的原型对象。

const proto = {
  name: 'proto',
};

const obj = {
  name: 'obj',
  find() {
    return super.name;
  },
};

Object.setPrototypeOf(obj, proto);

console.log(obj.find()); // 'proto'

super 关键字表示原型对象时,只能用在对象的方法中,用在其他地方都会报错。

只有在对象方法的简写法中可以使用 super。

const obj = {
  name: 'obj',
  find: () =>  super.name // Uncaught SyntaxError: 'super' keyword unexpected here
};
const obj = {
  name: 'obj',
  find: function() {
    return super.name // Uncaught SyntaxError: 'super' keyword unexpected here
  }
};
// 以上两种都认为把 super.name 赋值给 find,所以报错

对象的扩展运算符

解构赋值

将目标对象自身可遍历的、但未被读取的属性,分配到指定的对象上。

const { a, b, ...c } = { a: 1, b: 2, c: 3, d: 4, e: 5 };
console.log(a); // 1
console.log(b); // 2
console.log(c); // {c: 3, d: 4, e: 5}

解构赋值的右边如果不是对象,是 undefined 或 null 会报错。

const { ...a } = undefined; // Uncaught TypeError:  Cannot destructure 'undefined' or 'null'.

注意:解构赋值的拷贝是浅拷贝。

扩展运算符的解构赋值,不能复制继承自原型的属性。

let o1 = {
  a: 1,
};
let o2 = {
  b: 2,
};
o2.__proto__ = o1;
const { ...a } = o2;
console.log(a); // {b: 2}
const o = Object.create({
  a: 1,
  b: 2,
});
o.c = 3;
const { a, ...d } = o; // a 通过解构赋值,可以读取对象 o 继承的属性 a
console.log(d); // {c: 3} 通过扩展运算符解构赋值,只能读取对象自身的属性
const { b, c } = d;
console.log(a); // 1
console.log(b); // undefined
console.log(c); // 3

用处:扩展某个函数的参数

function a({ a, b }) {}
function b(x, y, ...z) {
  console.log(x, y);
  a(z);
}

扩展运算符

对象的扩展运算符用于取出参数对象的所有可遍历属性,拷贝到当前对象上。

const o1 = {
  a: 1,
  b: 2,
};
Object.defineProperty(o1, 'c', {
  value: 3,
  enumerable: false,
});
console.log(o1); // {a: 1, b: 2, c: 3}
const o2 = { ...o1 }; // 只拷贝可遍历属性
console.log(o2); // {a: 1, b: 2}

数组是特殊对象,对象的扩展运算符也适用于数组。

const arr = ['a', 'b', 'c'];
const obj = { ...arr };
console.log(obj); // {0: "a", 1: "b", 2: "c"}

扩展运算符后面是一个空对象,没效果。

const obj = { ...{} };
console.log(obj); // {}

扩展运算符后面不是一个对象,会将其转为对象。

const obj = { ...1 }; // 会将 1 转为数值的包装对象 Number(1),该对象没有自身的属性,所以返回一个空对象
console.log(obj); // {}

扩展运算符后面是字符串,会转成类似数组的对象。

const obj = { ...'abcd' };
console.log(obj); // {0: "a", 1: "b", 2: "c", 3: "d"}

对象的扩展运算符等同于使用 Object.assign()

const o1 = {};
const o2 = { ...o1 }; // 只能拷贝对象实例的属性
// 等同于
const o3 = Object.assign({}, o1); // 只能拷贝对象实例的属性

同时拷贝对象实例的属性和原型属性。

// 方法 1
const o1 = {
  __proto__: Object.getPrototypeOf(obj), // __proto__ 在非浏览器环境下不一定部署,所以不推荐
  ...obj,
};
// 方法 2
const o2 = Object.assign(Object.getPrototypeOf(obj), obj);
// 方法 3
const o3 = Object.assign(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

扩展运算符可合并两个对象。

const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { ...o1, ...o2 };
const o4 = Object.assign({}, o1, o2);
console.log(o3); // {a: 1, b: 2}
// 等同于
console.log(o4); // {a: 1, b: 2}

自定义的属性放在扩展运算符后面,会把扩展运算符内部的同名属性覆盖掉。

const o = {
  a: 1,
  b: 2,
};
const o2 = { ...o, a: 3 };
console.log(o2); // {a: 3, b: 2}

等同于

const o = {
  a: 1,
  b: 2,
};
const o2 = { ...o, ...{ a: 3 } };
console.log(o2); // {a: 3, b: 2}

等同于

const o = {
  a: 1,
  b: 2,
};
const a = 3;
const o2 = { ...o, a };
console.log(o2); // {a: 3, b: 2}

等同于

const o = {
  a: 1,
  b: 2,
};
const a = 3;
const o2 = Object.assign({}, o, { a: 3 });
console.log(o2); // {a: 3, b: 2}

可以方便地修改现有对象的部分属性。

const oldVal = {
  a: 1,
  b: 2,
  c: 3,
};

const newVal = {
  ...oldVal,
  a: 'a',
};

console.log(newVal); // {a: "a", b: 2, c: 3}

和数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式。

const a = 2;

const o = {
  ...(a > 2 ? { a: 2 } : { a: 1 }),
  b: 2,
};

console.log(o); // {a: 1, b: 2}

扩展运算符的参数对象中,如果有取值函数 get,会执行。

const obj = {
  a: 1,
};

const objWithGetter = {
  ...obj,
  get x() {
    console.log('不会执行');
  },
};

const runTimeObj = {
  ...obj,
  ...{
    get x() {
      console.log('会执行');
    }
  },
};

// 会执行
Last Updated: 6/27/2019, 5:21:52 PM