[TOC] # **Object 类型** &emsp;&emsp;在 ECMAScript 规范中,引用类型除 Object 本身外,Date、Array、RegExp 也属于引用类型 。 &emsp;&emsp;引用类型也即对象类型,ECMA262 把对象定义为:**无序属性的集合,其属性可以包含基本值、对象或者函数**。 也就是说,对象是一组没有特定顺序的值 。由于其值的大小会改变,所以不能将其存放在栈中,否则会降低变量查询速度。因此,对象的值存储在堆(heap)中,而存储在变量处的值,是一个指针,指向存储对象的内存处,即按址访问。具备这种存储结构的,都可以称之为引用类型 。 <br> ## 7.1 **对象拷贝** &emsp;&emsp;由于引用类型的变量只存指针,而对象本身存储在堆中 。因此,当把一个对象赋值给多个变量时,就相当于把同一个对象地址赋值给了每个变量指针 。这样,每个变量都指向了同一个对象,当通过一个变量修改对象,其他变量也会同步更新。 ``` var obj = {name:'Jack'}; var obj2 = obj; obj2.name ='Tom' console.log(obj.name,obj2.name);//Tom,Tom ``` &emsp;&emsp;ES6 提供了一个原生方法用于对象的拷贝,即 Object.assign() 。 ``` var obj = {name:'Jack'}; var obj2 = Object.assign({},obj); obj2.name ='Tom' console.log(obj.name,obj2.name);//Jack Tom ``` &emsp;&emsp;需要注意的是,Object.assign() 拷贝的是属性值。当属性值是基本类型时,没有什么问题 ,但如果该属性值是一个指向对象的引用,它也只能拷贝那个引用值,而不会拷贝被引用的那个对象。 ``` var obj = {base:{name:'Jack'}}; var obj2 = Object.assign({},obj); obj2.base.name ='Tom' console.log(obj.base.name,obj2.base.name);//Tom Tom   ``` &emsp;&emsp;从结果可以看出,obj 和 obj2 的属性 base 指向了同一个对象的引用。因此,Object.assign 仅仅是拷贝了一份对象指针作为副本 。这种拷贝被称为 “一级拷贝” 或 “浅拷贝”**。** &emsp;&emsp;如果要彻底的拷贝一个对象作为副本,两者之间的操作相互不受影响,则可以通过 JSON 的序列化和反序列化方法来实现 。 ``` var obj = {base:{name:'Jack'}}; var obj2 = JSON.parse(JSON.stringify(obj)) obj2.base.name ='Tom' console.log(obj.base.name,obj2.base.name);//Jack Tom ``` &emsp;&emsp;这种拷贝被称为 “多级拷贝” 或 “深拷贝” 。  <br> ## 7.2 **属性类型** &emsp;&emsp;ECMA-262 第 5 版定义了一些内部特性(attribute),用以描述对象属性(property)的各种特征。ECMA-262 定义这些特性是为了实现 JavaScript 引擎用的,因此在 JavaScript 中不能直接访问它们。为了表示特性是内部值,该规范把它们放在了两对儿方括号中,例如\[\[Enumerable\]\]。 这些内部特性可以分为两种:数据属性 和 访问器属性 。 <br> ### 【1】**数据属性** &emsp;&emsp;数据属性包含一个数据值的位置,在这个位置可以读取和写入值 。数据属性有4个描述其行为的内部特性: * \[\[Configurable\]\]:能否通过 delete 删除属性从而重新定义属性,或者能否把属性修改为访问器属性。该默认值为 true。 * \[\[Enumerable\]\]:表示能否通过 for-in 循环返回属性。默认值为 true。 * \[\[Writable\]\]:能否修改属性的值。默认值为 true。 * \[\[Value\]\]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认值为 undefined 。 &emsp;&emsp;要修改属性默认的特性,必须使用 ECMAScript 5 的 Object.defineProperty() 方法。这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符(descriptor)对象的属性必须是:configurable、enumerable、writable 和 value。设置其中的一或多个值,可以修改对应的特性值。例如: ``` var person = {}; Object.defineProperty(person,"name", { writable:false, value:"Nicholas" }); console.log(person.name);//"Nicholas" person.name ="Greg"; console.log(person.name);//"Nicholas" ``` &emsp;&emsp;在调用 Object.defineProperty() 方法时,如果不指定 configurable、enumerable 和 writable 特性,其默认值都是 false 。 ### **【2】访问器属性** &emsp;&emsp;访问器属性不包含数据值,它们包含一对 getter 和 setter 函数(不过,这两个函数都不是必需的)。在读取访问器属性时,会调用getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用setter 函数并传入新值,这个函数负责决定如何处理数据。访问器属性有如下4 个特性。 * \[\[Configurable\]\]:表示能否通过 delete 删除属性从而重新定义属性,或者能否把属性修改为数据属性。默认值为true 。 * \[\[Enumerable\]\]:表示能否通过 for-in 循环返回属性。默认值为 true。 * \[\[Get\]\]:在读取属性时调用的函数。默认值为 undefined 。 * \[\[Set\]\]:在写入属性时调用的函数。默认值为 undefined 。 访问器属性不能直接定义,也必须使用 Object.defineProperty() 来定义。请看下面的例子: ``` var book = { _year: 2004 }; Object.defineProperty(book,"year", { get: function () { return this._year; }, set: function (newValue) { if (newValue &gt; 2004) { this._year = newValue; console.log('set new value:'+ newValue) } } }); book.year = 2005;//set new value:2005 ``` ##  **Object 新增 API** &emsp;&emsp;ECMA-262 第 5 版对 Object 对象进行了增强,包括 defineProperty 在内,共定义了 9 个新的 API: * create(prototype\[,descriptors\]):用于原型链继承。创建一个对象,并把其 prototype 属性赋值为第一个参数,同时可以设置多个 descriptors 。 * defineProperty(O,Prop,descriptor) :用于定义对象属性的特性。 * defineProperties(O,descriptors) :用于同时定义多个属性的特性。 * getOwnPropertyDescriptor(O,property):获取 defineProperty 方法设置的 property 特性。 * getOwnPropertyNames:获取所有的属性名,不包括 prototy 中的属性,返回一个数组。 * keys():和 getOwnPropertyNames 方法类似,但是获取所有的可枚举的属性,返回一个数组。 * preventExtensions(O) :用于锁住对象属性,使其不能够拓展,也就是不能增加新的属性,但是属性的值仍然可以更改,也可以把属性删除。 * Object.seal(O) :把对象密封,也就是让对象既不可以拓展也不可以删除属性(把每个属性的 configurable 设为 false),单数属性值仍然可以修改。 * Object.freeze(O) :完全冻结对象,在 seal 的基础上,属性值也不可以修改(每个属性的 wirtable 也被设为 false)。