1. 对象是什么?

JavaScript中的对象就像现实生活中的物体。比如一辆汽车,它有属性(颜色、品牌、型号)和行为(启动、停止、加速)。

💡 现实类比:把对象想象成盒子,里面装有各种特性(属性)和能力(方法)。

在JavaScript中,对象是键值对的集合:

  • 键(key):属性的名称(字符串)
  • 值(value):属性的值(可以是任何类型:字符串、数字、数组、函数,甚至其他对象)
// 一个简单的对象示例:汽车
let car = {
  brand: “Toyota”, // 属性: 品牌
  model: “Camry”, // 属性: 型号
  year: 2020, // 属性: 年份
  start: function() { // 方法: 启动
    console.log(“Engine started!”);
  }
};

📝 注意:JavaScript中几乎所有东西都是对象,包括数组、函数、日期等。

2. 如何创建对象?

有几种常见方式来创建对象:

2.1 对象字面量(最常用)

使用大括号 {} 直接创建

let person = {
  name: “张三”,
  age: 30,
  job: “工程师”
};

2.2 使用 new Object()

let person = new Object();
person.name = “张三”;
person.age = 30;
person.job = “工程师”;

2.3 使用构造函数(稍后详细介绍)

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}

let person1 = new Person(“张三”, 30, “工程师”);

2.4 使用类(ES6)

class Person {
  constructor(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
  }
}

let person1 = new Person(“张三”, 30, “工程师”);

3. 对象的属性

3.1 访问属性

两种方式:点记法(.)和方括号记法([])

let person = { name: “张三”, age: 30 };

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

// 方括号记法(属性名作为字符串)
console.log(person[“age”]); // 输出: 30

// 方括号记法在属性名是变量时特别有用
let prop = “name”;
console.log(person[prop]); // 输出: 张三

3.2 添加/修改属性

let person = { name: “张三” };

// 添加新属性
person.age = 30;

// 修改现有属性
person.name = “李四”;

console.log(person); // { name: “李四”, age: 30 }

3.3 删除属性

使用 delete 操作符

let person = { name: “张三”, age: 30 };

delete person.age;

console.log(person); // { name: “张三” }
console.log(person.age); // undefined

3.4 枚举属性

使用 for…in 循环遍历对象属性

let person = {
  name: “张三”,
  age: 30,
  job: “工程师”
};

for (let key in person) {
  console.log(key + “: ” + person[key]);
}

// 输出:
// name: 张三
// age: 30
// job: 工程师

4. 对象的方法

对象的方法是对象属性的一种,只不过它的值是一个函数

let person = {
  name: “张三”,
  sayHello: function() {
    console.log(“你好,我是” + this.name);
  }
};

person.sayHello(); // 输出: “你好,我是张三”

💡 理解方法:把方法看作对象的能力行为。比如手机有”打电话”的能力,汽车有”启动”的能力。

📝 简洁写法(ES6):

// ES6中更简洁的方法写法
let person = {
  name: “张三”,
  sayHello() {
    console.log(`你好,我是${this.name}`);
  }
};

5. this关键字

this 在 JavaScript 中是一个特殊关键字,它引用的是当前执行上下文

⚠️ 重要this 的值取决于函数是如何被调用的,而不是定义的位置。

常见情况:

// 情况1:在对象方法中
let person = {
  name: “张三”,
  sayName() {
    console.log(this.name); // this引用person对象
  }
};
person.sayName(); // “张三”

// 情况2:单独使用
console.log(this); // 在浏览器中,this指向window对象

// 情况3:在函数中
function test() {
  console.log(this); // 严格模式为undefined,非严格模式为window
}
test();

// 情况4:事件处理函数中
button.onclick = function() {
  console.log(this); // this指向触发事件的button元素
}

🎯 解决this问题:使用箭头函数或bind()方法固定this的指向

// 问题示例
let person = {
  name: “张三”,
  friends: [“李四”, “王五”],
  showFriends() {
    this.friends.forEach(function(friend) {
      // 这里的this不是person对象!
      console.log(this.name + “的朋友: ” + friend);
    });
  }
};
person.showFriends(); // 输出undefined的朋友…

// 解决方法1:箭头函数
showFriends() {
  this.friends.forEach(friend => {
    console.log(this.name + “的朋友: ” + friend);
  });
}

// 解决方法2:使用bind
showFriends() {
  this.friends.forEach(function(friend) {
    console.log(this.name + “的朋友: ” + friend);
  }.bind(this));
}

6. 构造函数

构造函数是用于创建多个相似对象的特殊函数。

💡 类比:构造函数就像对象的蓝图模具

// 1. 创建构造函数(通常首字母大写)
function Person(name, age) {
  // this指向新创建的对象
  this.name = name;
  this.age = age;
  this.sayHello = function() {
    console.log(`你好,我是${this.name}`);
  };
}

// 2. 使用 new 关键字创建对象实例
let person1 = new Person(“张三”, 30);
let person2 = new Person(“李四”, 25);

person1.sayHello(); // 你好,我是张三
person2.sayHello(); // 你好,我是李四

📝 new 关键字的四个步骤

  1. 创建一个新的空对象 {}
  2. 将这个新对象的原型指向构造函数的prototype属性
  3. 将这个新对象绑定到构造函数中的this
  4. 执行构造函数中的代码(添加属性)
  5. 返回这个新对象(除非构造函数返回另一个对象)

7. 原型(prototype)

原型是JavaScript实现继承的机制。每个对象都有一个隐藏属性 __proto__(原型),指向其构造函数的prototype对象。

💡 理解原型:把原型看作对象的基因家族特征

function Person(name) {
  this.name = name;
}

// 给Person的原型添加方法
Person.prototype.sayHello = function() {
  console.log(“你好,” + this.name);
};

let person1 = new Person(“张三”);
person1.sayHello(); // 输出: “你好,张三”

// 检查sayHello方法是不是在原型上
console.log(person1.hasOwnProperty(‘sayHello’)); // false
console.log(Person.prototype.hasOwnProperty(‘sayHello’)); // true

原型链

当访问对象的属性时,JavaScript引擎会:

  1. 检查对象本身是否有该属性
  2. 如果没有,查找对象的原型(__proto__)
  3. 如果还没有,查找原型的原型,依此类推
  4. 直到找到属性或到达原型链的末端(null)
// person1自身没有toString方法
console.log(person1.hasOwnProperty(‘toString’)); // false

// 但可以调用
console.log(person1.toString()); // “[object Object]”

// 因为原型链:
// person1 -> Person.prototype -> Object.prototype -> null

8. 类(Class)语法(ES6)

ES6引入了class语法,让JavaScript开发者能用更接近传统面向对象语言的方式创建对象和实现继承。

📝 注意:JavaScript的class本质上是基于原型的语法糖,它没有改变JavaScript的原型继承机制。

// 定义类
class Person {
  // 构造函数
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 方法定义
  sayHello() {
    console.log(`你好,我是${this.name}`);
  }
}

// 使用类创建对象
let person1 = new Person(“张三”, 30);
person1.sayHello(); // “你好,我是张三”

继承

class Student extends Person {
  constructor(name, age, grade) {
    super(name, age); // 调用父类的构造函数
    this.grade = grade;
  }

  study() {
    console.log(`${this.name}正在学习`);
  }
}

let student1 = new Student(“李四”, 18, “高三”);
student1.sayHello(); // “你好,我是李四”(继承自Person)
student1.study(); // “李四正在学习”

9. 对象的拷贝

复制对象时有浅拷贝深拷贝之分:

⚠️ 注意:直接赋值(=)只是创建了一个新引用,指向同一个对象,不是真正的复制!

9.1 浅拷贝

只复制第一层属性,嵌套对象还是引用

// 方法1:Object.assign()
let obj = { a: 1, b: { c: 2 } };
let shallowCopy = Object.assign({}, obj);

// 方法2:展开运算符(…)
let shallowCopy2 = { …obj };

// 浅拷贝后
shallowCopy.b.c = 999; // 修改会影响原对象!
console.log(obj.b.c); // 999

9.2 深拷贝

完全复制对象及其嵌套对象

// 方法1:JSON.parse(JSON.stringify(obj))
let obj = { a: 1, b: { c: 2 } };
let deepCopy = JSON.parse(JSON.stringify(obj));

deepCopy.b.c = 999; // 不会影响原对象
console.log(obj.b.c); // 2

// 方法2:使用第三方库如lodash的_.cloneDeep()
// 或者自己实现递归复制函数

📝 JSON方法的限制:无法复制函数、undefined、循环引用等

10. 对象相关的常用静态方法

Object构造函数提供了一些实用的静态方法:

Object.keys(obj)

返回对象自身可枚举属性组成的数组

let person = { name: “张三”, age: 30 };
console.log(Object.keys(person)); // [“name”, “age”]

Object.values(obj)

返回对象自身可枚举属性值组成的数组

console.log(Object.values(person)); // [“张三”, 30]

Object.entries(obj)

返回对象自身可枚举属性的键值对数组

console.log(Object.entries(person)); // [ [“name”, “张三”], [“age”, 30] ]

Object.assign(target, …sources)

将多个源对象的属性复制到目标对象(浅拷贝)

let obj1 = { a: 1 };
let obj2 = { b: 2 };
let merged = Object.assign({}, obj1, obj2);
console.log(merged); // { a: 1, b: 2 }

Object.freeze(obj)

冻结对象,使其不能被修改(添加、删除、修改属性)

let frozen = Object.freeze({ name: “张三” });
frozen.name = “李四”; // 静默失败(严格模式下报错)
console.log(frozen.name); // “张三”