js怎样控制浏览器前进、后退、页面跳转?

JavaScript 原型与继承终极指南:从原理到实战(2025 版)

原型与继承是 JavaScript 的核心灵魂,也是前端面试的 "高频重灾区"。很多开发者深陷 "原型链迷宫",仅停留在 "__proto__指向原型对象" 的表层认知,却不懂其底层设计逻辑与实战应用。本文从 "内存模型→核心概念→继承实现→框架应用→避坑指南" 五层逻辑,结合 V8 引擎执行机制和 React/Vue 实战案例,彻底拆解原型与继承的本质,帮你真正掌握 JavaScript 的面向对象编程思想。

一、底层原理:为什么 JavaScript 没有 "类" 却能实现继承?

JavaScript 是一门 "基于原型的语言",而非传统面向对象的 "基于类的语言"。这一设计源于 Brendan Eich(JS 创始人)的初衷 ------ 在极短时间内设计一门兼具函数式和面向对象特性的语言,原型机制正是折中后的最优解。

1. 核心设计思想:原型链继承

  • 本质 :通过 "原型对象"(prototype)实现属性和方法的共享,通过 "原型链"(__proto__串联)实现属性的查找与继承。
  • 核心逻辑 :每个对象都有一个 "原型对象",当访问对象的属性 / 方法时,若对象本身没有,会通过__proto__向上查找原型对象,直到找到或抵达原型链顶端(Object.prototype.__proto__ = null)。

2. V8 引擎视角的原型内存模型

V8 引擎中,原型相关的三个核心对象构成了继承的基础,其内存关系如下(Mermaid 流程图直观展示):

proto

prototype

constructor

proto

proto

属性name

方法sayHello

实例对象obj

原型对象Foo.prototype

构造函数Foo

Object.prototype

null

张三

console.log(`Hello, ${this.name}`)

proto

prototype

constructor

proto

proto

属性name

方法sayHello

实例对象obj

原型对象Foo.prototype

构造函数Foo

Object.prototype

null

张三

console.log(`Hello, ${this.name}`)

  • 关键关联
    1. 实例对象的__proto__指向其构造函数的prototype(原型对象);
    2. 原型对象的constructor指向其对应的构造函数;
    3. 所有原型对象最终继承自Object.prototype,形成完整的原型链。

3. 原型与构造函数的 "三角关系"(必懂)

这是理解原型机制的核心,三者相互关联、不可分割:

对象 / 函数 核心属性 / 方法 作用
构造函数(如 Foo) prototype属性 指向原型对象,供实例继承属性 / 方法
实例对象(如 obj) __proto__属性(隐式原型) 指向构造函数的prototype,开启原型链查找
原型对象(Foo.prototype) constructor属性 指向构造函数,标识原型所属的构造函数

验证代码(浏览器控制台可直接执行):

javascript

运行

复制代码
// 1. 定义构造函数
function Foo(name) {
  this.name = name; // 实例属性(每个实例独立)
}

// 2. 原型对象上定义共享方法
Foo.prototype.sayHello = function() {
  console.log(`Hello, ${this.name}`);
};

// 3. 创建实例
const obj = new Foo("张三");

// 验证三角关系
console.log(obj.__proto__ === Foo.prototype); // true(实例→原型对象)
console.log(Foo.prototype.constructor === Foo); // true(原型对象→构造函数)
console.log(obj.constructor === Foo); // true(实例通过原型链继承constructor)

二、核心概念:彻底分清 prototype、__proto__与 constructor

原型机制的混淆,本质是对这三个核心概念的理解模糊。以下从 "定义、作用、使用场景" 三个维度彻底拆解:

1. prototype(原型属性)

  • 定义:仅函数(构造函数)拥有的属性,指向一个 "原型对象"。
  • 作用:存储共享的属性和方法,供其创建的所有实例继承(避免每个实例重复创建相同方法,节省内存)。
  • 注意点
    • 普通对象没有prototype属性(如const obj = {}; obj.prototype → undefined);
    • 箭头函数没有prototype属性(无法作为构造函数使用)。

示例:共享方法的实现(核心用途)

javascript

运行

复制代码
// 错误做法:每个实例都创建独立方法(浪费内存)
function Bar(name) {
  this.name = name;
  this.sayHi = function() { // 实例方法,每个实例一份
    console.log(`Hi, ${this.name}`);
  };
}

// 正确做法:原型上定义共享方法(所有实例共用一份)
function Bar(name) {
  this.name = name;
}
Bar.prototype.sayHi = function() { // 原型方法,所有实例共享
  console.log(`Hi, ${this.name}`);
};

const bar1 = new Bar("李四");
const bar2 = new Bar("王五");
console.log(bar1.sayHi === bar2.sayHi); // true(方法共享,内存优化)

2. proto(隐式原型)

  • 定义 :所有对象(包括函数)都拥有的隐式属性(ES6 标准化后可通过Object.getPrototypeOf()访问),指向其 "原型对象"。
  • 作用:构建原型链,实现属性 / 方法的查找与继承。
  • 注意点
    • 不建议直接修改__proto__(性能差,易导致原型链混乱),优先使用Object.create()指定原型;
    • __proto__是实例对象与原型对象的 "桥梁",而非构造函数的属性。

示例:原型链查找机制

javascript

运行

复制代码
// 原型链:obj → Foo.prototype → Object.prototype → null
const obj = new Foo("张三");
console.log(obj.name); // "张三"(实例本身拥有)
console.log(obj.sayHello()); // "Hello, 张三"(Foo.prototype拥有)
console.log(obj.toString()); // "[object Object]"(Object.prototype拥有)
console.log(obj.nonExistent); // undefined(原型链顶端仍未找到)

3. constructor(构造函数指针)

  • 定义:原型对象上的属性,指向其对应的构造函数。
  • 作用 :标识对象的 "创建者",可通过实例对象的constructor获取其构造函数。
  • 注意点
    • 若重写原型对象,会覆盖constructor属性,需手动修复(否则指向错误);
    • 实例对象的constructor是通过原型链继承自原型对象的。

示例:constructor 的修复场景

javascript

运行

复制代码
function Person(age) {
  this.age = age;
}

// 重写原型对象(覆盖默认constructor)
Person.prototype = {
  run: function() {
    console.log(`跑步,年龄${this.age}`);
  }
};

const p = new Person(25);
console.log(p.constructor); // Object(错误,应为Person)

// 手动修复constructor
Person.prototype.constructor = Person;
console.log(p.constructor); // Person(正确)

三、JavaScript 的 6 种继承方式:从基础到进阶

JavaScript 没有原生的 "类继承" 语法(ES6 的class本质是原型继承的语法糖),但通过原型机制可实现多种继承方式,不同方式各有优劣,需根据场景选型。

1. 原型链继承(基础方式)

  • 实现原理:让子类的原型对象指向父类的实例,通过原型链继承父类的属性和方法。
  • 实战代码

javascript

运行

复制代码
// 父类:Animal
function Animal(type) {
  this.type = type; // 实例属性
  this.eat = function() { // 实例方法(会被所有子类实例共享吗?不!)
    console.log(`${this.type}在吃东西`);
  };
}

// 父类原型方法(共享)
Animal.prototype.sleep = function() {
  console.log(`${this.type}在睡觉`);
};

// 子类:Dog(继承Animal)
function Dog(name) {
  this.name = name;
}

// 核心:子类原型指向父类实例,构建原型链
Dog.prototype = new Animal("狗");
// 修复constructor
Dog.prototype.constructor = Dog;

// 子类添加自有方法
Dog.prototype.bark = function() {
  console.log(`${this.name}在叫`);
};

// 测试
const dog = new Dog("旺财");
dog.eat(); // "狗在吃东西"(继承自父类实例)
dog.sleep(); // "狗在睡觉"(继承自父类原型)
dog.bark(); // "旺财在叫"(子类自有方法)
console.log(dog.__proto__ === Dog.prototype); // true
console.log(dog.__proto__.__proto__ === Animal.prototype); // true(原型链层级)
  • 优点:实现简单,直接通过原型链继承父类所有属性和方法。
  • 缺点
    1. 父类的实例属性会被所有子类实例共享(如type属性,修改一个实例会影响其他);
    2. 子类实例创建时无法向父类构造函数传递参数;
    3. 无法实现多继承。

2. 构造函数继承(解决实例属性共享问题)

  • 实现原理 :在子类构造函数中通过call()/apply()调用父类构造函数,将父类的实例属性绑定到子类实例上。
  • 实战代码

javascript

运行

复制代码
// 父类:Animal
function Animal(type) {
  this.type = type;
  this.skills = ["跑", "跳"]; // 引用类型实例属性
}

// 子类:Dog(构造函数继承)
function Dog(name, type) {
  // 核心:调用父类构造函数,绑定this为子类实例
  Animal.call(this, type); 
  this.name = name; // 子类自有属性
}

// 测试
const dog1 = new Dog("旺财", "狗");
const dog2 = new Dog("来福", "狗");

dog1.skills.push("叫");
console.log(dog1.skills); // ["跑", "跳", "叫"]
console.log(dog2.skills); // ["跑", "跳"](实例属性独立,无共享问题)
console.log(dog1.type); // "狗"(继承自父类)

// 缺点:无法继承父类原型上的方法
dog1.sleep(); // 报错:dog1.sleep is not a function
  • 优点
    1. 父类实例属性独立(无共享问题);
    2. 子类实例创建时可向父类传递参数。
  • 缺点
    1. 无法继承父类原型上的方法(只能继承实例属性和方法);
    2. 父类的实例方法会被每个子类实例重复创建(浪费内存)。

3. 组合继承(原型链 + 构造函数,最常用基础方式)

  • 实现原理:结合 "原型链继承" 和 "构造函数继承" 的优点 ------ 原型链继承父类原型方法(共享),构造函数继承父类实例属性(独立)。
  • 实战代码

javascript

运行

复制代码
// 父类:Animal
function Animal(type) {
  this.type = type;
  this.skills = ["跑", "跳"];
}

// 父类原型方法(共享)
Animal.prototype.sleep = function() {
  console.log(`${this.type}在睡觉`);
};

// 子类:Dog(组合继承)
function Dog(name, type) {
  // 1. 构造函数继承:继承实例属性(独立)
  Animal.call(this, type);
  this.name = name;
}

// 2. 原型链继承:继承原型方法(共享)
Dog.prototype = new Animal();
// 修复constructor
Dog.prototype.constructor = Dog;

// 子类原型方法
Dog.prototype.bark = function() {
  console.log(`${this.name}在叫`);
};

// 测试
const dog1 = new Dog("旺财", "狗");
const dog2 = new Dog("来福", "狗");

// 实例属性独立
dog1.skills.push("叫");
console.log(dog1.skills); // ["跑", "跳", "叫"]
console.log(dog2.skills); // ["跑", "跳"]

// 原型方法共享
console.log(dog1.sleep === dog2.sleep); // true
dog1.sleep(); // "狗在睡觉"

// 子类方法正常
dog1.bark(); // "旺财在叫"
  • 优点
    1. 实例属性独立(无共享问题);
    2. 原型方法共享(节省内存);
    3. 支持向父类构造函数传递参数。
  • 缺点
    1. 父类构造函数会被调用两次(一次是new Animal(),一次是Animal.call());
    2. 子类原型上会存在父类的实例属性(冗余,如typeskills),但会被子类实例属性覆盖。

4. 寄生组合继承(最优基础继承方式)

  • 实现原理:优化组合继承的缺点,通过 "寄生构造函数" 创建父类原型的副本,避免父类构造函数被调用两次。
  • 实战代码

javascript

运行

复制代码
// 父类:Animal
function Animal(type) {
  this.type = type;
  this.skills = ["跑", "跳"];
}

Animal.prototype.sleep = function() {
  console.log(`${this.type}在睡觉`);
};

// 核心:创建父类原型的副本(不调用父类构造函数)
function createProto(Parent) {
  function F() {} // 空构造函数(寄生)
  F.prototype = Parent.prototype; // 继承父类原型
  return new F(); // 返回原型副本实例
}

// 子类:Dog(寄生组合继承)
function Dog(name, type) {
  Animal.call(this, type); // 构造函数继承(仅调用一次父类构造)
  this.name = name;
}

// 原型链继承:子类原型指向父类原型副本
Dog.prototype = createProto(Animal);
// 修复constructor
Dog.prototype.constructor = Dog;

// 子类方法
Dog.prototype.bark = function() {
  console.log(`${this.name}在叫`);
};

// 测试
const dog = new Dog("旺财", "狗");
console.log(dog.__proto__.__proto__ === Animal.prototype); // true(原型链正常)
console.log(dog.type); // "狗"(实例属性正常)
dog.sleep(); // "狗在睡觉"(原型方法正常)
  • 优点
    1. 组合继承的所有优点(实例独立、原型共享、支持传参);
    2. 父类构造函数仅调用一次(无冗余属性);
    3. 原型链清晰,无多余层级。
  • 缺点:实现稍复杂(可封装为工具函数)。

5. ES6 class 继承(语法糖,推荐现代开发)

  • 实现原理 :ES6 引入的class语法,本质是原型继承的 "语法糖",底层逻辑与寄生组合继承一致,但写法更简洁、直观。
  • 实战代码

javascript

运行

复制代码
// 父类:Animal(class定义)
class Animal {
  // 构造函数(对应ES5的构造函数)
  constructor(type) {
    this.type = type;
    this.skills = ["跑", "跳"];
  }

  // 原型方法(对应ES5的Animal.prototype.method)
  sleep() {
    console.log(`${this.type}在睡觉`);
  }

  // 静态方法(不会被实例继承,仅属于类本身)
  static eat() {
    console.log("动物都需要吃东西");
  }
}

// 子类:Dog(extends继承)
class Dog extends Animal {
  constructor(name, type) {
    // 核心:调用父类构造函数(必须在this前调用)
    super(type); 
    this.name = name; // 子类自有属性
  }

  // 子类原型方法
  bark() {
    console.log(`${this.name}在叫`);
  }

  // 重写父类方法(多态)
  sleep() {
    console.log(`${this.name}(${this.type})在打盹`);
  }
}

// 测试
const dog = new Dog("旺财", "狗");
dog.bark(); // "旺财在叫"(子类方法)
dog.sleep(); // "旺财(狗)在打盹"(重写父类方法)
Animal.eat(); // "动物都需要吃东西"(静态方法,类调用)
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true(继承关系成立)
  • 优点
    1. 语法简洁直观,符合传统面向对象编程习惯;
    2. 原生支持继承、静态方法、方法重写(多态);
    3. 底层逻辑优化,无组合继承的缺点。
  • 缺点:ES6 语法,需兼容旧浏览器(可通过 Babel 转译)。

6. 寄生继承(补充方式)

  • 实现原理:基于现有对象创建新对象,增强其属性和方法,本质是 "原型继承 + 对象增强"。
  • 实战代码

javascript

运行

复制代码
// 父类:Animal
function Animal(type) {
  this.type = type;
}

Animal.prototype.sleep = function() {
  console.log(`${this.type}在睡觉`);
};

// 寄生继承函数
function createDog(original, name) {
  // 创建父类实例的副本(原型继承)
  const clone = Object.create(original.prototype);
  // 增强对象(添加自有属性和方法)
  clone.name = name;
  clone.bark = function() {
    console.log(`${this.name}在叫`);
  };
  return clone;
}

// 测试
const dog = createDog(Animal, "旺财");
dog.type = "狗";
dog.sleep(); // "狗在睡觉"(继承父类原型方法)
dog.bark(); // "旺财在叫"(增强方法)
  • 优点:实现简单,可快速增强现有对象。
  • 缺点
    1. 无法实现多继承;
    2. 增强的方法无法共享(每个实例一份,浪费内存)。

四、6 种继承方式对比选型表(一目了然)

继承方式 优点 缺点 适用场景
原型链继承 实现简单,继承完整 实例属性共享、无法传参、无多继承 简单场景,无需独立实例属性
构造函数继承 实例属性独立、支持传参 无法继承原型方法、方法重复创建 仅需继承父类实例属性的场景
组合继承 实例独立、原型共享、支持传参 父类构造调用两次、原型有冗余属性 ES5 环境的主流场景
寄生组合继承 实例独立、原型共享、支持传参、无冗余 实现稍复杂 ES5 环境的最优选择
ES6 class 继承 语法简洁、原生支持多态、无底层缺陷 需 ES6 + 环境(可转译) 现代开发(React/Vue/Node.js)
寄生继承 实现简单、可增强对象 方法无法共享、无多继承 快速增强现有对象的临时场景

五、实战场景:原型与继承的核心应用

1. 框架中的原型应用(React/Vue)

(1)Vue 组件的原型链

Vue 组件实例的原型链为:VueComponent实例 → VueComponent.prototype → Vue.prototype,因此可通过Vue.prototype挂载全局方法 / 属性:

javascript

运行

复制代码
// 全局挂载工具方法(通过原型链共享)
Vue.prototype.$formatDate = function(date) {
  return date.toLocaleDateString("zh-CN");
};

// 所有组件实例均可访问
new Vue({
  mounted() {
    console.log(this.$formatDate(new Date())); // 所有组件共享该方法
  }
});
(2)React 类组件的继承

React 类组件基于 ES6 class继承,Component类的原型方法(如setStatecomponentDidMount)被所有子类组件继承:

javascript

运行

复制代码
// 子类组件继承React.Component
class App extends React.Component {
  constructor(props) {
    super(props); // 调用父类构造函数
    this.state = { count: 0 };
  }

  render() {
    return (
      <div>
        <p>计数:{this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          加1
        </button>
      </div>
    );
  }
}

2. 原型链排错实战

问题:实例属性被意外覆盖

javascript

运行

复制代码
// 父类
function Person() {
  this.hobbies = ["读书"];
}

// 子类
function Student() {}
Student.prototype = new Person();

const s1 = new Student();
const s2 = new Student();
s1.hobbies.push("运动");
console.log(s2.hobbies); // ["读书", "运动"](意外共享)
原因:父类实例属性被子类实例共享(原型链继承的缺陷)
解决方案:改用组合继承或 ES6 class 继承,通过call()绑定实例属性

3. 手动实现new关键字(原型核心面试题)

new关键字的本质是创建实例对象并关联原型链,手动实现可深度理解原型机制:

javascript

运行

复制代码
function myNew(constructor, ...args) {
  // 1. 创建空对象
  const obj = {};
  // 2. 关联原型链(obj.__proto__ = constructor.prototype)
  Object.setPrototypeOf(obj, constructor.prototype);
  // 3. 调用构造函数,绑定this为obj
  const result = constructor.apply(obj, args);
  // 4. 若构造函数返回对象,返回该对象;否则返回obj
  return typeof result === "object" && result !== null ? result : obj;
}

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

const obj = myNew(Foo, "张三");
obj.sayHello(); // "Hello, 张三"(原型链关联成功)

六、避坑指南:90% 开发者踩过的 5 个坑

1. 坑点 1:重写原型后未修复constructor

  • 问题代码

javascript

运行

复制代码
function Person() {}
Person.prototype = {
  run: function() {}
};
const p = new Person();
console.log(p.constructor === Person); // false(指向Object)
  • 原因 :重写原型对象时,覆盖了默认的constructor属性。
  • 解决方案 :手动修复constructor指向:

javascript

运行

复制代码
Person.prototype.constructor = Person;

2. 坑点 2:混淆 "原型链查找优先级"

  • 问题代码

javascript

运行

复制代码
function Foo() {
  this.name = "实例属性";
}
Foo.prototype.name = "原型属性";
const obj = new Foo();
console.log(obj.name); // "实例属性"(预期原型属性)
  • 原因:实例属性优先级高于原型属性,查找时先找实例,再找原型。
  • 解决方案 :若需访问原型属性,需通过Object.getPrototypeOf(obj).name

3. 坑点 3:直接修改__proto__导致性能问题

  • 问题__proto__是访问器属性,直接修改会触发原型链重构,性能极差。
  • 解决方案 :创建对象时通过Object.create()指定原型:

javascript

运行

复制代码
const proto = { name: "张三" };
const obj = Object.create(proto); // 替代 obj.__proto__ = proto

4. 坑点 4:认为 "ES6 class 是真正的类继承"

  • 问题 :误以为class是 JavaScript 新增的 "类继承" 机制,忽略其原型本质。
  • 真相class是原型继承的语法糖,底层仍依赖prototype__proto__
  • 验证

javascript

运行

复制代码
class Foo {}
console.log(Foo.prototype); // 存在原型对象,证明其原型本质

5. 坑点 5:原型上定义引用类型属性

  • 问题代码

javascript

运行

复制代码
function Foo() {}
Foo.prototype.list = [];
const obj1 = new Foo();
const obj2 = new Foo();
obj1.list.push(1);
console.log(obj2.list); // [1](意外共享)
  • 原因:原型上的引用类型属性会被所有实例共享。
  • 解决方案:将引用类型属性定义在构造函数中(实例属性):

javascript

运行

复制代码
function Foo() {
  this.list = []; // 每个实例独立拥有
}

七、总结:核心知识点速查表与原则

1. 核心知识点速查表

核心概念 关键结论
原型链 实例→构造函数.prototype→父类.prototype→Object.prototype→null
继承方式选型 现代开发优先 ES6 class,ES5 环境用寄生组合继承
查找优先级 实例属性 > 原型属性 > 父类原型属性
核心属性关系 实例.proto === 构造函数.prototype
静态方法 属于类本身,不被实例继承(ES6 class 用 static 关键字)

2. 核心原则(记住 3 句话)

  1. 原型用于共享:方法和不变属性定义在原型上,节省内存;
  2. 实例用于独立:引用类型属性和可变属性定义在实例上,避免共享;
  3. 现代优先 ES6 class:语法简洁、无底层缺陷,是当前开发的最优选择。

八、拓展学习资源

  1. MDN 官方文档:原型链与继承
  2. V8 引擎官方博客:《JavaScript 原型机制解析》
  3. 面试高频题:《手动实现 ES6 class 继承》《原型链查找的实现原理》

原型与继承的核心是理解 "共享与独立" 的设计思想 ------ 原型负责共享方法,实例负责存储独立属性。掌握这一思想,再结合实战场景反复练习,就能彻底走出 "原型链迷宫",写出更高效、易维护的 JavaScript 代码。

相关推荐
走,带你去玩2 小时前
uniapp live-pusher + 腾讯云直播
前端·javascript·uni-app
徐同保2 小时前
electron打包项目
前端·javascript·electron
Maybyy2 小时前
如何在项目里面添加一个可以左右翻动并显示指定日期的日历
前端·vue.js
柯南二号2 小时前
【大前端】【Android】用 Python 脚本模拟点击 Android APP —— 全面技术指南
android·前端·python
南棱笑笑生2 小时前
20251211给飞凌OK3588-C开发板跑飞凌Android14时让OV5645摄像头以1080p录像
c语言·开发语言·rockchip
Arvin_Rong2 小时前
前端动态 API 生成与封装:前端 API 调用的一种思路
前端
2401_860319522 小时前
DevUI组件库实战:从入门到企业级应用的深度探索,如何实现支持表格扩展和表格编辑功能
前端·前端框架
LYFlied2 小时前
从循环依赖检查插件Circular Dependency Plugin源码详解Webpack生命周期以及插件开发
前端·webpack·node.js·编译原理·plugin插件开发
翔云 OCR API2 小时前
赋能文档的数字化智能处理:通用文字/文档/合同识别接口
开发语言·人工智能·python·计算机视觉·ocr