JS面向对象程序设计(OOP)与原型机制,到底是如何一步步走向实用的

引言:探索 JavaScript 原型链的奥秘

在现代软件开发中,面向对象编程(OOP)是一种非常重要的编程范式。它通过封装、继承和多态等特性,使得代码更加模块化、易于维护和扩展。然而,与传统的 OOP 语言(如 Java 和 C++)不同,JavaScript 的面向对象机制并不是基于类的,而是基于**原型(Prototype)**的。

JavaScript 中的独特之处

当你初次接触 JavaScript 的面向对象编程时,可能会感到困惑。为什么我们不需要定义"类"就可以创建对象?为什么每个对象都有一个 __proto__ 属性?这些问题的答案都指向了 JavaScript 的核心机制之一------原型链

JavaScript 的设计哲学强调灵活性和动态性,这使得它在处理对象和继承方面有着独特的机制。尽管 ES6 引入了 class 关键字作为语法糖来简化类的定义,但其底层实现仍然是基于原型链的。因此,深入理解原型链对于掌握 JavaScript 的精髓至关重要。

原型链的重要性

原型链不仅仅是 JavaScript 的一个技术细节,它是整个语言运行机制的基础。无论是你正在构建一个简单的网页应用,还是复杂的大型系统,了解原型链都能帮助你写出更高效、更健壮的代码。

  • 共享方法:通过原型链,多个对象实例可以共享同一个方法,从而减少内存占用。
  • 灵活继承:支持多层次的继承结构,使得代码复用变得更加容易。
  • 属性查找规则:清晰的属性查找路径,确保你在访问对象属性时不会遇到意外的行为。

一.OOP(面向对象)的基本特征以及早期如何实现一些功能

在大多数传统面向对象语言中(如 Java、C++),OOP 有三大基本特征:

  • 封装(Encapsulation) :将数据和行为打包成一个类。
  • 继承(Inheritance) :子类可以继承父类的属性和方法。
  • 多态(Polymorphism) :同一接口的不同实现方式。

如在Java中,若我想定义一个类,只需public class一下就可以了,并在其中直接声明相应的成员变量,若想实现一些功能,就在类中构造相应方法,实例:

//定义Puppy类 复制代码
public class Puppy{
    //成员变量
    int puppyAge;
    //构造方法
    public Puppy(age){
        //This constructor has one parameter, age.
        puppyAge = age;
    }
    //公有方法
    public void{
        System.out.println("wolf! wolf!");
    }

不过,在Java中,这些都是要提前声明的,相当于得去手动修改,这就损失了一定的灵活性,但在 JavaScript 中,它并没有"类"的原生支持(ES6 之前) ,它的 OOP 是基于原型(Prototype) 实现的,这是一种更灵活、但也更容易让人困惑的机制。

二、JavaScript 中的对象创建方式

1. 对象字面量(Object Literal)

在JS中,可以直接声明一个变量。

css 复制代码
var Person={
    name:'hgh',
    hobbies:['basketball','music','tennis table'],
}

观察其类型,发现这就是一个Object(对象)类型

这种方式适合创建单个对象,但不适合批量创建相同结构的对象,如果有一堆一样的的对象,创建太麻烦了,那没有class的JS是如何苦苦追求OOP的呢?


2. 构造函数(Constructor Function)

JavaScript 使用构造函数来模拟"类"的行为(函数对象,原型对象,解决了类的方法):
ini 复制代码
function Person(name,age){
    //this 指向当前实例化对象
    this.name=name;
    this.age=age;
}
let hu = new Person('hu',18)
console.log(hu)

输出了一个对象,包含name和age属性

我们一般也会模拟Java中的做法,将其首字母大写,不过这并不是强制爱,只是为了更清楚的确定类的概念,让我们知道这是一个构造函数,是用来声明一个类的,不至于混淆。

同时不能忘了,调用时,必须要new一下,把它变成实例化对象,为什么呢?原因如下:

使用 new 调用构造函数,是为了创建一个新对象,并让 this 正确地指向这个新对象 ,如果不加 new,构造函数中的 this 就不会指向新对象,而是指向全局对象(如浏览器中的 window 或 Node.js 中的 global),这会带来意想不到的错误和副作用。

所以说,使用 new 是一种规范,也是一种安全编程的习惯。在后期编程中,也可以添加一些检测的函数来确保new的存在,避免一些数据泄露到全局

当然,也少不了一些"方法"的构造:
javascript 复制代码
Person.prototype = {
    sayHello:function(){
        console.log('hello,my name is ${this.name}')
    }
}
hu.sayHello()

注:prototype 是构造函数的一个属性,它指向一个对象,这个对象被称为原型对象。所有通过该构造函数创建的实例对象都会共享原型对象上的属性和方法

调用了(sayHello)方法

构造函数的特点:
  • 函数名通常大写,表示这是一个"类"。
  • 必须使用 new 来调用。
  • this 指向新创建的对象。
  • 构造函数本身没有类的概念,是通过原型链实现继承(后面会讲)

三、JS 中的原型(Prototype)机制

1. 所有对象都有 __proto__ 属性

  • __proto__ 是对象的一个内部引用属性 ,指向其构造函数的 prototype
  • 它构成了 JavaScript 的原型链(Prototype Chain)
arduino 复制代码
console.log(hu.__proto__)
ini 复制代码
console.log(hu.__proto__==Person)
console.log(hu.__proto__==Person.prototype)

它可以用来访问对象的原型,每个对象都有一个 proto 属性,它指向该对象的原型对象。当访问对象的某个属性或方法时,如果对象本身没有这个属性或方法,JavaScript 会沿着 proto 链向上查找,直到找到该属性或方法,或者到达原型链的顶端( null ),这里就是因为hu的原型为Person.prototype,所以再往上级1的Person则会返回flase

若对单独的某个属性:

arduino 复制代码
console.log(hu.__proto__.say);
console.log(hu.__proto__.sayHello);

2. 所有函数都有 prototype 属性

  • 这个属性是一个对象,默认包含一个 constructor 属性,指向该函数本身。
  • 我们可以在 prototype 上添加方法,供所有实例共享。
javascript 复制代码
function Person(name,age){
    //this 指向当前实例化对象
    this.name=name;
    this.age=age;
}
Person.prototype = {
    sayHello:function(){
        console.log(`hello,my name is ${this.name}`)
    },
    happy:function(){
        console.log(`wish ${this.name} have a happy day! `)
    }
}
let hsm=new Person('hsm',18);
hsm.sayHello();
hsm.happy();

3. JS 并没有真正的"类"

  • ES6 引入了 class 关键字,但这只是语法糖,底层依然是原型机制。
  • 在 JS 中,"类"只是一个约定俗成的大写函数。
csharp 复制代码
class Animal {}
// 等价于
function Animal() {}

四、new 的执行过程详解

当你使用 new 创建一个对象时,JavaScript 内部会经历以下几个步骤:

步骤解析:

ini 复制代码
const hu = new Person('hu');
  1. 创建一个空对象 {}

    • 这就是即将返回的新对象。
  2. 绑定 this

    • 将构造函数中的 this 指向这个新对象。
  3. 执行构造函数体

    • 给这个新对象添加属性和方法。
  4. 设置原型链

    • 新对象的 __proto__ 指向构造函数的 prototype
  5. 返回新对象

    • 如果构造函数没有显式返回其他对象,则返回这个新对象。

原型链结构如下:

new -> {} -> constructor 运行 -> this -> {} ->完成了构造 -> proto -> constructor.prototype -> Object 原型链 -> null 终点


五、总结

构造函数、实例、原型之间的关系图解
名称 类型 说明
Person 构造函数 用于创建对象,定义对象的属性和方法
Person.prototype 原型对象 存放所有实例共享的方法
hu 实例对象 new Person() 创建出来的具体对象
hu.__proto__ 实例的原型 指向构造函数的 prototype
Object.prototype 根原型 所有对象的最终原型,值为 null

经过一系列的学习,我们总结一下:

  • 每个对象都有一个 __proto__ 属性,它指向该对象的原型对象;
  • 函数拥有 prototype 属性,用于指定其实例对象的原型;
  • 访问对象属性时,JavaScript 引擎会沿着原型链向上查找 ,直到找到目标属性或到达链的终点 null
  • 原型链构成了 JavaScript 实现继承的基础,使得多个对象实例能够共享方法和属性;
  • 合理使用原型链可以提高代码复用性和性能,但也要注意避免滥用或错误修改原型带来的副作用。

此外,我们明白了为什么必须使用 new 来调用构造函数------因为只有这样才能正确地创建新对象并设置好原型链,从而确保对象之间的继承关系清晰、安全且可维护。尽管 ES6 引入了 class 语法来简化面向对象编程的写法,但其底层依然是基于原型链实现的。因此,无论你是前端开发者还是全栈工程师,深入理解原型链都是掌握 JavaScript 的重点,原型链不仅是 JavaScript 的一大特色,更是构建高效、可扩展应用的关键工具。

相关推荐
PasserbyX8 分钟前
ES6 WeakMap 生效的证明: FinalizationRegistry
前端·javascript
努力学习的小刘11 分钟前
如何使用react-router实现动态路由
前端·javascript
PasserbyX11 分钟前
JS原型链
前端·javascript
Danta17 分钟前
从0开始学习three.js(1)😁
前端·javascript·three.js
coderYYY37 分钟前
element树结构el-tree,默认选中当前setCurrentKey无效
前端·javascript·vue.js
GISer_Jing1 小时前
Three.js中AR实现详解并详细介绍基于图像标记模式AR生成的详细步骤
开发语言·javascript·ar
GISer_Jing1 小时前
[总结篇]个人网站
前端·javascript
ss.li1 小时前
TripGenie:畅游济南旅行规划助手:个人工作纪实(二十二)
javascript·人工智能·python
海的诗篇_2 小时前
前端开发面试题总结-JavaScript篇(二)
开发语言·前端·javascript·typescript
琹箐2 小时前
ant-design4.xx实现数字输入框; 某些输入法数字需要连续输入两次才显示
前端·javascript·anti-design-vue