对象就是一组没有特定顺序的值,对象的每个属性或者方法都可由一个名称来标识,这个名称映射到一个值。可以把对象想象成一张散列表,其中的内容就是一组名值对,值可以是数据或者函数
创建自定义对象的通常方式是创建Object的一个新实例,然后再给它添加属性和方法。早期开发者频繁使用这种方式创建新对象,几年后,对象字面量变成了更流行的方式
ES使用了一些内部特性来描述属性的特征,这些特征是由为JS实现引擎的规范定义的,因此,开发者不能在JS中直接访问这些特性。为了将某个特性标识为内部特性,规范会用两个中括号把特性的名称括起来
属性分两种:数据属性和访问器属性
数据属性
数据属性包含一个保存数据值的位置,值会从这个位置读取,也会写入到这个位置。
数据属性有4个特性描述它们的行为:
- [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true
- [[Enumerable]]:表示属性是否可以通过for-in循环返回,默认情况下,所有直接定义在对象上的属性的这个特性都是true
- [[Writable]]:表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的这个特性都是true
- [[Value]]:包含属性实际的值,这个特性默认值为undefined
将属性添加到对象之后[[Configurable]]、[[Enumerable]]、[[Writable]]都会被设置为true,而[[Value]]特性会被设置为指定的值
要修改属性的默认特性,就必须使用Object.defineProperty()方法,这个方法接收3个参数:要给其添加属性的对象、属性的名称和一个描述符对象。最后一个参数,即描述符对象上的属性可以包含:configurable、enumerable、writable和value,跟相关特性的名称一一对应。根据要修改的特性,可以设置其中一个或多个值
在调用Object.defineProperty()时,如果描述符对象上的属性的值都不指定,则都默认为false
访问器属性
访问器属性不包含数据值,相反,他们包含一个获取函数和一个设置函数,不过这两个函数不是必须的,在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效的值,在写入访问器属性时,会调用设置函数并传入新值,这个函数必须决定对数据做出什么修改。
访问器属性有4个特性描述它们的行为:
- [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true
- [[Enumerable]]:表示属性是否可以通过for-in循环返回,默认情况下,所有直接定义在对象上的属性的这个特性都是true
- [[Get]]:获取函数,在读取属性时调用,默认值为undefined
- [[Set]]:设置函数,在写入属性时调用,默认值为undefined
访问器属性是不能直接定义的,必须使用Object.defineProperty()
获取函数和设置函数不一定都要定义,只定义获取函数意味着属性是只读的,尝试修改属性会被忽略,严格模式下会报错,类似的,只有一个设置函数的属性是不能读取的,非严格模式下读取会返回undefined,严格模式下会报错
Object.defineProperties()方法可以通过多个描述符一次性定义多个属性,它接受两个参数:要为之添加或修改属性的对象和另一个描述符对象,其属性与要添加或修改的属性一一对应
使用Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符,这个方法接收两个参数:属性所在的对象和要取其描述符的属性名。返回值是一个对象
ES8新增了Object.getOwnPropertyDescriptors()静态方法。这个方法实际上会在每个自有属性上调用Object.getOwnPropertyDescriptor()并在一个新对象中返回它们
合并两个对象:把源对象所有的本地属性一起复制到目标对象上,这种操作也被称为混入,因为目标对象通过混入源对象的属性得到了增强
ES6专门为合并对象提供了Object.assign()方法,这个方法接受一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举和自有属性复制到目标对象,以字符串和符号为键的属性会被复制,对每个符合条件的属性,这个方法会使用源对象上的[[Get]]取得属性的值,然后使用目标对象上的[[Set]]设置属性的值
Object.assign()实际上对每个源对象执行的是浅复制,如果多个源对象都有相同的属性,则使用最后一个复制的值。此外,从源对象访问器属性取得的值,比如获取函数,会作为一个静态值赋给目标对象。换句话说,不能在两个对象间转移获取函数和设置函数
如果赋值期间出错,则操作会中止并退出,同时抛出错误。Object.assign()没有回滚之前赋值的概念,因此它是一个尽力而为,可能只会完成部分复制的方法
ES6规范新增了Object.is()方法,这个方法和===很像,但同时也考虑到了一些边界情形
在给对象添加变量的时候,开发者经常会发现属性名和变量名是一样的,简写属性名只要使用变量名就会自动被解释为同名的属性键,如果没有找到同名变量就会报错
在引入可计算属性之前,如果想使用变量的值作为属性,那么必须先声明对象,然后使用中括号语法来添加属性。换句话说,不能在对象字面量中直接动态命名属性
有了可计算属性,就可以在对象字面量中完成动态属性赋值。中括号包围的对象属性键告诉运行时将其作为JS表达式而不是字符串来求值
可计算属性表达式中抛出任何错误都会中断对象创建,不能回滚
在给对象定义方法时,通常都要写一个方法名、冒号,然后再引用一个匿名函数表达式,新的简写方法的语法遵循同样的模式,但开发者要放弃给函数表达式命名,这样可以明显缩短方法声明。简写方法名对获取和设置函数也是适用的。简写方法名与可计算属性键相互兼容
对象解构语法可以在一条语句中使用嵌套数据实现一个或多个赋值操作,简单地说,对象解构就是使用与对象匹配的结构来实现对象属性赋值
使用解构可以在一个类似对象字面量的结构中,声明多个变量,同时执行多个赋值操作。如果想让变量直接使用属性的名称,那么可使用简写语法。
解构赋值不一定与对象的属性匹配,赋值的时候可以忽略某些属性,而如果引用的属性不存在,则该变量的值就是undefined。也可以在解构赋值的同时定义默认值。
解构在内部使用函数toObject()把源数据结构转换为对象,这意味着对象解构的上下文中,原始值会被当成对象,这也意味着,null和undefined不能被解构,否则会报错
解构并不要求变量必须在解构表达式中声明,不过,如果是给事先声明的变量赋值,则赋值表达式必须包含在一对括号中
解构对于引用嵌套的属性或目标没有限制,为此,可以通过解构来复制对象属性,解构赋值可以使用嵌套解构,以匹配嵌套的属性,在外层属性没有定义的情况下不能使用嵌套解构,无论源对象还是目标对象都一样
涉及多个属性的解构赋值是一个输出无关的顺序化操作,如果一个解构表达式涉及多个赋值,开始的赋值成功而后面的赋值出错,则整个解构赋值只会完成一部分
在函数参数列表中也可以进行解构赋值,对参数的解构赋值不会影响arguments对象,但可以在函数签名中声明在函数体内使用局部变量