0

0

掌握JavaScript原型链的核心概念与继承机制

betcha

betcha

发布时间:2025-09-19 11:23:01

|

934人浏览过

|

来源于php中文网

原创

JavaScript原型链通过委托实现继承,对象查找属性时会沿原型链向上搜索。每个对象的[[Prototype]]指向其原型,如构造函数实例的原型指向构造函数的prototype属性,而prototype默认包含constructor属性指回构造函数。使用new创建实例时,实例的[[Prototype]]被设为构造函数的prototype,从而实现方法共享。ES6的class是原型继承的语法糖,本质仍是基于原型链的委托机制,不同于传统类继承的复制模式。直接覆盖prototype会丢失constructor连接,需手动修复或避免覆盖。原型链广泛用于共享方法、实现继承(如Object.create)和Polyfill,但需注意长链带来的查找开销及动态修改原型可能引发的性能与稳定性问题。遍历属性时应使用hasOwnProperty过滤继承属性,确保正确性。

掌握javascript原型链的核心概念与继承机制

JavaScript原型链是其实现继承的核心机制,它不像传统面向对象语言那样通过类来复制属性,而是通过一种链式查找的方式,让对象能够访问其原型上的属性和方法。本质上,每个JavaScript对象都有一个指向另一个对象的内部链接,这个被链接的对象就是它的原型。当试图访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript就会沿着这个原型链向上查找,直到找到该属性或到达原型链的顶端(

null
)。这种基于委托的模式,让JavaScript的继承显得非常灵活且动态。

解决方案

要真正掌握原型链,我们得从几个核心概念入手。在我看来,理解它首先要抛开一些传统OOP语言的固有思维。JavaScript对象,它们生来就不是“类”的实例,而是直接从其他对象那里“借用”或“委托”功能。

每个对象在内部都有一个

[[Prototype]]
属性,它指向这个对象的原型。我们不能直接访问
[[Prototype]]
,但可以通过
Object.getPrototypeOf()
方法来获取,或者在非严格模式下通过
__proto__
属性(这是一个历史遗留且不推荐在生产代码中直接使用的属性,但对于理解概念很有帮助)来查看。

当你创建一个对象,比如

const obj = {};
,它的
[[Prototype]]
默认会指向
Object.prototype
Object.prototype
又是原型链的顶端(除了
null
),它包含了所有对象通用的方法,比如
toString()
hasOwnProperty()
等。

立即学习Java免费学习笔记(深入)”;

当你尝试访问

obj.method()
时,JavaScript引擎会:

  1. 检查
    obj
    自身是否有
    method
    属性。
  2. 如果没有,它会沿着
    obj
    [[Prototype]]
    向上查找,也就是在
    Object.prototype
    上寻找
    method
  3. 如果找到了,就执行它。如果没找到,并且原型链上已经没有其他对象了(即到达
    null
    ),就会返回
    undefined
    或抛出错误。

继承的实现,很大程度上就是通过设置对象的

[[Prototype]]
来完成的。比如,当我们使用构造函数来创建对象时:

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

Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}`);
};

const alice = new Person('Alice');
alice.sayHello(); // 输出: Hello, my name is Alice

这里

Person
是一个函数,但当它作为构造函数被
new
调用时,
alice
这个新创建的对象的
[[Prototype]]
就会被设置为
Person.prototype
。这样,
alice
就可以访问
Person.prototype
上定义的
sayHello
方法了。所有由
Person
构造的实例,都会共享
Person.prototype
上的方法,这不仅节省内存,也体现了原型继承的精髓。

JavaScript原型链与传统类继承有何本质区别

这可能是许多从Java或C++背景转过来的开发者最容易感到困惑的地方。在我看来,最核心的区别在于“复制”与“委托”。

传统类继承,比如Java,当你声明一个类

B extends A
时,类
B
会“复制”或拥有类
A
定义的所有属性和方法的副本(当然,这背后有更复杂的内存布局和虚函数表机制,但从概念上讲,你可以把它想象成一种复制)。每个实例都有自己的数据成员,方法则指向类定义好的代码块。这是一种“is-a”(是一个)的关系,
Dog
是一个
Animal

而JavaScript的原型链继承,它更像是一种“委托”或“共享”。当你创建一个对象,并让它的原型指向另一个对象时,它并没有复制那个原型上的属性和方法。相反,它只是在自己的属性查找失败时,把这个查找请求“委托”给它的原型去完成。所以,这更像是一种“behaves-like”(表现得像)或“delegates-to”(委托给)的关系。一个

Dog
对象可以“委托”
Animal.prototype
来处理
eat()
方法,如果
Dog
自己没有
eat()
的话。

这种委托机制带来了极大的灵活性。原型对象本身可以在运行时被修改,这意味着所有依赖这个原型的对象,其行为也会动态地改变。这在类继承中是不可想象的,因为类一旦定义,其结构通常是相对固定的。ES6引入的

class
关键字,虽然看起来像传统类,但它本质上只是JavaScript原型继承的语法糖,背后依然是原型链在运作。理解这一点,能帮助我们更深入地理解JavaScript的面向对象特性。

如何理解
constructor
属性与原型链的关系?

constructor
属性在原型链中扮演了一个有趣且有时会让人迷惑的角色。简单来说,每个函数(包括用作构造函数的函数)都有一个
prototype
属性,这个
prototype
属性是一个对象,并且这个对象默认会包含一个
constructor
属性,这个
constructor
属性指回它所属的那个函数本身。

举个例子:

function Car(make) {
  this.make = make;
}

console.log(Car.prototype.constructor === Car); // true

当我们使用

new Car('Honda')
创建一个实例
myCar
时,
myCar
[[Prototype]]
会指向
Car.prototype
。现在,如果你尝试访问
myCar.constructor

const myCar = new Car('Honda');
console.log(myCar.constructor === Car); // true

这里发生了什么?

myCar
自身并没有
constructor
属性。所以,JavaScript引擎会沿着原型链向上查找。它会在
myCar
[[Prototype]]
上找到
constructor
属性,而
myCar.[[Prototype]]
正是
Car.prototype
Car.prototype.constructor
指向
Car
函数,所以
myCar.constructor
最终返回
Car

Kite
Kite

代码检测和自动完成工具

下载

这个机制在判断一个实例是由哪个构造函数创建时非常有用。然而,这里有个常见的“坑”:如果你完全覆盖了一个构造函数的

prototype
对象,而没有重新设置
constructor
属性,那么这个连接就会断裂。

function Bike(brand) {
  this.brand = brand;
}

// 错误示范:直接覆盖prototype,没有保留constructor
Bike.prototype = {
  getBrand: function() {
    return this.brand;
  }
};

const myBike = new Bike('Giant');
console.log(myBike.constructor === Bike); // false (通常会是 Object)
console.log(myBike.constructor); // ƒ Object() { [native code] }

在这种情况下,

myBike.constructor
会指向
Object
,因为
myBike.[[Prototype]]
(即我们新设置的
{ getBrand: ... }
对象)的
[[Prototype]]
Object.prototype
,而
Object.prototype
上的
constructor
指向
Object
。为了避免这种情况,我们通常会手动把
constructor
指回来:

function Scooter(model) {
  this.model = model;
}

Scooter.prototype = {
  constructor: Scooter, // 手动修复constructor指向
  getModel: function() {
    return this.model;
  }
};

const myScooter = new Scooter('Vespa');
console.log(myScooter.constructor === Scooter); // true

或者更推荐的做法是,不要直接覆盖整个

prototype
对象,而是在其上添加属性:

function Skateboard(type) {
  this.type = type;
}

Skateboard.prototype.getType = function() { // 直接在原型上添加方法
  return this.type;
};

const mySkateboard = new Skateboard('Longboard');
console.log(mySkateboard.constructor === Skateboard); // true

理解

constructor
属性如何通过原型链查找,对于正确使用和维护JavaScript的继承关系至关重要。

实际开发中,原型链有哪些常见的应用场景与性能考量?

在日常的JavaScript开发中,原型链并非只是一个理论概念,它无处不在,并且深刻影响着我们代码的结构和性能。

常见的应用场景:

  1. 共享方法与属性: 这是原型链最直接也是最重要的应用。将方法定义在构造函数的

    prototype
    上,而不是
    this
    上,可以确保所有实例共享同一个方法,从而节省内存。

    // 假设我们有成千上万个User实例
    function User(name, email) {
      this.name = name;
      this.email = email;
      // 如果在这里定义 greet 方法,每个实例都会有一个自己的副本
      // this.greet = function() { console.log(`Hello, ${this.name}`); };
    }
    
    // 将方法定义在原型上,所有User实例共享同一个greet方法
    User.prototype.greet = function() {
      console.log(`Hello, ${this.name}`);
    };
    
    const user1 = new User('Alice', 'alice@example.com');
    const user2 = new User('Bob', 'bob@example.com');
    user1.greet(); // Hello, Alice
    user2.greet(); // Hello, Bob
    // 实际上 user1.greet 和 user2.greet 指向的是同一个函数对象
    console.log(user1.greet === user2.greet); // true

    这对于内置对象也一样,比如

    Array.prototype.map
    String.prototype.toUpperCase
    等,它们都是定义在原型上的,所有数组和字符串实例都可以直接调用。

  2. 实现继承: 无论是ES5时代的组合继承、寄生组合继承,还是ES6的

    class extends
    语法糖,它们底层都是通过操作原型链来实现继承关系的。
    Object.create()
    也是一个非常强大的工具,可以直接创建一个新对象,并指定其原型。

    const animal = {
      eats: true,
      walk() {
        console.log("Animal walks");
      }
    };
    
    const rabbit = Object.create(animal); // rabbit 的原型是 animal
    rabbit.jumps = true;
    rabbit.walk(); // Animal walks (方法从 animal 原型继承)
  3. Polyfills和功能扩展: 虽然不推荐直接修改

    Object.prototype
    或其他内置对象的
    prototype
    (因为它可能导致全局污染和意外行为),但在某些受控的环境下,例如为老旧浏览器提供新功能的Polyfill,会向
    Array.prototype
    String.prototype
    添加方法。例如,为不支持
    Array.prototype.flat
    的浏览器添加此功能。

性能考量:

  1. 属性查找开销: 原型链越长,查找一个不存在于当前对象上的属性所需的时间就越长,因为它需要遍历更多的原型对象。在现代JavaScript引擎中,这种开销通常可以忽略不计,因为引擎做了大量的优化。然而,如果你的原型链设计得异常复杂或过长,理论上确实会增加查找时间。

  2. 动态修改原型: JavaScript允许在运行时修改对象的原型。当你修改一个原型对象时,所有继承自该原型的实例都会立即反映出这些改变。这既是原型链的强大之处,也可能成为潜在的性能问题或bug源。频繁或无意地修改共享原型,可能会导致难以预测的行为,尤其是在大型应用中。

  3. hasOwnProperty
    的使用: 当你需要区分一个属性是对象自身的还是从原型链上继承而来时,
    Object.prototype.hasOwnProperty.call(obj, prop)
    obj.hasOwnProperty(prop)
    就变得非常重要。它能有效避免遍历原型链,只检查对象自身的属性。这对于迭代对象属性(例如使用
    for...in
    循环)时,过滤掉继承属性尤其有用。

总的来说,原型链是JavaScript的基石,深入理解它不仅能帮助我们写出更高效、更优雅的代码,也能更好地理解各种库和框架的内部机制。它提醒我们,JavaScript的面向对象思维与传统语言有所不同,更侧重于对象间的委托和共享。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
es6新特性
es6新特性

es6新特性有:1、块级作用域变量;2、箭头函数;3、模板字符串;4、解构赋值;5、默认参数;6、 扩展运算符;7、 类和继承;8、Promise。本专题为大家提供es6新特性的相关的文章、下载、课程内容,供大家免费下载体验。

103

2023.07.17

es6新特性有哪些
es6新特性有哪些

es6的新特性有:1、块级作用域;2、箭头函数;3、解构赋值;4、默认参数;5、扩展运算符;6、模板字符串;7、类和模块;8、迭代器和生成器;9、Promise对象;10、模块化导入和导出等等。本专题为大家提供es6新特性的相关的文章、下载、课程内容,供大家免费下载体验。

195

2023.08.04

JavaScript ES6新特性
JavaScript ES6新特性

ES6是JavaScript的根本性升级,引入let/const实现块级作用域、箭头函数解决this绑定问题、解构赋值与模板字符串简化数据处理、对象简写与模块化提升代码可读性与组织性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

221

2025.12.24

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

422

2023.08.02

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

235

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

437

2024.03.01

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

56

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

52

2025.11.27

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 4.2万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.5万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号