原型链和作用域链互不相关:前者响应对象属性访问(如a.b),沿__proto__向上查找;后者响应标识符解析(如b),沿词法嵌套环境向上查找。

原型链和作用域链不结合——它们根本不在同一层面上运作,也从不交叉或协同工作。JS 引擎内部严格区分这两条链:一个管“怎么找属性”,一个管“怎么找变量”,各自独立触发、各自独立查找,互不干扰。
原型链只响应对象属性访问
当你写 obj.name 或 arr.push() 这类带点号(.)或方括号([])的表达式时,引擎启动原型链查找。它检查 obj 自身有没有 name 属性;没有就查 obj.__proto__,再没有就查 obj.__proto__.__proto__,一路向上直到 Object.prototype,最后是 null。
- 触发条件明确:必须是对象属性/方法的读取或调用
- 依赖结构:靠 __proto__ 链接,本质是对象之间的继承关系
- 不看变量名:哪怕你把变量名起成 obj,只要没用 . 或 [] 访问,原型链就不动
作用域链只响应标识符解析
当你写 console.log(x)、foo() 或直接使用 count 这样的裸名称时,引擎启动作用域链查找。它从当前函数的词法环境开始找 x;找不到就跳到外层函数的词法环境,再找不到就到全局环境(globalThis/window)。
- 触发条件明确:仅发生在变量、函数、参数等标识符被引用时
- 依赖结构:靠函数定义时嵌套位置决定,与对象无关
- 不查属性:哪怕全局变量 window.x 存在,你写 x 用的仍是作用域链,不是原型链
为什么有人觉得它们“像”?
表面看都是“一层层往上找”,但机制完全不同:
- 原型链终点是 Object.prototype.__proto__ === null
- 作用域链终点是 全局词法环境(globalLexicalEnvironment)
- 全局变量能被访问,是因为 JS 把全局对象(如 window)的属性和全局作用域做了映射,这是运行时约定,不是设计等价
- 闭包能记住外层变量,靠的是保留作用域链引用;new Date() 能调 toISOString(),靠的是 Date.prototype 提供的方法——两者毫无技术交集
一句话分清关键场景
写 a.b → 查原型链;写 b(单独出现)→ 查作用域链。前者是对象身上的东西,后者是代码里声明的名字。搞混往往是因为没盯住语法形式——点号就是分水岭。

















