欢迎光临
我们一直在努力

撕开 JS 的 Class 面具:从构造函数的 new 降生到顶层原型链的终极通关

撕开 JS 的 Class 面具:从构造函数的 new 降生到顶层原型链的终极通关

  • 前言:没有类的面向对象?打破你的传统世界观
  • 第一章:函数的双面人生——“一等公民”的秘密武器
    • 幕后黑盒:函数本质上就是对象
  • 第二章:视图与模板的内存大分工——`index.html` 的技术伏笔
    • 架构师思维:为什么属性写体内,方法挂体外?
  • 第三章:控制台的物理考古——揭秘“原型链三角铁律”与终极拉网
    • 3.1 现场一:撕开实例的口袋
    • 3.2 现场二:身份与关系的硬核等价
  • 第四章:终极通关——`tx.toString()` 的跳跃链路
    • 4.1 虚无的终点:null
    • 4.2 殊途同归的字面量对象(结合图开源代码四)
  • 🏁 总结:你学到的不是语法糖,是 JS 的世界观

前言:没有类的面向对象?打破你的传统世界观

对于许多从 C++、Java 或老版 C 语言转过来的开发者而言,“面向对象”往往与 class(类)这个词死死绑定在一起:类是写在纸上的标准图纸,实例是通过图纸实例化出来的物理房子。

然而,在 JavaScript 的世界里,存在着一个极具颠覆性的底层哲学:一切皆对象,唯独早期没有类(Class-less)。

在 ES6 引入 class 语法糖之前,JS 靠一套精妙绝伦的 “原型式面向对象(Prototypal OOP)” 玩转了世界。今天,我们将通过一行惊艳的 tx.toString(),配合浏览器控制台的“像素级物理考古”,彻底揭开 JavaScript 内存世界的底层潜规则。


第一章:函数的双面人生——“一等公民”的秘密武器

在深挖面向对象之前,我们必须先理解 JS 里的“函数”到底是个什么怪物。请看下面这段看似不可思议的代码:

function greeting(){
console.log('hello world');
}

greeting.a = '1'; // 👈 惊悚操作:给函数点(.)一个属性!
console.log(greeting.a); // 打印 '1'
greeting(); // 打印 'hello world'

幕后黑盒:函数本质上就是对象

在传统语言中,函数是一段写死的机器指令代码块。但在 JavaScript 里,函数是一等公民,且其本质就是一个超强对象。 当你声明一个函数时,引擎在堆内存(Heap)里开辟的空间其实是一个特殊的函数对象字典。它具有双重身份:

  • 普通对象的一面: 它和普通对象 {} 一样,可以任意挂载键值对。执行 greeting.a = '1',就是往这个对象的属性空间里塞入了一个属性 a。
  • 可调用的另一面(Callable): 这个特殊对象在底层被赋予了一个隐藏的内部属性 [[Call]],里面存放着你写的可执行代码块。当你加上括号 greeting() 时,引擎就会触发这个机关去执行里面的代码。
  • 正是因为“函数本质上就是对象,能自由生长属性与方法”这一底层特权,才让它有资格化身为面向对象的终极发动机——构造函数(Constructor)。


    第二章:视图与模板的内存大分工——index.html 的技术伏笔

    在实际工程中,我们会约定首字母大写的函数来充当“类”的模版。让我们来看这篇博客的核心主线页面 index.html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>
    <script>
    function Person(name, age){
    // 构造实例的,实例私有的空间
    console.log(this);
    this.name = name;
    this.age = age;
    }

    // 原型对象上的方法和属性是公用的(共享资产)
    Person.prototype.poem = '仁义礼智信';
    Person.prototype.say = function(){
    console.log(`我叫${this.name},很高兴认识你`);
    }
    Person.prototype.timeMF = function(){
    console.log('时间管理魔法');
    }

    const tx = new Person('苔藓', 18);
    console.log(tx.toString()); // 👈 终极悬念:Person 里明明没写 toString,为何不报错?
    </script>
    </body>
    </html>

    架构师思维:为什么属性写体内,方法挂体外?

    在这段代码中,你展现了极其漂亮的内存性能优化技巧:

    • 体内(this.name = name): 属性是每个实例私有的(苔藓是 18 岁,别的人可能是 20 岁)。通过 this 把属性直接绑在每个新诞生对象的独立内存里,互不干扰。
    • 体外(Person.prototype.say): 方法是公用的。所有实例的说话逻辑一模一样。如果在体内写 this.say = function…,当 new 出一万个对象时,内存里就会疯狂复制一万个一模一样的函数,内存瞬间死机。挂在显式原型 prototype 上,在内存中该方法永远只有一份,所有实例共享访问,高阶榨干内存性能。

    第三章:控制台的物理考古——揭秘“原型链三角铁律”与终极拉网

    现在,我们以四张控制台现场截图登场来讲解。它们用无视辩驳的运行时铁证,彻底还原了 const tx = new Person('苔藓', 18); 执行后,整个堆内存织就的三角拓扑网。

    3.1 现场一:撕开实例的口袋

    在这里插入图片描述

    当你输入 tx 并回车,控制台打印出了:

    Person {name: '苔藓', age: 18}

    这证明了构造函数通过 this 确实把私有属性扣在了 tx 自身身上。

    然而当你展开 tx.__proto__(即隐式原型属性)时,魔术发生了 !

    控制台赫然吐出了:{poem: '仁义礼智信', say: ƒ, timeMF: ƒ, constructor: ƒ}。

    🔍 考古发现: 任何通过 new 传授降生的实例对象,身上都自带一个隐藏的秘密通道 __proto__。它死死地指向了其父亲的公共资产库——Person.prototype。


    3.2 现场二:身份与关系的硬核等价

    在这里插入图片描述

    在上图中,控制台敲下了两行灵魂拷问,得到了大写的 true:

    > tx.__proto__ === Person.prototype
    < true
    > tx.__proto__.constructor
    < ƒ Person(name, age) { … }

    ** 这直接印证了 new 运算符在幕后黑盒里偷干的“四部曲”**:

  • 造空房: let obj = {}; 在内存里开辟全新空对象。
  • 织纽带: obj.__proto__ = Person.prototype;(图中来源)。
  • 魂附体: 让 Person 内部的 this 强行指向这个 obj,开始执行 this.name = '苔藓',让空房长出私有属性。
  • 交钥匙: 自动返回这个打通了任督二脉的实例,赋值给变量 tx。
  • 同时,原型对象 Person.prototype 身上自带一个 constructor 属性,它像回音壁一样,精准反向指回了构造函数 Person 本身。“实例、构造函数、原型对象”三者自此结成了坚不可摧的铁三角关系。


    第四章:终极通关——tx.toString() 的跳跃链路

    现在,我们可以完美回答那个悬念了:为什么 tx.toString() 能够成功运行?

    当你在代码最后一行执行 tx.toString() 时,引擎并不会魔法,它是一个顺着 __proto__ 链条疯狂爬行的“剥洋葱”捕快:

    在这里插入图片描述

  • 第一站(tx 自身): 引擎翻了翻 tx 自身的口袋,发现里面只有 {name: '苔藓', age: 18}。没有 toString。
  • 第二站(Person.prototype): 引擎顺着 tx.__proto__ 瞬间位移到 Person.prototype。在这里翻出了 poem、say 和 timeMF。依然没有 toString!
  • 第三站(Object.prototype,结合上图): 引擎绝不放弃。Person.prototype 本身也是个对象,它也有自己的 __proto__。于是引擎沿着 Person.prototype.__proto__ 发起了更高级别的空间跳跃,瞬间抵达了全 JS 对象的共同始祖:Object.prototype。
  • 在图中,输入:

    > tx.__proto__.__proto__ === Object.prototype
    < true

    铁证凿实! 在 Object.prototype 的皇家公共库里,静静地躺着内建的 toString() 方法。引擎一霸抓取,回传执行,全网通关!


    4.1 虚无的终点:null

    如果到了世界之巅 Object.prototype 还找不到方法呢?在上图三的第二行,探测了整条铁链的物理物理边界:

    > tx.__proto__.__proto__.__proto__
    < null

    当撞向 null(无/虚无) 时,意味着整条链条彻底摸到了底。如果此时还没找到方法,引擎就会当场崩溃,对你抛出绝望的 TypeError: is not a function。


    4.2 殊途同归的字面量对象(结合图开源代码四)

    有些同学可能会问:我平时不写构造函数,我直接写一句话 var obj = {name: '苔藓'},这也有原型链吗? 在图四中,你给出了教科书级别的答案: 代码片 真相大白: var obj = {} 在 JS 引擎眼里,底层就是隐式执行了 new Object()。所以,即便是一个最普通的对象,它的隐式原型 __proto__ 也毫无例外地直通大boss Object.prototype。任何对象,在抵达终点 null 前的一站,必定是 Object.prototype!


    🏁 总结:你学到的不是语法糖,是 JS 的世界观

    通过这节课的深入探秘,我们彻底打破了对 class 的迷信。

    JavaScript 的面向对象从来不是靠僵硬的图纸去复刻,而是靠对象与对象之间通过 __proto__ 织就的一条血脉相连的“传送带”。

    • 实例靠 __proto__ 向上孝敬原型。
    • 原型靠 constructor 认祖归宗指回构造函数。
    • 萬物沿着链条向上攀爬,汇流于 Object.prototype,最终归于 null 的虚无。

    吃透了控制台打印出来的 true 与 null,你就彻底拿捏了 JavaScript 最硬核、最底层的灵魂。

    赞(0)
    未经允许不得转载:171主机测评 » 撕开 JS 的 Class 面具:从构造函数的 new 降生到顶层原型链的终极通关
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址