对象简介:ECMA-262将对象定义为一组属性的无序集合。严格来说,这意味着对象就是一组没有特定顺序的值。对象的每个属性或方法都由一个名称来标识,这个名称映射到一个值。
理解对象
早期创建对象的方式是通过new Object()构造函数。现在都是使用更加简便的对象字面量方式创建对象。
属性的类型
ECMA-262使用一些内部特性来描述属性的特征。这些特性是由为JavaScript实现引擎的规范定义的。开发者不能直接在JavaScript中直接访问这些特性。对象属性分为两种:数据属性和访问器属性。
1-数据属性:
数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置,数据属性有四个特性描述他们的行为。
- [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
- [[Enumberable]]:表示属性是否可以通过for-in循环返回。默认情况下,这个特性是true。
- [[Writable]]:表示属性的值是否可以被修改。默认是true。
- [[Value]]:包含属性实际的值,这就是前面提到的那个读取和写入属性值的位置,这个特性默认为undefined。
let person = {
name: 'jury'
}
// 这里我创建了一个name属性,并给它赋予一个值。这意味着[[Value]]特性会被设置为jury。另外的三个特性的值都是true。
如果要修改属性的默认特性,就必须使用Object.defineProperty()方法。这个方法接受三个参数,对象,属性名称,一个描述符对象。
let per = {}
Object.defineProperty(per, 'name', {
writable: false,
value: 'kangkang'
})
per.name = 'abc';
console.log(per.name); // kangkang
// 不可再修改name值。
// 注意: 一个属性一旦被定义为不可配置之后,就不能再变回可配置的了,再次调用Object.defineProperty()会
//报错。在调用Object.defineProperty()的时候,如果configurable,enumerable,writable的值如果不指定,则都默认为false。
2-访问器属性:
访问器属性不包含数据值,它们包含一个获取函数(getter)和一个设置函数(setter)。不过这两个函数不是必须的。在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效的值。在写入访问器属性时,会调用设置函数并传入新值,这个函数必须决定对数据作出什么修改。访问器属性有四个特性描述它们的行为。
- [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
- [[Enumberable]]:表示属性是否可以通过for-in循环返回。默认情况下,这个特性是true。
- [[Get]]: 获取函数,在读取属性时调用,默认值为undefined。
- [[Set]]: 设置函数,在写入属性时调用,默认值为undefined。
访问器属性是不能直接定义的,必须使用Object.defineProperty()。
let book = {
year_: 2017,
edition: 1
}
Object.defineProperty(book, 'year', {
get() {
return this.year_;
}
set() {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
})
// 注意:获取函数和设置函数不一定都要定义,但是如果没有定义其中一个,则该属性就不具备相应的功能
同时定义多个属性
Object.defineProperties(),它接受两个参数,对象和一个描述符对象。
let book = {}
Object.defineProperties(book, {
year_: {
value: 2017
},
edition: {
value: 1,
},
year: {
get() {
return this.year_;
}
set() {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
}
})
读取属性的特性
Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。该方法接受两个参数,第一个是属性所在的对象,第二个是要取得属性描述符的属性。返回值是一个对象。对于访问器属性,包含configurable、enumerable、get和set属性。对于数据属性包含configurable、enumerable、writable和value属性。比如:
let book = {};
Object.defineProperties(book, {
year_: {
value: 2017
},
edition: {
value: 1
},
year: {
get() {
return this.year_
},
set() {
if (newValue>2017) {
this.year_ = newValue;
this.edition += newValue -2017;
}
}
}
});
let desc = Object.getOwnPropertyDescriptor(book, "year_");
console.log(desc.value) // 2017
console.log(desc.configurable) // false
console.log(desc.get) // undefined
let desc2 = Object.getOwnPropertyDescriptor(book, "year");
console.log(desc2.value) // undefined
console.log(desc2.enumerable) // false
console.log(typeof desc.get) // function
ECMAScript 2017新增了Object.getOwnPropertyDescriptors()静态方法。这个方法会获取整个对象所有属性的属性描述符。相当于在每个属性上调用Object.getOwnPropertyDescriptor方法,然后在一个对象中返回所有的属性描述符。比如上面的例子会像以下这样:
let book = {};
Object.defineProperties(book, {
year_: {
value: 2017
},
edition: {
value: 1
},
year: {
get() {
return this.year_
},
set() {
if (newValue>2017) {
this.year_ = newValue;
this.edition += newValue -2017;
}
}
}
});
console.log(Object.getOwnPropertyDescriptors(book));
{
year_: {
configurable: false,
enumerable: false,
writable: false,
value: 2017
},
year: {
configurable: false,
enumerable: false,
get: f(),
set: f(newValue);
},
edition: {
configurable: false,
enumerable: false,
writable: false,
value: 1
}
}
合并对象
合并对象,顾名思义就是将多个对象的属性合并到一个对象里面去,ES6专门提供了一个方法叫Object.assign()方法。该方法接受一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举和自有属性复制到目标对象。对每个符合条件的属性,这个方法会使用源对象上的[[get]]取得属性的值,然后使用目标对象上的[[set]]设置属性的值。
let dest = {};
let src = {
id: 'src'
},
result = Object.assign(dest, src);
console.log(dest === result); // true
console.log(dest !== src); // true
//Object.assign会修改目标对象,然后返回目标对象。
// 当有对个源对象的时候,如果这些源对象中的属性名有重复的,那么会按照参数的位置,就是拥有该属性的最后一个源对象参数的值为准。
//Object.assign方法执行的是浅复制,而且不能在对象之间转移获取函数和设置函数。
let obj = { a: {} };
Object.assign(dest, obj);
console.log(dest.a === src.a); // true
//如果赋值之间出错了,则操作会终止并退出,同时抛出错误,Object.assign没有回滚之前
//赋值的概念,它是一个尽力而为,可能只会完成部分复制的方法。
对象标识及相等判定
Object.is()方法大部分情况下和===操作符的效果是一样的,不同的是在一些边界情况下会有不同的结果。如+0,-0的判断,两个NaN之间的判断。
console.log(+0 === -0); // true
console.log(+0 === 0); // true
console.log(0 === -0); // true
console.log(Object.is(+0,-0)); // false
console.log(Object.is(+0,0)); // true
console.log(Object.is(-0,0)); // false
console.log(Object.is(NaN,NaN)); // true
增强的对象语法
属性值简写:
当给对象添加变量的时候,属性名和变量名是一样的,那样就可以只写一个属性名就能达到同样的效果。
let name = 'a'
let obj = {
name: name
}
// 下面和上面是等价的
let obj = {
name
}
可计算属性
const namekey = 'name';
let person = {
[nameky]: 'john'
}
console.log(person); //{ name: 'john' }
对象解构赋值
let obj = {
a: '123',
b: '234',
}
let { a : o1, b: o2} = obj; // 此步骤既声明o1,o2变量,又执行了赋值操作。
console.log(o1,o2) // 123,234
// 如果想让变量直接使用属性的名称,可以使用简洁方法:
let obj = {
a: '123',
b: '234',
}
let { a, b} = obj;
console.log(a,b) // 123,234
// 赋值的时候如果引用的属性不存在,则该变量的值就是undefined。
let obj = {
a: '123',
b: '234',
}
let { a, c} = obj;
console.log(a,c) // 123 undefined
// 也可以在解构赋值的同时定义默认值,以防上面不存在属性的情况
let obj = {
a: '123',
b: '234',
}
let { a, c = '456'} = obj;
console.log(a,c) // 123 456
// 注意:解构在内部使用ToObject()(不能在运行时环境直接访问)把源数据结构转换为对象。
//这意味着在对象解构的上下文中,原始值也会被当成对象。但是null和undefined不能被解
//构,否则会抛出错误。
let { length } = 'abc'
console.log(lenght); // 3
let { constructor:c } = 2;
console.log(c === Number); // true
嵌套解构
let person = {
name: 'a',
age: 12,
job: {
title: 'doctor'
}
}
let obj = {};
({name:obj.name, age:obj.age, job: obj.job} = person);
person.job.title = 'police';
console.log(person.job); // police
console.log(obj.job); // police 由此可知,两个对象中的job是一个引用
let { job : { title }} = person;
console.log(title); // doctor
// 在外层属性没有定义的情况下不能使用嵌套解构,无论源对象还是目标对象都一样。
let person = {
name: 'a',
age: 12,
}
let obj = {};
({foo:{obj.age}} = person); // foo是undefined,会报错,不可解构它
({job: {title: obj.a.job }}= person) // obj上不存在a属性,报错。