揭秘JavaScript面向对象编程:从构造函数到原型链的深度探索!

349 阅读6分钟

引言

在当今的Web开发世界里,JavaScript已经从一个简单的脚本语言进化成了一个功能强大的编程工具。无论是前端开发、后端服务,还是移动应用,JavaScript无处不在。而掌握JavaScript中的面向对象编程概念,无疑是每一个开发者必备的技能之一。今天将带你从零开始,逐步探索JavaScript中的OOP,通过丰富的示例和实用技巧,让你在编程的道路上更进一步。

1.创建对象的方法有哪些?

13.jpg

1.1 对象字面量

最简单的创建对象的方式就是使用对象字面量,这种方式直观易懂,适合快速定义少量的数据结构,但如果需要创建多个相似的对象,这种方式会显得冗余且难以维护。

const person = {
  name: '张三',
  age: 25,
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

person.greet(); // 输出: Hello, my name is 张三

1.2 Class关键字

随着ES6 的发布,JavaScript引入了class关键字,使得面向对象编程更加接近传统语言如Java或C++的语法,使用class关键字可以使代码更加清晰和易于维护,特别是在处理复杂对象和继承关系时。

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

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const person1 = new Person('李四', 30);
person1.greet(); // 输出: Hello, my name is 李四

1.3 构造函数

在ES6之前的版本中,通常使用构造函数来模拟类的行为,这也是我们今天介绍的重点地方,接下来将重点围绕构造函数这一块,分享我的理解。

下面我定义了一个构造函数与一些实例:

function Person(name, age) {
    this.name = name;
    this.age = age;
}
//实例
const z = new Person('张三', 18);
console.log(z.name,l.age);
const l = new Person('李四', 18);
console.log(z.name,l.age);

在这里面,我们可以看到这里运用this指针和new关键字,那这两个东西在这里有什么作用,充当着什么角色呢?

function Person(name, age) {
    console.log(this);
    this.name = name;
    this.age = age;
}
Person('小丽', 19)
const xl = new Person('小李', 19)
const we = new Person('王二', 20) 
}

试一试在代码栏中打出这一段代码,你会发现出现了这样的一段代码结果:

image.png

出现这个结果当然和this指针和new关键字脱不了关系,在这里this指针指向新创建的实例对象,由于小丽是以普通函数的方式执行,所以this会指向全局对象global,而小李和王二是使用new关键字创建的新对象,它会将构造函数内的this绑定到新创建的对象,是以构造函数的方式运行,所以会产生两个Person{}对象,这里先浅浅的了解一下,下面会有更详细的过程。

14.jpg

2. 接下来讲讲原型与原型链

2.1 原型(Prototype)

在JS中,每个函数都有一个prototype属性,这个属性是一个对象,可以用来定义所有实例共享的属性和方法。当通过new关键字调用构造函数创建实例时,这些实例会继承原型对象上的属性和方法。

function Person(name, age) {
    console.log(this);
    this.name = name;
    this.age = age;
}
//每个函数都有一个原型对象   原型对象是一个对象   
//原型对象的作用:给实例对象共享方法   实例对象可以访问原型对象的方法
Person.prototype = {
    eat: function () {
        console.log(`${this.name}爱吃饭`);
    }
}

const we = new Person('王二', 18);
we.eat();
const xl = new Person('小李', 19)
xl.eat();

结果展示如下:

image.png

通过这一段代码我们可以原型对象的作用:

  • 共享属性和方法:通过原型对象,多个实例可以共享相同的属性和方法,节省内存。
  • 动态扩展:可以在运行时动态地向原型对象添加属性和方法,所有实例都会立即获得这些新的属性和方法。

2.2 原型链

1.原型链的基本概念

在JavaScript中,每个对象都有一个内部属性Prototype,指向另一个对象。这个被指向的对象称为原型对象。当访问一个对象的属性时,JavaScript引擎会首先检查该对象自身是否有这个属性。如果没有,引擎会沿着原型链向上查找,直到找到该属性或到达原型链的末端(即null)。

2. 原型链的工作原理

  • 属性查找:当访问一个对象的属性时,JavaScript引擎会按照以下步骤进行查找:

    • 首先检查对象自身是否有该属性。
    • 如果对象自身没有该属性,引擎会检查对象的Prototype对象是否有该属性。
    • 如果Prototype对象也没有该属性,引擎会继续检查Prototype对象的Prototype对象,依此类推,直到找到该属性或到达原型链的末端(即null)。
  • 方法查找:方法的查找过程与属性查找类似,也是沿着原型链向上查找。

3. 构造函数、原型对象与实例的关系

我们可以通过下面这段代码来梳理它们三者之间的关系:

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

Person.prototype.name = '孔子';
Person.prototype.hometown = '山东';

let person1 = new Person('张', 19);
let person2 = new Person('郭', 19);

console.log(person1 === person2);   // false 不相同
console.log(person1.name, person1.hometown, person2.hometown, person2.name); 
// 输出: 张 山东 山东 郭

3.1 person1 和 person2 是否相同?

console.log(person1 === person2);   // false 不相同
  • 解释:person1person2是两个不同的实例对象,即使它们都由同一个构造函数Person创建。因此,person1person2是不相同的对象,person1 === person2的结果是false

3.2 访问属性的结果是什么?

console.log(person1.name, person1.hometown, person2.hometown, person2.name);  // 输出: 张 山东 山东 郭
  • person1.name 和 person2.name

    • 在构造函数Person中,this.name被赋值为传入的参数name。因此,person1.nameperson2.name
  • person1.hometown 和 person2.hometown

    • hometown属性是在Person.prototype上定义的。当我们在实例对象上访问hometown属性时,如果实例对象本身没有这个属性,JavaScript引擎会沿着原型链向上查找,找到Person.prototype上的hometown属性。因此,person1.hometownperson2.hometown都输出山东

3.3 原型链是怎么样的?

  • person1person2 的原型链

    • person1 和 person2 的[[Prototype]]属性都指向Person.prototype
    • Person.prototype 的[[Prototype]]属性指向Object.prototype
    • Object.prototype 的[[Prototype]]属性指向null

    原型链可以表示为:

    person1 -> Person.prototype -> Object.prototype -> null
    person2 -> Person.prototype -> Object.prototype -> null
    

3.4 总结

  • 构造函数:用于创建和初始化对象。通过new关键字调用构造函数时,this指针指向新创建的实例对象。
  • 原型对象:每个构造函数都有一个prototype属性,可以用来定义所有实例共享的属性和方法。
  • 实例:通过new关键字调用构造函数创建,每个实例都继承了原型对象上的属性和方法。

4. 实例化过程详解

当我们理解了以上内容后,我们就可以得出使用new关键字调用构造函数时,JS引擎会执行的步骤:

  • 创建一个新对象 => 将新对象的[[Prototype]]属性设置为构造函数的prototype属性 => 将构造函数内的this绑定到新对象 => 执行构造函数内的代码 => 返回新对象

5. 结语

JavaScript的面向对象编程模型与其他语言有所不同,它基于原型而非经典的类继承机制,这种模型提供了极大的灵活性,同时也要求开发者对其内部机制有深刻的理解。希望本文能够帮助你更好地掌握JavaScript中的OOP,从而在实际项目中更加游刃有余。