学习ES6中的对象扩展小记

对象扩展

1. 属性的简洁表示法

ES6 允许直接写入变量和方法,作为对象的属性和方法,需要注意的是简洁写法的属性名总是字符串

const obj = {
    class () {}
}

// 这里的 class 是字符串,所以不会因为它属于关键字,而导致语法解析报错
var obj = {
    'class': function () {}
}

// 如果某个方法的值是一个 Generator 函数,前面需要加上星号
const obj = {
    * m() {
        yield 'hello world' 
    }
}

2. 属性名表达式

ES6 允许字面量定义对象时,用表达式作为对象的属性名,即把表达式放在方括号内

let obj = [
    [name]: 'han',
    ['a' + 'ge']: 21,
    'last name': 'haiguang'
]

表达式还可以用在定义方法名上

let obj = {
    ['h' + 'ello']() {
        return 'hi'
    }
}

obj.hello()  // hi

注意:属性名和简洁表示法,不能同时使用,会报错。还有就是属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串 [object Object],这一点需要特别小心

// 报错
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };

// 正确
const foo = 'bar';
const baz = { [foo]: 'abc'};
const keyA = {a: 1};
const keyB = {b: 2};

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

myObject // Object {[object Object]: "valueB"}

3. 方法的 name 属性

函数的 name 属性,返回函数名,对象方法也是函数,因此也有 name 属性,如:obj.sayName.name 就返回 sayName

如果对象的方法使用了取值函数(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"

还有几种特殊情况:
1. bind 方法创造的函数u,name 属性返回 bound 加上原函数的名字
2. Function 构造函数,name 属性返回 anonymous
3. 如果对象的方法是一个 Symbol 值,那么 name 属性返回的是这个 Symbol 值得描述

const key1 = Symbol('description');
let obj = {
  [key1]() {}
};
obj[key1].name // "[description]"

4. Object.is()

用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
不同之处有两个:
1. +0 不等于 -0
2. NaN 等于自身

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

5. Object.assign()

用于对象的合并,将源对象的所有可枚举属性,复制到目标对象。(参数:第一个是目标对象,后面的参数都是源对象)

注意:在合并过程中,如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。而且 undeifned 和 null 无法转为对象,所以他们作为参数的话会出现报错。当时如果这两种值没有作为首参数的话,他们会被跳过,这时候又不会报错。

同样的其他类型的值(数值、字符串、布尔值)不在首参数的话,也不会报错。字符串则会以数组形式拷贝入目标对象

同时,这个方法只拷贝对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性

注意点
1. 浅拷贝:Object.assign 方法实行的是浅拷贝,不是深拷贝。所以如果源对象的某个属性的值是对象,那么只是拷贝了这个对象的引用
2. 同名属性的替换
3. 数组的处理:这个方法会把数组视为属性名为0、1、2的对象,这里如果是多个数组的合并,会出现相同key值被覆盖掉的情况
4. 取值函数的处理:如果要复制的值是一个取值函数,那么将求值后再进行复制

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3],相同的 key 值被替换
const source = {
  get foo() { return 1 }
};
const target = {};

Object.assign(target, source)
// { foo: 1 },这里的 source 对象的 foo 属性是一个取值函数,这里会直接拿到值以后,将这个值复制过去

常见用途(重要多看几遍)
1. 为对象添加属性,如在一个类的构造函数中,将对象的属性添加到类的对象实例上面Object.assign(this, {x, y})
2. 为对象添加方法,如直接将一个对象的原型对象作为目标对象,然后源对象是这种形式:{fnA (a, b) {...}, fnB (c) {..}},这相当于直接给这个对象添加原型方法
3. 克隆对象,这个是比较常用的方法
4. 为属性指定默认值(这里需要留意这个方法只是浅拷贝的问题)

// 为属性指定默认值
const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}

6. 属性的可枚举性和遍历

可枚举性:对象的每个属性都有一个描述对象,用来控制该属性的行为(Object.getOwnPropertyDescriptor),这个描述对象的 enumerable 属性,成为可枚举性,如果这个属性为 false,表示某些操作会忽略当前的属性,如以下几种操作:

  1. for…in 循环(相比于其他三个方法,这个方法可以返回继承的属性,其他的则不会)
  2. Object.keys()
  3. JSON.stringify():只串行化对象自身的可枚举的属性
  4. Object.assign

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

他们都遵守同样的属性遍历次序规则
- 首先遍历所有数值键,按照数值升序排列
- 其次遍历所有字符串键,按照加入时间升序排列
- 最后遍历所有 Symbol 键,按照加入时间升序排列

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

7. Object.getOwnPropertyDescriptors()

该方法会返回指定对象所有自身属性(非继承属性)的描述对象。所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象

该方法引入的目的在于
1. 解决 Object.assign() 无法正确拷贝 get 属性和 set 属性的问题。
2. 还有就是配合 Object.create 方法,将对象属性克隆到一个新对象(浅拷贝)

// 两个对象合并为一个对象
const shallowMerge = (target, source) => Object.defineProperties(
  target,
  Object.getOwnPropertyDescriptors(source)
)

8. proto 属性,Object.setPrototypeOf(),Object.getPrototypeOf()

_proto_属性
用来读取或设置当前对象的 prototype 对象,但是最好不要使用这个属性,可以使用以下属性来进行替代:
1. Object.setPrototypeOf():写操作
2. Object.getPrototypeOf():读操作
3. Object.create():生成操作

Object.setPrototypeOf()
用来设置一个对象的 prototype 对象,返回参数对象本身(ES6中正式推荐的设置原型对象的方法)
格式:

Object.setPrototypeOf(object, prototype)
// 将 proto 对象设为 obj 对象的原型,所以 obj 对象可以读取 proto 对象的属性
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

注意:如果第一个参数不是对象,会自动转为对象,但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。而且由于 undefined 和 null 无法转为对象,所以如果第一个参数是 undefined 或 null,就会报错

Object.getPrototypeOf()
用来读取一个对象的原型对象,同样的,如果参数不是对象,会被自动转为对象,如果参数是 undefined 或者 null,就会报错

9. super 关键字

这个关键字指向当前对象的原型对象

const proto = {
  foo: 'hello'
};

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

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

注意:super 关键字表示原型对象时,只能用在对象的方法中,用在其他地方都会报错。目前只有对象方法的简写可以让 JavaScript 引擎确认,定义的对象的方法

JavaScript 引擎内部,super.foo等同于Object.getPrototypeOf(this).foo(属性)或Object.getPrototypeOf(this).foo.call(this)(方法)

10. Object.keys(),Object.values(),Object.entries()

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名、或值(或键值对数组)

这个方法和 Object.value 和 Object.entries 作为遍历一个对象的补充手段,供 for…of 循环使用

如果参数不是对象,都会被先转为对象。由于数值和布尔值的包装对象都不会为实例添加非继承的属性,所以会返回空数组

// 注意这里的返回顺序规则
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)
// ["b", "c", "a"]
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

关于 Object.entries 方法的另一个用处是,将对象转为真正的 Map 结构

const obj = {foo: 'bar', baz: 22}
const map = new Map(Object.entries(obj))
map  // Map { foo: 'bar', baz: 22 }

11. 对象的扩展运算符

利用扩展运算符进行解构赋值,如:let {x, y, z...} = {x: 1, y: 2, a: 3, b: 4},这里的 z 最终为 { a: 3, b: 4 }

注意:解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值得引用,而不是这个值得副本。(同时,并不能复制继承自原型对象的属性)

// 理解
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

如果使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式

// 报错
let { x, ...{ y, z } } = 0

这个方法等同于 Object.assign 方法

let aClone = {...a}
// 相当于
let aClone = Object.assign({}, a)

// 可以用于合并两个对象
let ab = { ...a, ...b }
// 相当于
let ab = Object.assign({}, a, b)

如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖

// 用来修改现有对象部分的属性很方便
let newVersion = {
    ...previousVersion,
    name: 'New name'
}
// name 可以覆盖掉 previousVersion 里面的同名属性

如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值

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

// 并不会抛出错误,因为 x 属性只是被定义,但没执行
let aWithXGetter = {
  ...a,
  get x() {
    throw new Error('not throw yet');
  }
};

// 会抛出错误,因为 x 属性被执行了
let runtimeError = {
  ...a,
  ...{
    get x() {
      throw new Error('throw now');
    }
  }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值