JavaScript对象与继承机制详解:原型链与new陷阱

作者:小括号

时间:2026-10-30

浏览:328

本文深入解析JavaScript的对象模型与继承机制,澄清基于类继承的常见误解。详细讲解对象本质、new运算符的行为差异及原型链工作原理,对比构造函数指定原型、直接修改__proto__与Object.create三种继承方式,帮助开发者掌握JavaScript函数式与基于原型的编程特性。

文章目录

JavaScript对象与继承机制详解:原型链与new陷阱

JavaScript是我接触的第二门编程语言,大一暑假时曾在图书馆阅读过一本中文编写的JavaScript程序设计书籍。由于当时编程基础薄弱,且该书未深入阐述JavaScript的编程机制,甚至包含一些误导性内容,导致我对JavaScript长期存在误解,误以为它仅是一门在浏览器中运行的、类似Java的面向对象语言。本文旨在澄清JavaScript中那些极具迷惑性且易被误解的核心概念。受限于作者经验与水平,文中难免有疏漏,恳请批评指正。

JavaScript的对象

JavaScript对象的本质

对象是什么

JavaScript代码中随处可见new关键字,这容易让人误以为JavaScript是像Java那样基于类继承的语言。然而事实并非如此,JavaScript中并不存在“类”的概念。那么,JavaScript的对象究竟是什么?从某种意义上讲,JavaScript的对象类似于Python中的字典(即哈希表),本质上是由键值对构成的集合:


me={
  "fisrtName" : "seek",
  "lastName" : "truth" ,
  "getName" : function(){
    return this.firstName+this.lastName; //this相当于指向这个对象的指针
  }
}

这一概念初看令人困惑,但经过实际使用后会发现其合理性。我们可以像Python那样使用[]运算符来获取元素,也可以使用.操作符来获取元素:


me.firstName // => seek
me["lastName"] //=> truth
me.getName() // => seektruth

new运算符的行为与陷阱

new运算符

既然JavaScript中没有类,那么new运算符的作用是什么?这是JavaScript设计中最为令人误解的部分之一。JavaScript是一门函数式编程语言,函数在其中是一等公民,且函数本身也是对象。当函数对象被创建时,会被添加调用属性。JavaScript函数的调用方式有两种:一种是使用new关键字调用,另一种是不使用new关键字调用。前者返回一个对象,后者返回return语句中的内容。考虑以下函数示例:


function Obj(name){
  this.name=name;
  return name;
}

如果我们使用new运算符来调用该函数:

obj = new Obj("seektruth") //obj会是一个对象:{"name": "seektruth"}

如果我们直接调用(不使用new):

obj = Obj("seektruth") //obj会是一个字符串:"seektruth"

这种设计确实容易让人困惑,调用时需谨慎判断是否使用new。通常,建议使用new关键字调用的函数会采用大写开头命名。

更令人困惑的情况是,如果函数返回的是一个对象:


function Obj(name){
  this.name=name;
  return {};
}

此时,无论是否使用new运算符调用,都会返回return语句中的值:


new Obj("seektruth") //=> {}
Obj("seektruth") //=> {}

这种设计确实令人费解。

对象继承

基于原型链的继承机制

原型

前文已提及JavaScript中没有类,那么它是如何实现继承的呢?答案是通过原型链。在JavaScript中,每个对象都有一个原型。创建对象时,若未特别说明,对象继承的原型是Object.prototype;函数对象则继承Function.prototype(Function.prototype本身也继承Object.prototype):


Object.prototype // => {}
Function.prototype // => [Function]

我们可以通过对象的__proto__属性来查看对象的原型:


a={}
a.__proto__ // => {}

JavaScript通过指定对象的原型来实现继承。指定原型主要有三种方式:一是在构造函数中指定原型,二是直接修改对象的__proto__属性,三是利用Object.create函数。下面依次介绍:

三种指定原型的方式

在构造函数当中指定原型

我们可以在构造函数中指定对象的原型:


me={
  "firstName" : "seek",
  "lastName" : "truth" ,
  "getName" : function(){
    return this.firstName+this.lastName; //this相当于指向这个对象的指针
  }
}

function Obj(name){
  this.firstName = name;
  this.__proto__ = me; //指定原型为me对象
}

指定原型后,新建的对象即可访问原型的属性:


obj = new Obj("foo"); // => { firstName: 'foo' }
obj.firstName // => foo
obj.lastName // => truth
obj.getName() // => "footruth"

访问对象属性时,首先会在该对象自身中查找,若未找到则回溯到原型中查找,直至Object.prototype。如果在子对象中重写了原型中的属性(或方法),实际使用时子对象的定义会覆盖原型的定义,这类似于基于类的语言中的函数重载。

注意,如果原型me对象的lastname属性发生更改,由于obj对象是从原型中查找该属性,因此obj对象的lastname属性也会随之改变:


me.lastName = "me"
obj.lastName // => "me"
obj.getName() // => "foome"

直接改变对象的原型

我们也可以直接指定(或修改)对象的原型:


obj2 = {}
obj2.__proto__ = me
obj2.firstName // => seek
obj2.lastName // => "me"
obj2.getName() // => "seekme"

使用Object.create函数

尽管前两种方法可以解决问题,但写法并不优雅。因为JavaScript并非基于类的语言,第一种写法易产生误解。《JavaScript语言精粹》作者Crockford认为JavaScript中不应出现new关键字,而推荐使用Object.create函数基于原型创建对象。Object.create函数的用法很简单:


obj3 = Object.create(me) // 以me为原型创建新的对象
obj3.firstName // => seek
obj3.lastName // => "me"
obj3.getName() // => "seekme"

obj3 = Object.create(me) 与 obj2 = {}; obj2.proto = me 是等价的,但前一种写法更优雅且易于理解。

总结

总结

JavaScript作为一门基于原型和函数式的编程语言,在设计上既有优雅与强大之处,也存在不少糟粕与陷阱,正因如此,它也是被误解最多的语言之一。通过学习JavaScript的对象与继承机制,我的理解得到了显著提升。

以上这篇浅谈JavaScript对象与继承就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持本站。

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章