ES6篇(9)--对象的扩展


(1)属性的简洁表示法
ES6允许直接写入变量和函数作为对象的属性和方法。
在对象之中直接写变量,这时,属性名为变量名,属性值为变量的值
例子:
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

// 等同于
const baz = {foo: foo};
例子:
const o = {
  method() {
    return "Hello!";
  }
};

// 等同于

const o = {
  method: function() {
    return "Hello!";
  }
};
注意:简洁写法的属性名是字符串,所以下面的写法不会报错(但是看起来很奇怪)
const obj = {
  class () {}
};

// 等同于

var obj = {
  'class': function() {}
};
如果某个方法的值是一个Generator函数,前面需要加上星号。
例子:
const obj = {
  * m() {
    yield 'hello world';
  }
};

(2)属性名表达式
JavaScript定义对象的属性,有两种方法
// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;
方法一直接用标识符作为属性名,方法二用表达式作为属性名,这时要将表达式放在方括号之内。
但是如果采用字面量方式定义对象(使用大括号),ES5中只能使用方法一(标识符)定义属性。
ES6允许字面量定义对象时,使用方法二(表达式)作为对象的属性名。
例如:let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};

表达式还可以用于定义方法名
let obj = {
  ['h' + 'ello']() {
    return 'hi';
  }
};

obj.hello() // hi

注意:1.属性名表达式与简洁表示法,不能同时使用,会报错。
例子:
// 报错
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };

// 正确
const foo = 'bar';
const baz = { [foo]: 'abc'};
2.属性表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]
例子:
const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};

myObject // Object {[object Object]: "valueB"}
说明:[keyA]和[keyB]得到的都是[object Object],所以[keyB]会把[keyA]覆盖掉,而myObject最后只有一个[object Object]属性。

(3)方法的name属性
函数的name属性返回函数名,对象方法也是函数,因此也有name属性。
const person = {
  sayName() {
    console.log('hello!');
  },
};

person.sayName.name   // "sayName"
注意:1.如果对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不在该
方法上面,而是该方法的属性的描述对象的get和set属性上面,返回值是方法名前加上get和set。
const obj = {
  get foo() {},
  set foo(x) {}
};

obj.foo.name
// TypeError: Cannot read property 'name' of undefined

const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');

descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
2.bind方法创造的函数,name属性返回bound加上原函数的名字

var doSomething = function() {
  // ...
};
doSomething.bind().name // "bound doSomething"

3.Function构造函数创造的函数,name属性返回anonymous
(new Function()).name // "anonymous"

4.如果对象的方法是一个Symbol值,name属性返回的是这个Symbol值的描述。
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
  [key1]() {},
  [key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""

(4)属性的可枚举性和遍历
1.描述对象:对象的每个属性都有一个描述对象,用来控制该属性的行为。
Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

2.描述对象的属性,例如value,writable,enumerable,configurable等
其中enumerable属性,称为可枚举性,如果为false,则表示某些操作会忽略当前操作。
例如目前这4个操作会忽略enumerable为false的属性:
for...in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

注:1.Object.assign()是ES6新增的。
2.ES6规定所有的Class的原型的方法都是不可枚举的。
3.大多时候,我们只关心对象自身的属性,继承的属性会让问题复杂化,所以,尽量不要使用
for...in循环,而是用Object.keys()代替。

(5)属性的遍历
ES6共有5中方法
1.for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
2.Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
3.Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
4.Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
5.Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

这5种方法遍历对象的键名,都遵循以下次序规则:
首先遍历所有数值键,按照数值升序排列。
其次遍历所有字符串键,按照加入时间升序排列。
最后遍历所有 Symbol 键,按照加入时间升序排列。

例子:Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]

(6)super关键字
this关键字:指向函数所在的当前对象
super关键字:指向当前对象的原型对象
例子:
const proto = {
  foo: 'hello'
};

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

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
注:super关键字表示原型对象时,只能用在对象的方法中,用在其他地方会报错。
例子:
// 报错
const obj = {
  foo: super.foo//用在属性里
}

// 报错
const obj = {
  foo: () => super.foo//用在函数中,然后将函数赋值给foo属性
}

// 报错
const obj = {
  foo: function () {
    return super.foo//用在函数中,然后将函数赋值给foo属性
  }
}

(7)对象的扩展运算符
数组有扩展运算符,ES2018将这个运算符引入了对象

1.解构赋值
对象的解构赋值:从一个对象取值,相当于将目标对象自身的所有可遍历的,但尚未
被读取的属性,分配到指定的对象上,所有的键和它们的值,都会拷贝到新的对象上。
例子:
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

注意:a.由于解构赋值要求等号右边是一个对象,所以如果等号右表是undefined或null,会报错【因为它们无法转为对象】
例子:
let { ...z } = null; // 运行时错误
let { ...z } = undefined; // 运行时错误
b.解构赋值必须是最后一个参数,否则报错
例子:let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误
c.解构赋值的拷贝是浅拷贝,如果一个键的值是复合类型的值(数组、对象、函数),那么,解构赋值拷贝的是这个值的引用,
而不是这个值的副本。
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2

d.扩展运算符的解构赋值,不能继承自原型对象的属性。

let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined

2.扩展运算符
对象的扩展运算符:用于取出参数对象的所有可遍历属性,拷贝到当前对象中。
注:a.对象的扩展运算符可以用于数组【数组是特殊的对象】
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}
b.如果扩展运算符后是一个空对象,则没有任何效果
{...{}, a: 1}
// { a: 1 }
c.如果扩展运算符后不是一个对象(例如数字,布尔值,undefined,null),则会自动将其转化为对象。
// 等同于 {...Object(1)}
{...1} // {}
// 等同于 {...Object(true)}
{...true} // {}

// 等同于 {...Object(undefined)}
{...undefined} // {}

// 等同于 {...Object(null)}
{...null} // {}
d.如果扩展运算符后不是对象,是字符串,则它会自动转成一个类似数组的对象,所有返回的不是空对象。

{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
e.对象的扩展运算符等同于使用Object.assign()方法

let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);

3.扩展运算符的作用
a.合并两个对象
例子:
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

b.修改现有对象的部分属性
【用户自定义属性在扩展运算符之后,扩展运算符内部同名属性被自定义属性覆盖】
let aWithOverrides = { ...a, x: 1, y: 2 };
// 等同于
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
// 等同于
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
// 等同于
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });

c.设置新对象的默认属性值
【用户自定义属性在扩展运算符之前】

let aWithDefaults = { x: 1, y: 2, ...a };
// 等同于
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
// 等同于
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);

阮一峰ES6 对象的扩展

猜你喜欢

转载自blog.csdn.net/u012149906/article/details/92839716