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的自动分号插入(ASI)机制

    浅析Javascript的自动分号插入(ASI)机制

    我们大家都知道在写java和c时,必须要在语句后加分号,否则编译通不过。而js不同,存在自动分好插入机制,下文简称ASI。它会给源代码的 token 流自动插入分号。下面这篇文章我们就来谈谈Javascript的自动分号插入(ASI)机制。
    2016-09-09
  • JavaScript之AOP编程实例

    JavaScript之AOP编程实例

    这篇文章主要介绍了JavaScript的AOP编程,以实例形式分析了javascript面向切面编程的实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • JavaScript六种继承方式总结大全

    JavaScript六种继承方式总结大全

    JavaScript中最基本的继承方式,其核心思想是利用原型让一个引用类型继承另一个引用类型的属性和方法,下面这篇文章主要介绍了JavaScript六种继承方式的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-10-10
  • 小程序实现列表倒计时功能

    小程序实现列表倒计时功能

    这篇文章主要为大家详细介绍了小程序实现列表倒计时功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-01-01
  • JavaScript中Undefined类型的使用

    JavaScript中Undefined类型的使用

    JavaScript中,Undefined类型是一个特殊且基础的数据类型, 本文就来详细的介绍一下JavaScript中Undefined类型的使用,感兴趣的可以了解一下
    2026-02-02
  • JS数组扁平化、去重、排序操作实例详解

    JS数组扁平化、去重、排序操作实例详解

    这篇文章主要介绍了JS数组扁平化、去重、排序操作,结合实例形式详细分析了JS数组扁平化、去重、排序等相关操作原理、实现技巧与注意事项,需要的朋友可以参考下
    2020-02-02
  • 详解JavaScript中的链式调用

    详解JavaScript中的链式调用

    这篇文章主要介绍了JavaScript中的链式调用的相关资料,帮助大家更好的理解和学习JavaScript,感兴趣的朋友可以了解下
    2020-11-11
  • 微信小程序之裁剪图片成圆形的实现代码

    微信小程序之裁剪图片成圆形的实现代码

    最近在开发小程序,产品经理提了一个需求,要求微信小程序换头像,用户剪裁图片必须是圆形。这篇文章主要介绍了微信小程序之裁剪图片成圆形 ,需要的朋友可以参考下
    2018-10-10
  • javascript面向对象创建对象的方式小结

    javascript面向对象创建对象的方式小结

    这篇文章主要介绍了javascript面向对象创建对象的方式,结合实例形式总结分析了javascript常见的7种创建对象的方式,需要的朋友可以参考下
    2019-07-07
  • 前端处理文本换行展示4种处理方法

    前端处理文本换行展示4种处理方法

    在处理前端显示后端传递的包含换行符的文本时,可以通过多种方法实现换行显示,这篇文章主要介绍了前端处理文本换行展示4种处理方法,这些方法帮助前端正确展示格式化文本,解决了文本堆叠的问题,需要的朋友可以参考下
    2024-10-10

最新评论