03 object类型对象
ES 中一共有两种对象: function 类型对象和 object 类型对象。object类型对象是ES的基础,它主要是通过属性使用。本文将详细介绍object类型对象的相关内容。
1 创建 object 类型对象的三种方式
ES 中 object 类型的对象大致有三种创建方式 : 直接使用花括号创建、使用 function 创建以及使用 Object.create 方法创建 。
1.1 直接使用花括号创建
使用花括号创建对象时直接将属性写到花括号中就可以了,属性名和属性值使用冒号(:) 分割,不同属性之间使用逗号(,)分割(注意,最后一个属性后面没有逗号),属性的值可以是直接量,也可以是 object 类型对象或者 function 类型对象 。
object 对象调用属性有两种方法:直接使用点操作符调用;使用方括号调用 。 因为使用方括号没有使用点操作符方便,所以一般使用点操作符调用的比较多,不过当属性名为一个变量时则只能使用方括号来调用。
1.2 使用 function 创建
关于使用 function 创建 object 实例对象,02 function类型对象#4 创建对象讲过了,是使用 new 关键字来创建的,而且创建出来的对象还可以使用 function 的 prototype 属性对象中的属性 。
1.3 使用 Object.create 方法创建
还有一种创建 object 类型对象的方式,那就是使用 Object.create 来创建。 Object 是 ES 中内置的一个 function 类型的对象, create 是 Object 对象的一个属性方法,其作用是根据传入的参数创建 object 类型的对象 。 create 方法的调用语法如下 。
1 | Object.create { prototype, [ propertiesObject ] ) |
其中,第一个参数 prototype 是创建的对象所对应的 prototype ,相当于使用 function 创建时 function 中的 prototype 属性对象,创建出来的 object 对象实例可以直接调用 。 第二个参数 propertiesObject 为属性描述对象,是可选参数,用于描述所创建对象的自身属性 。 属性描述对象是 object 类型对象,它里面的属性名会成为所创建对象的属性名,属性值为属性的描述对象,其中包含属性的特性(属性的特性我们后面会详细讲解,这里大家只要知道其中的 value 就是属性值就可以了) 。 我们来看下面的例子。
1 | var obj = Object.create( |
这个例子中,使用 Object.create 创建了一个 obj 对象,第一个参数 prototype 中有一个属性 type,第二个参数 propertiesObject 有两个属性: color 和 size 。对于创建的 obj 对象来说,这三个属性都可以使用,但其自身其实只有两个属性。虽然 obj 可以调用 prototype 中的 type 属性,但是并不属于 obj ,使用 Object. getOwnPropertyNames 方法就可以获取 obj 自己所拥有的属性。
另外,需要注意的一点是,使用花括号和 function 创建的对象都可以调用 Object 的 prototype 属性对象中的属性。即这两种方法创建出来的对象首先会拥有公共的 prototype,然后使用 function 创建的对象还可以调用 function 的 prototype 中的属性,而且即使 function 的 prototype 为 null ,创建的对象也可以调用 Object 的 prototype 中的属性。但是,使用 Object. create 创建对象时,如果第一个参数为 null ,那么它所创建的对象将不可以调用 Object 的 prototype 中的属性。我们来看下面的例子。
1 | //使用花括号创建对象 braceObj |
在这个例子中, toString 方法是 Object 的 prototype 属性中的一个方法,如果创建出来的对象可以调用 toString 方法,就说明可以调用 Object 的 prototype 属性对象中的方法,否则就是不可以调用。从这个例子中可以看出,使用花括号和 function 创建的对象都可以调用 tostring 方法,而且即使将 function 的 prototype 设置为 null ,创建出来的对象也可以调用,但使用 Object.create 创建出来的对象,如果其 prototype 参数为 null 就不可以调用了。
Object的prototype属性对象里有什么
2 对象的属性
对象是通过其属性来发挥作用的,因此对象的属性是对象的核心 。
2.1 三种属性类型
ES 中对象的属性其实有三种类型,前面使用的属性只是其中的一种 。 属性的三种类型分别为:命名数据属性( named data properties )、命名访问器属性( named accessor properties) 和内部属性( internal properties ) 。下面我们分别学习 。
命名数据属性Data Property
命名数据属性是我们平时使用最多的属性,由属性名和属性值组成。前面例子中所使用的都是这种属性。
命名访问器属性Accessor Property
命名访问器属性是使用 getter、 setter 或者其中之一来定义的属性。getter 和 setter 是对应的方法, setter 方法用于给属性赋值,而 getter 方法用于获取属性的值。如果只有 getter、 setter 其中之一,就只能进行单一的操作,例如,只有 getter 方法的属性就是只读属性,只有 setter 方法的属性就是只可以写入的属性。例如下面的例子。
1 | function log(msg) { |
在这个例子中,我们定义了一个 colorManager 对象。它有三个访问器属性: colorNum 、 accessColors 和 color 。其中, colorNum 表示可以使用的 color 的数量,只有 getter 方法是只读属性; accessColors 表示可以使用的 color 的数组,只有 setter 方法是只写属性; color 表示当前的 color 值,是可读写属性。当给相应的属性设置值的时候就会调用相应的 setter 方法,调用属性值的时候就会调用相应的 getter 方法。我们在各个访问器方法中除了修改(或读取) 属性值之外,还做了一些逻辑判断以及打印日志的相关工作。
从这个例子可以看到 getter 和 setter 方法本身并不可以保存属性的内容。通常另外定义一个以下画线开头的属性来保存访问器属性的值,而且为了方便,一般会将保存访问器属性值的属性的名字设置为访问器属性名前加下画线。例如,在上面例子中使用 _color 来保存 color 访问器属性的值,使用 _colorNum 来保存 colorNum 访问器属性的值。这并不是强制性的,保存值的属性的名称也可以用其他名称。但是,为了方便和代码容易理解最好还是按照这个规则来命名。
这里大家可以会有一个疑问,那就是在定义了保存访问器属性值的属性之后,如果直接操作这个属性,不就可以绕过访问器来操作其值了吗?例如,直接操作 _color 属性不就可以绕过访问器方法了吗?对于这个问题,下文会有解决办法。
内部属性
内部属性是对象的一种特殊属性 。 它没有自己的名字,当然也就不可以像前两种属性那样直接访问了 。 正是因为内部属性没有名字所以前面两种属性才叫作命名属性 。 内部属性使用两对方括号表示 。 例如, Extensible 表示 Extensible 内部属性 。
内部属性的作用是用来控制对象本身的行为。所有对象共有的内部属性共 12 个: Prototype、Class、Extensible、Get、GetOwnProperty、GetProperty、Put、CanPut、HasProperty、Delete、DefaultValue和 DefineOwnPrope 。
除了这 12 个之外,不同的对象可能还会有自己的内部属性例如 Function 类型对象的 Haslnstance 、 RegExp 类型对象的 Match 等。通用的 12 个内部属性中的前 3 个可用来指示对象本身的一些特性,后 9 个属性可对对象进行特定的操作。它们在进行相应的操作时会自动调用,这里就不详细介绍了。下面主要解释下前3个属性。
Prototype
Prototype 属性就是前面讲过的使用 function 创建对象时 function 中的 prototype 属性。在创建完的实例对象中,这个属性并不可以直接调用,但可以使用 Object 的 getPrototypeOf 方法来获取,例如下面的例子。
1 | function Car() {} |
这个例子中,使用 Car 新建了 car 对象, Car 的 prototype 属性对象中有一个 color 属性,这个对象就是 car 实例的 Prototype 属性,不能使用 car. prototype 获取,可使用 Object. getPrototypeOf( car )获取。在有些浏览器(例如 Firefox )中还可以使用 __proto__ 属性来获取,例如,这里使用 car.__proto__同样可以获取Prototype属性。但是,因为 __proto__ 属性不是通用属性,所以最好还是使用 Object 的 getPrototypeOf 方法来获取。
Class
Class 属性可用来区分不同对象的类型,不能直接访问, toString 方法默认会返回这个值。默认 Object. prototype. toString 方法返回的字符串是 [ object, Class ],即方括号里面两个值,第一个是固定的 object ,第二个是 Class,因此使用 toString 方法就可以获取对象的 Class 属性。但是,因为 ES 中的内置对象在 prototype 中重写了 toString 方法,所以内置对象的返回值可能不是这个形式。浏览器中的宿主对象并没有重写此方法,在浏览器中调用它们的 toString 方法可以获取 Class 的值,例如下面的例子。
1 | function log(msg) { console.log(msg); } |
我们自己创建的 object 类型对象默认都属于 Object 类型,因此,它们的 toString 方法默认都会返回[ object Object ] 。另外,内置对象因为重写了 toString 方法,所以不会返回这种结构的返回值,例如,字符串对象会返回字符串自身,数组会返回数组元素连接成的字符串等。对于这种情况,我们可以使用 Object. prototype. toString 的 apply 属性方法来调用 Object 原生的 toString 方法,这样就会得到 [object,Class ] 这样的结果,例如下面的例子。
1 | var str = "", |
Extensible
Extensible 属性用来标示对象是否可扩展,即是否可以给对象添加新的命名属性,默认为 true ,也就是可以扩展。我们可以使用 Object 的 preventExtensions 方法将一个对象的 Extensible 值变为 false ,这样就不可以扩展了。另外,可以使用 Object 的 isExtensible 方法来获取 Extensible 的值。我们来看个例子。
1 | var person = { |
这个例子中 ,我们定义了 person 对象,它的Extensible属性本来为 true ,我们可以给它添加命名属性 。当调用 preventExtensions 方法对其操作后Extensible属性就变为了 false ,这时就不能给它添加新的命名属性了 。 需要注意的是,一旦使用 preventExtensions 方法将Extensible的值设置为 false 后就无法改回 true 了 。
5 种创建属性的方式
本节主要指创建命名属性 。 对象的命名属性一共有 5 种创建方式 。
使用花括号创建
这种方式是在使用花括号创建对象时创建属性,例如下面的例子。
1 | var obj = { |
这个例子中,使用花括号创建了 obj 对象,其中包含直接量属性( v ) 、 function 对象属性( getV )以及访问器属性( name ) 。注意,在定义访问器属性的 getter 和 setter 方法时没有冒号 。
使用点操作符创建
当使用点操作符给一个对象的属性赋值时,如果对象存在此属性则会修改属性的值,否则会添加相应的属性并赋予对应的值,例如下面的例子。
1 | var person = {name :"张三"} |
这个例子中,首先使用花括号定义了 person 对象,其中包含 name 属性,当给它的 name 属性赋予新值时会改变其 name 属性的值,而当给 age 属性赋值时,由于 person 原来没有 age 属性,所以会先添加 age 属性,然后将其值设置为 88 。
在 function 中使用 this 创建属性其实也是这种添加属性方式的一种特殊用法。因为在 function 创建 object 类型对象时,其中的 this 就代表创建出来的对象,而且刚创建出来的对象是没有自定义的命名属性的,所以使用 this 和点操作符就可以将属性添加到创建的对象中,例如下面的例子。
1 | function Person() { |
在这个例子中,首先定义了 function 类型的 Person ,然后用其创建了 person 对象,创建完成后会自动调用 Person 方法体中的 this.name = "孙悟空"语句,这时,由于 this 所代表的 person 对象并没有 name 属性,所以会自动给它添加 name 属性,这也就是创建的 person 对象具有 name 属性的原因了。
Object 的 create 方法
我们在前面已经介绍过 Object 的 create 方法,它有两个参数,第一个参数中的属性为创建的对象的Prototype 属性,第二个参数为属性描述对象 。
Object 的 defineProperty 、defineProperties 方法
我们可以使用 Object 的 defineProperty 和 defineProperties 方法给对象添加属性。 defineProperty方法可添加单个属性, defineProperties 方法可以添加多个属性。 Object 的 defineProperty 方法一共有三个参数,第一个是要添加属性的对象,第二个是要添加属性的属性名,第三个是属性的描述。前两个参数都很简单,第三个我们会在后面详细讲解,先来看个例子。
1 | var obj = {}; |
在这个例子中,我们使用 defineProperty 方法给 obj 对象添加了 color 属性。 Object 的 defineProperties 方法可以创建多个属性,它有两个参数,第一个参数是要添加属性的对象,第二个参数是属性描述对象,和 create 方法中的第二个参数一样,例如下面的例子。
1 | var obj = {}; |
这个例子使用 Object 的 defineProperties 方法给 obj 对象添加了 name 和 color 两个属性 。 在这个例子中, 因为 name 属性的 writable 为 false ,所以 obj 的 name 属性是不可以修改的 。 当我们将其值修改为 peter 后, 打印出的还是原来的 lucy ,这说明修改并没有作用 。 而且, 使用 defineProperties 方 法添加属性时 writable 的默认值就是 false 。
通过 prototype 属性创建
使用 function 创建的 object 实例对象可以使用 function 对象的 prototype 属性对象中的属性,这一点我们在前面已经多次证实过。严格来说, function 对象的 prototype 中的属性并不会添加到创建的实例对象中,但创建的对象可以调用,这样就相当于可以将 prototype 中的属性添加到创建的对象中。因此,如果给 function 的 prototype 添加了属性,那么也就相当于给创建的对象添加了属性,而且在对象创建完成之后还可以再添加,例如下面的例子。
1 | function Shop(){} |
这个例子中,首先使用 Shop 创建了 shop 对象,然后给 Shop 的 prototype 添加了 type 属性,这时调用 shop.type 也可以获取属性值。在调用 shop. type 时,因为 shop 没有 type 属性, shop 就会实时到 Shop 的 prototype 中查找,而不是提前将 Shop 的 prototype 属性对象保存起来,所以创建完 shop 对象后再修改 Shop 的 prototype 属性,已修改的属性也可以被 shop 实例对象调用。这一点在前面已经介绍过。
属性的描述
属性的描述也可以称为属性的特性,类似于对象的内部属性,其主要作用就是描述属性自己的一些特征 。 它的表示方法和对象的内部属性一样,也使用两个方括号表示 。 对象的命名数据属性和命名访问器属性各有 4 个特性(没有内部属性),其中两个特性是命名数据属性和命名访问器属性所共有的 。 下面我们来分别学习 。
命名数据属性的 4 个特性
命名数据属性的 4 个特性分别为:Value Writable Enumerable Configurable。Value表示属性的值;Writable 表示属性值是否可以修改Enumerable 表示属性是否可枚举,如果为 false 则不会被 for-in 循环遍历到;Configurable 表示属性是否可以被删除和属性的特性(除 Value 外)是否可修改。
属性的特性可以使用 Object 的 getOwnPropertyDescriptor 方法查询。如果想修改,那么可以使用 Object 的 defineProperty 和 defineProperties 方法,这两个方法所操作的属性如果存在就会对其进行修改,否则就会创建。我们来看下面这个例子。
1 | function log(msg) { |
这个例子中,我们定义了 person 对象,然后使用花括号定义了 name 属性,并使用 defineProperty 方法定义了 age 属性。使用Object.getOwnPropertyDescriptor 可以看出,使用花括号定义的属性默认 Value Writable Enumerable Configurable 都为 true ,而使用 Object 的 defineProperty 方法定义的属性,如果没有明确声明,那么 Value Writable Enumerable Configurable 都默认为 false 。 Writable 为 false 时不能修改属性的值;Enumerable 为 false 时, for-in 循环遍历不到此属性,但是,使用 Object. getOwnPropertyNames 方法仍然可以获取;Configurable 属性为 false 时不能使用 defineProperty 方法修改属性的特性。
当 Writable 为 false 而 Configurable 为 true 时,我们还可以使用 defineProperty方法修改属性的值,但是 Configurable 为 false 的时候就不可以修改了,例如下面的例子。
1 | var obj = {}; |
这个例子中,使用 defineProperty 方法添加的 name 属性因为默认 Writable 为 false, 所以不能直接修改它的值,但是因为 Configurable 为 true ,所以可以使用 defineProperty 方法通过 Value 特性来修改。当我们使用 defineProperty方法将 Configurable 设置为 false 的时候,如果再使用defineProperty方法就会抛出异常。另外,当 Configurable 为 false 的时候,属性也不可以使用 delete 删除。
可以使用 propertyIsEnumerable 方法检查 enumerable 特性。因为这个方法是 Object. prototype 中的一个,所以一般对象都可以直接调用( create 创建的 prototype 为 null 的对象除外),例如下面的例子。
1 | var 圣人 = { |
从这个例子可以看出,使用花括号和点操作符创建的属性的 enumerable 特性默认为 true ,使用 defineProperty 方法创建的属性,如果没有明确声明,那么enumerable默认为 false 。其他两个属性Configurable和Writable的默认值也是这样的。另外,这个例子中的对象名和属性名都使用了中文,对于现在的浏览器来说一般都是支持的,而且现在很多 C++、 Java 编译器也支持中文变量名。但是,因为 JS 是直接将源代码发送到客户端的浏览器中运行的,而我们并不能保证所有客户端的浏览器都可以支持中文变量名,所以在 JS 中最好还是使用英文的变量名。如果是 C ++或者 Java 等编译型的语言就无所谓了,因为它们是将编译后的结果发给用户使用的。
命名访问器属性的 4 个特性
命名访问器属性因为没有值,所以没有 Value 特性,同时也就没有 Writable 特性,但它比命名数据属性多了 Get 和 Set 特性,它们分别代表访问器属性的 getter 和 setter 方法。因此,命名访问器属性也有 4 个特性:Get 、 Set 、Enumerable 和 Configurable 。其中,后两个特性和命名数据属性的含义是相同的,主要介绍它的前两个特性。下面看个例子。
1 | function log(msg) { |
在这个例子中,使用 Object 的 defineProperty 方法给 person 对象添加了 name 访问器属性,其值保存在 name 命名数据属性中,当我们获取 name 的值或者给 name 设置新值的时候就会调用相应的 getter 和 setter 方法。我们可以使用 Object 的 getOwnPropertyDescriptor 方法来获取 name 属性的所有特性。另外,我们也可以在 function 中使用 Object 的 defineProperty 方法给其创建的对象实例添加属性,这时只要将对象写为 this 即可,而且这种方式还可以使用 function 的内部变量。例如,我们将上个例子中的 person 对象改为由 function 类型的 Person 来创建。
1 | function log(msg) { |
这个例子就在 function 中使用 defineProperty 方法创建了名为 name 的访问器属性,并在其中定义了getter 和 setter ,即Get 和 Set 特性 。 在这个例子中,我们将它的值保存到 Person 的局部变量 name 中,这样就可以屏蔽通过实例对象直接调用访问器属性的值 。