JavaScript

JavaScript 知识量:26 - 101 - 483

7.1 理解对象><

对象- 7.1.1 -

JavaScript中的对象是一种数据结构,用于存储键值对集合。对象可以包含多个属性,每个属性都有一个名称(或键)和一个值。这些值可以是任何JavaScript数据类型,包括其他对象。

在JavaScript中,对象可以通过字面量语法创建,如下所示:

const person = {  
  name: 'John',  
  age: 30,  
  city: 'New York'  
};

在这个示例中,创建了一个名为person的对象,它有三个属性:name、age和city。每个属性都有一个名称和一个对应的值。

还可以使用new关键字和构造函数来创建对象,如下所示:

const person = new Object();  
person.name = 'John';  
person.age = 30;  
person.city = 'New York';

这个示例与之前的示例达到相同的效果,但是使用了不同的语法。

JavaScript对象提供了很多用于操作对象属性的方法和语法,包括点符号(.)和方括号([])语法来访问和设置属性,以及Object.keys()、Object.values()和Object.entries()等方法来获取对象的键、值或键值对数组。

属性的类型- 7.1.2 -

ECMA-262使用一些内部特性来描述属性的特征。这些特性是由为JavaScript实现引擎的规范定义的。因此,开发者不能在JavaScript中直接访问这些特性。为了将某个特性标识为内部特性,规范会用两个中括号把特性的名称括起来,比如[[Enumerable]]。

JavaScript对象的属性可以分为两种类型:数据属性和访问器属性。

数据属性:数据属性包含一个数据值、一个可写性标志和一个可枚举性标志。数据属性有四个描述其行为的特性:

  • [[Value]]:包含这个属性的数据值。默认值为undefined。

  • [[Writable]]:如果为true,可以修改属性的值。默认值为false。

  • [[Enumerable]]:如果为true,可以通过for-in循环返回属性。默认值为false。

  • [[Configurable]]:如果为true,可以修改属性的类型并删除属性。默认值为false。

访问器属性:访问器属性不包含数据值,它们包含一对getter和setter函数(但不是必须的)。在读取访问器属性时,会调用getter函数,这个函数负责返回有效的值;在写入访问器属性时,会调用setter函数并传入新值,这个函数负责决定如何处理数据。访问器属性有以下四个特性:

  • [[Get]]:在读取属性时调用的函数。默认值为undefined。

  • [[Set]]:在写入属性时调用的函数。默认值为undefined。

  • [[Enumerable]]:如果为true,可以通过for-in循环返回属性。默认值为false。

  • [[Configurable]]:如果为true,可以修改属性的类型并删除属性。默认值为false。

可以使用Object.defineProperty()方法来定义数据属性和访问器属性,并修改它们的特性。

定义多个属性- 7.1.3 -

Object.defineProperties()方法用于在一个对象上定义多个属性,每个属性都由一个属性描述符描述。这个方法接受两个参数:要添加和修改属性的对象和一个对象,该对象的属性名和属性值就是将要添加和修改的属性。

以下是一个示例:

const obj = {};  
  
Object.defineProperties(obj, {  
  property1: {  
    value: true,  
    writable: true  
  },  
  property2: {  
    value: "Hello",  
    writable: false  
  },  
  property3: {  
    get: function() { return this._property3; },  
    set: function(value) { this._property3 = value; }  
  }  
});

在这个示例中,Object.defineProperties()方法在obj对象上定义了三个属性:property1、property2和property3。每个属性都有相应的值、可写性和访问器函数。

此外,还可以使用对象字面量语法定义多个属性。可以通过在大括号内定义属性和值的方式来创建一个包含多个属性的对象。例如:

const person = {  
  name: "John",  
  age: 30,  
  city: "New York"  
};

在这个示例中,定义了一个名为person的对象,它包含了name、age和city三个属性。

读取属性的特性- 7.1.4 -

可以使用特殊的属性描述符(或特性)来控制属性的行为。这些特性包括:

  • [[Value]]:这个特性包含了属性的实际值。在读取属性时,会从这个特性中读取属性的值。

  • [[Writable]]:这个特性决定了属性值是否可以被修改。如果这个特性为false,那么尝试修改属性的值将无效。

  • [[Enumerable]]:这个特性决定了属性是否可以通过for...in循环返回。如果这个特性为false,那么在for...in循环中,属性将不会被返回。

  • [[Configurable]]:这个特性决定了属性描述对象是否可以被重新配置,也就是说,是否可以修改这个特性的默认值。如果这个特性为false,那么尝试修改属性的特性(如修改[[Writable]]或[[Enumerable]])将会失败。

需要注意的是,每个特性都是一个对象,它们可以通过Object.getOwnPropertyDescriptor()方法获取。例如,可以使用以下代码读取和打印一个对象的属性特性:

let obj = {  
  value: 123,  
  writable: false,  
  enumerable: true,  
  configurable: true  
};  
  
let prop = Object.getOwnPropertyDescriptor(obj, 'value');  
  
console.log(prop.value); // 123  
console.log(prop.writable); // false  
console.log(prop.enumerable); // true  
console.log(prop.configurable); // true

在这个示例中,创建了一个对象obj,它具有四个特性:value、writable、enumerable和configurable。然后,使用Object.getOwnPropertyDescriptor()方法获取了value属性的特性对象,并打印了它的值、可写性、可枚举性和可配置性。

合并对象- 7.1.5 -

可以使用Object.assign()方法或者展开运算符(...)来合并对象。

Object.assign()方法接收一个目标对象和一个或多个源对象,然后将源对象的所有可枚举属性复制到目标对象。如果多个源对象具有相同的属性,那么后面的属性将覆盖前面的属性。

以下是一个示例:

const obj1 = { a: 1, b: 2 };    
const obj2 = { b: 3, c: 4 };    
const mergedObj = Object.assign({}, obj1, obj2);    
console.log(mergedObj); // { a: 1, b: 3, c: 4 }

在这个示例中,obj1和obj2被合并成了一个新的对象mergedObj。因为obj2的b属性值覆盖了obj1的b属性值,所以mergedObj的b属性值为3。

展开运算符(...)也可以用于合并对象,它的行为类似于Object.assign()。以下是一个示例:

const obj1 = { a: 1, b: 2 };    
const obj2 = { b: 3, c: 4 };    
const mergedObj = { ...obj1, ...obj2 };    
console.log(mergedObj); // { a: 1, b: 3, c: 4 }

这个示例的结果与使用Object.assign()方法的示例相同。

对象标识及相等判定- 7.1.6 -

在JavaScript中,对象的标识是通过引用进行的。这意味着,即使两个对象具有相同的属性和值,它们也被认为是不同的对象,除非它们实际上引用的是同一个对象。例如:

var obj1 = { name: "John", age: 30 };    
var obj2 = { name: "John", age: 30 };    
console.log(obj1 === obj2); // 输出 false

在这个例子中,obj1和obj2都有相同的属性和值,但是它们并不相等,因为它们引用的是不同的对象。

1. JSON.stringify()方法:要比较两个对象是否相等,可以使用JSON.stringify()方法将对象转换为字符串,然后比较这些字符串是否相等。例如:

var obj1 = { name: "John", age: 30 };    
var obj2 = { name: "John", age: 30 };    
console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // 输出 true

在这个例子中,使用JSON.stringify()方法将obj1和obj2转换为字符串,然后比较这些字符串是否相等。如果字符串相等,那么对象就相等。但是需要注意的是,这种方法只适用于对象的可枚举自有属性,不包括继承的属性或者函数。

2. Object.is()方法:Object.is()是JavaScript中的一个静态方法,用于比较两个值是否相等。这个方法与常规的比较运算符(==和===)有所不同,它提供了一种更严格的相等性检查。

Object.is()的比较规则如下:

  1. 对于两个完全相同的原始类型(Primitive)值(例如,42或'hello'),Object.is()会返回true。

  2. 对于两个相同的对象引用,Object.is()会返回true。即使这两个对象具有相同的属性和值,但如果它们不是同一个对象,Object.is()也会返回false。

  3. 对于null和undefined,Object.is()的行为与===一致。

  4. 对于+0和-0,Object.is()会返回false,而===会返回true。

  5. 对于NaN,Object.is()会返回true,而===会返回false。

以下是一些示例:

console.log(Object.is('hello', 'hello'));     // true      
console.log(Object.is(window, window));       // true      
console.log(Object.is('hello', 'Hello'));     // false      
console.log(Object.is([], []));               // false      
var foo = { a: 1 };    
var bar = { a: 1 };    
console.log(Object.is(foo, foo));             // true      
console.log(Object.is(foo, bar));             // false      
console.log(Object.is(null, null));           // true      
// 特殊值      
console.log(Object.is(0, -0));                // false      
console.log(Object.is(-0, -0));               // true      
console.log(Object.is(NaN, 0/0));             // true

3. 库函数:还有一些方法是使用库函数,例如 lodash 的 _.isEqual(),这个函数可以更深入地比较两个对象,包括它们的原型、继承的属性和方法等。

增强的对象语法- 7.1.7 -

JavaScript 提供了很多用于操作对象的语法,这些语法在 ES6(也被称为 ECMAScript 2015)之后得到了增强。以下是一些JavaScript增强的对象语法:

1. 字面量语法:ES6引入了新的对象字面量语法,可以更加简洁地创建和初始化对象。例如:

let person = {  
  firstName: "John",  
  lastName: "Doe",  
  age: 25  
};

2. 计算属性名称:允许在对象字面量内部使用方括号([])来计算属性名称。例如:

let propName = "firstName";  
let person = {  
  [propName]: "John",  
  lastName: "Doe",  
  age: 25  
};

3. 方法简写:在ES6中,可以使用方法简写语法,直接在对象字面量中定义方法,而不需要使用 this 关键字。例如:

let person = {  
  firstName: "John",  
  lastName: "Doe",  
  age: 25,  
  greet() {  
    console.log(`Hello, my name is ${this.firstName}`);  
  }  
};  
// 等价于:  
let person = {  
  firstName: "John",  
  lastName: "Doe",  
  age: 25,  
  greet: function() {  
    console.log(`Hello, my name is ${this.firstName}`);  
  }  
};

4. 可迭代协议:ES6引入了 @@iterator 符号,使得对象可以拥有自己的迭代器。例如,可以创建一个返回数字的迭代器:

let myNumbers = {  
  [Symbol.iterator]() {  
    let index = 0;  
    return {  
      next: () => {  
        if (index < 5) {  
          return { done: false, value: index++ };  
        } else {  
          return { done: true };  
        }  
      }  
    };  
  }  
};

然后使用 for...of 循环遍历这个对象:

for (let number of myNumbers) {  
  console.log(number); // 输出 0 到 4 的数字  
}

5. 属性简写:如果对象的值是其自身的属性或方法,可以使用属性简写语法。例如:

let person = {  
  firstName: "John",  
  lastName: "Doe",  
  age: 25,  
  greet() {  
    console.log(`Hello, my name is ${this.firstName}`);  
  },  
  fullName: this.firstName + " " + this.lastName // 等价于 this.firstName + " " + this.lastName  
};

6. 拷贝对象:可以使用 ... 运算符来从一个对象复制属性到另一个对象。例如:

let person1 = { firstName: "John", lastName: "Doe" };  
let person2 = {...person1}; // 复制 person1 的所有属性到 person2

对象解构- 7.1.8 -

JavaScript中的对象解构允许将对象的属性分配给新的变量,或者从数组或对象中提取值。这在处理具有多个属性的对象时特别有用。

以下是对象解构的基本语法:

let { property1, property2 } = object;

在这里,property1 和 property2 是从 object 中提取的属性。

解构也可以在数组中使用:

let [element1, element2] = array;    
这里,element1 和 element2 是从 array 中提取的元素。

还可以将解构赋值与变量同时进行:

let property1, property2;    
({property1, property2} = object);

或者在数组中:

let element1, element2;    
([element1, element2] = array);

还可以将默认值分配给未定义的属性或元素:

let { property1 = defaultValue } = object;    
let [element1 = defaultValue] = array;

在以上代码中,如果 object 的 property1 属性或 array 的 element1 元素未定义,那么它们将被赋予 defaultValue 的值。

此外,还可以嵌套解构:

let { property: { subproperty } } = object;

在这个例子中,subproperty 变量将包含 object.property.subproperty 的值。

下面是一个简单的示例:

let person = {  
    firstName: "John",  
    lastName: "Doe"  
};  
  
// 对象解构  
let { firstName, lastName } = person;  
  
console.log(firstName);  // 输出 "John"  
console.log(lastName);   // 输出 "Doe"

在这个例子中,直接从 person 对象中提取了 firstName 和 lastName 属性,并将它们分别赋值给了新的 firstName 和 lastName 变量。

另一个示例是对象解构用于嵌套对象:

let profile = {  
    user: {  
        firstName: "John",  
        lastName: "Doe"  
    }  
};  
  
// 嵌套对象解构  
let { user: { firstName, lastName } } = profile;  
  
console.log(firstName);  // 输出 "John"  
console.log(lastName);   // 输出 "Doe"

在这个例子中,通过在目标属性名(在解构语句的左边)中再次使用冒号(:)来指定嵌套属性。注意:在这种情况下,不能直接在解构语句中使用变量名(在这里是 user),因为这将导致语法错误。