ECMAScript对象

本章将详细介绍ECMAScript中的对象,包括对象的基本概念、创建方式、属性操作和常用方法,帮助您深入理解JavaScript对象系统。

11.1 对象概述

对象是ECMAScript中的一种复合数据类型,用于存储键值对集合。对象是JavaScript的核心概念,几乎所有的值都是对象或可以像对象一样使用。

对象的基本特征

  • 属性集合:对象包含多个属性,每个属性都有键和值
  • 动态性:对象的属性可以在运行时添加、修改或删除
  • 引用类型:对象是引用类型,变量存储的是对象的引用而非对象本身
  • 原型继承:对象可以通过原型链继承其他对象的属性和方法

11.2 对象的创建方式

ECMAScript提供了多种创建对象的方式。

对象字面量

// 对象字面量是最常用的创建方式
let person = {
    name: "张三",
    age: 25,
    greet: function() {
        return `你好, 我是${this.name}`;
    }
};

// ES6增强对象字面量
let name = "李四";
let age = 30;

let user = {
    name,  // 属性简写
    age,
    greet() {  // 方法简写
        return `你好, 我是${this.name}`;
    },
    [ "user" + age ]: true  // 计算属性名
};

new Object()构造函数

// 使用Object构造函数
let obj1 = new Object();
obj1.name = "王五";
obj1.age = 28;

// 等价于
let obj2 = {};
obj2.name = "王五";
obj2.age = 28;

工厂函数

// 工厂函数
function createPerson(name, age) {
    return {
        name: name,
        age: age,
        greet: function() {
            return `你好, 我是${this.name}`;
        }
    };
}

let person1 = createPerson("张三", 25);
let person2 = createPerson("李四", 30);

构造函数

// 构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.greet = function() {
        return `你好, 我是${this.name}`;
    };
}

let person = new Person("张三", 25);
console.log(person instanceof Person); // true

Object.create()方法

// Object.create()方法
let proto = {
    greet: function() {
        return `你好, 我是${this.name}`;
    }
};

let person = Object.create(proto);
person.name = "张三";
person.age = 25;

console.log(person.greet()); // "你好, 我是张三"

11.3 对象属性操作

ECMAScript提供了丰富的API来操作对象属性。

属性访问

let person = {
    name: "张三",
    age: 25,
    "full-name": "张三丰"  // 包含特殊字符的属性名
};

// 点表示法
console.log(person.name); // "张三"

// 方括号表示法
console.log(person["age"]); // 25
console.log(person["full-name"]); // "张三丰"

// 动态属性访问
let prop = "name";
console.log(person[prop]); // "张三"

属性添加和修改

let person = {name: "张三"};

// 添加属性
person.age = 25;
person["city"] = "北京";

// 修改属性
person.name = "李四";
person["age"] = 30;

console.log(person); // {name: "李四", age: 30, city: "北京"}

属性删除

let person = {name: "张三", age: 25, city: "北京"};

// 删除属性
delete person.city;
delete person["age"];

console.log(person); // {name: "张三"}

属性检测

let person = {name: "张三", age: 25};

// in操作符
console.log("name" in person); // true
console.log("city" in person); // false

// hasOwnProperty方法
console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("toString")); // false

// propertyIsEnumerable方法
console.log(person.propertyIsEnumerable("name")); // true

11.4 对象属性描述符

ES5引入了属性描述符,可以更精确地控制对象属性的行为。

数据属性描述符

let obj = {};

// 定义带描述符的属性
Object.defineProperty(obj, "name", {
    value: "张三",
    writable: false,    // 不可写
    enumerable: true,   // 可枚举
    configurable: false // 不可配置
});

console.log(obj.name); // "张三"
obj.name = "李四";     // 严格模式下会报错
console.log(obj.name); // "张三" (未改变)

// 获取属性描述符
let descriptor = Object.getOwnPropertyDescriptor(obj, "name");
console.log(descriptor);
// {value: "张三", writable: false, enumerable: true, configurable: false}

访问器属性描述符

let obj = {
    _name: "张三"
};

Object.defineProperty(obj, "name", {
    get: function() {
        return this._name;
    },
    set: function(value) {
        if (value.length > 0) {
            this._name = value;
        }
    },
    enumerable: true,
    configurable: true
});

console.log(obj.name); // "张三"
obj.name = "李四";
console.log(obj.name); // "李四"
obj.name = "";        // 不会改变_name的值
console.log(obj.name); // "李四"

批量定义属性

let obj = {};

Object.defineProperties(obj, {
    name: {
        value: "张三",
        writable: true,
        enumerable: true,
        configurable: true
    },
    age: {
        value: 25,
        writable: true,
        enumerable: true,
        configurable: true
    },
    fullName: {
        get: function() {
            return this.name + " (" + this.age + "岁)";
        },
        enumerable: true,
        configurable: true
    }
});

console.log(obj.fullName); // "张三 (25岁)"

11.5 对象遍历

ECMAScript提供了多种遍历对象属性的方法。

for...in循环

let person = {
    name: "张三",
    age: 25,
    city: "北京"
};

// for...in遍历对象属性
for (let key in person) {
    console.log(key, person[key]);
}
// name 张三
// age 25
// city 北京

Object.keys()方法

let person = {
    name: "张三",
    age: 25,
    city: "北京"
};

// 获取对象的所有可枚举属性名
let keys = Object.keys(person);
console.log(keys); // ["name", "age", "city"]

keys.forEach(key => {
    console.log(key, person[key]);
});

Object.values()方法 (ES2017)

let person = {
    name: "张三",
    age: 25,
    city: "北京"
};

// 获取对象的所有可枚举属性值
let values = Object.values(person);
console.log(values); // ["张三", 25, "北京"]

Object.entries()方法 (ES2017)

let person = {
    name: "张三",
    age: 25,
    city: "北京"
};

// 获取对象的所有可枚举属性的键值对数组
let entries = Object.entries(person);
console.log(entries); 
// [["name", "张三"], ["age", 25], ["city", "北京"]]

// 转换为Map
let map = new Map(Object.entries(person));
console.log(map); // Map(3) {"name" => "张三", "age" => 25, "city" => "北京"}

11.6 对象保护

ECMAScript提供了几种保护对象不被修改的机制。

Object.preventExtensions()

let obj = {name: "张三"};

// 防止扩展对象
Object.preventExtensions(obj);

obj.age = 25; // 非严格模式下静默失败,严格模式下抛出错误
console.log(obj.age); // undefined

console.log(Object.isExtensible(obj)); // false

Object.seal()

let obj = {name: "张三"};

// 密封对象
Object.seal(obj);

obj.age = 25; // 不能添加新属性
delete obj.name; // 不能删除属性
obj.name = "李四"; // 可以修改现有属性

console.log(Object.isSealed(obj)); // true

Object.freeze()

let obj = {name: "张三"};

// 冻结对象
Object.freeze(obj);

obj.age = 25; // 不能添加新属性
delete obj.name; // 不能删除属性
obj.name = "李四"; // 不能修改属性(严格模式下抛出错误)

console.log(Object.isFrozen(obj)); // true

11.7 对象合并与复制

ECMAScript提供了多种合并和复制对象的方法。

Object.assign()

// 浅拷贝和合并对象
let target = {a: 1, b: 2};
let source1 = {b: 4, c: 5};
let source2 = {c: 3, d: 6};

Object.assign(target, source1, source2);
console.log(target); // {a: 1, b: 4, c: 3, d: 6}

// 克隆对象
let original = {name: "张三", age: 25};
let clone = Object.assign({}, original);
console.log(clone); // {name: "张三", age: 25}

扩展运算符 (ES2018)

// 对象扩展运算符
let obj1 = {a: 1, b: 2};
let obj2 = {c: 3, d: 4};

// 合并对象
let merged = {...obj1, ...obj2};
console.log(merged); // {a: 1, b: 2, c: 3, d: 4}

// 克隆对象
let original = {name: "张三", age: 25};
let clone = {...original};
console.log(clone); // {name: "张三", age: 25}

// 属性覆盖
let user = {name: "张三", age: 25};
let updatedUser = {...user, age: 26};
console.log(updatedUser); // {name: "张三", age: 26}

11.8 对象的实际应用

配置对象

// 使用对象作为配置参数
function createChart(options = {}) {
    const defaultOptions = {
        width: 800,
        height: 600,
        color: "#000000",
        backgroundColor: "#ffffff"
    };
    
    const config = {...defaultOptions, ...options};
    
    console.log(`创建${config.width}x${config.height}的图表`);
    // 实际的图表创建逻辑...
}

createChart({width: 1000, color: "#ff0000"});
createChart(); // 使用默认配置

模块模式

// 使用对象实现模块模式
const calculator = (function() {
    let history = []; // 私有变量
    
    return {
        add: function(a, b) {
            const result = a + b;
            history.push(`${a} + ${b} = ${result}`);
            return result;
        },
        
        subtract: function(a, b) {
            const result = a - b;
            history.push(`${a} - ${b} = ${result}`);
            return result;
        },
        
        getHistory: function() {
            return [...history]; // 返回副本
        }
    };
})();

console.log(calculator.add(5, 3)); // 8
console.log(calculator.subtract(10, 4)); // 6
console.log(calculator.getHistory()); 
// ["5 + 3 = 8", "10 - 4 = 6"]

提示:对象是JavaScript的核心概念,深入理解对象的创建、操作和保护机制对于编写高质量的JavaScript代码至关重要。在实际开发中,合理使用对象可以大大提高代码的组织性和可维护性。