原型链查找属性时,先查对象自身,再沿__proto__逐级向上,直到Object.prototype.__proto__ === null为止;写属性默认只改自身,不污染原型。

原型链怎么查找属性?一句话说清机制
当你写 obj.method(),JS 先查 obj 自身有没有 method;没有就顺着 obj.__proto__ 找,再没就继续找 obj.__proto__.__proto__,直到碰到 Object.prototype.__proto__ === null 停止。这个“一级一级往上翻家谱”的过程就是原型链查找。
- 所有普通对象的终点都是
Object.prototype,所以都能用toString()、hasOwnProperty() - 数组、函数、日期等内置类型也走这条路:比如
[].push是从Array.prototype→Object.prototype→null - 写属性(如
obj.x = 1)默认只改自身,不会污染原型——这是避免意外共享的关键设计
为什么 Child.prototype = Parent.prototype 是危险操作?
这会让子类和父类共用同一份原型对象,改一个就等于改两个。比如你给 Child.prototype.sayHi 赋值,Parent 实例也会突然多出这个方法——这不是继承,是“原型污染”。
- 正确做法是用
Object.create(Parent.prototype),它新建一个空对象,并把它的__proto__指向Parent.prototype - ES6 的
class A extends B底层干的就是这事:A.prototype.__proto__被设为B.prototype,同时保留A.prototype.constructor指向A - 漏掉
constructor重置会导致instanceof或序列化时出问题,例如new A() instanceof A可能返回false
new Foo() 到底发生了什么?三步拆解
不是语法魔法,是明确的三步执行:
- 创建一个空对象,内部
[[Prototype]]指向Foo.prototype(即obj.__proto__ === Foo.prototype) - 以该对象为
this执行Foo.call(obj, ...args),把实例属性挂到对象上 - 如果构造函数显式返回一个对象,则返回它;否则返回刚创建的对象
这意味着:构造函数里定义的属性(如 this.name)属于每个实例;而挂在 Foo.prototype 上的方法(如 Foo.prototype.say)被所有实例共享——这才是内存友好的关键。
立即学习“Java免费学习笔记(深入)”;
类语法糖(class)掩盖了哪些原型细节?
class 看起来像 Java,但底层完全依赖原型链。比如这段代码:
class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise'); } }
class Dog extends Animal { constructor(name, breed) { super(name); this.breed = breed; } speak() { console.log(this.name + ' barks'); } }
它等价于手动设置原型链 + 调用 super(),而 super() 本质就是确保子类构造函数里能正确访问父类的 this 和原型方法。但要注意:
-
extends null是合法的,此时Dog.prototype.__proto__为null,不再继承Object.prototype的方法(toString就会报错) - 静态方法(
static method)其实是挂在构造函数本身上的,即Dog.method()→Dog.__proto__.method(),和实例原型链无关 - 箭头函数不绑定
this,在类方法中用箭头函数定义回调时,this会指向定义时的实例,而不是调用时的上下文——这点和原型链无关,但常被误认为是“继承问题”
真正容易被忽略的,是原型链的动态性:你在运行时改 Animal.prototype.speak,所有已存在的 Dog 实例也会立刻生效——这不是 bug,是设计,但也是调试时最让人抓狂的点之一。








