JavaScript构造函数与原型、原型链示例详解

 更新时间:2026年03月16日 09:07:11   作者:Hello--_--World  
在JavaScript中,构造函数和原型链是实现面向对象编程的核心机制,它们共同构成了JavaScript的类继承模型,这篇文章主要介绍了JavaScript构造函数与原型、原型链的相关资料,需要的朋友可以参考下

一 构造函数

在 JavaScript 中,构造函数(Constructor)是用于批量生成对象。虽然 ES6 引入了 class 关键字,但其底层逻辑依然是基于构造函数和原型链的。

1 什么是构造函数?

在 JS 中,构造函数本质上就是一个普通的函数,但为了区分,通常遵循以下约定:

  • 首字母大写(如 function Person() {…})。

  • 使用 new 关键字调用。

2 new 关键字背后的核心逻辑

当你使用 new 调用函数时,JS 引擎在后台完成了四件事:

  1. 创建一个新的空对象

  2. 将这个新对象的原型__proto__指向构造函数的 prototype 属性。

  3. 将构造函数内部的 this 绑定到这个新对象上。

  4. 如果函数没有返回其他对象,则自动返回这个新对象。

function User(name, age) {
  // 1. 这里的 this 指向新创建的对象
  this.name = name;
  this.age = age;
  
	//这个 sayHi 函数,是每个对象独有的,每个对象都会在堆中创建一个
  this.sayHi = function() {
    console.log(`你好,我是 ${this.name}`); 
  };
  // 4. 隐式返回 this
}

// 实例化对象
const user1 = new User("张三", 25);
const user2 = new User("李四", 30);

user1.sayHi(); // 输出: 你好,我是 张三

2.1原型方法优化(性能关键)

在上面的例子中,sayHi 方法直接写在构造函数里。这意味着每创建一个实例,都会在内存中复制一份该函数
改进方法:将方法写在 prototype上,所有实例共享同一个函数。

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

// 所有 Dog 的实例都共享这一个方法,节省内存
Dog.prototype.bark = function() {
  console.log(`${this.name} 在汪汪叫`);
};

const d1 = new Dog("大黄");
const d2 = new Dog("小白");
console.log(d1.bark === d2.bark); // true

2.2 构造函数的返回值

如果构造函数返回的是原始类型(数字、字符串等),会被忽略,依然返回 this 对象。

如果构造函数返回的是一个对象,则 new 的结果会变成那个返回的对象。

2.3 强制检查 new 调用

如果忘记写 new,this 会指向全局对象(浏览器中是 window),这会导致 Bug。 可以使用 new.target 来检查。

new 本身并不是一个对象,它是一个运算符(Operator)(类似于 +, -, instanceof, typeof)。 运算符不是对象,因此它没有属性。

你可能看到的 new.target 是 JavaScript 语法中唯一的元属性。这里的“点”号并不代表对象访问属性,而是一个特殊的语法结构,用来从运算符环境中提取信息。

补充:为什么叫“元属性”?

元属性(Meta Property)是指那些非对象属性。除了 new.target,在较新的 JS 规范中几乎没有类似的结构。它存在的唯一目的就是让函数内部能够感知到“外部调用者”是通过什么方式来触发我的。

function Person(name) {
  if (!new.target) {
    throw new Error("必须使用 new 关键字调用构造函数");
  }
  this.name = name;
}

2.3.1 new.target 返回的是什么?

new.target 是一个元属性(Meta Property)。它不是一个普通的变量,而是专门用来检测函数是否是通过 new 运算符调用的。

  • 如果是通过 new 调用:new.target 返回该构造函数本身。

  • 如果是普通函数调用(如 Person()):new.target 返回 undefined。

它的核心作用:

  1. 强制初始化:确保构造函数不会被当作普通函数误用。

  2. 在继承中识别子类:在父类构造函数中,new.target 指向的是真正被实例化的那个类。

class Parent {
  constructor() {
    console.log("当前创建实例的类是:", new.target.name);
  }
}

class Child extends Parent {}

new Parent(); // 输出: "当前创建实例的类是: Parent"
new Child();  // 输出: "当前创建实例的类是: Child" (即便是在父类构造函数里打印)

3 ES6 Class 写法(现代推荐)

ES6 的 class 只是构造函数的“语法糖”,结构更清晰:

class Animal {
  // 相当于以前的构造函数体
  constructor(species) {
    this.species = species;
  }

  // 相当于在 Animal.prototype 上定义方法
  eat() {
    console.log(`${this.species} 正在吃东西`);
  }
}

const cat = new Animal("猫");
cat.eat();

二 原型对象prototype 与 对象原型__proto__

1 什么是原型对象与对象原型?

  • 所有函数都有个属性叫 原型(prototype) ,它是一个对象,称之为 原型对象
  • 原型对象 中有个属性(constructor),它指回函数本身
    function Pig(name, age) {
      this.name = name
      this.age = age
    }
    
    console.log(Pig.prototype.constructor === Pig); // true

🧠 关系公式:构造函数.prototype.constructor === 构造函数

  • 所有对象 都有个属性(__proto__),它也是一个对象,并且指向的就是它构造函数原型对象
    function Pig(name, age) {
      this.name = name
      this.age = age
    }

    const peiqi = new Pig("peiqi", 18)
    console.log(peiqi.__proto === peiqi.prototype); // true

🧠 关系公式: 实例.__proto__ === 构造函数.prototype

2 为什么设计原型?有什么好处?

JavaScript 最初被设计为一种轻量级的脚本语言,没有传统类的概念。

  • 节省内存:如果没有原型,每个实例都要在内存中存储一份相同的方法(如 sayHello)。有了原型,方法只需在原型对象上存一份,所有实例共用

  • 数据共享与继承:方便实现属性和方法的继承,修改原型对象,所有实例会立即同步更新。

3 什么是原型链?

原型对象本身也是一个对象,它也有自己的对象原型(构造函数.prototype.__proto__

当你访问一个对象的属性时,如果对象本身没有这个属性,JS 引擎就会去它的 __proto__(原型对象)里找。

如果原型对象也没有,就去原型的原型里找,直到找到 Object.prototype 为止。

这种由 __proto__ 层层链接形成的链式结构,就是原型链。原型链的终点是 null

4 调用属性或方法的流程

流程遵循“就近原则”:

  1. 搜索自身:检查对象实例本身是否有该属性/方法。

  2. 搜索原型:若无,顺着 __proto__ 找到原型对象。

  3. 搜索原型链:若原型对象也没有,继续向上查找,直到 Object.prototype。

  4. 返回结果:找到则返回,直到终点还没找到则返回 undefined(方法调用则报错 is not a function)。

5 实例可以覆盖原型中的属性吗?

可以,但这不是真正的“覆盖”,而是“屏蔽(Shadowing)”。

如果你给实例添加了一个与原型同名的属性,查找流程会在第一步(搜索自身)就找到并返回,从而不再去原型上找。原型的属性依然存在,只是被“挡住”了。删除实例上的该属性后,原型上的属性会重新“露出来”。

总结

到此这篇关于JavaScript构造函数与原型、原型链的文章就介绍到这了,更多相关JS构造函数与原型、原型链内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JavaScript中数据结构与算法(二):队列

    JavaScript中数据结构与算法(二):队列

    这篇文章主要介绍了JavaScript中数据结构与算法(二):队列,队列是只允许在一端进行插入操作,另一个进行删除操作的线性表,队列是一种先进先出(First-In-First-Out,FIFO)的数据结构,需要的朋友可以参考下
    2015-06-06
  • JavaScript面向对象编程

    JavaScript面向对象编程

    暂时放弃js框架吧 开始写javascript的时候都是自己写,后来发现了prototype.js框架,发现很好用,就一直用的,他的对象创建方法被修改了,但很好用,再后来又转用jquery框架,受此框架影响,也不用自己创建类了,渐渐的竟然忘记了如何自己定义类了,猛的给一个一般方法,竟然看着别扭,混淆了很多东西,忘记了很多东西。今天回头整理下。 一下方法参考prototype.js
    2008-03-03
  • JS控制div跳转到指定的位置的几种解决方案总结

    JS控制div跳转到指定的位置的几种解决方案总结

    这篇文章主要介绍了JS控制div跳转到指定的位置的几种解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。
    2016-11-11
  • webpack构建的详细流程探底

    webpack构建的详细流程探底

    目前,几乎所有业务的开发构建都会用到 webpack 。所以下面这篇文章主要给大家介绍了关于webpack构建的详细流程的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2018-01-01
  • JS仿Base.js实现的继承示例

    JS仿Base.js实现的继承示例

    这篇文章主要介绍了JS仿Base.js实现的继承,结合具体实例形式分析了javascript扩展操作及面向对象程序设计相关实现技巧,需要的朋友可以参考下
    2017-04-04
  • JavaScript定义类和对象的方法

    JavaScript定义类和对象的方法

    这篇文章主要介绍了JavaScript定义类和对象的方法,分别以函数方式与Object类方式实现,是javascript非常重要的技巧,需要的朋友可以参考下
    2014-11-11
  • 如何在JavaScript中实现私有属性的写类方式(二)

    如何在JavaScript中实现私有属性的写类方式(二)

    这篇文章主要介绍了如何在JavaScript中实现私有属性的写类方式。需要的朋友可以过来参考下,希望对大家有所帮助
    2013-12-12
  • js如何将元素滚动到可见区域

    js如何将元素滚动到可见区域

    文章介绍了如何使用scrollIntoViewIfNeeded方法将元素滚动到可见区域,以及如何通过配置对象控制滚动行为,还提供了一个纯JavaScript的解决方案,可以实现类似的功能
    2024-12-12
  • 关于Promise 异步编程的实例讲解

    关于Promise 异步编程的实例讲解

    下面小编就为大家带来一篇关于Promise 异步编程的实例讲解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • js下判断 iframe 是否加载完成的完美方法

    js下判断 iframe 是否加载完成的完美方法

    一般来说,我们判断 iframe 是否加载完成其实与 判断JavaScript 文件是否加载完成。
    2010-10-10

最新评论