《从分遗产说起:JS 原型与继承详解》

"天天开心就好"


先来讲讲概念:

原型(Prototype)

什么是原型?

原型是 JavaScript 中实现对象间共享属性和方法的机制。每个 JavaScript 对象(除了 null)都有一个内部链接指向另一个对象,这个对象就是它的"原型"(prototype)。

继承(Inheritance)

什么是继承?

继承是面向对象编程中的一个核心概念,它允许一个对象(子对象)获取另一个对象(父对象)的属性和方法。在 JavaScript 中,继承主要通过原型链来实现。

好的现在我们明确了什么是原型,什么是继承。简单来说,原型就是一个机制,每个对象内部都一个内部链接指向他的原型。继承我的理解就是一种行为,就是像继承财产那样继承父对象的属性和方法,可谓是形容十分贴切。

原型基础

原型对象 (prototype)

  • 每个函数都有一个 prototype 属性(箭头函数除外)
  • 这个属性指向一个对象,称为"原型对象"
  • 原型对象包含可以被特定类型的所有实例共享的属性和方法
javascript 复制代码
function Person() {}
Person.prototype.name = 'Default';
Person.prototype.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
};

__proto__ 属性

  • 每个对象都有一个 __proto__ 属性(现已标准化为 Object.getPrototypeOf()
  • 指向创建该对象的构造函数的原型对象
javascript 复制代码
const person = new Person();
console.log(person.__proto__ === Person.prototype); // true

这个很好理解了,我在这里想用c语言里面的指针来形容了。prototype就像是地址对应的数据,而_proto_就像是指向他的指针。不太恰当哈

我们经常这样说:对象的继承是通过原型链实现的。

那么什么是原型链:

什么是原型链?

原型链(Prototype Chain)是 JavaScript 中实现继承的核心机制。当访问一个对象的属性或方法时,JavaScript 引擎会沿着对象的原型链向上查找,直到找到该属性或到达原型链的末端(null)。

原型链的构成

  1. ​每个对象都有一个 __proto__ 属性​ (现已标准化为 Object.getPrototypeOf()
  2. ​每个函数都有一个 prototype 属性​
  3. ​原型链的终点是 null

原型链的工作原理

当访问一个对象的属性时:

  1. 首先在对象自身查找该属性
  2. 如果没有找到,则查找对象的 __proto__(即其构造函数的 prototype
  3. 如果还没有找到,继续查找 __proto__.__proto__,依此类推
  4. 直到找到该属性或到达 null(此时返回 undefined
javascript 复制代码
function Animal(name) {
    this.name = name;
}
Animal.prototype.eat = function() {
    console.log(`${this.name} is eating`);
};

function Dog(name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
    console.log('Woof!');
};

const myDog = new Dog('Buddy', 'Golden Retriever');

// 原型链:
// myDog -> Dog.prototype -> Animal.prototype -> Object.prototype -> null

我们讲原型和继承就是在意js中继承这种行为是怎么实现的,就像现实中大家只在乎怎么分遗产一样!

继承实现方式

1.原型链继承

javascript 复制代码
function Parent() {
    this.parentProperty = true;
}
Parent.prototype.getParentValue = function() {
    return this.parentProperty;
};

function Child() {
    this.childProperty = false;
}
// 继承 Parent
Child.prototype = new Parent();

const instance = new Child();
console.log(instance.getParentValue()); // true

​问题​​:

  • 所有子类实例共享同一个父类实例
  • 无法向父类构造函数传参

2. 构造函数继承

javascript 复制代码
function Parent(name) {
    this.name = name;
}

function Child(name) {
    Parent.call(this, name); // 在子类构造函数中调用父类构造函数
}

const child = new Child('Alice');
console.log(child.name); // 'Alice'

​优点​​:

  • 可以向父类传参
  • 每个子类实例都有独立的父类属性副本

​缺点​​:

  • 无法继承父类原型上的方法

3.组合继承(最常用)

javascript 复制代码
function Parent(name) {
    this.name = name;
}
Parent.prototype.sayName = 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 child = new Child('Alice', 25);
child.sayName(); // 'Alice'

​优点​​:

  • 结合了原型链和构造函数的优点
  • 既能继承原型方法,又能保证实例属性独立

​缺点​​:

  • 父类构造函数被调用了两次

4. 原型式继承

javascript 复制代码
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

const parent = { name: 'Parent' };
const child = object(parent);
console.log(child.name); // 'Parent'

ES5 标准化为 Object.create()

javascript 复制代码
const child = Object.create(parent);

5. 寄生式继承

javascript 复制代码
function createAnother(original) {
    const clone = Object.create(original);
    clone.sayHi = function() {
        console.log('Hi');
    };
    return clone;
}

6. 寄生组合式继承(最佳实践)

javascript 复制代码
function inheritPrototype(child, parent) {
    const prototype = Object.create(parent.prototype);
    prototype.constructor = child;
    child.prototype = prototype;
}

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

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

inheritPrototype(Child, Parent);

const child = new Child('Alice', 25);
child.sayName(); // 'Alice'

​优点​​:

  • 只调用一次父类构造函数
  • 原型链保持正确
  • 最理想的继承方式

ES6 的 class 继承

javascript 复制代码
class Parent {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log(this.name);
    }
}

class Child extends Parent {
    constructor(name, age) {
        super(name); // 调用父类构造函数
        this.age = age;
    }
}

const child = new Child('Alice', 25);
child.sayName(); // 'Alice'

​注意​​:

  • class 本质上是语法糖,底层仍然是基于原型的继承
  • extends 实现了寄生组合式继承
  • 必须在使用 this 前调用 super()

继承是js中很核心的机制了,有很多中方式来实现继承,继承的好处就是我们可以直接继承父对象的方法和属性而不用自己再次定义了。用好继承可以大大提升我们的代码水平,帮助我们实现更多复杂需求。


相关推荐
前端付豪几秒前
1、为什么浏览器要有渲染流程? ——带你一口气吃透 Critical Rendering Path
前端·后端·浏览器
前端付豪3 分钟前
3、Node.js异步编程彻底吃透
前端·后端·node.js
Bob99987 分钟前
Amlogic S905L3系列盒子 ROM DIY相关
java·javascript·数据仓库·vscode·eclipse·tomcat·vim
孤鸿玉8 分钟前
[Flutter小试牛刀] 低配版signals,添加局部刷新
前端·flutter
亦黑迷失9 分钟前
轻量级 Express 服务器:用 Pug 模板引擎实现动态参数传递
前端·javascript·后端
吃瓜群众i1 小时前
理解Javascript闭包
前端·javascript
安大桃子1 小时前
Mapbox GL + Deck.gl 三维实战:Mapbox 加载 Tileset3D 倾斜摄影模型
前端·webgl
yede1 小时前
多行文本省略号显示,更多按钮展开全部
前端
就是我1 小时前
React 应用性能优化实战
前端·react.js·性能优化