对象的属性也有属性:

  • 可写属性指定属性的值是否可以设置。
  • 枚举属性指定属性名是否能由for/in循环返回。
  • 可配置属性指定属性是否可以删除和更改。

在ES5之前,自己代码创建的对象中的所有属性都是可写的、可枚举的和可配置的。每个对象都有三个相关的对象属性:

  • 对象的prototype是对另一个对象的引用,从这个对象继承属性。
  • 对象的class是区分对象类型的字符串。
  • 对象的可扩展标志(ES5)指定是否可以向对象添加新的属性。

我们使用以下术语来区分三大类JavaScript对象和两类属性:

  • 原生对象是由ECMAScript规范定义的对象。Array、function、Date和RegExp都是是原生对象。
  • 宿主对象是由嵌入JavaScript解释器的宿主环境(如web浏览器)定义的对象。在客户端JavaScript中表示web页面结构的HTMLElement对象就是宿主对象。
  • 用户定义对象是JavaScript代码执行时创建的任何对象。
  • 自有属性是对象本身定义的属性。
  • 继承属性是由原型对象定义的属性。

创建对象

对象字面值

对象字面值用逗号分隔的键值对。属性名是JavaScript标识符或字符串字面值(允许空字符串)。属性值是任何JavaScript表达式。

1
var o = {x:1, 'y':2};

在ES5中,保留字可以作为属性名使用,不需要用引号括起来。在ES3中保留字作为属性名必须用引号括起来。在ES5中,最后一个属性后面的逗号被忽略。在大多数ES3实现中,结尾逗号被忽略,但IE认为它们是错误的。

用new创建对象

new操作符创建并初始化一个新对象。其后面必须有一个构造函数,用于初始化新创建的对象。JavaScript包含原生类型的内置构造函数。

1
2
3
4
var o = new Object(); // Create an empty object: same as {}.
var a = new Array(); // Create an empty array: same as [].
var d = new Date(); // Create a Date object representing the current time
var r = new RegExp("js"); // Create a RegExp object for pattern matching.

原型

每个JavaScript对象都有一个与之关联的原型对象。JavaScript对象从原型对象继承属性。

由对象字面值创建的所有对象都有相同的原型对象,这个原型对象是Object.prototype。使用new关键字和构造函数创建的对象的原型是构造函数的prototype属性。因此,new object()创建的对象继承自Object.prototype。new Array()创建的对象使用Array.prototype作为其原型,由new Date()创建的对象使用 Date.prototype作为原型。

Object.prototype是少有的没有原型的对象,它不继承任何属性。其他原型对象是有原型的普通对象。所有内置的构造函数(以及大多数用户定义的构造函数)都继承自Object.prototype。这一系列链接的原型对象被称为原型链。

Object.create()

ES5定义了一个方法Object.create()用于创建对象。第一个参数指定对象的原型。第二个参数可选,用于描述新对象的属性。Object.create()是一个静态函数,而不是方法:

1
var o1 = Object.create({x:1, y:2}); // o1 inherits properties x and y.

您可以传递null来创建一个没有原型的新对象,该对象没有继承任何东西:

1
var o2 = Object.create(null); // o2 inherits no props or methods.

如果您想创建一个普通的空对象(比如{}返回的对象或new object()),Object.prototype:

1
var o3 = Object.create(Object.prototype); // o3 is like {} or new Object().

我们可以在ES3中用下面这个函数模拟它:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// inherit() returns a newly created object that inherits properties from the
// prototype object p. It uses the ECMAScript 5 function Object.create() if
// it is defined, and otherwise falls back to an older technique.
function inherit(p) {
    if (p == null) throw TypeError(); // p must be a non-null object
    if (Object.create) // If Object.create() is defined...
        return Object.create(p); // then just use it.
    var t = typeof p; // Otherwise do some more type checking
    if (t !== "object" && t !== "function") throw TypeError();
    function f() {}; // Define a dummy constructor function.
    f.prototype = p; // Set its prototype property to p.
    return new f(); // Use f() to create an "heir" of p.
}

inherit()函数的一种用法是当你想防止你无法控制的库函数无意修改对象时使用。不直接将对象传递给函数,而是传递一个继承对象。如果函数读取继承对象的属性,它将看到继承的值。如果它设置属性,这些属性只会影响继承对象,而不会影响原始对象:

1
2
var o = { x: "don't change this value" };
library_function(inherit(o)); // Guard against accidental modifications of o

查询和设置属性

使用.或[]获取属性。运算符左边是一个值为对象的表达式。如果使用点运算符,右边必须是一个表示属性名字的简单的标识符。如果使用方括号,方括号中的值必须是结果为字符串的表达式,该字符串包含所需的属性名:

1
2
3
var author = book.author; // Get the "author" property of the book.
var name = author.surname // Get the "surname" property of the author.
var title = book["main title"] // Get the "main title" property of the book.

要创建或设置属性,将.或[]表达式放在等号左边:

1
2
book.edition = 6; // Create an "edition" property of book.
book["main title"] = "ECMAScript"; // Set the "main title" property.

在ES3中,点操作符后面的标识符不能是保留字,ES5放宽了这个限制。

继承

JavaScript对象有一组“自己的属性”,它们也从原型对象继承一组属性:

1
2
3
4
5
6
7
8
var o = {} // o inherits object methods from Object.prototype
o.x = 1; // and has an own property x.
var p = inherit(o); // p inherits properties from o and Object.prototype
p.y = 2; // and has an own property y.
var q = inherit(p); // q inherits properties from p, o, and Object.prototype
q.z = 3; // and has an own property z.
var s = q.toString(); // toString is inherited from Object.prototype
q.x + q.y // => 3: x and y are inherited from o and p

假设你给对象o的属性x赋值。如果o已经有了一个自己的属性x,那么赋值只会改变这个现有属性的值。否则赋值将在对象o上创建一个名为x的新属性。如果o之前继承了属性x,那么这个继承的属性现在被新创建的同名属性所隐藏。

属性赋值检查原型链以确定是否允许赋值。如果o继承一个名为x的只读属性,则不允许赋值。如果允许赋值,则总是在原对象中创建或设置一个属性,并且从不修改原型链。继承发生在查询属性时,而不是设置属性时,这是JavaScript的一个关键特性,因为它允许我们有选择地覆盖继承属性:

1
2
3
4
5
var unitcircle = { r:1 }; // An object to inherit from
var c = inherit(unitcircle); // c inherits the property r
c.x = 1; c.y = 1; // c defines two properties of its own
c.r = 2; // c overrides its inherited property
unitcircle.r; // => 1: the prototype object is not affected

如果o继承了一个带有setter方法的访问属性x,则调用该setter方法,而不是在o中创建新的属性x。但是setter方法是在对象o上调用的,因此如果setter方法定义了任何属性,都作用于o上,不会修改原型链。

属性访问错误

查询不存在的属性不是错误。如果属性x不是o的自有属性或继承属性,则o.x值为undefined。null和undefined没有属性,查询或者设置它们的属性抛出TypeError。

1
2
3
4
5
6
7
// A verbose and explicit technique
var len = undefined;
if (book) {
    if (book.subtitle) len = book.subtitle.length;
}
// A concise and idiomatic alternative to get subtitle length or undefined
var len = book && book.subtitle && book.subtitle.length;

设置属性不总是成功的:有些属性是只读的,不能设置,有些对象不允许添加新属性。这些操作通常是悄悄失败的:

1
2
// The prototype properties of built-in constructors are read-only.
Object.prototype = 0; // Assignment fails silently; Object.prototype unchanged

在ES5严格模式下,任何设置属性失败都会抛出TypeError。设置对象o的属性p在以下情况失败:

  • o有一个只读属性p,不能设置只读属性。
  • o有只读的继承属性p,不能使用同名的属性隐藏继承的只读属性。
  • o没有自有属性p,o也没有通过setter方法继承属性p,而且o的可扩展属性为false。

删除属性

delete从对象中删除属性,它的操作数是一个属性访问表达式。delete并不操作属性的值,而是操作属性本身:

1
2
delete book.author; // The book object now has no author property.
delete book["main title"]; // Now it doesn't have "main title", either.

delete操作符只删除自己的属性,而不删除继承的属性。要删除继承的属性,必须从定义它的原型对象中删除它。这样做会影响从原型继承的每个对象。delete删除成功或删除没有效果时,返回true:

1
2
3
4
5
o = {x:1}; // o has own property x and inherits property toString
delete o.x; // Delete x, and return true
delete o.x; // Do nothing (x doesn't exist), and return true
delete o.toString; // Do nothing (toString isn't an own property), return true
delete 1; // Nonsense, but evaluates to true

delete不会删除不可配置的属性,但是它可以删除不可扩展对象的可配置属性。内置对象的某些属性是不可配置的,通过变量声明和函数声明创建的全局对象的属性也是不可配置的。在严格模式下,删除不可配置属性会导致TypeError。在非严格模式下,delete返回false:

1
2
3
4
5
delete Object.prototype; // Can't delete; property is non-configurable
var x = 1; // Declare a global variable
delete this.x; // Can't delete this property
function f() {} // Declare a global function
delete this.f; // Can't delete this property either

在非严格模式下删除全局对象的可配置属性时,可以省略this:

1
2
this.x = 1; // Create a configurable global property (no var)
delete x; // And delete it

在严格模式下,如果delete的操作数是一个非限定的标识符,那么就会引发SyntaxError:

1
2
delete x; // SyntaxError in strict mode
delete this.x; // This works

测试属性

in操作符左边是一个属性名,右边是一个对象。如果对象这个名字的自有属性或继承的属性,则返回true:

1
2
3
4
var o = { x: 1 }
"x" in o; // true: o has an own property "x"
"y" in o; // false: o doesn't have a property "y"
"toString" in o; // true: o inherits a toString property

对象的hasOwnProperty()方法测试该对象是否具有具有给定名称的自己的属性。对于继承的属性,返回false:

1

propertyIsEnumerable()只有当指定的属性是自己的属性且是可枚举的时,它才返回true:

1
2
3
4
5
var o = inherit({y: 2});
o.x = 1;
o.propertyIsEnumerable("x"); // true: o has an own enumerable property x
o.propertyIsEnumerable("y"); // false: y is inherited, not own
Object.prototype.propertyIsEnumerable("toString"); // false: not enumerable
通常查询属性只需简单使用!==来确保它不是undefined:

1
2
3
4
5
var o = { x: 1 }
o.x !== undefined; // true: o has a property x

o.y !== undefined; // false: o doesn't have a property y
o.toString !== undefined; // true: o inherits a toString property

有一件事是in操作符可以做的,而上面所示的简单属性访问技术做不到的。in可以区分不存在的属性和设置为undefined 的属性:

1
2
3
4
5
6
7
var o = { x: undefined } // Property is explicitly set to undefined
o.x !== undefined // false: property exists but is undefined
o.y !== undefined // false: property doesn't even exist
"x" in o // true: the property exists
"y" in o // false: the property doesn't exists
delete o.x; // Delete the property x
"x" in o // false: it doesn't exist anymore

枚举属性

通常使用for/in循环来枚举属性。对象继承来的内置方法是不可枚举的,但是自己代码添加的属性是可枚举的:

1
2
3
4
var o = {x:1, y:2, z:3}; // Three enumerable own properties
o.propertyIsEnumerable("toString") // => false: not enumerable
for(p in o) // Loop through the properties
    console.log(p); // Prints x, y, and z, but not toString

一些库向会添加新方法或属性到Object.prototype,以便所有对象可以继承和使用用。在ES5之前,没有办法使这些属性不可枚举,因此for/in循环可以枚举它们。为了防止这种情况发生,您可能需要过滤for/in返回的属性:

1
2
3
4
5
6
7
for(p in o) {
    if (!o.hasOwnProperty(p)) continue; // Skip inherited properties
}

for(p in o) {
    if (typeof o[p] === "function") continue; // Skip methods
}

下面定义了几个实用函数:

 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*
* Copy the enumerable properties of p to o, and return o.
* If o and p have a property by the same name, o's property is overwritten.
* This function does not handle getters and setters or copy attributes.
*/
function extend(o, p) {
    for(prop in p) { // For all props in p.
        o[prop] = p[prop]; // Add the property to o.
    }
    return o;
}

/*
* Copy the enumerable properties of p to o, and return o.
* If o and p have a property by the same name, o's property is left alone.
* This function does not handle getters and setters or copy attributes.
*/
function merge(o, p) {
    for(prop in p) { // For all props in p.
        if (o.hasOwnProperty[prop]) continue; // Except those already in o.
        o[prop] = p[prop]; // Add the property to o.
    }
    return o;
}

/*
* Remove properties from o if there is not a property with the same name in p.
* Return o.
*/
function restrict(o, p) {
    for(prop in o) { // For all props in o
        if (!(prop in p)) delete o[prop]; // Delete if not in p
    }
    return o;
}

/*
* For each property of p, delete the property with the same name from o.
* Return o.
*/
function subtract(o, p) {
    for(prop in p) { // For all props in p
        delete o[prop]; // Delete from o (deleting a nonexistent prop is harmless)
    }
    return o;
}

/*
* Return a new object that holds the properties of both o and p.
* If o and p have properties by the same name, the values from o are used.
*/
function union(o,p) { return extend(extend({},o), p); }

/*
* Return a new object that holds only the properties of o that also appear
* in p. This is something like the intersection of o and p, but the values of
* the properties in p are discarded
*/
function intersection(o,p) { return restrict(extend({}, o), p); }

/*
* Return an array that holds the names of the enumerable own properties of o.
*/
function keys(o) {
    if (typeof o !== "object") throw TypeError(); // Object argument required
    var result = []; // The array we will return
    for(var prop in o) { // For all enumerable properties
        if (o.hasOwnProperty(prop)) // If it is an own property
            result.push(prop); // add it to the array.
    }
    return result; // Return the array.
}

ES5定义了两个新函数来枚举属性名。第一个是Object.keys(),它返回一个对象的可枚举属性名数组。第二个是Object.getOwnPropertyNames(),它返回指定对象的所有自身属性名。

属性getter和setter

在ES5中,对象值可以被getter和setter方法替换。getter和setter定义的属性有时称为访问器属性,以区分数据属性。

当查询访问器属性时,JavaScript调用getter方法,返回值即属性值。当设置访问器属性时,JavaScript调用setter方法,传递赋值右侧的值,setter方法的返回值被忽略。

访问器属性不像数据属性那样具有可写属性。如果属性同时具有getter和setter方法,那么它就是可读/可写属性。如果它只有getter方法,它就是只读属性。如果它只有一个setter方法,那么它就是一个只写的属性,这对于数据属性不可能实现的,并且读取它总是返回undefined。

定义访问器属性的最简单方法是使用对象字面值语法:

1
2
3
4
5
6
7
var o = {
    // An ordinary data property
    data_prop: value,
    // An accessor property defined as a pair of functions
    get accessor_prop() { /* function body here */ },
    set accessor_prop(value) { /* function body here */ }
};

访问器属性定义为一个或两个名称与属性名称相同的函数,并将function替换为get或set。没有使用冒号将属性的名称与访问该属性的函数分隔开,但是在函数体之后仍然需要使用逗号与下一个方法或数据属性分隔开。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
var p = {
    // x and y are regular read-write data properties.
    x: 1.0,
    y: 1.0,
    // r is a read-write accessor property with getter and setter.
    // Don't forget to put a comma after accessor methods.
    get r() { return Math.sqrt(this.x * this.x + this.y * this.y); },
    set r(newvalue) {
        var oldvalue = Math.sqrt(this.x * this.x + this.y * this.y);
        var ratio = newvalue/oldvalue;
        this.x *= ratio;
        this.y *= ratio;
    },
    // theta is a read-only accessor property with getter only.
    get theta() { return Math.atan2(this.y, this.x); }
};

JavaScript将getter和setter函数作为定义它们的对象的方法,这意味着这些函数可以引用对象的属性。访问器属性是可以继承的,就像数据属性一样:

1
2
3
4
var q = inherit(p); // Create a new object that inherits getters and setters
q.x = 0, q.y = 0; // Create q's own data properties
console.log(q.r); // And use the inherited accessor properties
console.log(q.theta);

使用访问器属性的另一个原因是对属性写入进行完整性检查,并且可以在读取的属性返回不同的值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// This object generates strictly increasing serial numbers
var serialnum = {
    // This data property holds the next serial number.
    // The $ in the property name hints that it is a private property.
    $n: 0,
    // Return the current value and increment it
    get next() { return this.$n++; },
    // Set a new value of n, but only if it is larger than current
    set next(n) {
        if (n >= this.$n) this.$n = n;
        else throw "serial number can only be set to a larger value";
    }
};

最后,这里还有一个使用getter方法实现具有“神奇”行为的属性的示例:

1
2
3
4
5
6
7
8
// This object has accessor properties that return random numbers.
// The expression "random.octet", for example, yields a random number
// between 0 and 255 each time it is evaluated.
var random = {
    get octet() { return Math.floor(Math.random() * 256); },
    get uint16() { return Math.floor(Math.random() * 65536); },
    get int16() { return Math.floor(Math.random()*65536)-32768; }
};

属性的属性

数据属性的四个属性是值、可写、可枚举和可配置。访问器属性没有值属性和可写属性,它们是否可写取决于setter存在与否。因此访问器属性的四个属性是get、set、枚举和可配置。

ES5查询和设置属性的方法使用一个称为属性描述符的对象来表示四个属性的集合。使用Object.getOwnPropertyDescriptor()获得指定对象的属性描述符:

1
2
3
4
5
6
7
8
// Returns {value: 1, writable:true, enumerable:true, configurable:true}
Object.getOwnPropertyDescriptor({x:1}, "x");
// Now query the octet property of the random object defined above.
// Returns { get: /func/, set:undefined, enumerable:true, configurable:true}
Object.getOwnPropertyDescriptor(random, "octet");
// Returns undefined for inherited properties and properties that don't exist.
Object.getOwnPropertyDescriptor({}, "x"); // undefined, no such prop
Object.getOwnPropertyDescriptor({}, "toString"); // undefined, inherited

getownpropertydescriptor()只适用于自己的属性。要查询继承属性的属性,必须显式地遍历原型链。

object.defineproperty()用于设置指定对象属性:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var o = {}; // Start with no properties at all
// Add a nonenumerable data property x with value 1.
Object.defineProperty(o, "x", { value : 1,
    writable: true,
    enumerable: false,
    configurable: true});
// Check that the property is there but is nonenumerable
o.x; // => 1
Object.keys(o) // => []
// Now modify the property x so that it is read-only
Object.defineProperty(o, "x", { writable: false });
// Try to change the value of the property
o.x = 2; // Fails silently or throws TypeError in strict mode
o.x // => 1
// The property is still configurable, so we can change its value like this:
Object.defineProperty(o, "x", { value: 2 });
o.x // => 2
// Now change x from a data property to an accessor property
Object.defineProperty(o, "x", { get: function() { return 0; } });
o.x // => 0

传递给Object.defineProperty()的属性描述符不需要包含所有四个属性。如果创建一个新属性,那么省略的属性值被设为false或undefined。如果修改现有属性,则忽略的属性将保持不变。Object.defineProperty()只能用于创建或修改自有属性,不会修改继承的属性。

如果要创建或修改多个属性,使用Object.defineProperties()。第一个参数是要修改的对象,第二个参数是一个对象,它将要创建或修改的属性名映射到相应的属性描述符:

1
2
3
4
5
6
7
8
9
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.xthis.x + this.ythis.y) },
        enumerable:true,
        configurable:true
    }
});

Object.create方法的第一个参数是新创建对象的原型对象,它的第二个可选参数是与Object.defineProperties()的第二个参数相同的对象。如果您将一组属性描述符传递给object.create(),那么它们将用于向新创建的对象添加属性。

当不允许创建或修改属性时,Object.defineProperty()和Object.defineProperties()会抛出TypeError:

  • 如果对象不可扩展,可以编辑现有的属性,但不能向其添加新属性。
  • 如果属性不可配置,则无法更改其可配置或可枚举属性。
  • 如果访问器属性不可配置,则不能更改其getter或setter方法,也不能将其更改为数据属性。
  • 如果数据属性不可配置,则不能将其更改为访问器属性。
  • 如果数据属性不可配置,则不能将其可写属性从false更改为true,但可以将其从true更改为false。
  • 如果数据属性不可配置且不可写入,则无法更改其值。但是您可以更改可配置但不可写入的属性的值。
 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
26
27
/*
* Add a nonenumerable extend() method to Object.prototype.
* This method extends the object on which it is called by copying properties
* from the object passed as its argument. All property attributes are
* copied, not just the property value. All own properties (even non-
* enumerable ones) of the argument object are copied unless a property
* with the same name already exists in the target object.
*/
Object.defineProperty(Object.prototype, "extend", // Define Object.prototype.extend
    {
        writable: true,
        enumerable: false, // Make it nonenumerable
        configurable: true,
        value: function(o) { // Its value is this function
            // Get all own props, even nonenumerable ones
            var names = Object.getOwnPropertyNames(o);
            // Loop through them
            for(var i = 0; i < names.length; i++) {
                // Skip props already in this object
                if (names[i] in this) continue;
                // Get property description from o
                var desc = Object.getOwnPropertyDescriptor(o,names[i]);
                // Use it to create property on this
                Object.defineProperty(this, names[i], desc);
            }
        }
    });

getter和setter的遗留API

__lookupGetter__()__lookupSetter__()返回指定属性的getter或setter方法。__defineGetter__()__defineSetter__()定义getter或setter方法,先传递属性名,然后传递getter或setter方法。这些方法是非标准方法。

对象属性

每个对象都有相关的原型、类和可扩展属性。

原型属性

对象的原型属性指定它从哪个对象继承属性。原型属性是在创建对象时设置的。用new创建的对象使用其构造函数的原型属性的值作为原型。使用Object.create()创建的对象使用该函数的第一个参数(可能为空)作为原型。

在ES5中,可以使用Object.getPrototypeOf()来查询任何对象的原型。ES3中没有等价的函数,但是通常可以使用o.constructor.prototype来确定对象o的原型。

要确定一个对象是另一个对象的原型或其原型链的一部分,使用isPrototypeOf()方法:

1
2
3
4
var p = {x:1}; // Define a prototype object.
var o = Object.create(p); // Create an object with that prototype.
p.isPrototypeOf(o) // => true: o inherits from p
Object.prototype.isPrototypeOf(o) // => true: p inherits from Object.prototype

Mozilla的JavaScript实现通过特殊命名的__proto__属性公开了原型属性,您可以使用该属性直接查询或设置任何对象的原型。

类属性

对象的类属性是一个字符串,它提供关于对象类型的信息。ES3和ES5都没有提供任何方法来设置这个属性,并且只有一种间接的技术来查询它。默认的toString()方法返回这种形式的字符串:

1
[object class]

因此要获得对象的类,可以在其上调用toString()方法,并通过返回的字符提取。棘手的是,许多对象继承了其他更有用的toString()方法,为了调用正确的toString()版本,我们必须使用Function.call()方法间接地这样做:

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

数组和Date等对象具有与构造函数名称一样的类属性。宿主对象通常也具有有意义的类属性。通过对象字面值或Object.create创建的对象类属性为"Object”。自己定义的构造函数,创建的任何对象类属性都是"Object”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
classof(null) // => "Null"
classof(1) // => "Number"
classof("") // => "String"
classof(false) // => "Boolean"
classof({}) // => "Object"
classof([]) // => "Array"
classof(/./) // => "Regexp"
classof(new Date()) // => "Date"
classof(window) // => "Window" (a client-side host object)
function f() {}; // Define a custom constructor
classof(new f()); // => "Object"

可扩展属性

对象的可扩展属性指定是否可以向对象添加新属性。所有内置和用户定义的对象都是隐式可扩展的,宿主对象的可扩展性是由实现定义的。但在ES5中,可以将它们被转换为不可扩展的。

ES5定义了查询和设置对象可扩展性的函数。Object.isExtensible()确定对象是否可扩展。Object.preventExtensions()使对象不可扩展,一旦使对象不可扩展,就无法使其再次可扩展。preventExtensions()只会影响对象本身的可扩展性,如果将新属性添加到不可扩展对象的原型中,该对象将继承这些新属性。

可扩展属性的目的是能够将对象“锁定”为已知状态,并防止外部篡改。可扩展对象属性通常与可配置和可写的属性属性一起使用,ES5定义了一些函数,可以很容易地设置这些属性。

Object.seal()除了使对象不可扩展之外,它还使该对象的所有属性都不可配置。这意味着不能向对象添加新属性,也不能删除或配置现有属性。但是仍然可以设置可写的现有属性。没有办法解封这个对象。可以使用Object.isSealed()来确定一个对象是否被密封。

Object.freeze()将对象锁得更紧。除了使对象不可扩展和其属性不可配置之外,它还使对象的所有数据属性都是只读的。(如果对象具有setter方法的访问器属性,这些属性不会受到影响)使用Object.isFrozen()来确定一个对象是否被冻结。

Object.seal() 和Object.freeze()只影响传递给它们的对象,对它们对原型对象没有影响。如果您想彻底锁定一个对象,您可能还需要密封或冻结原型链中的对象。

Object.preventExtensions() ,Object.seal() 和Object.freeze()都返回传递给它们的对象:

1
2
3
// Create a sealed object with a frozen prototype and a nonenumerable property
var o = Object.seal(Object.create(Object.freeze({x:1}),
    {y: {value: 2, writable: true}}));

序列化对象

ES5提供JSON.stringify()和JSON.parse()来序列化和反序列化JavaScript对象:

1
2
3
o = {x:1, y:{z:[false,null,""]}}; // Define a test object
s = JSON.stringify(o); // s is '{"x":1,"y":{"z":[false,null,""]}}'
p = JSON.parse(s); // p is a deep copy of o

JSON语法是JavaScript语法的一个子集,它不能表示所有JavaScript值。对象、数组、字符串、有限数字、true、false和null可以序列化和还原。NaN、 Infinity和-Infinity序列化为null。日期对象被序列化为ISO格式的日期字符串,但是JSON.parse()将这些字符串保留为字符串形式。函数、RegExp和Error对象以及undefined无法序列化或恢复。JSON.stringify()只序列化对象的可枚举属性。如果一个属性值不能被序列化,则忽略这个属性。JSON.stringify()和JSON.parse()都接受可选的第二个参数,具体参考文档。

对象方法

toString()

toString()方法返回一个字符串,该字符串以某种方式表示对象的值。当需要将对象转换为字符串时,JavaScript将调用对象的此方法。由于这个默认方法没有太多有用的信息,许多类都定义了自己的toString()版本。

toLocaleString()

除了toString()方法外,对象都有一个toLocaleString()。此方法的目的是返回对象的本地化字符串表示。Object定义的默认toLocaleString()方法本身不做任何本地化,它只调用toString()。Date和Number类定义了toLocaleString()的定制版本,这些版本试图根据本地约定格式化数字、日期和时间。Array定义了一个toLocaleString()方法,它的工作原理与toString()类似,只是它通过调用数组元素的toLocaleString()方法。

toJSON()

Object.prototype没有定义toJSON()方法,但是JSON.stringify()方法寻找toJSON()序列化对象。如果该方法存在,则调用该方法,而不是序列化原始对象。

valueOf()

valueOf()方法与toString()方法非常相似,但是当JavaScript需要将对象转换为字符串以外的基本类型时调用它。默认的valueOf()方法没有什么有趣的地方,但是一些内置类定义了它们自己的valueOf()方法。