“别再被原型搞晕了!轻松掌握 JavaScript 原型继承要点”

前言

JavaScript 是一门基于原型的语言,这意味着每个 JavaScript 对象都有一个原型对象,原型对象包含了一些属性和方法,这些属性和方法可以被该对象共享和继承。在本文中,我们将深入探讨 JavaScript 中原型的概念、作用和使用。

原型 (Prototype)

原型是函数具有的属性 (prototype), 它定义了构造函数制造出来的对象的公共祖先 ,通过构造函数创建的对象,可以隐式的继承函数原型上的属性和方法。

我们来看下面这段代码,输出结果会是什么?

1 复制代码
function Person() {
    this.name = '小明'
    this.age = 18
}
let p = new Person() 
let p2 = new Person() 
console.log(p === p2); //输出 'false'

虽然这两个对象都是通过 Person 构造函数创建的,但它们是独立的实例,每个实例都有自己的内存空间来存储属性和方法。因此,尽管它们的构造函数相同,但它们是两个不同的对象,所以 p === p2 不会相等。

基于上面代码我们做些改动:

1 复制代码
     function Person() {
    this.name = '小明'
    this.age = 18
}
     
Person.eat = function () {
    console.log('eating food');
}
     
let p = new Person() 
let p2 = new Person() 
p.eat()

上面代码会执行错误这是因为,当我们定义一个函数对象时,比如 Person,它实际上是一个特殊的 JavaScript 函数。在 JavaScript 中,函数也是对象,因此可以像操作普通对象一样操作函数。

在上面代码中,通过 Person.eat = function() {...} 的方式,给 Person 函数对象添加了一个 eat 方法。这种方式将 eat 方法直接添加到了 Person 函数对象上,并且该方法是一个静态方法(类方法),它不会被 Person 的实例继承。

因此,我们尝试通过 p.eat() 调用 eat 方法时,会导致错误,因为 pPerson 的一个实例,而 eat 方法并不属于实例,无法通过实例直接访问。

要调用静态方法 eat,应该使用 Person.eat() 的方式来调用,这样就可以正常输出 "eating food"。

scss 复制代码
Person.eat(); // 输出:eating food

如果想让实例能够访问该方法,可以将方法定义在 Person.prototype 上。原型对象是构造函数 Person 的一个属性,它包含了可以被构造函数的实例共享的属性和方法。

下面是一个修改后的代码示例:

ini 复制代码
function Person() {
    this.name = '小明';
    this.age = 18;
}

Person.prototype.eat = function () {
    console.log('eating food');
};

let p = new Person();
let p2 = new Person();
p.eat(); // 输出:eating food

当我们使用 new 关键字创建实例对象时,实例对象会继承构造函数的原型上的方法。在第一个示例中,我们通过 Person.prototype.eat 的方式将 eat 方法添加到了 Person 构造函数的原型上。

因此,当我们通过 new Person() 创建实例对象 p 时,p 实际上会继承 Person 构造函数的原型上的 eat 方法。这样,调用 p.eat() 时,实际上是找到了 Person 构造函数原型上的 eat 方法,并成功输出 "eating food"。

换句话说,实例对象 p 继承了构造函数 Person 原型上的方法,所以能够成功调用 eat 方法并输出结果。

隐式原型(proto)

每个 JavaScript 对象都有一个隐式原型属性 __proto__,它指向了该对象的原型。如果原型也是一个对象,那么这个对象也具有隐式原型,指向它的原型。这样一直沿着原型链查找下去,直到找到为止。

我们可以使用 __proto__ 属性来访问对象的原型:

javascript 复制代码
function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function() {
  console.log('Hello, ' + this.name);
};

var person1 = new Person('Alice');
console.log(person1.__proto__ === Person.prototype); // 输出:true

当我们创建一个函数时,JavaScript 会自动为该函数创建一个原型对象,这个原型对象被赋值给构造函数的 prototype 属性。在这个例子中,Person.prototype 就是 Person 构造函数的原型对象。

当我们使用 new 关键字创建一个实例对象时,JavaScript 会执行以下操作:

  1. 创建一个新的空对象。
  2. 将该空对象的 __proto__ 属性指向构造函数的原型对象。这样,实例对象就能够访问到原型对象上定义的属性和方法。
  3. 执行构造函数,并将实例对象作为构造函数的上下文(this)。
  4. 如果构造函数没有显式地返回一个对象,则返回该实例对象。

在这个例子中,我们通过 new Person('Alice') 创建了一个名为 person1 的实例对象。这个实例对象的 name 属性被设置为 'Alice'

然后,我们使用 console.log(person1.__proto__ === Person.prototype) 进行判断。person1.__proto__ 表示实例对象 person1 的原型,而 Person.prototype 表示 Person 构造函数的原型对象。根据 JavaScript 对象的原型链机制,实例对象的 __proto__ 属性指向其构造函数的原型对象。

因此,person1.__proto__ 指向 Person.prototype,所以判断结果为 true。这说明 person1 实例对象的原型是 Person.prototype

也就是说:实例对象的隐式原型 === 构造函数的显示原型

原型链

原型链是 JavaScript 中实现对象继承的一种机制。每个对象都有一个隐式原型(__proto__)属性,它指向该对象的原型对象(prototype)。而原型对象本身也可以有自己的原型对象,这样就形成了一个原型链。

下面是一个示例代码:

javascript 复制代码
// 构造函数
function Person(name) {
  this.name = name;
}

// 在原型对象上添加方法
Person.prototype.sayHello = function() {
  console.log('Hello, my name is ' + this.name);
};

// 创建对象实例
var person1 = new Person('Alice');
var person2 = new Person('Bob');

// 调用方法
person1.sayHello(); // 输出: Hello, my name is Alice
person2.sayHello(); // 输出: Hello, my name is Bob

在这个例子中,我们定义了一个构造函数 Person,它接受一个 name 参数,并将其赋值给实例对象的 name 属性。然后,在 Person.prototype 上添加了一个 sayHello 方法。

通过 new Person('Alice') 创建了一个名为 person1 的对象实例。当我们调用 person1.sayHello() 时,JavaScript 首先检查 person1 自身是否具有 sayHello 方法,由于没有找到,它会沿着原型链的 __proto__ 属性继续查找,最终找到了 Person.prototype.sayHello 方法并执行。

同样,通过 new Person('Bob') 创建了另一个对象实例 person2,并成功调用了 person2.sayHello() 方法。

这样,所有通过 Person 构造函数创建的对象实例都可以共享 Person.prototype 上定义的方法,实现了方法的复用和继承。

小结: 通过原型链,对象可以继承其原型对象中的属性和方法。当我们对对象进行属性或方法访问时,JavaScript 首先在对象本身查找,然后在其原型对象上查找,再根据原型对象的原型继续向上查找,直到找到所需的属性或方法。这种机制使得多个对象可以共享同一个原型对象,并实现代码的复用和继承。

总结

当我们访问一个对象的属性或方法时,如果对象本身没有定义该属性或方法,JavaScript 就会沿着原型链向上查找,直到找到相应的属性或方法或者到达原型链的末端(即 Object.prototype)为止。

这里再次总结一下显示原型和隐式原型的关系:

  • 每个函数都有一个 prototype 属性,指向该函数的原型对象,用于定义函数的公共属性和方法。
  • 每个对象都有一个隐式原型属性 __proto__,它指向了该对象的原型,通过原型链实现属性和方法的共享和继承。

通过掌握显示原型和隐式原型的概念,我们将更好地理解 JavaScript 中的原型继承。这是 JavaScript 中重要且强大的特性之一,能够帮助我们实现代码的复用和继承。

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax