对象简述(了解)

  对象是 JavaScript 的基本数据类型。对象是一种复合值:它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。对象也可看做是属性的无序集合,每个属性都是一个名/值对。属性名是字符串,因此我们可以把对象看成是从字符串到值的映射。这种基本数据结构还有很多种叫法,有些我们已然非常熟悉,比如“散列”(hash)、 “散列表”(hashtable)、“字典”(dictionary)、“关联数组”(associative array)。
  然而对象不仅仅是字符串到值的映射,除了可以保持自有的属性,JavaScript 对象还可以从一个称为原型的对象继承属性。对象的方法通常是继承的属性。这种“原型式继承”(prototypal inheritance)是 JavaScript 的核心特征。
  属性包括名字和值。属性名可以是包含空字符串在内的任意字符串,但对象中不能存在两个同名的属性。值可以是任意 JavaScript 值,或者(在 ECMAScript5 中)可以是一个 getter 或 setter 函数(或两者都有)。
  除了名字和值之外,每个属性还有一些与之相关的值,称为“属性特性”(property attribute):

  • 可写(writable attribute),表明是否可以设置该属性的值。
  • 可枚举(enumerable attribute),表明是否可以通过 for/in 循环返回该属性。
  • 可配置(configurable attribute),表明是否可以删除或修改该属性。

  在 ECMAScript5 之前,通过代码给对象创建的所有属性都是可写的、可枚举的和可配置的。在 ECMAScript5 中则可以对这些特性加以配置。
  除了包含属性之外,每个对象还拥有三个相关的对象特性(object attribute):

  • 对象的原型(prototype)指向另外一个对象,本对象的属性继承自它的原型对象。
  • 对象的类(class)是一个标识对象类型的字符串。
  • 对象的扩展标记(extensible flag)指明了(在 ECMAScript5 中)是否可以向该对象添加新属性。

  最后,我们用下面这些术语来对三类 JavaScript 对象和两类属性作区分:

  • 内置对象(native object)是由 ECMAScript 规范定义的对象或类。例如,数组、函数、日期和正则表达式都是内置对象。
  • 宿主对象(host object)是由 JavaScript 解释器所嵌入的宿主环境(比如 Web 浏览器)定义的。客户端 JavaScript 中表示网页结构的 HTMLElement 对象均是宿主对象。既然宿主环境定义的方法可以当成普通的 JavaScript 函数对象,那么宿主对象也可以当成内置对象。
  • 自定义对象(user-defined object)是由运行中的 JavaScript 代码创建的对象。
  • 自有属性(own property)是直接在对象中定义的属性。
  • 继承属性(inherited property)是在对象的原型对象中定义的属性。

创建对象(掌握)

对象直接量

  对象直接量是由若干名/值对组成的映射表,名/值对中间用冒号分隔,名/值对之间用逗号分隔,整个映射表用花括号括起来。属性名可以是 JavaScript 标识符也可以是字符串直接量(包括空字符串)。属性的值可以是任意类型的 JavaScript 表达式。

var empty={}//没有任何属性的对象
var point={x:0,y:0}//两个属性
var pointz={x:point.x,y:point.y+1}//更复杂的值
var book={
    "main title""Javascript"//属性名字里有空格,必须用字符串表示
    'sub-title'"The Definitive Guide"//属性名字里有连字符,必须用字符串表示
    "for""all audiences"//"for"是保留字,因此必须用引号
    author:{
        firstname:"David"//注意,这里的属性名都没有引号
        surname:"Flanagan"
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13

  在 ECMAScript5(以及 ECMAScript3 的一些实现)中,保留字可以用做不带引号的属性名。然而对于 ECMAScript3 来说,使用保留字作为属性名必须使用引号引起来。
  在 ECMAScript5 中,对象直接量中的最后一个属性后的逗号将忽略,且在 ECMAScript3 的大部分实现中也可以忽略这个逗号,但在 IE 中则报错。
  对象直接量是一个表达式,这个表达式的每次运算都创建并初始化一个新的对象。每次计算对象直接量的时候,也都会计算它的每个属性的值。也就是说,如果在一个重复调用的函数中的循环体内使用了对象直接量,它将创建很多新对象,并且每次创建的对象的属性值也有可能不同。

通过 new 创建对象

  new 运算符创建并初始化一个新对象。关键字 new 后跟随一个函数调用。这里的函数称做构造函数(constructor),构造函数用以初始化一个新创建的对象。JavaScript 语言核心中的原始类型都包含内置构造函数。

var o=new Object();//创建一个空对象,和{}一样
var a= new Array();//创建一个空数组,和[]一样
var d=new Date();//创建一个表示当前时间的Date对象
var r=new RegExp"js");//创建一个可以进行模式匹配的EegExp对象
1
2
3
4

Object.create()

原型

  在讲述第三种对象创建技术之前,我们应当首先解释一下原型。每一个 JavaScript 对象(nu11 除外)都和另一个对象相关联。“另一个”对象就是我们熟知的原型,每一个对象都从原型继承属性。
  所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过 JavaScript 代码 object.prototype 获得对原型对象的引用。
  通过关键字 new 和构造函数调用创建的对象的原型就是构造函数的 prototype 属性的值。因此,同使用{}创建对象一样,通过 new Object()创建的对象也继承自 object.prototype。同样,通过 new Array()创建的对象的原型就是 Array.prototype,通过 new Date()创建的对象的原型就是 Date.prototype。
  没有原型的对象为数不多,Object.prototype 就是其中之一。它不继承任何属性。其他原型对象都是普通对象,普通对象都具有原型。
  所有的内置构造函数(以及大部分自定义的构造函数)都具有一个继承自 object.prototype 的原型。例如,Date.prototype 的属性继承自 Object.prototype,因此由 new Date()创建的 Date 对象的属性同时继承自 Date.prototype 和 Object.prototype。这一系列链接的原型对象就是所谓的“原型链”(prototype chain)。

Object.create()

  ECMAScript 5 定义了一个名为 Object.create()的方法,它创建一个新对象,其中第一个参数是这个对象的原型。Object.create()提供第二个可选参数,用以对对象的属性进行进一步描述。后面会详细讲述第二个参数。
  Object.create()是一个静态函数,而不是提供给某个对象调用的方法。使用它的方法很简单,只须传入所需的原型对象即可:

var o1=Object.create({x:1,y:2});//o1继承了属性x和y
1

  可以通过传入参数 nu11 来创建一个没有原型的新对象,但通过这种方式创建的对象不会继承任何东西,甚至不包括基础方法,比如 toString(),也就是说,它将不能和“+” 运算符一起正常工作。
  如果想创建一个普通的空对象(比如通过{}或 new 0bject()创建的对象),需要传入 Object.prototype:

var o3=Object.create(Object.prototype);//o3和{}和new Object()一样
1

  通过原型继承创建一个新对象

//1inherit()返回了一个继承自原型对象p的属性的新对象
//这里使用ECMAScript 5中的Object.create()函数(如果存在的话)
//如果不存在Object.create(),则退化使用其他方法
function inherit(p){
    if(p==nullthrow TypeError();//p是一个对象,但不能是null
    if(Object.create)//如果0bject.create()存在
        return Object.create(p);//直接使用它
    var t=typeof p;//否则进行进一步检测
    if(t l=="object"&&t!=="function"throw TypeError();
    function f(){}//定义一个空构造函数
    f.prototype=p;//将其原型属性设置为p
    return new f();/使用f()创建p的继承对象
}
1
2
3
4
5
6
7
8
9
10
11
12
13

  注意,inherit()并不能完全代替 0bject.create(),它不能通过传入 nul1 原型来创建对象,而且不能接收可选的第二个参数。
  inherit()函数的其中一个用途就是防止库函数无意间(非恶意地)修改那些不受你控制的对象。当函数读取继承对象的属性时,实际上读取的是继承来的值。如果给继承对象的属性赋值,则这些属性只会影响这个继承对象自身,而不是原始对象:

属性的查询和设置(掌握)

  可以通过点(.)或方括号([])运算符来获取属性的值,当使用方括号时,我们说方括号内的表达式必须返回字符串。其实更严格地讲,表达式必须返回字符串或返回一个可以转换为字符串的值。

作为关联数组的对象.

object.property;
object['property'];
1
2

  使用方括号和一个字符串,看起来更像数组,只是这个数组元素是通过字符串索引而不是数字索引。这种数组就是我们所说的关联数组(associative array),也称做散列、映射或字典(dictionary)。JavaScript 对象都是关联数组。

继承

  JavaScript 对象具有“自有属性”(own property),也有一些属性是从原型对象继承而来的。假设要查询对象 o 的属性 x,如果 o 中不存在 x,那么将会继续在 o 的原型对象中查询属性 x。如果原型对象中也没有 x,但这个原型对象也有原型,那么继续在这个原型对象的原型上执行查询,直到找到 x 或者查找到一个原型是 nul1 的对象为止。可以看到,对象的原型属性构成了一个“链”,通过这个“链”可以实现属性的继承。
  在 JavaScript 中,只有在查询属性时才会体会到继承的存在,而设置属性则和继承无关,这是 JavaScript 的一个重要特性,该特性让程序员可以有选择地覆盖(override)继承的属性。
  属性赋值要么失败,要么创建一个属性,要么在原始对象中设置属性,但有一个例外,如果 o 继承自属性 x,而这个属性是一个具有 setter 方法的 accessor 属性,那么这时将调用 setter 方法而不是给 o 创建一个属性 x。需要注意的是,setter 方法是由对象 o 调用的,而不是定义这个属性的原型对象调用的。因此如果 setter 方法定义任意属性,这个操作只是针对 o 本身,并不会修改原型链。

属性访问错误

  查询一个不存在的属性并不会报错,如果在对象 0 自身的属性或继承的属性中均未找到属性 x,属性访问表达式 o.x 返回 undefined。但是,如果对象不存在,那么试图查询这个不存在的对象的属性就会报错。nu11 和 undefined 值都没有属性,因此查询这些值的属性会报错。
  当然,给 nu11 和 undefined 设置属性也会报类型错误。给其他值设置属性也不总是成功,有一些属性是只读的,不能重新赋值,有一些对象不允许新增属性,但让人颇感意外的是,这些设置属性的失败操作不会报错:这是一个历史遗留问题,这个 bug 在 ECMAScript5 的严格模式中已经修复。在严格模式中,任何失败的属性设置操作都会抛出一个类型错误异常。

//内置构造函数的原型是只读的
Object.prototype=o;//赋值失败,但没报错,Object.prototype没有修改
1
2

删除属性(掌握)

  delete 运算符可以删除对象的属性。它的操作数应当是一个属性访问表达式。让人感到意外的是,delete 只是断开属性和宿主对象的联系,而不会去操作属性中的属性:
  a={p:{x:1}};b=a.p;delete a.p;执行这段代码之后 b.x 的值依然是 1。由于已经删除的属性的引用依然存在,因此在 JavaScript 的某些实现中,可能因为这种不严谨的代码而造成内存泄漏。所以在销毁对象的时候,要遍历属性中的属性,依次删除。
  delete 运算符只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象)。
  当 delete 表达式删除成功或没有任何副作用(比如删除不存在的属性)时,它返回 true。如果 delete 后不是一个属性访问表达式,delete 同样返回 true:

0={x:1}//o有一个属性x,并继承属性tostring
delete o.x;//删除x,返回true
delete o.x;//什么都没做(x已经不存在了),返回true
delete o.toString;//什么也没做(tostring是继承来的),返回true
delete 1//无意义,返回true
1
2
3
4
5

  delete 不能删除那些可配置性为 fa1se 的属性(尽管可以删除不可扩展对象的可配置属性)。某些内置对象的属性是不可配置的,比如通过变量声明和函数声明创建的全局对象的属性。在严格模式中,删除一个不可配置属性会报一个类型错误。在非严格模式中(以及 ECMAScript3 中),在这些情况下的 delete 操作会返回 false:

delete object.prototype;//不能删除,属性是不可配置的
Var x=1//声明一个全局变量
delete this.x;//不能删除这个属性
function f(){}//声明一个全局函数
delete this.f;//也不能删除全局函数
1
2
3
4
5

  在严格模式中,delete 后跟随一个非法的操作数(比如 x),则会报一个语法错误,因此必须显式指定对象及其属性。

this.x=1//创建一个可配置的全局属性(没有用var)
delete x;//将它删除,在严格模式下报错
1
2

检测属性(掌握)

  JavaScript 对象可以看做属性的集合,我们经常会检测集合中成员的所属关系——判断某个属性是否存在于某个对象中。可以通过 in 运算符、hasOwnPreperty()和 propertyIsEnumerable()方法来完成这个工作,甚至仅通过属性查询也可以做到这一点。

in 运算符

  in 运算符的左侧是属性名(字符串),右侧是对象。如果对象的自有属性或继承属性中包含这个属性则返回 true。

var o={x:1}
"x" in o;//true:"x“是o的属性
"y" in o;//false:"y"不是o的属性
"tostring" in o;//true:o继承toString属性
1
2
3
4

hasOwnPreperty()

  对象的 hasownProperty()方法用来检测给定的名字是否是对象的自有属性。对于继承属性它将返回 false:

var o={x:1}
o.hasownProperty(“x");//true:o有一个自有属性x
o.hasownProperty("y");//false:o中不存在属性y
o.hasownProperty("tostring");//false:toString是继承属性
1
2
3
4

propertyIsEnumerable()

  propertyIsEnumerable()是 hasOwnProperty()的增强版,只有检测到是自有属性且这个属性的可枚举性(enumerable attribute)为 true 时它才返回 true。某些内置属性是不可枚举的。通常由 JavaScript 代码创建的属性都是可枚举的,除非在 ECMAScript5 中使用一个特殊的方法来改变属性的可枚举性,随后会提到:

var o=inherit({y:2});
o.x=1;
o.propertyIsEnumerable("x");//true:o有一个可枚举的自有属性x
o.propertyIsEnumerable("y");//false:y是继承来的
object.prototype.propertyIsEnumerable("toString");//false:不可枚举
1
2
3
4
5

  除了使用 in 运算符之外,另一种更简便的方法是使用“l==”判断一个属性是否是 undefined:

var o={x:1}
o.x !==undefined//true:o中有属性x
o.y !==a undefined//false:o中没有属性y
o.toString !==undefined//true:o继承了toString属性
```js
  然而有一种场景只能使用in运算符而不能使用上述属性访问的方式。in可以区分不存在的属性和存在但值为undefined的属性。
```js
var o={x:undefined}//属性被显式赋值为undefined
o.x!==undefined//false:属性存在,但值为undefined o.
y !== undefined//false:属性不存在
"x” in o//true:属性存在
"y" in o//false:属性不存在
delete o.x;//删除了属性x
“x” in o//false:属性不再存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14

枚举属性(掌握)

for/in 循环

  for/in 循环可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承的属性),把属性名称赋值给循环变量。对象继承的内置方法不可枚举的,但在代码中给对象添加的属性都是可枚举的(除非用下文中提到的一个方法将它们转换为不可枚举的)。

var o={x:1,y:2,z:3}//三个可枚举的自有属性
o.propertyIsEnumerable("toString"//=>false,不可枚举
for(p in o)//遍历属性
console.log(p);//输出x、y和z,不会输出tostring
1
2
3
4

  有许多实用工具库给 0bject.prototype 添加了新的方法或属性,这些方法和属性可以被所有对象继承并使用。然而在 ECMAScript 5 标准之前,这些新添加的方法是不能定义为不可枚举的,因此它们都可以在 for/in 循环中枚举出来。为了避免这种情况,需要过滤 for/in 循环返回的属性,下面两种方式是最常见的:

for(p in o){
    if(lo.hasownProperty(p))continue//跳过继承的属性
}
for(p in o){
    iftypeof o[p]==="function"continue//跳过方法
}
1
2
3
4
5
6

Object.keys()

  除了 for/in 循环之外,ECMAScript5 定义了两个用以枚举属性名称的函数。第一个是 0bject.keys(),它返回一个数组,这个数组由对象中可枚举的自有属性的名称组成。

Object.getownPropertyNames()

  ECMAScript 5 中第二个枚举属性的函数是 0bject.getownPropertyNames(),它和 0jbect.keys()类似,只是它返回对象的所有自有属性的名称,而不仅仅是可枚举的属性。在 ECMAScript3 中是无法实现的类似的函数的,因为 ECMAScript3 中没有提供任何方法来获取对象不可枚举的属性。

属性 getter 和 setter(掌握)

  在 ECMAScript 5 中,属性值可以用一个或两个方法替代,这两个方法就是 getter 和 setter。由 getter 和 setter 定义的属性称做“存取器属性”(accessor property),它不同于“数据属性”(data property),数据属性只有一个简单的值。
  当程序查询存取器属性的值时,JavaScript 调用 getter 方法(无参数)。这个方法的返回值就是属性存取表达式的值。
  当程序设置一个存取器属性的值时,JavaScript 调用 setter 方法,将赋值表达式右侧的值当做参数传入 setter。从某种意义上讲,这个方法负责“设置”属性值。可以忽略 setter 方法的返回值。
  和数据属性不同,存取器属性不具有可写性(writable attribute)。如果属性同时具有 getter 和 setter 方法,那么它是一个读/写属性。如果它只有 getter 方法,那么它是一个只读属性。如果它只有 setter 方法,那么它是一个只写属性(数据属性中有一些例外),读取只写属性总是返回 undefined。

var o={
    //普通的数据属性
    data_prop:value,//存取器属性都是成对定义的函数
    get accessor_prop(){/*这里是函数体*/}set accessor_prop(value){/*这里是函数体*/}
}
1
2
3
4
5
6

  存取器属性定义为一个或两个和属性同名的函数,这个函数定义没有使用 function 关键字,而是使用 get 和(或)set。注意,这里没有使用冒号将属性名和函数体分隔开,但在函数体的结束和下一个方法或数据属性之间有逗号分隔。和数据属性一样,存取器属性是可以继承的。

属性的特性(掌握)

  除了包含名字和值之外,属性还包含一些标识它们可写、可枚举和可配置的特性。在 ECMAScript3 中无法设置这些特性,所有通过 ECMAScript3 的程序创建的属性都是可写的、可枚举的和可配置的,且无法对这些特性做修改。
  可以认为一个属性包含一个名字和 4 个特性。
  数据属性的 4 个特性分别是它的值(value)、可写性(writable)、可枚举性(enumerable)和可配置性(configurable)。
  存取器属性不具有值(value)特性和可写性,它们的可写性是由 setter 方法存在与否决定的。因此存取器属性的 4 个特性是读取(get)、写入(set)、可枚举性和可配置性。   为了实现属性特性的查询和设置操作,ECMAScript5 中定义了一个名为“属性描述符”(property descriptor)的对象,这个对象代表那 4 个特性。
  数据属性的描述符对象的属性有 value、writable、enumerable 和 configurable。存取器属性的描述符对象则用 get 属性和 set 属性代替 value 和 writable。
  其中 writable、enumerable 和 configurable 都是布尔值,当然,get 属性和 set 属性是函数值。

Object.getOwnPropertyDescriptor()

读取

  通过调用 0bject.getownPropertyDescriptor()可以获得某个对象特定属性的属性描述符:

//返回{value:1,writable:true,enumerable:true,configurable:true}
Object.getownPropertyDescriptor({x:1}"x");
//返回{get:/*func*/,set:undefned,enumerable:true,configurable:true}
Object.getownPropertyDescriptor(random,"octet");
//对于继承属性和不存在的属性,返回undefined
Object.getownPropertyDescriptor({}"x");//undefined,没有这个属性
Object.getownPropertyDescriptor({}"tostring");//undefined,继承属性
1
2
3
4
5
6
7

  从函数名字就可以看出,Object.getownPropertyDescriptor()只能得到自有属性的描述符。要想获得继承属性的特性,需要遍历原型链(Object.getPrototypeof())。

设置

Object.defineProperty()

  要想设置属性的特性,或者想让新建属性具有某种特性,则需要调用 Object.definePeoperty(),传入要修改的对象、要创建或修改的属性的名称以及属性描述符对象:

var o={}//创建一个空对象
//添加一个不可枚举的数据属性x,并赋值为1
Object.defineProperty(o,"x"{value:1,writable:true,enumerable:false,configurable:true});
//属性是存在的,但不可枚举
o.x;//=>1
Object.keys(o)//=>[]
//现在对属性x做修改,让它变为只读
Object.defineproperty(o,"x"{writable:false});
//试图更改这个属性的值
o.x=2//操作失败但不报错,而在严格模式中抛出类型错误异常
o.x//=>1
//属性依然是可配置的,因此可以通过这种方式对它进行修改:
Object.defineproperty(o,"x"{value:2});
o.x//=>2
//现在将x从数据属性修改为存取器属性
Object.defneProperty(o,"x"{get:function(){return o;}});o.x//=>0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

  传入 object.defineProperty()的属性描述符对象不必包含所有 4 个特性。对于新创建的属性来说,默认的特性值是 false 或 undefined。对于修改的已有属性来说,默认的特性值没有做任何修改。注意,这个方法要么修改已有属性要么新建自有属性,但不能修改继承属性。

Object.defineProperties()

  如果要同时修改或创建多个属性,则需要使用 0bject.defineProperties()。第一个参数是要修改的对象,第二个参数是一个映射表,它包含要新建或修改的属性的名称,以及它们的属性描述符,例如:

var p=Object. defineProperties({),{
    x:{ value:1, writable: true, enumerable: true, configurable: true},
    y:{ value:1, writable: true, enumerable: true, configurable: true}, r:{
    get: function(){ return Math. sqrt(this.x* this.x+ this.y* this.y)},
    enumerable: true,
    configurable: true
    }
));
1
2
3
4
5
6
7
8

  对于那些不允许创建或修改的属性来说,如果用 Object.defineProperty()和 Object.defineProperties()对其操作(新建或修改)就会抛出类型错误异常,比如,给一个不可扩展的对象新增属性就会抛出类型错误异常。
  下面是完整的规则,任何对 0bject.defineProperty()或 Object.defineProperties()违反规则的使用都会抛出类型错误异常:

  • 如果对象是不可扩展的,则可以编辑已有的自有属性,但不能给它添加新属性。
  • 如果属性是不可配置的,则不能修改它的可配置性和可枚举性。
  • 如果存取器属性是不可配置的,则不能修改其 getter 和 setter 方法,也不能将它转换为数据属性。
  • 如果数据属性是不可配置的,则不能将它转换为存取器属性。
  • 如果数据属性是不可配置的,则不能将它的可写性从 false 修改为 true,但可以从 true 修改为 false。
  • 如果数据属性是不可配置且不可写的,则不能修改它的值。然而可配置但不可写属性的值是可以修改的(实际上是先将它标记为可写的,然后修改它的值,最后转换为不可写的)。

  复制属性的特性

/*
*给Object.prototype添加一个不可枚举的extend()方法
*这个方法继承自调用它的对象,将作为参数传入的对象的属性一一复制
*除了值之外,也复制属性的所有特性,除非在目标对象中存在同名的属性,
*参数对象的所有自有对象(包括不可枚举的属性)也会一一复制。
*/
Object.defineProperty(object.prototype,
    “extend",定义Object.prototype.extend
    {
        writable:true,enumerable:false//将其定义为不可枚举的
        configurable:truevalue:function(o){//值就是这个函数
            //得到所有的自有属性,包括不可枚举属性
            var names=Object.getownPropertyNames(o);
            //遍历它们
            forvar i=o;i<names.length;i++{
            //如果属性已经存在,则跳过
                if(names[i]in thiscontinue//获得0中的属性的描述符
                var desc=Object.getOwnPropertyDescriptor(o,names[i]);
                //用它给this创建一个属性
                Object.defineProperty(this,names[i],desc);
            }
        }
    });
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

getter 和 setter 的老式 API

  可以通过对象直接量语法给新对象定义存取器属性,但不能查询属性的 getter 和 setter 方法或给已有的对象添加新的存取器属性。在 ECMAScript5 中,可以通过 Object.getOwnPropertyDescriptor()和 0bject.defineproperty()来完成这些工作。
  在 ECMAScript5 标准被采纳之前,大多数 JavaScript 的实现(IE 浏览器除外)已经可以支持对象直接量语法中的 get 和 set 写法。这些实现提供了非标准的老式 API 用来查询和设置 getter 和 setter。这些 API 由 4 个方法组成,所有对象都拥有这些方法。 _lookupGetter_()和_lookupSetter_()用以返回一个命名属性的 getter 和 setter 方法。defineGetter()和 defineSetter()用以定义 getter 和 setter,这两个函数的第一个参数是属性名字,第二个参数是 getter 和 setter 方法。这 4 个方法都是以两条下划线作前缀,两条下划线作后缀,以表明它们是非标准的方法。

对象的三个属性(掌握)

原型属性

  对象的原型属性是用来继承属性的,这个属性如此重要,以至于我们经常把“o 的原型属性”直接叫做“o 的原型”。
  原型属性是在实例对象创建之初就设置好的,通过对象直接量创建的对象使用 Object.prototype 作为它们的原型。通过 new 创建的对象使用构造函数的 prototype 属性作为它们的原型。通过 Object.create()创建的对象使用第一个参数(也可以是 nul1)作为它们的原型。
  在 ECMAScript5 中,将对象作为参数传入 Object.getPrototypeOf()可以查询它的原型。在 ECMAScript3 中,则没有与之等价的函数,但经常使用表达式 o.constructor.prototype 来检测一个对象的原型。通过 new 表达式创建的对象,通常继承一个 constructor 属性,这个属性指代创建这个对象的构造函数。
  后面还解释了使用这种方法来检测对象原型的方式并不可靠的原因。注意,通过对象直接量或 Object.create()创建的对象包含一个名为 constructor 的属性,这个属性指代 Object()构造函数。因此,constructor.prototype 才是对象直接量的真正的原型,但对于通过 Object.create()创建的对象则往往不是这样。
  要想检测一个对象是否是另一个对象的原型(或处于原型链中),请使用 isPrototypeof()方法。例如,可以通过 p.isPrototypeOf(o)来检测 p 是否是 o 的原型,isPrototypeOf()函数实现的功能和 instanceof 运算符非常类似:

var p={x:1}/定义一个原型对象
var o=Object.create(p);//使用这个原型创建一个对象
p.isPrototype0f(o)//=>true:o继承自p
Object.prototype.isPrototypeOf(o)//=>true:p继承自object.prototype
1
2
3
4

  Mozilla 实现的 JavaScript(包括早些年的 Netscape)对外暴露了一个专门命名为proto的属性,用以直接查询/设置对象的原型。但并不推荐使用proto,因为尽管 Safari 和 Chrome 的当前版本都支持它,但 IE 和 Opera 还未实现它(可能以后也不会实现)。实现了 ECMAScript 5 的 Firefox 版本依然支持_proto,但对修改不可扩展对象的原型做了限制。

类属性

  对象的类属性(class attribute)是一个字符串,用以表示对象的类型信息。ECMAScript 3 和 ECMAScript5 都未提供设置这个属性的方法,并只有一种间接的方法可以查询它。 默认的 toString()方法(继承自 Object.prototype)返回了如下这种格式的字符串:

[object class]
1

  因此,要想获得对象的类,可以调用对象的 tostring()方法,然后提取已返回字符串的第 8 个到倒数第二个位置之间的字符。不过让人感觉棘手的是,很多对象继承的 tostring()方法重写了,为了能调用正确的 tostring()版本,必须间接地调用 Function.ca11()方法。
  classof()函数可以返回传递给它的任意对象的类:

function classof(o) {
  if (o === null) return 'Null';
  if (o === undefined) return 'Undefined';
  return Object.prototype.toString.cal1(o).slice(8, -1);
}
1
2
3
4
5

  通过内置构造函数(比如 Array 和 Date)创建的对象包含“类属性”(class atribute),它与构造函数名称相匹配。宿主对象也包含有意义的“类属性”,但这和具体的 JavaScript 实现有关。通过对象直接量和 Object.create 创建的对象的类属性是“Object”,那些自定义构造函数创建的对象也是一样,类属性也是“Object”,因此对于自定义的类来说,没办法通过类属性来区分对象的类:

classof(nul1)//=>"Null"
classof(1//=>"Number"
c1assof(""//=>"String"
classof(false//=>"Boolean"
classof({}//=>"Object"
classof([]//=>"Array"
classof(/.///=>"Regexp"
classof(new Date())//=>"Date".
classof(window)//=>"Window”(这是客户端宿主对象)
function f(){}//定义一个自定义构造函数
classof(new f());//=>"Object"
1
2
3
4
5
6
7
8
9
10
11

可扩展性

  对象的可扩展性用以表示是否可以给对象添加新属性。所有内置对象和自定义对象都是显式可扩展的,宿主对象的可扩展性是由 JavaScript 引擎定义的。除非将它们转换为不可扩展的。
  ECMAScript5 定义了用来查询和设置对象可扩展性的函数。通过将对象传入 Object.esExtensible(),来判断该对象是否是可扩展的。如果想将对象转换为不可扩展的,需要调用 Object.preventExtensions(),将待转换的对象作为参数传进去。注意,一旦将对象转换为不可扩展的,就无法再将其转换回可扩展的了。同样需要注意的是,preventExtensions()只影响到对象本身的可扩展性。如果给一个不可扩展的对象的原型添加属性,这个不可扩展的对象同样会继承这些新属性。
  可扩展属性的目的是将对象“锁定”,以避免外界的干扰。对象的可扩展性通常和属性的可配值性与可写性配合使用。
  Object.seal()和 Object.preventExtensions()类似,除了能够将对象设置为不可扩展的,还可以将对象的所有自有属性都设置为不可配置的。也就是说,不能给这个对象添加新属性,而且它已有的属性也不能删除或配置,不过它已有的可写属性依然可以设置。对于那些已经封闭(sealed)起来的对象是不能解封的。可以使用 Object.isSealed()来检测对象是否封闭。
  Object.freeze()将更严格地锁定对象——“冻结”(frozen)。除了将对象设置为不可扩展的和将其属性设置为不可配置的之外,还可以将它自有的所有数据属性设置为只读(如果对象的存取器属性具有 setter 方法,存取器属性将不受影响,仍可以通过给属性赋值调用它们)。使用 Object.isFrozen()来检测对象是否冻结。
  Object.preventExtensions()、Object.seal()和 Object.freeze()都返回传入的对象,也就是说,可以通过函数嵌套的方式调用它们:

//创建一个封闭对象,包括一个冻结的原型和一个不可枚举的属性
var o=object.seal(Object.create(Object.freeze({x:1}),
                        {y:{value:2,writable:true}}));
1
2
3

序列化对象(掌握)

  对象序列化(serialization)是指将对象的状态转换为字符串,也可将字符串还原为对象。ECMAScript 5 提供了内置函数]S0N.stringify()和]S0N.parse()用来序列化和还原 JavaScript 对象。这些方法都使用 JSON 作为数据交换格式,JSON 的全称是“JavaScript Object Notation”——JavaScript 对象表示法,它的语法和 JavaScript 对象与数组直接量的语法非常相近:

o={x:1,y:{z:[falsenull,“]]}}//定义一个测试对象
s=JS0N.stringify(o);//s是{"x":1,"y":{"z":[false,mull,""]}}
p=JS0N.parse(s);//p是o的深拷贝
1
2
3

  NaN、Infinity 和-Infinity 序列化的结果是 null,日期对象序列化的结果是 ISO 格式的日期字符串(参照 Date.toJSON()函数),但 JSON.parse()依然保留它们的字符串形态,而不会将它们还原为原始日期对象。函数、RegExp、Error 对象和 undefined 值不能序列化和还原。JSON.stringify()只能序列化对象可枚举的自有属性。对于一个不能序列化的属性来说,在序列化后的输出字符串中会将这个属性省略掉。
  JSON.stringify()和 JSON.parse()都可以接收第二个可选参数,通过传入需要序列化或还原的属性列表来定制自定义的序列化或还原操作。

对象方法(掌握)

toString()方法

  toString()方法没有参数,它将返回一个表示调用这个方法的对象值的字符串。默认的 toString()方法的返回值带有的信息量很少,例如,下面这行代码的计算结果为字符串“[object Object]”:

var s={x:1,y:1}.tostring();
1

  由于默认的 toString()方法并不会输出很多有用的信息,因此很多类都带有自定义的 toString()。例如,当数组转换为字符串的时候,结果是一个数组元素列表,只是每个元素都转换成了字符串,再比如,当函数转换为字符串的时候,得到函数的源代码。

toLocaleString()方法

  除了基本的 toString()方法之外,对象都包含 tolocaleString()方法,这个方法返回一个表示这个对象的本地化字符串。0bject 中默认的 toLocaleString()方法并不做任何本地化自身的操作,它仅调用 toString()方法并返回对应值。Date 和 Number 类对 toLocaleString()方法做了定制,可以用它对数字、日期和时间做本地化的转换。Array 类的 toLocalestring()方法和 toString()方法很像,唯一的不同是每个数组元素会调用 tolocalestring()方法转换为字符串,而不是调用各自的 toString()方法。

toJSON()方法

  Object.prototype 实际上没有定义 toJSON()方法,但对于需要执行序列化的对象来说,JSON.stringify()方法会调用 toJSON()方法。如果在待序列化的对象中存在这个方法,则调用它,返回值即是序列化的结果,而不是原始的对象。具体示例参见 Date.toJSON()。

valueOf()方法

  valueOf()方法和 toString()方法非常类似,但往往当 JavaScript 需要将对象转换为某种原始值而非字符串的时候才会调用它,尤其是转换为数字的时候。如果在需要使用原始值的上下文中使用了对象,JavaScript 就会自动调用这个方法。默认的 valueOf()方法不足为奇,但有些内置类自定义了 valueOf()方法(比如 Date.valueOf())。

TOC