JS面向对象的核心:构造函数、原型对象和实例对象

3 阅读6分钟

JavaScript中,构造函数、原型对象以及实例对象是构建面向对象程序设计的核心组成部分。它们各自扮演着不同的角色,并且通过特定的方式相互作用,共同支撑起JavaScript独特的面向对象模型。本文将深入探讨这三者之间的关系及其重要性。

在讨论之前,先了解下如何创建一个对象。

创建对象

在JS中,创建一个对象最直接的方式是对象字面量。它简单、直接、随意,但不够灵活。

let  cao = {
    name: '小超',
}
let  fan = {
    name: '范总',
    age: 17,
}

从这代码中很明显发现,我们直接用let创建了caofan两个对象,非常简单直接。

在ES5的时候,是通过构造函数和new关键字来创建对象。该方法是基于JS的原型继承机制。

function Person(name, age) {
    console.log(this);
    this.name = name;
    this.age = age;
}
const dys = new Person('昌总', 19) // 以构造函数方式运行  产生对象
const dyf = new Person('啊威', 20)

在这,则是用构造函数和new来创建对象。

但在ES6出现以后,引入了class关键字,使得可以批量创建对象,更加直观和易于理解。虽然该方法很像Java或c语言中创建对象,但本质上还是基于原型的继承。

// class  es6
class Person {
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }
    eat() {
        console.log('吃饭');
    }
}
let wei = new Person('啊威',18);
let guo = new Person('过帅',18);

很明显,因为ES6的存在,创建对象相对之前更简单方便。

从上文中不难发现,在ES6出现之前,一直是通过构造函数和 new关键字来创建对象。既然如此,那就让我们来探究下构造函数

构造函数

在JS中,创建对象的任务是由函数承担起来的,为此就诞生了构造函数。构造函数是JavaScript中用于创建和初始化对象的一种特殊类型的函数。构造函数的首字母要大写,用来与普通函数进行区分。

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

判断函数是否是构造函数,是根据new关键字决定的。只有用new来创建对象,该函数才会是构造函数。

请看如何用new关键字创建对象:

  1. 用new关键字创建一个空对象
  2. 将这个新对象的内部[[Prototype]](即__proto__)链接到构造函数的prototype属性上
  3. 将构造函数内的this绑定到新创建的对象上
  4. 执行构造函数中的代码,初始化对象的属性或方法
  5. 返回新创建的对象.
const wen = new Person('文', 18);
console.log(wen.name, wen.age);
const chen = new Person('陈', 18);
console.log(chen.name, chen.age);

这样我们就创建了一个对象。但我们可以发现,该对象没有任何方法,如果要调用方法的话,这就涉及另一个知识点:原型。

原型(Prototype)

原型能使得JavaScript能够实现基于原型的继承。每个函数都有一个prototype属性,指向一个对象,这个对象就是该函数作为构造函数时所创建实例的原型。通过原型链,JavaScript实现了对象间的属性和方法共享。

因为有原型的存在,我们可以直接将方法定义在构造函数的prototype上,让所有通过该构造函数创建的实例共享同一个方法。

const cy = {
    name: '',
    palyBasketball: function() {
        console.log('我在打篮球');
    }
}

function Person(name,age) {
    console.log(this);
    this.name = name;
    this.age = age;
}
// 原型  cy
Person.prototype = cy;

const wu = new Person('',19);
wu.palyBasketball();
console.log(wu.__proto__ === cy);

原型的运行机制则依赖于原型链。当尝试访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript引擎会沿着原型链向上查找,直到找到该属性或到达原型链的顶端。

// 定义父类
function Animal(name) {
    this.name = name;
}

// 在Animal的原型上定义方法
Animal.prototype.speak = function() {
    console.log(this.name + ' makes a noise.');
};

// 定义子类
function Dog(name, breed) {
    Animal.call(this, name);  // 调用父类构造函数
    this.breed = breed;
}

// 设置Dog的原型为Animal的一个实例
Dog.prototype = Object.create(Animal.prototype);

// 修正constructor属性
Dog.prototype.constructor = Dog;

// 在Dog的原型上定义方法
Dog.prototype.bark = function() {
    console.log(this.name + ' barks.');
};

// 创建Dog的实例
var dog = new Dog('Rex', 'German Shepherd');

// 属性和方法的访问
dog.speak();  // 输出: Rex makes a noise.
dog.bark();   // 输出: Rex barks.

console.log(dog instanceof Dog);  // true
console.log(dog instanceof Animal);  // true

// 查看原型链
console.log(dog.__proto__ === Dog.prototype);  // true
console.log(Dog.prototype.__proto__ === Animal.prototype);  // true
console.log(Animal.prototype.__proto__ === Object.prototype);  // true
console.log(Object.prototype.__proto__);  // null

通过这个例子,展示了原型链是如何进行继承和查找的,让我们来仔细分析下。 1.原型链的构建: Dog.prototype被设置为Animal.prototype的一个实例,这意味着所有Dog实例都可以访问Animal.prototype上的方法。

2.属性查找: 当调用dog.speak()时,首先会在dog中查找方法,如果没有则沿着 dog.__proto__(即Dog.prototype)查找,如果还找不到,则依旧往上层寻找,直到找得到为止。

3.扩展: 如果在Animal.prototype上添加一个新方法,所有Animal及其子类的实例都会立即拥有这个新方法。

在这简单提下,原型和原型对象的区别。

  • 原型是一个概念,描述了JavaScript中的继承机制,它涉及如何通过原型链查找属性和方法。
  • 原型对象是具体的对象,它是函数的prototype属性所指向的对象,用于存储可以被构造函数生成的所有实例共享的方法和属性。

实例对象

实例对象是通过构造函数或者是类创建的具体对象。每个实例对象都拥有自己的属性和方法,并可以通过原型链去访问。创建实例对象在上文都有提及,就不过多赘述。现在来讲下实例对象的特性。

1.独立属性:每个实例对象都有自己的属性。这些属性可以是构造函数中直接定义的,也可以是通过其他方式动态添加的。

2.共享方法:实例对象可以通过原型链访问其构造函数的prototype上定义的方法,可以通过this指针来找到对象。这些方法在所有实例之间共享,节省了资源且提高了性能。

3.原型链:每个实例对象都有一个内部[[Prototype]]属性(通常可以通过__proto__访问),指向其构造函数的prototype。当尝试访问一个实例对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript引擎会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的顶端。

三者关系

构造函数、原型对象和实例对象三者关系可以概括如下:

  • 构造函数通过new关键字创建实例对象,把this作为指针,指向实例对象。
  • 实例对象通过_proto_来指向原型对象。
  • 原型对象通过构造函数中prototype属性进行访问和修改。

正是这三者相互关联,才形成了JS面向对象的原型继承机制。

总结

理解这些重要概念不仅能让我们了解JS面向对象的底层机制,还有助于提高我们的代码编写能力。希望本文能在你走向JS的道路上有所帮助。