一、基础概念:对象与面向对象思想

1. 什么是对象?

  • 本质:对象是属性和方法的集合,属性描述事物的 “特征”(如身高、颜色),方法描述事物的 “行为”(如吃饭、行驶)。
  • 现实映射:现实世界中的万物皆可视为对象(如士兵、汽车、人),编程中通过对象模拟这些事物。
  • JavaScript 特性:“一切皆可看作对象”—— 即使是基本数据类型(如stringnumber),也可通过包装类转化为对象使用。

2. 面向对象 vs 面向过程

两者是不同的编程范式,核心区别在于 “解决问题的关注点” 不同:

对比维度 面向过程(POP) 面向对象(OOP)
核心关注点 解决问题的步骤和过程 解决问题的对象
编程特点 由独立函数实现步骤,无类 / 对象 先定义 “类”,再从类创建 “对象”
调用者视角 需手动调用所有函数,关注实现细节 只需调用对象的方法,无需关注细节
典型场景 简单任务(如计算两数和) 复杂项目(如电商系统、游戏)

3. 面向对象三大核心特征

  • 封装:将对象的属性和方法 “包裹” 在一起,隐藏内部实现细节,仅对外暴露接口(如调用car.run()无需知道 “如何行驶”)。
  • 继承:一个对象(子类)可获取另一个对象(父类)的属性和方法,减少代码重复(如 “电动汽车” 继承 “汽车” 的 “行驶” 方法)。
  • 多态:同一方法在不同对象上有不同实现(如 “动物叫”,猫是 “喵喵”,狗是 “汪汪”),JavaScript 通过 “方法重写” 实现。

4. 关键概念补充

概念 定义
属性 对象的特征(如car.colorperson.age),分为 “实例属性” 和 “原型属性”
方法 对象的行为(如car.run()arr.sum()),分为 “实例方法” 和 “原型方法”
抽象 从多个对象中提取共性特征的过程(如从 “宝马”“比亚迪” 中抽象出 “汽车” 类)
重写 子类修改父类继承的方法(如子类ElectricCar重写父类Carrun()
实例化对象 通过 “类 / 构造函数” 创建的具体对象(如var bmw = new Car()bmw是实例)

二、JavaScript 创建对象的 4 种方式

1. 方式 1:对象字面量(最常用)

直接用{}定义对象,适合创建单个、简单的对象。

// 定义汽车对象
var car = {
  // 属性
  color: "白色",
  brand: "BMW",
  price: "56W",
  // 方法
  run: function () {
    console.log(this.brand + "在路上行驶"); // this指向当前对象car
  },
  stop: function (msg) {
    console.log(this.brand + "刹不住车了!" + msg);
  }
};

// 调用属性和方法
console.log(car.color); // 访问属性:白色
car.run(); // 调用方法:BMW在路上行驶
car.stop("危险!"); // 调用方法:BMW刹不住车了!危险!

2. 方式 2:Object 构造函数

通过new Object()创建空对象,再动态添加属性和方法,适合创建空对象后需动态扩展的场景。

// 1. 创建空对象
var car = new Object();
// 2. 动态添加属性
car.color = "白色";
car.brand = "BMW";
// 3. 动态添加方法
car.run = function () {
  console.log(this.brand + "在路上行驶");
};

// 调用
console.log(car.brand); // BMW
car.run(); // BMW在路上行驶

3. 方式 3:在已有对象上扩展属性 / 方法

给数组、函数等内置对象添加自定义属性和方法(需谨慎,避免污染内置对象)。

// 给数组对象添加属性和方法
var arr = [1, 2, 3];
// 添加属性
arr.num = 10;
// 添加方法
arr.getNum = function () {
  console.log(this.num); // this指向arr
};

// 调用
arr.getNum(); // 10

4. 方式 4:构造函数(批量创建对象)

当需要创建多个结构相同的对象(如多辆汽车、多个人)时,用构造函数批量生成,函数名首字母建议大写(区分普通函数)。

4.1 基础构造函数
// 定义“汽车”构造函数(相当于“类”)
function Car(width, color, brand) {
  // 实例属性(每个实例独有)
  this.width = width;
  this.color = color;
  this.brand = brand;
  // 实例方法(每个实例都会创建新函数,浪费内存)
  this.run = function () {
    console.log(this.brand + "在路上行驶");
  };
}

// 批量创建实例对象
var byd = new Car("1500mm", "红色", "比亚迪");
var bmw = new Car("2000mm", "白色", "BMW");

// 调用
byd.run(); // 比亚迪在路上行驶
bmw.run(); // BMW在路上行驶
console.log(byd.run === bmw.run); // false(每个实例的run是新函数,内存地址不同)
4.2 优化:原型(prototype)添加方法

为解决 “实例方法重复创建” 的内存浪费问题,将方法添加到构造函数的原型对象上(所有实例共享原型方法)。

// 1. 定义构造函数(只放实例属性)
function Car(width, color, brand) {
  this.width = width;
  this.color = color;
  this.brand = brand;
}

// 2. 原型对象添加方法(所有实例共享)
Car.prototype.run = function () {
  console.log(this.brand + "在路上行驶");
};
Car.prototype.stop = function () {
  console.log(this.brand + "刹住车了");
};

// 3. 创建实例
var byd = new Car("1500mm", "红色", "比亚迪");
var bmw = new Car("2000mm", "白色", "BMW");

// 调用
byd.run(); // 比亚迪在路上行驶
bmw.stop(); // BMW刹住车了
console.log(byd.run === bmw.run); // true(共享原型方法,内存地址相同)

三、原型与原型链(JavaScript 面向对象的核心)

1. 核心概念:原型对象(prototype)

  • 定义所有函数都有一个prototype属性,该属性指向一个 “原型对象”(普通对象),用于存储所有实例共享的属性和方法。
  • 作用:实现继承和方法共享,减少内存占用(如Car.prototyperun方法被所有Car实例共享)。

2. 构造函数(constructor)

  • 定义:原型对象的constructor属性指向 “创建该原型的构造函数”,形成闭环。
  • 示例Car.prototype.constructor === Car(true)、Object.prototype.constructor === Object(true)。

3. 实例的__proto__属性

  • 定义每个对象(包括实例)都有__proto__属性(浏览器实现,非标准但通用),指向 “其构造函数的原型对象”。
  • 作用:连接实例与原型对象,实现 “属性 / 方法查找”(如实例byd找不到run时,通过byd.__proto__Car.prototype.run)。
  • 示例
    var byd = new Car();
    console.log(byd.__proto__ === Car.prototype); // true(实例的__proto__指向构造函数的原型)
    console.log(byd.__proto__.constructor === Car); // true(原型的constructor指向构造函数)
    

4. new关键字的 4 个执行步骤

当用new 构造函数()创建实例时,底层执行以下操作:

  1. 创建空对象:生成一个空的实例对象(如var obj = {})。
  2. 绑定 this:让构造函数中的this指向空实例对象(如Carthis指向obj)。
  3. 执行构造函数:给实例对象添加属性(如obj.width = width)。
  4. 返回实例:默认返回this(即创建好的实例对象,无需手动return)。

5. 原型相关的 4 个常用方法

方法 作用 示例
Object.getPrototypeOf(实例) 获取实例的原型对象(替代__proto__,标准方法) Object.getPrototypeOf(byd) === Car.prototype(true)
原型.isPrototypeOf(实例) 判断 “原型对象” 是否是 “实例” 的原型 Car.prototype.isPrototypeOf(byd)(true)
实例.hasOwnProperty(属性名) 判断属性是否是 “实例自身的属性”(不包含原型属性) byd.hasOwnProperty("brand")(true,brand 是实例属性)
属性名 in 实例 判断属性是否存在于 “实例自身” 或 “原型对象” 中(包含原型属性) "run" in byd(true,run 是原型方法)

四、实战案例

案例 1:给数组添加 “求和” 方法

需求:让所有数组都能调用sum()方法,计算数组元素的总和。

// 原型添加sum方法(所有数组共享)
Array.prototype.sum = function () {
  var total = 0;
  for (var i = 0; i < this.length; i++) {
    total += this[i]; // this指向当前数组
  }
  return total;
};

// 测试
var arr1 = [1, 2, 3, 4, 5];
var arr2 = [10, 20, 30];
console.log(arr1.sum()); // 15
console.log(arr2.sum()); // 60

案例 2:给数组添加 “去重” 方法

需求:让所有数组调用noRepeat()方法,去除重复元素。

// 原型添加去重方法
Array.prototype.noRepeat = function () {
  var tempArr = [];
  for (var i = 0; i < this.length; i++) {
    // 若tempArr中没有当前元素,则添加
    if (tempArr.indexOf(this[i]) === -1) {
      tempArr.push(this[i]);
    }
  }
  return tempArr; // 返回去重后的新数组
};

// 测试
var numArr = [9, 9, 3, 4, 8, 9, 3];
var uniqueArr = numArr.noRepeat();
console.log(uniqueArr); // [9, 3, 4, 8]

更多推荐