深入理解 JavaScript 对象、原型链与继承机制

作者:代码拾荒

时间:2026-10-23

浏览:538

本文深入解析 JavaScript 基于对象的特性,区分基本类型与对象。详细阐述原型链 [[proto]] 的工作机制、函数作为对象的特殊性,以及通过构造器创建对象的全过程。重点讲解如何实现类继承,包括处理原型链、构造函数调用及 constructor 属性的修复,提供完整的继承扩展函数实现。

文章目录

JavaScript 对象基础与定义

1、 Javascript中的对象


JavaScript 被定义为一种基于对象的编程语言,而非严格的面向对象语言。这是因为 JavaScript 原生实现了封装,但未直接提供继承和多态机制。既然涉及对象,需澄清“JavaScript 中一切皆对象”的说法并不完全准确。虽然对象在 JS 中无处不在,包括构造函数本身也是对象,但存在数字、字符串、布尔值、null 和 undefined 等基本数据类型,它们并非对象。尽管这些类型拥有方法,但它们不被视为对象,原因在于其拥有的方法是不可变的,而对象的属性应当是可变的。


(1)JavaScript中的对象是可变的键控集合(keyed collections) (此定义来自老道的那本书的第三章)


(2)JavaScript中的对象是无序(unordered)的属性集合,这些属性可以含有简单的数据类型、对象、函数;保存在一个对象属性中的函数也被称为这个对象的方法。 (来自ECMA-262 的4.3.3)(注:这里所说的属性是可以在js脚本中创建和访问的(我们称之为显性属性),不包括系统为对象自动分配的内部属性)


之所以基本数据类型不是对象,核心原因在于其拥有的方法是不可变的,而对象的属性设计为可修改。


原型链 [[proto]] 机制

2、 对象中的原型链[[proto]]


JavaScript 在创建每个对象时,会自动为其分配一个内部原型属性 [[proto]],用于指向其原型对象。JS 正是通过每个对象的 [[proto]] 属性来实现继承关系。该属性是内部的,无法在 JS 脚本中直接访问或修改,且在对象创建时由系统自动设定。


当访问对象属性时,若该属性在当前对象中不存在,系统会沿 [[proto]] 指向的原型对象属性中查找。若找到则返回,否则继续沿 [[proto]] 链向下查找,直到 [[proto]] 连接为 null 时停止。


函数作为对象及其属性

3、 函数也是对象


JavaScript 中的函数本身即为对象(常称函数对象),且是 JS 中最重要的对象。其重要性体现在两方面:一是扮演其他语言中函数的角色,可被调用并接收参数;二是作为对象的构造器(constructor),结合 new 操作符创建对象。


既然函数是对象,它必然具备对象的所有性质,包括创建时设定的原型链 [[proto]] 属性。


函数对象与普通对象的区别在于,根据 ECMA-262 第 13.2 节,函数创建时系统默认赋予 [[call]] 和 [[constructor]] 两个内部属性。当函数作为普通函数调用(如 myFunc())时,执行 [[call]] 属性;当作为构造器调用(如 new myConst())时,执行 [[constructor]] 属性。此外,函数创建时系统还会默认创建一个显示属性 prototype,并赋值为


this.prototype = {constructor:this}


具体内容可参考相关书籍第五章。该 prototype 属性旨在支持将函数作为构造器实现继承,且可在 JS 脚本中访问和修改。需严格区分对象中的 [[proto]] 属性与函数对象中的 prototype 属性,二者常被混淆。


对象创建与构造器过程

4、 对象的创建


在 JS 中有两种创建对象的方法:字面量法和构造器法。


var Person = {


“first_name”:'liang',


‘last_name':'yang'


  }

另一种方法是通过构造器来创建


var my = new Person(‘liang','yang');


其实第一种方式的创建过程相当于调用Object构造器来实现,如下。


var Person = new Object();


Person.first_name = ‘liang';


Person.last_name = ‘yang'


因此,可将 JS 中所有对象的创建统一视为使用构造器实现。下面详细说明构造器创建对象的过程:


第一步,先创建一个空的对象(既没有任何属性),并将这个对象的[[proto]]指向这个构造器函数的prototype属性对象


第二步,将这个空的对象作为this指针传给构造器函数并执行


第三步,如果上面的函数返回一个对象,则返回这个对象,否则返回第一步创建的对象


第四步,把函数当做一个类来使用


由上述步骤可知,函数对象的 prototype 通常指向一个普通对象,而非函数对象。该普通对象中的属性可由构造器创建的对象访问。基于此,可设计代码:若函数代表一个类,则构造器生成的对象为该类实例。实例对象的属性和方法应置于构造器函数的 prototype 中,类的静态方法直接作为函数对象的属性,而函数体本身即为面向对象语言中的构造函数。需注意区分“构造函数”(其他 OOP 语言中类的构造函数)与“构造器函数”(JS 中作为构造器使用的函数)。


在第 3 节中提到,每个函数的 prototype 对象总包含一个 constructor 属性,指向该函数本身。加之由该函数生成的每个对象的 [[proto]] 均指向构造器函数的 prototype 对象,因此通过 [[proto]] 链,每个由构造器生成的对象都拥有 constructor 属性,指向生成它的构造器函数。借此可判断对象由哪个构造器生成。


类继承的实现与扩展函数

5、 函数继承(类继承)


至此可讨论 JS 中的继承。假设要从 superClass 继承到子类 subClass,需明确实现步骤。


为使 subClass 生成的对象能访问 superClass 生成的对象中的属性,可使 subClass.prototype 为 superClass 构造函数生成的对象。


subclass.prototye = new superClass();


但问题在于,根据第 4 节所述,new superClass() 不仅复制了 superClass.prototype 中的所有方法,还执行了 superClass() 函数(即类中的构造函数)。通常应在子类构造函数中调用父类构造函数以实现初始化。为此,可创建一个构造函数为空、但原型与 superClass 原型一致的函数,并使 subClass.prototype 指向该函数生成的对象。


var F = function() {};


F.prototype = superClass.prototype;


subClass.protptype = new F();


这样可在不调用构造函数的同时完成属性复制。但修改 subClass 的 prototype 属性会删除其中的 constructor 属性,导致无法识别对象由哪个构造器生成。可重新赋值


subClass.prototype.constructor = subClass;


如此可解决属性复制问题。但新问题出现:在 subClass 中无法得知其父类是哪个构造器函数,从而无法在构造函数中调用父类构造函数。为此,可为 subClass 添加属性以指明其父类


subClass.superClass = superClass.prototype;


这样即可在子类构造函数中使用 subClass.superClass.constructor 访问父类构造函数。最后,将上述思路封装为函数


myPro.extend = function (subClass,superClass) {


var F = function() {};


F.prototype = superClass.prototype;


subClass.protptype = new F();


subClass.prototype.constructor = subClass;


subClass.superClass = superClass.prototype;


superClass.prototype.constructor = superClass;


  }

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章