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 项目中,这些概念常用于组件复用和状态管理。理解原型链还能帮助调试继承相关的问题,如属性遮蔽。

相关推荐
渣波1 小时前
# TypeScript:给 JavaScript 穿上“防弹衣”的超能力语言
javascript·typescript
我叫张小白。1 小时前
Vue3 标签的 ref 属性:直接访问 DOM 和组件实例
前端·javascript·vue.js·typescript·vue3
2509_940880221 小时前
Spring Cloud GateWay搭建
android·前端·后端
一千柯橘1 小时前
Three.js 中的调试助手 OrbitControls + GUI
前端
一 乐1 小时前
购物商城|基于SprinBoot+vue的购物商城系统(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·后端
izx8881 小时前
ES6+ 核心语法精讲:让 JavaScript 更优雅、更强大
javascript
玥浛1 小时前
ELK.js 实战:大规模图布局性能优化方案
前端
特级业务专家1 小时前
React Fiber 和时间切片
前端
BlackWolfSky1 小时前
React Native学习路径与资源推荐
javascript·学习·react native