目录


一、原型链概述

1.1 什么是原型链

原型链(Prototype Chain)是JavaScript实现继承的核心机制。它是一种基于对象的继承模型,而非传统的基于类的继承。在JavaScript中,几乎所有对象都有一个内部链接指向另一个对象,这个对象被称为"原型"(prototype),形成一条链式结构,直到终点null

1.2 为什么需要原型链

  1. 代码复用:通过原型链,多个实例可以共享方法和属性,避免重复定义
  2. 内存优化:共享的方法只在原型上存储一份,减少内存占用
  3. 动态继承:可以在运行时动态修改原型,实现灵活的继承关系
  4. 实现多态:不同对象可以通过原型链实现方法重写和多态特性

二、核心概念详解

2.1 prototype(显式原型)

prototype函数对象特有的属性,指向一个对象,这个对象包含了所有实例共享的属性和方法。

function Person(name) {
  this.name = name;
}

// 在prototype上定义共享方法
Person.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
};

console.log(typeof Person.prototype); // "object"
console.log(Person.prototype.constructor === Person); // true

关键点:

  • 只有函数对象才有 prototype 属性
  • prototype 是一个对象,包含 constructor 属性指向函数本身
  • 通过构造函数创建的实例会继承 prototype 上的属性和方法

2.2 proto(隐式原型)

__proto__每个对象都有的属性,指向创建该对象的构造函数的原型对象。

const person = new Person('Alice');

console.log(person.__proto__ === Person.prototype); // true
console.log(person.__proto__.constructor === Person); // true

注意:

  • __proto__ 是非标准属性,ES6引入了 Object.getPrototypeOf()Object.setPrototypeOf() 作为标准方法
  • 现代开发中应避免直接操作 __proto__

2.3 constructor(构造函数)

constructor 是原型对象的一个属性,指向关联的构造函数。

function Animal(type) {
  this.type = type;
}

const dog = new Animal('dog');

console.log(dog.constructor === Animal); // true
console.log(dog.__proto__.constructor === Animal); // true
console.log(Animal.prototype.constructor === Animal); // true

2.4 三者关系图

                    ┌─────────────────┐
                    │   Function      │
                    │   (构造函数)     │
                    └────────┬────────┘
                             │
                             │ .prototype
                             ↓
        ┌────────────────────────────────┐
        │   Prototype Object             │
        │   { constructor: Function }    │
        └────────────┬───────────────────┘
                     ↑
                     │ .__proto__
                     │
              ┌──────┴──────┐
              │   Instance   │
              │   (实例对象)  │
              └──────────────┘

三、原型链的底层原理

3.1 对象创建过程

当使用 new 操作符创建对象时,JavaScript引擎执行以下步骤:

// 模拟 new 操作符的实现
function myNew(Constructor, ...args) {
  // 1. 创建一个新对象
  const obj = {};

  // 2. 将新对象的 __proto__ 指向构造函数的 prototype
  obj.__proto__ = Constructor.prototype;
  // 或使用标准方法:Object.setPrototypeOf(obj, Constructor.prototype);

  // 3. 执行构造函数,将 this 绑定到新对象
  const result = Constructor.apply(obj, args);

  // 4. 如果构造函数返回对象,则返回该对象;否则返回新创建的对象
  return result instanceof Object ? result : obj;
}

// 测试
function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function() {
  console.log(`Hello, ${this.name}`);
};

const person = myNew(Person, 'Bob');
person.sayHello(); // "Hello, Bob"

3.2 属性查找机制

JavaScript引擎在访问对象属性时,遵循以下查找顺序:

function Parent() {
  this.parentProp = 'parent';
}
Parent.prototype.sharedMethod = function() {
  return 'shared';
};

function Child() {
  Parent.call(this);
  this.childProp = 'child';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

const instance = new Child();

// 查找过程:
console.log(instance.childProp);      // 1. 在实例自身找到 ✓
console.log(instance.parentProp);     // 2. 在实例自身找到 ✓
console.log(instance.sharedMethod()); // 3. 实例 → Child.prototype → Parent.prototype ✓
console.log(instance.toString());     // 4. 继续向上到 Object.prototype ✓
console.log(instance.nonExist);       // 5. 直到 null,返回 undefined

查找算法伪代码:

function getProperty(obj, prop) {
  let current = obj;

  while (current !== null) {
    // 检查当前对象自身是否有该属性
    if (current.hasOwnProperty(prop)) {
      return current[prop];
    }

    // 沿着原型链向上查找
    current = Object.getPrototypeOf(current);
  }

  return undefined;
}

3.3 原型链的终点

所有原型链的终点都是 Object.prototype,而 Object.prototype.__proto__null

const obj = {};

console.log(obj.__proto__ === Object.prototype);           // true
console.log(Object.prototype.__proto__);                   // null

// 完整的原型链
function Foo() {}
const foo = new Foo();

console.log(foo.__proto__ === Foo.prototype);              // true
console.log(Foo.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__);                   // null

3.4 函数的原型链

函数是特殊的对象,具有双重身份:

function MyFunction() {}

// 作为对象,有 __proto__
console.log(MyFunction.__proto__ === Function.prototype); // true

// 作为构造函数,有 prototype
console.log(typeof MyFunction.prototype); // "object"

// Function.prototype 的原型是 Object.prototype
console.log(Function.prototype.__proto__ === Object.prototype); // true

// Function 自己也是一个函数
console.log(Function.__proto__ === Function.prototype); // true (特殊情况)

四、原型链的查找机制

4.1 属性读取

function Animal(name) {
  this.name = name;
  this.colors = ['black', 'white'];
}
Animal.prototype.species = 'mammal';
Animal.prototype.getSpecies = function() {
  return this.species;
};

const cat = new Animal('Cat');

// 读取实例属性(在对象自身)
console.log(cat.name); // "Cat" - 直接找到

// 读取原型属性(在原型链上)
console.log(cat.species); // "mammal" - 从 Animal.prototype 找到
console.log(cat.getSpecies()); // "mammal"

// 读取更上层的原型属性
console.log(cat.toString()); // "[object Object]" - 从 Object.prototype 找到

4.2 属性写入(屏蔽效应)

function Person() {}
Person.prototype.name = 'Prototype Name';

const person1 = new Person();
const person2 = new Person();

// 读取原型属性
console.log(person1.name); // "Prototype Name"
console.log(person2.name); // "Prototype Name"

// 写入实例属性(不会修改原型)
person1.name = 'Instance Name';

console.log(person1.name); // "Instance Name" - 实例属性屏蔽了原型属性
console.log(person2.name); // "Prototype Name" - 不受影响
console.log(Person.prototype.name); // "Prototype Name" - 原型未改变

// 删除实例属性后,原型属性重新可见
delete person1.name;
console.log(person1.name); // "Prototype Name"

4.3 引用类型的陷阱

function Parent() {
  this.list = [1, 2, 3];
}

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

const child1 = new Child();
const child2 = new Child();

// 修改引用类型会影响所有实例
child1.list.push(4);
console.log(child2.list); // [1, 2, 3, 4] - 被污染了!

// 正确做法:在构造函数中初始化引用类型
function BetterChild() {
  Parent.call(this); // 每个实例都有独立的 list
}
BetterChild.prototype = Object.create(Parent.prototype);
BetterChild.prototype.constructor = BetterChild;

4.4 hasOwnProperty vs in 操作符

function Person() {
  this.ownProp = 'own';
}
Person.prototype.protoProp = 'proto';

const person = new Person();

// hasOwnProperty: 只检查自身属性
console.log(person.hasOwnProperty('ownProp'));   // true
console.log(person.hasOwnProperty('protoProp')); // false

// in 操作符: 检查整个原型链
console.log('ownProp' in person);   // true
console.log('protoProp' in person); // true

// 判断属性是否在原型上
function isPrototypeProperty(obj, prop) {
  return (prop in obj) && !obj.hasOwnProperty(prop);
}

console.log(isPrototypeProperty(person, 'protoProp')); // true

五、在Vue中的应用

5.1 Vue 2.x的原型链扩展

Vue 2通过原型链实现全局方法和实例方法的共享:

// Vue源码中的实现(简化版)
function Vue(options) {
  // 初始化逻辑
  this._init(options);
}

// 在原型上定义方法,所有实例共享
Vue.prototype._init = function(options) {
  const vm = this;
  vm.$options = options;
  // 初始化数据、计算属性、侦听器等
};

// 全局API也通过原型挂载
Vue.prototype.$set = function(target, key, value) {
  // 响应式设置逻辑
};

Vue.prototype.$watch = function(expOrFn, cb, options) {
  // 侦听器逻辑
};

// 实例使用
const vm = new Vue({
  data: { message: 'Hello' }
});

vm.$set(vm, 'newProp', 'value'); // 可以访问原型方法

5.2 Vue插件系统

Vue插件通过修改Vue.prototype来扩展功能:

// 自定义插件
const MyPlugin = {
  install(Vue, options) {
    // 1. 添加全局方法或属性
    Vue.myGlobalMethod = function() {
      console.log('全局方法');
    };

    // 2. 添加实例方法(通过原型链)
    Vue.prototype.$myMethod = function(message) {
      console.log(`实例方法: ${message}`);
    };

    // 3. 注入组件选项
    Vue.mixin({
      created() {
        console.log('全局混入');
      }
    });
  }
};

// 使用插件
Vue.use(MyPlugin);

// 在组件中使用
export default {
  mounted() {
    this.$myMethod('Hello'); // 通过原型链访问
  }
};

5.3 Vue Router的原型链应用

// Vue Router源码实现(简化)
class VueRouter {
  constructor(options) {
    this.routes = options.routes || [];
  }

  push(location) {
    // 路由跳转逻辑
  }
}

VueRouter.install = function(Vue) {
  // 在Vue原型上挂载$router和$route
  Object.defineProperty(Vue.prototype, '$router', {
    get() {
      return this._routerRoot._router;
    }
  });

  Object.defineProperty(Vue.prototype, '$route', {
    get() {
      return this._routerRoot._route;
    }
  });
};

// 使用
Vue.use(VueRouter);

// 在组件中
export default {
  methods: {
    navigate() {
      this.$router.push('/home'); // 通过原型链访问
      console.log(this.$route.params); // 通过原型链访问
    }
  }
};

5.4 Vue 3的变化

Vue 3改用Composition API,减少了对原型链的依赖:

// Vue 2: 依赖原型链
export default {
  mounted() {
    this.$axios.get('/api'); // 通过原型
    this.$router.push('/'); // 通过原型
  }
};

// Vue 3: 使用组合式函数
import { getCurrentInstance } from 'vue';
import { useRouter } from 'vue-router';

export default {
  setup() {
    // 方式1: 使用组合式API
    const router = useRouter();

    // 方式2: 仍可访问实例(不推荐)
    const instance = getCurrentInstance();
    const axios = instance.appContext.config.globalProperties.$axios;

    return {
      navigate: () => router.push('/')
    };
  }
};

六、在React中的应用

6.1 React类组件的原型链

// React.Component 的简化实现
function Component(props, context) {
  this.props = props;
  this.context = context;
  this.refs = {};
}

// 在原型上定义共享方法
Component.prototype.setState = function(partialState, callback) {
  // 状态更新逻辑
  this.updater.enqueueSetState(this, partialState, callback);
};

Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback);
};

// 用户定义的组件继承Component
class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  // 实例方法
  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return <div onClick={this.handleClick}>{this.state.count}</div>;
  }
}

// 原型链结构
const instance = new MyComponent({ name: 'Test' });
console.log(instance.__proto__ === MyComponent.prototype); // true
console.log(MyComponent.prototype.__proto__ === Component.prototype); // true
console.log(instance.setState === Component.prototype.setState); // true

6.2 高阶组件(HOC)与原型链

// 高阶组件示例
function withLogger(WrappedComponent) {
  return class extends Component {
    componentDidMount() {
      console.log(`${WrappedComponent.name} mounted`);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

// 使用
class MyComponent extends Component {
  render() {
    return <div>Content</div>;
  }
}

// HOC包装后保持原型链
const EnhancedComponent = withLogger(MyComponent);

// 注意:displayName需要手动设置
EnhancedComponent.displayName = `withLogger(${MyComponent.name})`;

6.3 React Hooks取代原型链

React 16.8引入Hooks后,减少了对类和原型链的依赖:

// 类组件(基于原型链)
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={this.increment}>+</button>
      </div>
    );
  }
}

// 函数组件(不使用原型链)
function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>+</button>
    </div>
  );
}

6.4 自定义Hook的实现

// 自定义Hook不依赖原型链,而是闭包
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  const decrement = useCallback(() => {
    setCount(c => c - 1);
  }, []);

  return { count, increment, decrement };
}

// 使用
function App() {
  const { count, increment, decrement } = useCounter(10);

  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

七、实战案例分析

7.1 实现继承的多种方式

1) 原型链继承
function Parent() {
  this.name = 'parent';
  this.colors = ['red', 'blue'];
}
Parent.prototype.getName = function() {
  return this.name;
};

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

const child1 = new Child();
const child2 = new Child();

// 缺点:引用类型共享
child1.colors.push('green');
console.log(child2.colors); // ['red', 'blue', 'green']
2) 构造函数继承
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}
Parent.prototype.getName = function() {
  return this.name;
};

function Child(name) {
  Parent.call(this, name); // 借用构造函数
}

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

// 优点:引用类型独立
// 缺点:无法继承原型方法
console.log(child.getName); // undefined
3) 组合继承(最常用)
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}
Parent.prototype.getName = function() {
  return this.name;
};

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

const child = new Child('child', 10);

// 优点:结合了两者优点
console.log(child.getName()); // "child"
child.colors.push('green');
// 缺点:调用了两次父类构造函数
4) 寄生组合继承(最优方案)
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}
Parent.prototype.getName = function() {
  return this.name;
};

function Child(name, age) {
  Parent.call(this, name); // 只调用一次Parent
  this.age = age;
}

// 关键:使用Object.create
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.getAge = function() {
  return this.age;
};

const child = new Child('child', 10);
console.log(child.getName()); // "child"
console.log(child.getAge()); // 10

// 原型链完整
console.log(child instanceof Child); // true
console.log(child instanceof Parent); // true
5) ES6 Class继承
class Parent {
  constructor(name) {
    this.name = name;
    this.colors = ['red', 'blue'];
  }

  getName() {
    return this.name;
  }
}

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

  getAge() {
    return this.age;
  }
}

const child = new Child('child', 10);
console.log(child.getName()); // "child"

// ES6 Class本质上是语法糖,底层仍使用原型链
console.log(Child.prototype.__proto__ === Parent.prototype); // true

7.2 手写instanceof

function myInstanceof(instance, Constructor) {
  // 获取实例的原型
  let proto = Object.getPrototypeOf(instance);

  // 获取构造函数的原型对象
  const prototype = Constructor.prototype;

  // 沿着原型链查找
  while (proto !== null) {
    if (proto === prototype) {
      return true;
    }
    proto = Object.getPrototypeOf(proto);
  }

  return false;
}

// 测试
class Animal {}
class Dog extends Animal {}
const dog = new Dog();

console.log(myInstanceof(dog, Dog));    // true
console.log(myInstanceof(dog, Animal)); // true
console.log(myInstanceof(dog, Object)); // true
console.log(myInstanceof(dog, Array));  // false

7.3 实现私有属性

使用闭包(不依赖原型链)
function Person(name) {
  // 私有变量
  let _age = 0;

  // 公共属性
  this.name = name;

  // 公共方法(访问私有变量)
  this.getAge = function() {
    return _age;
  };

  this.setAge = function(age) {
    if (age > 0 && age < 150) {
      _age = age;
    }
  };
}

const person = new Person('Alice');
person.setAge(25);
console.log(person.getAge()); // 25
console.log(person._age); // undefined - 无法直接访问
使用Symbol(ES6)
const _age = Symbol('age');
const _salary = Symbol('salary');

class Person {
  constructor(name, age, salary) {
    this.name = name;
    this[_age] = age;
    this[_salary] = salary;
  }

  getAge() {
    return this[_age];
  }

  getSalary() {
    return this[_salary];
  }
}

const person = new Person('Bob', 30, 50000);
console.log(person.name); // "Bob"
console.log(person.getAge()); // 30
console.log(person[_age]); // undefined - Symbol作用域外无法访问
console.log(Object.keys(person)); // ["name"] - Symbol属性不可枚举
使用私有字段(ES2022)
class Person {
  #age; // 私有字段
  #salary;

  constructor(name, age, salary) {
    this.name = name;
    this.#age = age;
    this.#salary = salary;
  }

  getAge() {
    return this.#age;
  }

  #calculateTax() { // 私有方法
    return this.#salary * 0.2;
  }

  getNetSalary() {
    return this.#salary - this.#calculateTax();
  }
}

const person = new Person('Charlie', 35, 60000);
console.log(person.name); // "Charlie"
console.log(person.getAge()); // 35
// console.log(person.#age); // SyntaxError: 私有字段不可访问

7.4 实现发布-订阅模式

class EventEmitter {
  constructor() {
    // 存储事件及其回调
    this.events = {};
  }

  // 订阅事件
  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
  }

  // 触发事件
  emit(eventName, ...args) {
    const callbacks = this.events[eventName];
    if (callbacks) {
      callbacks.forEach(callback => callback(...args));
    }
  }

  // 取消订阅
  off(eventName, callback) {
    const callbacks = this.events[eventName];
    if (callbacks) {
      this.events[eventName] = callbacks.filter(cb => cb !== callback);
    }
  }

  // 一次性订阅
  once(eventName, callback) {
    const wrapper = (...args) => {
      callback(...args);
      this.off(eventName, wrapper);
    };
    this.on(eventName, wrapper);
  }
}

// 使用
const emitter = new EventEmitter();

const handler1 = (data) => console.log('Handler 1:', data);
const handler2 = (data) => console.log('Handler 2:', data);

emitter.on('message', handler1);
emitter.on('message', handler2);
emitter.emit('message', 'Hello'); // 两个handler都被调用

emitter.off('message', handler1);
emitter.emit('message', 'World'); // 只有handler2被调用

emitter.once('greeting', (msg) => console.log(msg));
emitter.emit('greeting', 'Hi'); // "Hi"
emitter.emit('greeting', 'Hey'); // 无输出

八、性能优化与最佳实践

8.1 避免原型链过长

// 不推荐:原型链过长
function A() {}
function B() {}
B.prototype = new A();
function C() {}
C.prototype = new B();
function D() {}
D.prototype = new C();

const d = new D();
// 查找属性时需要遍历很长的原型链

// 推荐:扁平化设计或使用组合
class Component {
  constructor() {
    this.mixins = [
      new LoggerMixin(),
      new ValidationMixin(),
      new CacheMixin()
    ];
  }

  callMixinMethod(method, ...args) {
    this.mixins.forEach(mixin => {
      if (mixin[method]) {
        mixin[method](...args);
      }
    });
  }
}

8.2 方法定义在原型上

// 不推荐:方法定义在构造函数内
function Person(name) {
  this.name = name;

  // 每个实例都创建新的函数
  this.sayHello = function() {
    console.log(`Hello, ${this.name}`);
  };
}

const p1 = new Person('A');
const p2 = new Person('B');
console.log(p1.sayHello === p2.sayHello); // false - 浪费内存

// 推荐:方法定义在原型上
function BetterPerson(name) {
  this.name = name;
}

BetterPerson.prototype.sayHello = function() {
  console.log(`Hello, ${this.name}`);
};

const bp1 = new BetterPerson('A');
const bp2 = new BetterPerson('B');
console.log(bp1.sayHello === bp2.sayHello); // true - 共享方法

8.3 使用Object.create优化

// 不推荐
function Child() {}
Child.prototype = new Parent(); // 调用构造函数,可能有副作用

// 推荐
function Child() {}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

// 或使用工具函数
function inherit(Child, Parent) {
  Child.prototype = Object.create(Parent.prototype, {
    constructor: {
      value: Child,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
}

8.4 属性查找缓存

class ExpensiveLookup {
  constructor() {
    this._cache = new Map();
  }

  getValue(key) {
    // 检查缓存
    if (this._cache.has(key)) {
      return this._cache.get(key);
    }

    // 昂贵的查找操作
    const value = this._expensiveOperation(key);

    // 缓存结果
    this._cache.set(key, value);
    return value;
  }

  _expensiveOperation(key) {
    // 模拟复杂计算
    let result = key;
    for (let i = 0; i < 1000000; i++) {
      result += Math.sqrt(i);
    }
    return result;
  }
}

8.5 避免动态修改原型

// 不推荐:运行时修改原型会导致去优化
function Person() {}
const person = new Person();

// 引擎已经优化了Person的原型链
Person.prototype.newMethod = function() {
  // 这会导致引擎重新优化
};

// 推荐:在创建实例前定义好所有原型方法
function BetterPerson() {}

BetterPerson.prototype.method1 = function() {};
BetterPerson.prototype.method2 = function() {};
BetterPerson.prototype.method3 = function() {};

// 或使用Object.assign一次性添加
Object.assign(BetterPerson.prototype, {
  method1() {},
  method2() {},
  method3() {}
});

const betterPerson = new BetterPerson();

九、常见问题与陷阱

9.1 忘记使用new操作符

function Person(name) {
  this.name = name;
}

// 忘记使用new
const person = Person('Alice');
console.log(person); // undefined
console.log(window.name); // "Alice" - 污染了全局对象

// 解决方案1:检测new
function SafePerson(name) {
  if (!(this instanceof SafePerson)) {
    return new SafePerson(name);
  }
  this.name = name;
}

// 解决方案2:使用ES6 Class(会强制new)
class ModernPerson {
  constructor(name) {
    this.name = name;
  }
}

// const wrong = ModernPerson('Bob'); // TypeError: Class constructor cannot be invoked without 'new'

9.2 原型属性被意外覆盖

function Person() {}
Person.prototype.friends = ['Alice'];

const p1 = new Person();
const p2 = new Person();

// 错误:修改了原型上的数组
p1.friends.push('Bob');
console.log(p2.friends); // ['Alice', 'Bob'] - 被污染

// 正确:赋值操作会创建实例属性
p1.friends = ['Charlie'];
console.log(p2.friends); // ['Alice'] - 不受影响
console.log(Person.prototype.friends); // ['Alice', 'Bob'] - 仍然被污染

// 最佳实践:在构造函数中初始化
function SafePerson() {
  this.friends = ['Alice']; // 每个实例独立
}

9.3 constructor属性丢失

function Parent() {}
function Child() {}

// 错误:直接赋值导致constructor丢失
Child.prototype = Parent.prototype;
console.log(new Child().constructor === Child); // false

// 错误:创建新对象但没有设置constructor
Child.prototype = Object.create(Parent.prototype);
console.log(new Child().constructor === Child); // false

// 正确:手动恢复constructor
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
console.log(new Child().constructor === Child); // true

9.4 instanceof的局限性

// 跨iframe或跨上下文失效
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);

const iframeArray = iframe.contentWindow.Array;
const arr = new iframeArray();

console.log(arr instanceof Array); // false
console.log(arr instanceof iframeArray); // true

// 解决方案:使用Array.isArray()
console.log(Array.isArray(arr)); // true

// 原型可以被修改
function Fake() {}
const obj = {};
Object.setPrototypeOf(obj, Fake.prototype);
console.log(obj instanceof Fake); // true - 但obj不是由Fake创建的

9.5 使用箭头函数的陷阱

function Person(name) {
  this.name = name;
}

// 错误:箭头函数没有prototype
Person.prototype.sayHello = () => {
  console.log(`Hello, ${this.name}`); // this指向错误
};

const person = new Person('Alice');
person.sayHello(); // "Hello, undefined"

// 正确:使用普通函数
Person.prototype.sayHello = function() {
  console.log(`Hello, ${this.name}`);
};

// 箭头函数适合内部回调
Person.prototype.greetAll = function(people) {
  people.forEach(person => {
    console.log(`${this.name} greets ${person}`); // 正确捕获外层this
  });
};

9.6 Object.create(null)的特殊性

// 普通对象有原型链
const normal = {};
console.log(normal.toString); // [Function: toString]
console.log(normal.__proto__ === Object.prototype); // true

// 无原型对象
const pure = Object.create(null);
console.log(pure.toString); // undefined
console.log(pure.__proto__); // undefined

// 适用场景:纯粹的字典对象
const map = Object.create(null);
map['__proto__'] = 'value'; // 安全:作为普通属性
console.log(map['__proto__']); // "value"

// 普通对象会有问题
const normalMap = {};
normalMap['__proto__'] = 'value'; // 尝试设置原型
console.log(normalMap['__proto__']); // {} - 不是我们期望的字符串

总结

核心要点

  1. 原型链是JavaScript继承的基础

    • 通过__proto__连接对象,形成链式查找
    • prototype是函数特有的,用于定义共享属性和方法
    • constructor连接原型与构造函数
  2. 属性查找遵循就近原则

    • 先查找自身属性
    • 再沿原型链向上查找
    • 直到Object.prototypenull
  3. 继承方式的演进

    • 原型链继承 → 构造函数继承 → 组合继承 → 寄生组合继承
    • ES6 Class是语法糖,底层仍是原型链
    • 现代框架倾向于使用Hooks/Composition API减少对原型链的依赖
  4. 性能优化建议

    • 方法定义在原型上,避免重复创建
    • 避免原型链过长
    • 不要在运行时频繁修改原型
    • 使用缓存优化属性查找
  5. 在Vue/React中的实践

    • Vue 2大量使用原型链实现全局API和插件系统
    • Vue 3和React Hooks减少了对原型链的依赖
    • 理解原型链有助于深入理解框架内部机制
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐