JavaScript 中的面向对象编程:从基础到继承

JavaScript 中的面向对象编程:从基础到继承

JavaScript(简称 JS)作为一种灵活的脚本语言,常被描述为"基于对象"(Object-based)的语言,而不是严格意义上的"面向对象"(Object-Oriented Programming,OOP)语言。这是因为 JS 的核心机制依赖于原型(prototype)系统,而非传统的类(class)继承模型。尽管如此,JS 提供了强大的工具来实现 OOP 的核心概念,如封装、继承和多态。早在 ES6 之前,开发者就通过构造函数和原型链来模拟 OOP;ES6 引入的 class 关键字则让代码更接近传统 OOP 的语法,但底层仍基于原型。

对象创建的基础:字面量和实例化

在 JS 中,几乎一切都是对象------甚至基本数据类型(如字符串和数字)都有对应的包装类(如 StringNumber)。最简单的对象创建方式是使用对象字面量(object literal),这是一种直接定义对象属性的语法。

例如:

javascript 复制代码
var Cat = {
    name: "",
    color: ""
};

var cat1 = {};
cat1.name = '加菲猫';
cat1.color = "橘色";

var cat2 = {};
cat2.name = '黑猫警长';
cat2.color = "黑色";

这里,Cat 作为一个模板对象,但每个实例(如 cat1cat2)都需要手动赋值属性。这虽然简单,但会导致代码重复,尤其当需要创建多个相似对象时。扩展来说,在实际项目中,这种方法适合快速原型开发,但不利于大规模代码维护,因为缺乏封装和复用机制。

为了解决这个问题,JS 使用构造函数(Constructor)来封装实例化过程。构造函数是一个普通函数,但通过 new 关键字调用时,会自动创建一个空对象,并将 this 指向该对象。

示例:

javascript 复制代码
function Cat(name, color) {
    console.log(this); // 输出一个空对象
    this.name = name;
    this.color = color;
}

// 作为构造函数调用
const cat1 = new Cat('加菲猫', '橘色');
console.log(cat1); // { name: '加菲猫', color: '橘色' }

// 实例间共享构造函数
const cat2 = new Cat('黑猫警长', '黑色');
console.log(cat1.constructor === cat2.constructor); // true
console.log(cat1 instanceof Cat); // true

扩展解释:new 操作符的内部过程包括:

  1. 创建一个空对象 {}
  2. 将该对象的 __proto__ 指向构造函数的 prototype
  3. this 绑定到该对象,并执行构造函数代码。
  4. 返回该对象(除非构造函数显式返回其他值)。

如果不使用 new,函数中的 this 会指向全局对象(如浏览器中的 window),这可能导致意外行为。这强调了构造函数在 JS OOP 中的核心作用:它封装了对象初始化逻辑,确保每个实例都有独立的属性。

原型模式:共享属性和方法

在构造函数中直接定义方法或共享属性(如 typeeat)会导致每个实例都复制一份相同的代码,这浪费内存。原型模式通过 prototype 属性解决这个问题:将不变的属性和方法放置在构造函数的原型对象上,所有实例通过原型链(prototype chain)共享它们。

示例:

javascript 复制代码
function Cat(name, color) {
    this.name = name;
    this.color = color;
}

Cat.prototype.type = '猫科动物';
Cat.prototype.eat = function() {
    console.log('喜欢jerry');
};

var cat1 = new Cat('tom', '黑色');
var cat2 = new Cat('咖啡猫', '橘色');

console.log(cat1.type, cat2.type); // '猫科动物' '猫科动物'
cat1.type = '铲屎官的主人'; // 只影响 cat1 的自身属性
console.log(cat1.type, cat2.type); // '铲屎官的主人' '猫科动物'

这里,typeeat 被所有实例共享。如果在实例上修改(如 cat1.type),它会创建一个自身属性(own property),遮蔽原型上的值,但不会影响其他实例。

扩展来说,原型链是 JS 继承的基础:每个对象都有一个 __proto__ 属性,指向其构造函数的 prototype。属性查找时,先检查自身属性,然后顺着原型链向上查找,直到 Object.prototypenull。这提高了效率,尤其在大型应用中(如 React 组件库),可以避免重复定义通用方法。

要检查属性来源:

  • hasOwnProperty(prop):检查是否为自身属性。
  • in 操作符:检查属性是否存在于自身或原型链。
  • for...in 循环:遍历自身和原型链上的可枚举属性。

示例:

javascript 复制代码
console.log(cat1.hasOwnProperty('type')); // false (来自原型)
console.log(cat1.hasOwnProperty('name')); // true
console.log("type" in cat1); // true

ES6 Class:语法糖的便利

ES6 引入 class 关键字,让 JS 的 OOP 代码更像 Java 或 C++,但底层仍是原型机制。

示例:

javascript 复制代码
class Cat {
    constructor(name, color) {
        this.name = name;
        this.color = color;
    }

    eat() {
        console.log("eat jerry");
    }
}

const cat1 = new Cat('tom', 'black');
cat1.eat(); // "eat jerry"
console.log(cat1.__proto__); // Cat 的 prototype

扩展:class 是构造函数的语法糖。方法定义在 prototype 上,属性在构造函数中初始化。这简化了代码,但开发者仍需理解原型链,以避免常见错误如修改共享状态。

继承:通过构造函数绑定和原型链

继承允许子类复用父类的属性和方法。JS 通过两种方式实现:

  1. 构造函数绑定 :使用 applycall 将父构造函数的 this 绑定到子实例。
  2. 原型继承 :将子类的 prototype 设置为父类的实例。

示例(构造函数绑定):

javascript 复制代码
function Animal() {
    this.species = '动物';
}

function Cat(name, color) {
    Animal.apply(this); // 绑定 Animal 的 this 到 Cat 实例
    this.name = name;
    this.color = color;
}

const cat = new Cat("加菲猫", "橘色");
console.log(cat.species); // '动物'

这继承了属性,但未继承方法。

完整继承示例(结合原型):

javascript 复制代码
function Animal() {
    this.species = '动物';
}

Animal.prototype.sayHi = function() {
    console.log('lll');
};

function Cat(name, color) {
    Animal.apply(this);
    this.name = name;
    this.color = color;
}

Cat.prototype = new Animal(); // 原型继承

const cat = new Cat('加菲猫', '橘色');
cat.sayHi(); // 'lll'

扩展:这种"组合继承"是最常见的模式。它避免了经典继承的缺点(如多次调用父构造函数)。在现代 JS 中,ES6 的 extendssuper 进一步简化:

javascript 复制代码
class Animal {
    constructor() {
        this.species = '动物';
    }
    sayHi() {
        console.log('哪哪哪啦');
    }
}

class Cat extends Animal {
    constructor(name, color) {
        super(); // 调用父构造函数
        this.name = name;
        this.color = color;
    }
}

这在实际开发中(如构建组件继承树)非常实用。

JS OOP 的实用价值

JS 的 OOP 机制虽不同于传统语言,但其灵活性使其在前端开发中大放异彩。通过构造函数、原型和继承,我们可以创建高效、可维护的代码库。例如,在 Vue 或 React 项目中,这些概念常用于组件复用和状态管理。理解原型链还能帮助调试继承相关的问题,如属性遮蔽。

相关推荐
没有鸡汤吃不下饭5 小时前
前端打包出一个项目(文件夹),怎么本地快速启一个服务运行
前端·javascript
liusheng5 小时前
Capacitor + React 的 iOS 侧滑返回手势
前端·ios
CUYG5 小时前
v-model封装组件(定义 model 属性)
前端·vue.js
子洋5 小时前
基于远程开发的大型前端项目实践
运维·前端·后端
用户35020158847485 小时前
基于react-routet v7 的配置式 + 约定式路由系统 第一步:引入react-routerv7
前端
用户35020158847485 小时前
基于react-routet v7 的配置式 + 约定式路由系统 第二步:一个简单的约定式路由系统
前端
攀登的牵牛花5 小时前
前端向架构突围系列 - 框架设计(七):反应式编程框架Flower的设计
前端·架构
佛系打工仔5 小时前
K线绘制前言
前端
遇见~未来6 小时前
JavaScript数组全解析:从本质到高级技巧
开发语言·前端·javascript
哈__6 小时前
基础入门 React Native 鸿蒙跨平台开发:TabBar 底部导航栏
javascript·react native·react.js