JavaScript构造函数与原型、原型链示例详解
作者:Hello--_--World
一 构造函数
在 JavaScript 中,构造函数(Constructor)是用于批量生成对象。虽然 ES6 引入了 class 关键字,但其底层逻辑依然是基于构造函数和原型链的。
1 什么是构造函数?
在 JS 中,构造函数本质上就是一个普通的函数,但为了区分,通常遵循以下约定:
首字母大写(如 function Person() {…})。
使用 new 关键字调用。
2 new 关键字背后的核心逻辑
当你使用 new 调用函数时,JS 引擎在后台完成了四件事:
创建一个新的空对象。
将这个新对象的原型
__proto__指向构造函数的prototype属性。将构造函数内部的 this 绑定到这个新对象上。
如果函数没有返回其他对象,则自动返回这个新对象。
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。
它的核心作用:
强制初始化:确保构造函数不会被当作普通函数误用。
在继承中识别子类:在父类构造函数中,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 调用属性或方法的流程
流程遵循“就近原则”:
搜索自身:检查对象实例本身是否有该属性/方法。
搜索原型:若无,顺着
__proto__找到原型对象。搜索原型链:若原型对象也没有,继续向上查找,直到 Object.prototype。
返回结果:找到则返回,直到终点还没找到则返回 undefined(方法调用则报错 is not a function)。
5 实例可以覆盖原型中的属性吗?
可以,但这不是真正的“覆盖”,而是“屏蔽(Shadowing)”。
如果你给实例添加了一个与原型同名的属性,查找流程会在第一步(搜索自身)就找到并返回,从而不再去原型上找。原型的属性依然存在,只是被“挡住”了。删除实例上的该属性后,原型上的属性会重新“露出来”。
总结
到此这篇关于JavaScript构造函数与原型、原型链的文章就介绍到这了,更多相关JS构造函数与原型、原型链内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
