JavaScript面向对象基于原型链,__proto__构成属性查找路径;class是语法糖,本质仍是原型机制;new绑定实例__proto__到构造函数prototype;Object.getPrototypeOf和constructor更可靠。
JavaScript 没有传统类继承的语法糖之前,原型链就是它实现面向对象的唯一底层机制;ES6 的 class 只是语法糖,背后仍是原型链在工作。
__proto__ 连接的对象查找路径每个对象都有一个内部属性 [[Prototype]](可通过 __proto__ 访问),指向它的原型对象;当访问一个属性或方法时,如果当前对象没有,引擎会沿着 __proto__ 一级级向上查找,直到找到或到达 null。
常见错误现象:
obj.__proto__ === Object.prototype 总成立 —— 实际上 Object.create(null) 创建的对象没有原型__proto__(如 obj.__proto__ = newProto)会影响性能且不推荐,应使用 Object.setPrototypeOf() 或构造时指定const parent = { say() { return 'hello'; } };
const child = Object.create(parent);
console.log(child.say()); // 'hello'
console.log(child.__proto__ === parent); // true
new 和构造函数如何触发原型绑定使用 new 调用函数时,JS 引擎会自动创建空对象,并将其 __proto__ 指向该函数的 prototype 属性所指向的对象。
关键点:
Foo.prototype 是给实例用的原型,不是 Foo 自己的原型(Foo.__proto__ 指向 Function.prototype)new Foo() 才能建立这个连接;直接调用 Foo() 不会设置 __proto__
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() { return `Hi, ${this.name}`; };
const p = new Person('Alice');
console.log(p.__proto__ === Person.prototype); // true
console.log(p.greet()); // 'Hi, Alice'
class 语法背后的原型操作没变class 定义的静态方法、实例方法、getter/setter,最终都挂载在 prototype 或构造函数自身上。继承(extends)也只是自动设置了子类的 prototype.__proto__ 和 constructor.__proto__。
容易踩的坑:
class A extends B 中,A.__proto__ === B 成立(静态继承),但 A.prototype.__proto__ === B.prototype 才是实例原型链的关键this,在原型方法中误用会导致丢失实例上下文super() 必须在子类构造函数中 this 使用前调用,否则报错 ReferenceError: Must call super constructor
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 必须先调用
this.breed = breed;
}
}
const dog = new Dog('Buddy', 'Golden');
console.log(dog.speak());
// 'Buddy makes a sound'
真正难的不是记住规则,而是调试时能快速判断某个方法到底从哪一层原型来的 —— 多用 Object.getPrototypeOf(obj) 和 obj.constructor 追踪,别只盯着 __proto__ 看。