理解对象

  • 创建自定义对象的通常方式是创建object的一个新实例,然后再给它添加属性和方法。
  • 也可以使用对象字面量进行创建。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let person = new Object(); 
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function() {
console.log(this.name);
};
//===========等价于============
let person = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() {
console.log(this.name);
}
};

属性类型

  • 属性分两种:数据属性和访回器属性

数据属性

  • 数据属性包含一个保存数据值的位置。
  • 值会从这个位置读取,也会写入到这个位置。
  • 数据属性有4个特性描述它们的行为。
  • 要修改属性的默认特性,就必须使用Object.defineProperty ()方法
  • 这个方法接收3个参数:要给其添加属性的对象、属性的名称和一个描述符对象。最后一个参数,即描述符对象上的属性可以包含: configurable、enumerable、writable和 value,跟相关特性的名称一一对应。根据要修改的特性,可以设置其中一个或多个值。
  • 在调用Object.defineProperty()时, configurable,enumerable和writable的值如果不指定,则都默认为 false
  • 在严格模式下,尝试修改只读属性的值会抛出错误。
1
2
3
4
5
6
7
8
let person = {}; 
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
console.log(person.name); // "Nicholas"
person.name = "Greg";
console.log(person.name); // "Nicholas"
  • 一个属性被定义为不可配置之后,就不能再变回可配置的了。再次调用object.defineProperty()并修改任何非 writable属性会导致错误。
1
2
3
4
5
6
7
8
9
10
let person = {}; 
Object.defineProperty(person, "name", {
configurable: false,
value: "Nicholas"
});
// 抛出错误
Object.defineProperty(person, "name", {
configurable: true,
value: "Nicholas"
});

访问器属性

  • 访问器属性不包含数据值。
  • 访问器属性有4个特性描述它们的行为。
  • 访问器属性是不能直接定义的,必须使用Object.defineProperty ()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义一个对象,包含伪私有成员 year_和公共成员 edition 
let book = {
year_: 2017,
edition: 1
};
Object.defineProperty(book, "year", {
get() {
return this.year_;
},
set(newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
});
book.year = 2018;
console.log(book.edition); // 2

在这个例子中,对象book 有两个默认属性: year_和 editiono。year_中的下划线常用来表示该属性并不希望在对象方法的外部被访问。另一个属性year被定义为一个访问器属性,其中获取函数简单地返回year_的值,而设置函数会做一些计算以决定正确的版本( edition )。因此,把year属性修改为2018会导致year_变成2018,edition变成2。这是访问器属性的典型使用场景,即设置一个属性值会导致一些其他变化发生。

定义多个属性

  • Object.defineProperties()方法可以通过多个描述符一次性定义多个属性。
  • 它接收两个参数:要为之添加或修改属性的对象和另一个描述符对象,其属性与要添加或修改的属性一一对应。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let book = {}; 
//数据属性的configurable、enumerable和writable特性值都是false
Object.defineProperties(book, {
year_: {
value: 2017
},
edition: {
value: 1
},
//访问器属性
year: {
get() {
return this.year_;
},
set(newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
}
});

读取属性的特征

  • Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。
  • 方法接收两个参数:属性所在的对象和要取得其描述符的属性名。
  • 返回值是一个对象,对于访问器属性包含configurable、enumerable、get和 set)属性,对于数据属性包含configurable、enumerable,writable和 value属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let book = {}; 
Object.defineProperties(book, {
year_: {
value: 2017
},
edition: {
value: 1
},
year: {
get: function() {
return this.year_;
},
set: function(newValue){
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
}
});
let descriptor = Object.getOwnPropertyDescriptor(book, "year_");
console.log(descriptor.value); // 2017
console.log(descriptor.configurable); // false
console.log(typeof descriptor.get); // "undefined"
let descriptor = Object.getOwnPropertyDescriptor(book, "year");
console.log(descriptor.value); // undefined
console.log(descriptor.enumerable); // false
console.log(typeof descriptor.get); // "function"
  • Object.getOwnPropertyDescriptors ()静态方法实际上会在每个自有属性上调用Object.getOwnPropertyDescriptor()并在一个新对象中返回它们。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
let book = {}; 
Object.defineProperties(book, {
year_: {
value: 2017
},
edition: {
value: 1
},
year: {
get: function() {
return this.year_;
},
set: function(newValue){
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
}
});
console.log(Object.getOwnPropertyDescriptors(book));
// {
// edition: {
// configurable: false,
// enumerable: false,
// value: 1,
// writable: false
// },
// year: {
// configurable: false,
// enumerable: false,
// get: f(),
// set: f(newValue),
// },
// year_: {
// configurable: false,
// enumerable: false,
// value: 2017,
// writable: false
// }
// }

合并对象

  • ECMAScript 6专门为合并对象提供了**Object.assign()**方法,这个方法接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举(Object.propertyIsEnumerable()返回true)和自有(Object.hasOwnProperty()返回true)属性复制到目标对象。
  • 以字符串和符号为键的属性会被复制。
  • 对每个符合条件的属性,这个方法会使用源对象上的[[Get]]取得属性的值,然后使用目标对象上的[ [Set]]设置属性的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
let dest, src, result; 
/**
* 简单复制
*/
dest = {};
src = { id: 'src' };
result = Object.assign(dest, src);
// Object.assign 修改目标对象
// 也会返回修改后的目标对象
console.log(dest === result); // true
console.log(dest !== src); // true
console.log(result); // { id: src }
console.log(dest); // { id: src }
/**
* 多个源对象
*/
dest = {};
result = Object.assign(dest, { a: 'foo' }, { b: 'bar' });
console.log(result); // { a: foo, b: bar }
/**
* 获取函数与设置函数
*/
dest = {
set a(val) {
console.log(`Invoked dest setter with param ${val}`);
}
};
src = {
get a() {
console.log('Invoked src getter');
return 'foo';
}
};
Object.assign(dest, src);
// 调用 src 的获取方法
// 调用 dest 的设置方法并传入参数"foo"
// 因为这里的设置函数不执行赋值操作
// 所以实际上并没有把值转移过来
console.log(dest); // { set a(val) {...} }
  • Object.assign()实际上对每个源对象执行的是浅复制。
  • 如果多个源对象都有相同的属性,则使用最后一个复制的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
let dest, src, result; 
/**
* 覆盖属性
*/
dest = { id: 'dest' };
result = Object.assign(dest, { id: 'src1', a: 'foo' }, { id: 'src2', b: 'bar' });
// Object.assign 会覆盖重复的属性
console.log(result); // { id: src2, a: foo, b: bar }
// 可以通过目标对象上的设置函数观察到覆盖的过程:
dest = {
set id(x) {
console.log(x);
}
};
Object.assign(dest, { id: 'first' }, { id: 'second' }, { id: 'third' });
// first
// second
// third
/**
* 对象引用
*/
dest = {};
src = { a: {} };
Object.assign(dest, src);
// 浅复制意味着只会复制对象的引用
console.log(dest); // { a :{} }
console.log(dest.a === src.a); // true
  • 如果赋值期间出错,则操作会中止并退出,同时抛出错误。