JavaScript对象是属性的无序集合,每个属性都有一个名称和对应的值。属性名称通常是字符串,因此我们可以说对象映射字符串到值。这种字符串到值的映射有各种各样的名称——类如众所熟知的以“hash”、“hashtable”、“dictionary”或“associative array”等为名的基本数据结构。

创建对象

我们可以使用对象字面量、new关键字和Object.create()函数来创建对象。

对象字面量

创建对象最简单方法是在JavaScript代码中包含对象字面量。对象字面量是定义在花括号内、用逗号分隔的name:value对。

let empty = {};                          // 定义一个空对象
let point = { x: 0, y: 0 };              // 有两个属性的对象
let p2 = { x: point.x, y: point.y+1 };   // 稍复杂的属性值
let book = {
    "main title": "JavaScript",          // 属性名可以包含空格,
    "sub-title": "The Definitive Guide", // 也可以在字符串之间使用连接线.
    for: "all audiences",                // for 是一个关键字,可以不用引号.
    author: {                            // 属性值本身也是一个对象
        firstname: "JX",
        surname: "DN"
    }
};

用new创建对象

我们可以通过new运算符创建并初始化一个新对象。

let o = new Object();  // 创建一个空对象,等效与{}.
let a = new Array();   // 创建一个空数组,等效与[].
let d = new Date();    // 根据当前时间创建日期对象
let r = new Map();     // 创建一个Map对象,以键值对的方式存储

原型

几乎每个JavaScript对象都有与之关联的第二个JavaScript对象。第二个对象称为原型,第一个对象从原型继承属性。

所有由对象字面量创建的对象都有相同的原型对象,在JavaScript代码中我们可以将这个原型对象称为Object.prototype

Object.create()

Object.create()创建新的对象,其第一个参数作为该对象的原型。

const person = {
  isHuman: false,
  printIntroduction: function() {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};

const me = Object.create(person);

me.name = 'Matthew'; // "name"是对象"me"的属性, 而不是对象"person"的属性
me.isHuman = true; // 继承下来的属性是可以被重写的

me.printIntroduction();
// 输出: "My name is Matthew. Am I human? true"

> ### 删除属性

delete操作符用于删除对象中的属性。单个操作数应该是一个属性访问表达式。

delete book.author;          // 将book对象中的author属性移除.
delete book["main title"];   // 现在属性"main title"也被移除了.

测试属性

JavaScript对象可以当作是一组属性,并且能够测试集合中的成员资格——检查对象是否具有给定名称的属性。

序列化对象

对象序列化是将对象的状态转换为字符串的过程,且之后还可以恢复。JSON.stringify()函数和JSON.parse()函数序列化和恢复JavaScript对象。

对象方法

toString()方法

toString()方法不带参数;返回一个以某种方式表示调用对象的值的字符串。每当需要将对象转换为字符串时,JavaScript会调用对象的此方法。

例如,当你使用+运算符连接字符串与对象或当你将对象传递给需要字符串的方法时,就会发生这种情况:

let s = { x: 1, y: 1 }.toString();  // s == "[object Object]"

因为这个默认方法并没有显示太多有用的信息,所以很多类都定义了自己的toString()版本。例如,当你将数组转换为字符串时,会获得数组元素的列表,这些元素每个都转换为了字符串,当你将函数转换为字符串时,会获得函数的源代码。你可以像这样定义自己的toString()方法:

let point = {
    x: 1,
    y: 2,
    toString: function() { return `(${this.x}, ${this.y})`; }
};
String(point)    // => "(1, 2)": toString() is used for string conversions

除了基本的toString()方法外,对象还有toLocaleString()方法。这个方法的目的是返回对象的本地化字符串。Object定义的默认toLocaleString()方法本身不进行任何本地化:只调用toString()并返回值。

let point = {
    x: 1000,
    y: 2000,
    toString: function() { return `(${this.x}, ${this.y})`; },
    toLocaleString: function() {
        return `(${this.x.toLocaleString()}, ${this.y.toLocaleString()})`;
    }
};
point.toString()        // => "(1000, 2000)"
point.toLocaleString()  // => "(1,000, 2,000)": note thousands separators

valueOf()方法

valueOf()方法很像toString()方法,但是当JavaScript需要将对象转换为字符串以外的一些基本类型(通常是数字)时,会调用valueOf()方法。如果在需要原始值的上下文中使用对象,JavaScript也会自动调用此方法。

let point = {
    x: 3,
    y: 4,
    valueOf: function() { return Math.hypot(this.x, this.y); }
};
Number(point)  // => 5: valueOf() is used for conversions to numbers
point > 4      // => true
point > 5      // => false
point < 6      // => true

toJSON()方法

Object.prototype实际上并没有定义toJSON()方法,但JSON.stringify()方法会在要求序列化的对象上查找toJSON()方法。如果要序列化的对象上存在此方法,则调用方法,并序列化返回值,而不是原始对象。

let point = {
    x: 1,
    y: 2,
    toString: function() { return `(${this.x}, ${this.y})`; },
    toJSON: function() { return this.toString(); }
};
JSON.stringify([point])   // => '["(1, 2)"]'

扩展对象字面量语法

简写属性

假设你有存储在变量x和y中的值,然后想要创建一个对象,这个对象有名为x和y的属性来保存这些值。

let x = 1, y = 2;
let o = {
    x: x,
    y: y
};

在ES6及之后的版本中,你可以删除冒号和标识符的副本,从而得到更简单的代码:

let x = 1, y = 2;
let o = { x, y };
o.x + o.y  // => 3v

计算属性名称

有时你需要创建具有特定属性的对象,且特定属性的名称不是可以在源代码中键入的编译时常量。相反你所需要的属性名称存储在变量中,或者是你调用的函数的返回值。

const PROPERTY_NAME = "p1";
function computePropertyName() { return "p" + 2; }

let o = {};
o[PROPERTY_NAME] = 1;
o[computePropertyName()] = 2;

使用称为计算属性的ES6特性来设置这样的对象则要简单得多,因为它允许你从前面的代码中取出方括号并直接移动到对象字面量中:

const PROPERTY_NAME = "p1";
function computePropertyName() { return "p" + 2; }

let p = {
    [PROPERTY_NAME]: 1,
    [computePropertyName()]: 2
};

p.p1 + p.p2 // => 3

符号作为属性名称

计算属性语法支持另一个非常重要的对象字面量特性。在ES6及之后的版本中,属性名称可以是字符串或符号。如果将符号分配给变量或常量,则可以使用计算属性语法将该符号用作属性名称:

const extension = Symbol("my extension symbol");
let o = {
    [extension]: { /* extension data stored in this object */ }
};
o[extension].x = 0; // This won't conflict with other properties of o

扩展运算符

你可以使用扩展运算符...将现有对象的属性复制到新对象中:

let position = { x: 0, y: 0 };
let dimensions = { width: 100, height: 75 };
let rect = { ...position, ...dimensions };
rect.x + rect.y + rect.width + rect.height // => 175

如果扩展的对象和要扩展到的对象都具有同名的属性,那前面对象的属性值就会被后面那个覆盖:

let o = { x: 1 };
let p = { x: 0, ...o };
p.x   // => 1: the value from object o overrides the initial value
let q = { ...o, x: 2 };
q.x   // => 2: the value 2 overrides the previous value from o.

简写方法

当函数被定义为对象的属性,调用该函数时,可以使用函数定义表达式在对象字面量中定义方法,就像定义对象的其他属性一样:

let square = {
    area: function() { return this.side * this.side; },
    side: 10
};
square.area() // => 100

对象字面量语法已扩展为允许使用省略函数关键字和冒号的快捷方式,最终生成的代码如下:

let square = {
    area() { return this.side * this.side; },
    side: 10
};
square.area() // => 100
文章目录