原型与原型链

97 阅读4分钟

一、原型系统总论

1. 设计背景与核心价值

JavaScript 原型系统诞生于1995年,Brendan Eich 在设计语言时参考了Self语言的基于原型的面向对象模型。与传统的基于类的面向对象语言(如Java)不同,JavaScript采用了更为灵活的原型继承机制。

核心价值

  • 内存效率:通过原型共享方法和属性,避免每个实例都创建副本
  • 动态性:可以在运行时修改原型,立即影响所有实例
  • 灵活性:支持多种继承模式和对象组合方式
// 传统类 vs JavaScript原型
class Person {        // Java/C++风格
  constructor(name) {
    this.name = name;
  }
}

function Person(name) { // JavaScript原型风格
  this.name = name;
}
Person.prototype.say = function() {};

2. 核心概念关系图

JavaScript中的每个对象都有一个内部属性[[Prototype]](可通过__proto__访问),而每个函数都有一个prototype属性。当使用new操作符调用函数时,新对象的[[Prototype]]会指向该函数的prototype属性。

构造函数 (Foo)
  │
  ├── prototype 属性
  │     ├── constructor → 构造函数本身
  │     └── [[Prototype]] → Object.prototype
  │
new操作创建
  │
实例对象 (obj)
  └── [[Prototype]] → Foo.prototype

二、函数原型深度解析

1. 构造函数的原型机制

每个函数在创建时都会自动获得一个prototype对象,这个对象包含:

  • 一个指向函数本身的constructor属性
  • 一个继承自Object.prototype[[Prototype]]
function Foo() {}
console.log(Foo.prototype); // { constructor: Foo, __proto__: Object.prototype }

// 默认关系验证
console.log(Foo.prototype.constructor === Foo); // true
console.log(Object.getPrototypeOf(Foo.prototype) === Object.prototype); // true

2. 实例化过程的原型绑定

使用new操作符调用函数时,会发生以下步骤:

  1. 创建一个新对象
  2. 将该对象的[[Prototype]]指向构造函数的prototype
  3. this绑定到新对象
  4. 执行构造函数代码
  5. 如果构造函数没有返回对象,则返回新对象
function Foo(name) {
  this.name = name;
}
const obj = new Foo('test');

// 原型关系验证
console.log(obj.__proto__ === Foo.prototype); // true
console.log(obj instanceof Foo); // true

三、原型链系统详解

1. 多级原型链构建

当访问对象属性时,JavaScript引擎会:

  1. 先在对象自身属性中查找
  2. 如果没有,沿着[[Prototype]]链向上查找
  3. 直到找到属性或到达原型链末端(null)
function GrandParent() { this.grandValue = 1; }
function Parent() { this.parentValue = 2; }
Parent.prototype = new GrandParent();

function Child() { this.childValue = 3; }
Child.prototype = new Parent();

const obj = new Child();
console.log(obj.grandValue); // 1 (通过原型链访问)

2. 原型链检测方法

instanceof操作符检查构造函数的prototype是否出现在对象的原型链上:

console.log(obj instanceof Child);  // true
console.log(obj instanceof Parent); // true
console.log(obj instanceof GrandParent); // true

isPrototypeOf()方法检查对象是否存在于另一个对象的原型链中:

console.log(Child.prototype.isPrototypeOf(obj)); // true
console.log(GrandParent.prototype.isPrototypeOf(obj)); // true

四、继承模式演进史

1. 原型链继承的问题

function Parent() { this.colors = ['red', 'blue']; }
function Child() {}
Child.prototype = new Parent();

const c1 = new Child();
c1.colors.push('green');

const c2 = new Child();
console.log(c2.colors); // ['red', 'blue', 'green'] (共享问题)

2. 组合继承优化

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}
Parent.prototype.say = function() { console.log(this.name); };

function Child(name, age) {
  Parent.call(this, name); // 第二次调用Parent
  this.age = age;
}
Child.prototype = new Parent(); // 第一次调用Parent
Child.prototype.constructor = Child;

const c1 = new Child('Tom', 10);
c1.colors.push('green');
const c2 = new Child('Jerry', 8);
console.log(c2.colors); // ['red', 'blue'] (不共享)

3. 寄生组合式继承(最优方案)

function inherit(Child, Parent) {
  const prototype = Object.create(Parent.prototype); // 创建对象
  prototype.constructor = Child; // 增强对象
  Child.prototype = prototype; // 指定对象
}

function Parent(name) {
  this.name = name;
}
Parent.prototype.say = function() { console.log(this.name); };

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
inherit(Child, Parent);

const child = new Child('Sam', 12);
child.say(); // "Sam"

五、ES6类语法糖解析

1. class的底层实现

class Foo {
  constructor(name) { this.name = name; }
  say() { console.log(this.name); }
  static staticMethod() { console.log('static'); }
}

// 等价于
function Foo(name) {
  this.name = name;
}
Foo.prototype.say = function() { console.log(this.name); };
Foo.staticMethod = function() { console.log('static'); };

2. extends的继承逻辑

class Parent {
  constructor(name) { this.name = name; }
  say() { console.log(this.name); }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 必须在使用this前调用
    this.age = age;
  }
}

const child = new Child('Lucy', 8);
child.say(); // "Lucy"
console.log(child instanceof Parent); // true

六、特殊对象原型研究

1. 内置对象原型链

const arr = [];
// arr -> Array.prototype -> Object.prototype -> null
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true

const str = '';
// str -> String.prototype -> Object.prototype -> null

2. 箭头函数的原型特性

const arrow = () => {};
console.log(arrow.prototype); // undefined

// 不能作为构造函数
try {
  new arrow(); // TypeError: arrow is not a constructor
} catch(e) {
  console.error(e);
}

七、性能与安全实践

1. 原型操作性能优化

// 创建无原型的纯净对象
const dict = Object.create(null);
dict.key = 'value';
console.log(dict.toString); // undefined (没有继承Object.prototype)

// 避免过长的原型链
function Level1() {}
function Level2() {}
function Level3() {}
Level2.prototype = Object.create(Level1.prototype);
Level3.prototype = Object.create(Level2.prototype); // 3层已经较深

2. 原型污染防御

// 冻结Object.prototype防止被修改
Object.freeze(Object.prototype);

// 安全地扩展原生原型
if (!Array.prototype.customMethod) {
  Array.prototype.customMethod = function() {
    // 实现
  };
}

八、调试与诊断技术

1. 原型链可视化

function Foo() {}
const obj = new Foo();

console.dir(obj);
// 控制台输出可展开的原型链:
// obj
//   __proto__: Foo.prototype
//     constructor: Foo()
//     __proto__: Object

2. 递归遍历原型链

function getPrototypeChain(obj) {
  const chain = [];
  while (obj = Object.getPrototypeOf(obj)) {
    chain.push(obj);
  }
  return chain;
}

const chain = getPrototypeChain(new Date());
console.log(chain);
// [Date.prototype, Object.prototype]

九、现代框架中的应用

1. Vue的响应式原型处理

Vue 2.x 使用原型拦截实现响应式:

const obj = { foo: 'bar' };
const vm = new Vue({
  data: obj
});

console.log(vm.foo === obj.foo); // true
console.log(vm.__proto__ === Vue.prototype); // true

2. React组件继承机制

React类组件基于原型继承:

class MyComponent extends React.Component {
  render() {
    return <div>Hello</div>;
  }
}

console.log(MyComponent.prototype.__proto__ === React.Component.prototype); // true

十、进阶话题

1. 原型与内存泄漏

不当的原型引用会导致内存泄漏:

function Leaky() {}
document.addEventListener('click', function() {
  new Leaky();
});

// 如果Leaky.prototype持有DOM引用,即使实例被销毁,DOM也无法GC

2. 未来语言演进方向

ES2022引入的静态类和静态块:

class Foo {
  static {
    // 类初始化时的静态代码块
    Foo.prototype.customMethod = function() {};
  }
}