惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
SegmentFault 最新的问题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
B
Blog RSS Feed
Y
Y Combinator Blog
T
Tailwind CSS Blog
博客园 - 三生石上(FineUI控件)
J
Java Code Geeks
Stack Overflow Blog
Stack Overflow Blog
aimingoo的专栏
aimingoo的专栏
Jina AI
Jina AI
The GitHub Blog
The GitHub Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
A
About on SuperTechFans
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
Docker
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Check Point Blog
M
MIT News - Artificial intelligence
Last Week in AI
Last Week in AI
V
V2EX
腾讯CDC
F
Fortinet All Blogs
博客园 - 叶小钗
T
The Blog of Author Tim Ferriss

博客园 - lzhdim

25个每个开发人员都应该掌握的JavaScript 数组方法 20260831 - 个人小作品更新 C#使用SQLite数据库例子 - 开源研究系列文章 20260830 - 个人小作品更新 20260829 - 个人小作品更新 13、JavaScript事件循环机制 - JavaScript学习系列文章 首款媲美分体水冷的一体水冷!微星CORELIQUID E15 360水冷散热器评测:目前为止 锐龙9 9950X3D最强性能释放 JavaScript常见的内存泄露问题 - JavaScript学习系列文章 C#开发的颜色取色器 - 开源研究系列文章 - 个人小作品 成长环境 - 我的闪存 提高 SQL 语句执行速度的方法 JavaScript安全最佳实践 关闭iPhone一些默认设置,让iPhone更流畅 20260814 - 个人小作品更新 JavaScript对象与元编程 20260812 - 个人小作品更新 JavaScript 模块化系统 Markdown 语法完全指南 C# 通过 Windows API 实现进程内存读写操作 JavaScript 函数进阶课题 C#开发的基于钩子技术监测键鼠事件的例子 - 开源项目研究文章 JavaScript异步编程的演进 近十年最帅风冷!酷冷至尊V8 ACE 3DHP散热器评测:单塔能干翻双塔 20260723 - 个人小作品更新 C# 中读取CPU、硬盘和内存温度 雷柏VT3s MAX鼠标评测:中小手型用户也没有被遗忘 顶尖性能的小号电竞鼠标来了 1分钟查清局域网所有IP 20260714 - 个人小作品更新 WorkBuddy 完整教程手册 专为AI而生的企业级QLC SSD!长江存储PE501 61.44TB评测:超过1PB的写入测试
JavaScript 中的原型与继承
lzhdim · 2026-07-16 · via 博客园 - lzhdim

一、从对象说起, 一切皆对象在 JS 中, 几乎所有的东西都是对象, 或者说最终都会指向某个对象. 比如:

let arr = [1, 2, 3];console.log(typeof arr); 
function foo() {}console.log(typeof foo); 

简单的对象创建: 

let person = {    name: '张三',    age: 25,    sayHi: function() {        console.log('你好,我是' + this.name);    }};
person.sayHi(); // 你好, 我是张三

但这样有个问题——每次创建新对象都要写一遍所有属性和方法, 太麻烦了! 于是我们有了后面的构造函数. 二、构造函数,对象的工厂

function Person(name, age) {    this.name = name;    this.age = age;    this.sayHi = function() {        console.log('你好, 我是' + this.name);    };}
let person1 = new Person('张三', 25);let person2 = new Person('李四', 30);
person1.sayHi(); // 你好, 我是张三person2.sayHi(); // 你好, 我是李四

这里有几个关键点: 

1. 函数名通常大写开头,这是约定俗成的构造函数命名方式

2. 使用 new 关键字调用

3. 函数内部使用 this 来指代新创建的对象

当你使用 new 调用函数时,背后发生了这些事: 

1. 创建一个全新的空对象

2. 将这个空对象的 __proto__ 指向构造函数的 prototype 属性

3. 将构造函数中的 this 绑定到这个新对象

4. 执行构造函数中的代码

5. 如果构造函数没有显式返回对象,则返回这个新对象

三、原型登场

上面的构造函数方式有个问题——每个实例都会创建自己的方法副本, 浪费内存, 这时候,原型(prototype)就派上用场了!

什么是原型?

每个函数都有一个 prototype 属性, 它指向一个对象. 当使用这个函数作为构造函数创建实例时, 所有实例都会共享这个原型对象上的属性和方法.

改进版的 Person: 

function Person(name, age) {    this.name = name;    this.age = age;}
// 将方法放在原型上Person.prototype.sayHi = function() {    console.log('你好,我是' + this.name);};
let person1 = new Person('张三'25);let person2 = new Person('李四'30);
console.log(person1.sayHi === person2.sayHi); 

什么是原型链?

当我们访问一个对象的属性时, JavaScript 会:

1. 先在对象自身属性中查找

2. 如果没找到, 就去对象的 __proto__ (即构造函数的 prototype) 中查找

3. 如果还没找到, 就继续往 __proto__.__proto__ 中查找

4. 直到找到 Object.prototype (最顶层的原型,它的 __proto__ 是 null)

这就是原型链, JavaScript的继承机制.

function Person() {}Person.prototype.species = '人类';
let p = new Person();console.log(p.species); //"人类" - 从原型上找到的console.log(p.__proto__ === Person.prototype); console.log(Person.prototype.__proto__ === Object.prototype); console.log(Object.prototype.__proto__); 

如果对象自身有属性, 就不会去原型上找了:

p.species = '地球人';console.log(p.species); //"地球人" - 自身的属性console.log(Person.prototype.species); 
delete p.species;console.log(p.species); //"人类" - 删除自身属性后又从原型上找到了

四、几种继承的方式

1)原型链继承: 

function Parent() {    this.parentProp = '父类属性';}Parent.prototype.parentMethod = function() {    console.log('父类方法');};
function Child() {    this.childProp = '子类属性';}
// 关键点: 子类的原型指向父类的实例Child.prototype = new Parent();
let child = new Child();console.log(child.childProp); // "子类属性"console.log(child.parentProp); // "父类属性"-通过原型链找到的child.parentMethod(); // "父类方法"

问题: 

  1. 所有子类实例共享同一个父类实例的属性(如果是引用类型就麻烦了)

  2. 创建子类实例时无法向父类构造函数传参

2)构造函数继承: 

function Parent(name) {    this.name = name;    this.colors = ['red', 'blue'];}
function Child(name, age) {    Parent.call(this, name); //关键点:在子类构造函数中调用父类构造函数    this.age = age;}
let child1 = new Child('张三', 25);child1.colors.push('green');
let child2 = new Child('李四', 30);console.log(child2.colors); //["red", "blue"]-不受child1影响

优点: 

1. 避免了引用类型属性被所有实例共享

2. 可以在子类中向父类传参

缺点: 

  1. 方法都在构造函数中定义,每次创建实例都会创建一遍方法

  2. 不能继承父类原型上的方法

3)组合继承:

结合原型链继承和构造函数继承的优点:

function Parent(name) {    this.name = name;    this.colors = ['red', 'blue'];}Parent.prototype.sayName = function() {    console.log(this.name);};
function Child(name, age) {    Parent.call(this, name); //第二次调用Parent    this.age = age;}
Child.prototype = new Parent(); Child.prototype.constructor = Child; Child.prototype.sayAge = function() {    console.log(this.age);};
let child1 = new Child('张三', 25);child1.colors.push('green');child1.sayName(); //"张三"child1.sayAge(); //25
let child2 = new Child('李四', 30);console.log(child2.colors); //["red", "blue"]

4)寄生组合继承:

function inheritPrototype(child, parent) {    let prototype = Object.create(parent.prototype);     //Object.create 是创建以指定对象为原型的新对象的方法    prototype.constructor = child; // 修复constructor    child.prototype = prototype; // 将副本作为子类的原型}
function Parent(name) {    this.name = name;    this.colors = ['red', 'blue'];}Parent.prototype.sayName = function() {    console.log(this.name);};
function Child(name, age) {    Parent.call(this, name);    this.age = age;}
// 关键: 使用我们封装的inheritPrototype函数inheritPrototype(Child, Parent);
Child.prototype.sayAge = function() {    console.log(this.age);};
let child = new Child('张三', 25);child.sayName(); // "张三"child.sayAge(); // 25

优点:

1. 只调用一次父类构造函数

2. 子类原型上不会有多余的父类属性

3. 原型链保持不变

五、ES6的class

ES6 引入了 class 语法, 让面向对象编程更加直观:

class Parent {    constructor(name) {        this.name = name;        this.colors = ['red''blue'];    }
    sayName() {        console.log(this.name);    }}
class Child extends Parent {    constructor(name, age) {        super(name); // 必须在使用this之前调用super        this.age = age;    }
    sayAge() {        console.log(this.age);    }}
let child = new Child('张三'25);child.sayName(); // "张三"child.sayAge(); // 25

class 本质上仍然是基于原型的语法糖. 上面的代码大致相当于:

function Parent(name) {    this.name = name;    this.colors = ['red''blue'];}
Parent.prototype.sayName = function() {    console.log(this.name);};
function Child(name, age) {    Parent.call(this, name);    this.age = age;}
Child.prototype = Object.create(Parent.prototype);Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {    console.log(this.age);};

class 还提供了静态方法和属性的简洁写法: 

class MyClass {    static staticMethod() {        console.log('我是静态方法');    }
    static staticProperty = '我是静态属性';}
MyClass.staticMethod(); //"我是静态方法"console.log(MyClass.staticProperty); 

六、其他相关内容

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上.

function myInstanceof(instance, constructor) {    let proto = Object.getPrototypeOf(instance);    while (proto) {        if (proto === constructor.prototype) {            return true;        }        proto = Object.getPrototypeOf(proto);    }    return false;}
console.log([] instanceof Array); console.log(myInstanceof([], Array)); 

原型可以被修改, 这可能导致安全问题: 

// 不要这样做!Object.prototype.hack = function() {    console.log('被黑了!');};
let obj = {};obj.hack(); // "被黑了!"