普通视图

发现新文章,点击刷新页面。
今天 — 2025年11月20日首页

揭秘 JS 继承的 “戏精家族” :原型、原型链与 new

2025年11月20日 11:00

前言

各位 前端er 朋友们,要搞懂 JS 的面向对象和继承逻辑,绕不开 原型(prototype)、隐式原型(proto)、原型链 这三个核心概念,而 new 关键字 正是串联起它们、实现实例创建的关键 “桥梁”。看到这里就已经觉得很绕了吧,没错。But!这些概念看似抽象,实则是 JS 引擎优化属性复用、实现继承的底层设计——就像家族里各有分工的成员,各司其职又紧密配合,才撑起了 JS 继承的 “大戏”。

一、函数的 “天赋”——prototype(显示原型)

咱先说说prototype,它就像函数天生带的 “天赋”,每个函数一出生就自带这个对象属性。你可以把它理解成一个 “公共仓库”,往里面放的属性和方法,所有通过这个函数创建的实例都能 “共享”

举个例子:

Array.prototype.abc = function(){
    return 'abc';
}
const arr = [];
console.log(arr.abc());

咱们给Arrayprototype(数组的 “公共仓库”)加了个abc方法,然后创建一个空数组arr,它就能直接调用这个方法。这就是因为实例对象的隐式原型和构造函数的显示原型是相通的,也就是说:

实例对象的隐式原型 == 构造函数的显示原型

结果也肯定了我们的结论,输出abc:

image.png

二、对象的 “隐形翅膀”—— proto(隐式原型)

每个对象(注意是所有对象,包括函数创建的实例)都有个__proto__,它就像 “隐形翅膀”,悄悄连接着自己的 “原型长辈”。V8 引擎在找属性的时候,是个 “势利眼”—— 先找对象自己的显示属性,找不到就顺着__proto__(隐式原型)去 “原型长辈” 那里扒拉。其实也很容易理解,我们找东西肯定先找放在桌子上看得见的,再去桌子的抽屉里面找。

咱还是拿su7举例子:

Car.prototype.name = 'su7-Ultra';
function Car(color){
    this.color = color; 
}
const car1 = new Car('pink');
console.log(car1.name); 

car1自己只有color属性,但它能通过__proto__找到Car.prototype里的name。这就是因为实例对象的__proto__ === 构造函数的 prototype,相当于car1.__proto__直接指向了Car.prototype,所以能拿到里面的name~

输出结果:

image.png

成功输出了我们的su7-Ultra

三、“造人机器” new 关键字的骚操作

new是啥,它干了啥呢?new关键字就像个 “造人机器”,它创建实例的过程堪称 “步骤大师”,咱们拆解一下:

  1. 创建空对象:先造一个 “空壳子” 对象,比如new Car()时,先弄一个{}
  2. 绑定 this:把构造函数里的this指向这个空对象,相当于告诉构造函数:“接下来给这个空壳子塞东西哈!”
  3. 执行构造函数代码:比如Car里的this.color = color,就是往空对象里加属性。
  4. 连接原型:把空对象的__proto__直接赋值为构造函数的prototype,让实例和 “公共仓库” 打通。
  5. 返回对象:最后把这个 “装修好” 的对象返回出去。

上代码更清晰:

function Car(){ 
    // const obj = {};  // 步骤1:创建空对象
    this.name = 'su7';  // 步骤2,3
    // obj.__proto__ = Car.prototype; // 步骤4:连接原型
    // return obj; // 步骤5:返回对象
}
const car = new Car();
console.log(car.constructor); // 能找到构造函数Car,因为原型链连起来了

结果也在我们意料之中:

image.png

这么一拆解,是不是觉得new其实就是个 “流水线包工头”,把创建对象的步骤安排得明明白白~

四、原型链:JavaScript 的 “族谱”

原型都搞定了,那原型链也就是顾名思义了。原型链就是把这些__proto__prototype串起来的 “族谱”。V8 找属性时,会沿着这个 “族谱” 往上扒,直到扒到null(族谱的 “老祖宗”,再往上没了)为止。

看这段 “祖孙三代” 的代码:

Grand.prototype.house = function(){
    console.log('四合院');
}
function Grand() {
    this.card = 10000;
}
Parent.prototype = new Grand(); // {card: 10000}.__proto__ = Grand.prototype.__proto__ = Object.prototype.__proto__ = null
function Parent() {
    this.lastName = 'harvest';
}
Child.prototype = new Parent(); // {lastName = 'harvest'}.__proto__ = arent.prototype
function Child() {
    this.age = 18;
}
const c = new Child(); // {age: 18}.__proto__ = Child.prototype
console.log(c.card);
c.house();
console.log(c.toString());

觉得很长很乱吧,没关系一起来,看看 “孙子c” 怎么凭着族谱 “蹭” 祖上的东西:

1. console.log(c.card); —— 输出:10000

咱一步步看 “认祖归宗” 的过程:

  • 先翻 c 自己的口袋:只有age:18,没card,掏族谱!
  • 顺着c.__proto__找爸爸的仓库(Child.prototype,也就是new Parent()的实例):爸爸的仓库里只有lastName: 'harvest',还没card,继续往上找!
  • 再顺着爸爸仓库的__proto__Parent.prototype.__proto__)找爷爷的仓库(Grand.prototype):爷爷的仓库里有card:10000(爷爷构造函数里的专属属性),找到了!
  • 所以直接输出爷爷给的 “启动资金” 10000—— 这就是 “戏精家族” 的传承,孙子能蹭到爷爷的银行卡💳!

2. c.house(); —— 输出:四合院

同样按族谱寻亲:

  • 先翻 c 自己的口袋:没house方法,掏族谱!
  • 找爸爸的仓库:只有lastName,没有house,继续往上!
  • 找爷爷的仓库(Grand.prototype):嘿,爷爷的祖传仓库里正好有house方法,直接调用!
  • 所以执行后输出 “四合院”—— 相当于孙子凭着族谱,直接用了爷爷的 “祖传房产” 技能!

3. console.log(c.toString()); —— 输出:[object Object]

这波是 “蹭到了家族的老老祖宗”(Object)的好处:

  • 先翻 c 自己的口袋:没toString方法,掏族谱!
  • 找爸爸仓库:没有,找爷爷仓库:也没有(爷爷只给了cardhouse),继续往上!
  • 顺着爷爷仓库的__proto__Grand.prototype.__proto__)找 “老老祖宗”Object的仓库(Object.prototype):这里藏着 JavaScript 所有对象都能共用的toString方法!
  • 调用后就输出默认格式[object Object]—— 相当于 “戏精家族” 的族谱最顶端,还连着所有对象的 “公共老祖宗”,好处能蹭到最上头!

输出结果和我们分析的一模一样:

image.png

OK,下课!

总结:“戏精家族” 的传承逻辑

  • prototype是每个 “家族长辈”(函数)的 “祖传仓库”,共享属性方法全在这;
  • __proto__是每个 “家族成员”(对象)的 “隐形族谱”,负责连接上一辈的仓库;
  • new是 “家族造人师”,不仅造新成员,还得给它上 “家族户口”(连族谱);
  • 原型链是 “完整族谱”,属性查找全靠它 “代代往上蹭”,直到蹭到null为止。

“戏精家族” 的传承逻辑,本质就是 JavaScript 的继承核心 —— 不用重复造轮子,晚辈凭着族谱就能共享祖上的 “资源”,既省空间又高效。每个属性和方法的查找,都是一场有趣的 “家族寻亲记”。

开启一场“寻亲之旅”吧!

❌
❌