JavaScript 教程

纯干货教学,从零开始学习 JavaScript

JavaScript 对象

对象是 JavaScript 中的核心概念,它是一种复合数据类型,可以存储多个键值对。对象在 JavaScript 中无处不在,几乎所有的东西都是对象。

🎯 对象的重要性

对象是 JavaScript 编程的基础,它允许我们将相关的数据和功能组织在一起。掌握对象的使用方法是成为一名优秀 JavaScript 开发者的关键。

什么是对象?

对象是一种包含键值对的复合数据类型,其中键是字符串(或 Symbol),值可以是任何数据类型,包括函数。

创建对象的方法

1. 对象字面量

// 使用对象字面量创建对象
const person = {
    name: "张三",
    age: 25,
    job: "工程师",
    greet: function() {
        return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
    }
};

// 访问对象属性
console.log(person.name); // 输出: 张三
console.log(person["age"]); // 输出: 25

// 调用对象方法
console.log(person.greet()); // 输出: 你好,我是 张三,今年 25 岁。

2. Object 构造函数

// 使用 Object 构造函数创建对象
const person = new Object();

// 添加属性
person.name = "李四";
person.age = 30;
person.job = "设计师";

// 添加方法
person.greet = function() {
    return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
};

// 访问对象属性和方法
console.log(person.name); // 输出: 李四
console.log(person.greet()); // 输出: 你好,我是 李四,今年 30 岁。

3. Object.create() 方法

// 创建原型对象
const personPrototype = {
    greet: function() {
        return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
    }
};

// 使用 Object.create() 创建对象
const person = Object.create(personPrototype);

// 添加属性
person.name = "王五";
person.age = 35;
person.job = "经理";

// 访问对象属性和方法
console.log(person.name); // 输出: 王五
console.log(person.greet()); // 输出: 你好,我是 王五,今年 35 岁。

4. 构造函数

// 定义构造函数
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.greet = function() {
        return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
    };
}

// 使用构造函数创建对象
const person1 = new Person("赵六", 28, "工程师");
const person2 = new Person("钱七", 32, "设计师");

// 访问对象属性和方法
console.log(person1.name); // 输出: 赵六
console.log(person2.greet()); // 输出: 你好,我是 钱七,今年 32 岁。

5. ES6 类

// 定义类
class Person {
    // 构造方法
    constructor(name, age, job) {
        this.name = name;
        this.age = age;
        this.job = job;
    }
    
    // 实例方法
    greet() {
        return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
    }
    
    // 静态方法
    static createPerson(name, age, job) {
        return new Person(name, age, job);
    }
}

// 使用类创建对象
const person1 = new Person("孙八", 26, "工程师");
const person2 = Person.createPerson("周九", 30, "设计师");

// 访问对象属性和方法
console.log(person1.name); // 输出: 孙八
console.log(person2.greet()); // 输出: 你好,我是 周九,今年 30 岁。

对象属性

1. 访问属性

const person = {
    name: "张三",
    age: 25,
    job: "工程师"
};

// 点表示法
console.log(person.name); // 输出: 张三

// 方括号表示法
console.log(person["age"]); // 输出: 25

// 使用变量访问属性
const propName = "job";
console.log(person[propName]); // 输出: 工程师

2. 添加和修改属性

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

// 添加新属性
person.job = "工程师";
person["salary"] = 10000;

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

console.log(person); // 输出: { name: "李四", age: 26, job: "工程师", salary: 10000 }

3. 删除属性

const person = {
    name: "张三",
    age: 25,
    job: "工程师"
};

// 删除属性
delete person.job;
console.log(person); // 输出: { name: "张三", age: 25 }

// 检查属性是否存在
console.log("job" in person); // 输出: false
console.log("name" in person); // 输出: true

// 检查属性是否是对象自身的
console.log(person.hasOwnProperty("name")); // 输出: true
console.log(person.hasOwnProperty("toString")); // 输出: false(toString 是继承的方法)

对象方法

对象方法是存储在对象属性中的函数:

const calculator = {
    // 方法
    add: function(a, b) {
        return a + b;
    },
    subtract: function(a, b) {
        return a - b;
    },
    multiply: function(a, b) {
        return a * b;
    },
    divide: function(a, b) {
        if (b === 0) {
            return "除数不能为零";
        }
        return a / b;
    },
    // ES6 方法简写
    power(a, b) {
        return Math.pow(a, b);
    }
};

// 调用对象方法
console.log(calculator.add(5, 3)); // 输出: 8
console.log(calculator.subtract(10, 4)); // 输出: 6
console.log(calculator.multiply(6, 7)); // 输出: 42
console.log(calculator.divide(15, 3)); // 输出: 5
console.log(calculator.power(2, 3)); // 输出: 8

this 关键字

在对象方法中,this 指向调用该方法的对象:

const person = {
    name: "张三",
    age: 25,
    greet: function() {
        return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
    }
};

console.log(person.greet()); // 输出: 你好,我是 张三,今年 25 岁。

// 注意:箭头函数中的 this 指向定义时的上下文
const person2 = {
    name: "李四",
    age: 30,
    greet: () => {
        return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
    }
};

console.log(person2.greet()); // 输出: 你好,我是 undefined,今年 undefined 岁。

对象遍历

1. for...in 循环

const person = {
    name: "张三",
    age: 25,
    job: "工程师"
};

// 使用 for...in 循环遍历对象
for (const key in person) {
    if (person.hasOwnProperty(key)) {
        console.log(`${key}: ${person[key]}`);
    }
}
// 输出:
// name: 张三
// age: 25
// job: 工程师

2. Object.keys() 方法

const person = {
    name: "张三",
    age: 25,
    job: "工程师"
};

// 使用 Object.keys() 获取对象的键
const keys = Object.keys(person);
console.log(keys); // 输出: ["name", "age", "job"]

// 遍历键
keys.forEach(key => {
    console.log(`${key}: ${person[key]}`);
});
// 输出:
// name: 张三
// age: 25
// job: 工程师

3. Object.values() 方法

const person = {
    name: "张三",
    age: 25,
    job: "工程师"
};

// 使用 Object.values() 获取对象的值
const values = Object.values(person);
console.log(values); // 输出: ["张三", 25, "工程师"]

4. Object.entries() 方法

const person = {
    name: "张三",
    age: 25,
    job: "工程师"
};

// 使用 Object.entries() 获取对象的键值对
const entries = Object.entries(person);
console.log(entries); // 输出: [["name", "张三"], ["age", 25], ["job", "工程师"]]

// 遍历键值对
entries.forEach(([key, value]) => {
    console.log(`${key}: ${value}`);
});
// 输出:
// name: 张三
// age: 25
// job: 工程师

对象继承

1. 原型继承

// 父对象
const person = {
    name: "张三",
    age: 25,
    greet: function() {
        return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
    }
};

// 子对象,继承自 person
const employee = Object.create(person);
employee.job = "工程师";
employee.salary = 10000;
employee.name = "李四"; // 覆盖继承的属性

// 添加新方法
employee.work = function() {
    return `${this.name} 正在工作。`;
};

// 访问继承的属性和方法
console.log(employee.greet()); // 输出: 你好,我是 李四,今年 25 岁。
console.log(employee.work()); // 输出: 李四 正在工作。

2. 构造函数继承

// 父构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 添加原型方法
Person.prototype.greet = function() {
    return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
};

// 子构造函数
function Employee(name, age, job, salary) {
    // 调用父构造函数
    Person.call(this, name, age);
    this.job = job;
    this.salary = salary;
}

// 设置原型继承
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;

// 添加子构造函数的方法
Employee.prototype.work = function() {
    return `${this.name} 正在工作,职位是 ${this.job}。`;
};

// 创建实例
const employee = new Employee("王五", 30, "工程师", 12000);

// 访问属性和方法
console.log(employee.name); // 输出: 王五
console.log(employee.greet()); // 输出: 你好,我是 王五,今年 30 岁。
console.log(employee.work()); // 输出: 王五 正在工作,职位是 工程师。

3. ES6 类继承

// 父类
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    greet() {
        return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
    }
}

// 子类
class Employee extends Person {
    constructor(name, age, job, salary) {
        // 调用父类构造函数
        super(name, age);
        this.job = job;
        this.salary = salary;
    }
    
    work() {
        return `${this.name} 正在工作,职位是 ${this.job}。`;
    }
}

// 创建实例
const employee = new Employee("赵六", 28, "设计师", 10000);

// 访问属性和方法
console.log(employee.name); // 输出: 赵六
console.log(employee.greet()); // 输出: 你好,我是 赵六,今年 28 岁。
console.log(employee.work()); // 输出: 赵六 正在工作,职位是 设计师。

对象操作方法

// 1. Object.assign():合并对象
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const obj3 = { d: 5 };

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

// 2. Object.freeze():冻结对象(不能添加、删除或修改属性)
const frozenObj = { a: 1, b: 2 };
Object.freeze(frozenObj);
frozenObj.a = 10; // 无效果
console.log(frozenObj); // 输出: { a: 1, b: 2 }

// 3. Object.seal():密封对象(不能添加或删除属性,但可以修改现有属性)
const sealedObj = { a: 1, b: 2 };
Object.seal(sealedObj);
sealedObj.a = 10; // 可以修改
sealedObj.c = 3; // 无效果
delete sealedObj.b; // 无效果
console.log(sealedObj); // 输出: { a: 10, b: 2 }

// 4. Object.is():比较两个值是否严格相等
console.log(Object.is(5, 5)); // 输出: true
console.log(Object.is(NaN, NaN)); // 输出: true
console.log(Object.is(0, -0)); // 输出: false

// 5. Object.keys():返回对象的自有可枚举属性的键
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj)); // 输出: ["a", "b", "c"]

// 6. Object.values():返回对象的自有可枚举属性的值
console.log(Object.values(obj)); // 输出: [1, 2, 3]

// 7. Object.entries():返回对象的自有可枚举属性的键值对
console.log(Object.entries(obj)); // 输出: [["a", 1], ["b", 2], ["c", 3]]

// 8. Object.fromEntries():将键值对数组转换为对象
const entries = [["a", 1], ["b", 2], ["c", 3]];
const newObj = Object.fromEntries(entries);
console.log(newObj); // 输出: { a: 1, b: 2, c: 3 }

对象最佳实践

  • 使用对象字面量创建对象:对象字面量语法简洁明了,是创建对象的首选方法
  • 使用 ES6 方法简写:对于对象方法,使用简写语法使代码更简洁
  • 使用 const 声明对象:除非需要重新赋值对象引用,否则使用 const 声明
  • 避免使用全局对象:全局对象容易导致命名冲突,应尽量避免
  • 使用对象解构:使用解构赋值简化对象属性的访问
  • 使用 Object.assign() 或扩展运算符合并对象:这两种方法都可以创建新对象,避免修改原对象
  • 使用 ES6 类:对于需要创建多个相似对象的情况,使用类可以提高代码的可维护性
  • 注意 this 的绑定:在使用对象方法时,注意 this 的指向问题
  • 使用 Object.keys()、Object.values() 和 Object.entries() 遍历对象:这些方法提供了更灵活的对象遍历方式
  • 考虑使用 Map 替代对象:当键不是字符串时,Map 可能是更好的选择

对象常见问题

  • 问题 1: this 指向错误
  • 解决方案:了解 this 的绑定规则,在需要时使用 bind()、call() 或 apply() 方法,或使用箭头函数。

  • 问题 2: 对象引用问题
  • 解决方案:了解对象是引用类型,当需要复制对象时,使用 Object.assign() 或扩展运算符创建浅拷贝,或使用 JSON.parse(JSON.stringify()) 创建深拷贝。

  • 问题 3: 原型链污染
  • 解决方案:避免修改 Object.prototype 等内置对象的原型,使用 Object.create(null) 创建没有原型的对象。

  • 问题 4: 属性枚举问题
  • 解决方案:了解可枚举属性和不可枚举属性的区别,使用 Object.getOwnPropertyNames() 获取所有自有属性(包括不可枚举的)。

  • 问题 5: 继承实现复杂
  • 解决方案:使用 ES6 类和 extends 关键字,这使继承实现更加简洁明了。

📝 学习检查

通过本章节的学习,你应该了解:

  • 对象的基本概念和创建方法
  • 对象属性的访问、添加、修改和删除
  • 对象方法的定义和调用
  • this 关键字在对象中的使用
  • 对象的遍历方法
  • 对象继承的实现方式
  • 常用的对象操作方法
  • 对象的最佳实践和常见问题