现实生活例子[特殊字符] 通俗易懂的解释[特殊字符] JS中的原型和原型链[特殊字符]

目录

1、先理解下什么是构造函数,又与普通函数的区别(可跳过这部分,回头再看)

2、原型对象

3、构造函数与原型

[4、打个现实比方解释 原型 Prototype、原型链](#4、打个现实比方解释 原型 Prototype、原型链)

[4.1 原型(Prototype)------「家族遗传(家族基因库)」](#4.1 原型(Prototype)——「家族遗传(家族基因库)」)

[4.2 原型链(Prototype Chain)------「家族族谱」](#4.2 原型链(Prototype Chain)——「家族族谱」)

[4.3 特殊案例------「DNA突变」‌](#4.3 特殊案例——「DNA突变」‌)

[4.4 现代家族(ES6 Class)------「家规手册」](#4.4 现代家族(ES6 Class)——「家规手册」)

‌总结比喻‌

‌关键点‌:

[prototype 与 proto 的关系------「基因库的访问路径」‌](#prototype 与 proto 的关系——「基因库的访问路径」‌)

[5、打比方解释 constructor、prototype 和 proto 的关系](#5、打比方解释 constructor、prototype 和 proto 的关系)

[6、拓展:Object.create() 和 new 的区别](#6、拓展:Object.create() 和 new 的区别)

[6.1 ‌核心作用不同‌](#6.1 ‌核心作用不同‌)

[6.2 原型链机制差异‌](#6.2 原型链机制差异‌)

7、拓展:new操作符都干了什么?


1、先理解下什么是构造函数,又与普通函数的区别(可跳过这部分,回头再看)

  1. 通过 new 调用

  2. 约定俗成 以大写字母开头(如PersonArray),(JavaScript不强制要求,但遵循约定可提高代码可读性)

  3. 显式或隐式分配实例属性

  • 显式赋值‌:通过this.xxx = yyy为实例添加属性。
  • ‌隐式分配‌:在构造函数中操作this的属性(如this.age++)。
javascript 复制代码
// 一
function Car(model) {
  this.model = model;      // 实例属性
  this.wheels = 4;         // 实例属性
  this.drive = function() { // 实例方法(不推荐,通常用原型)
    console.log(`${this.model} is driving!`);
  };
}
const alice = new Car("WW"); // 正确


// 二
function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function() { // 实例方法(推荐)

  console.log(`${this.name} makes a noise.`);
};

const cat = new Animal("Whiskers");
cat.speak(); // "Whiskers makes a noise."(方法来自原型)


// 三
function Person(name) {
  this.name = name; // 正确:通过new调用时,this指向新实例
}
const alice = new Person("Alice"); // 正确
const bob = Person("Bob");         // 错误:this指向全局对象(严格模式为undefined)

2、原型对象

原型对象是实现 JavaScript 继承和共享方法的核心机制。

  • js中,每个普通函数 ‌(非箭头函数)都有一个prototype 属性(可通过__proto__Object.getPrototypeOf()访问),这个属性指向的是一个对象,这个对象即为原型对象。
  • 这个原型对象(Person.prototype)默认包含一个 constructor 属性,指向函数本身(即 Person.prototype.constructor === Person)。
javascript 复制代码
function Person() {}
console.log(Person.prototype); // 输出: { constructor: [Function: Person] }

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ‌箭头函数的特殊性‌ * 箭头函数(() => {})‌没有 ‌自己的 prototype 属性,因此不能通过 new 调用(即不能作为构造函数)。 * 这是为了简化箭头函数的用途(通常用于函数式编程或需要绑定 this 的场景)。 * javascript const ArrowFunc = () => {}; console.log(ArrowFunc.prototype); // 输出: undefined |

原型对象干嘛的?

----共享属性和方法的

3、构造函数与原型

函数的 prototype 属性会在通过new创建实例时,自动成为实例的原型对象。

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

// 向构造函数的原型添加方法
Person.prototype.sayHi = function() { 
  console.log(`Hi, I'm ${this.name}`);
};

const john = new Person("John"); // 1. new Person()时,实例的[[Prototype]] 指向Person.prototype。所有实例共享Person.prototype上的方法,节省内存。
john.sayHi(); // "Hi, I'm John"(方法来自原型)



 // 检测原型关系的方法
john instanceof Constructor:检查构造函数是否在原型链上。
Person.prototype.isPrototypeOf(john):检查对象是否在原型链中。
Person.getPrototypeOf(john):获取对象的原型。

现代替代方案(ES6+)

javascript 复制代码
// class本质‌:语法糖,底层仍是原型继承。
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}


class Dog extends Animal { // extends‌:通过Object.setPrototypeOf(Child.prototype, Parent.prototype)实现。
  speak() {
    console.log(`${this.name} barks.`);
  }
}

const dog = new Dog("Rex");
dog.speak(); // "Rex barks."(方法覆盖)

4、打个现实比方解释 原型 Prototype、原型链

原型(Prototype)------「家族遗传(家族基因库)」

原型链(Prototype Chain)------「家族族谱」

现代家族(ES6 Class)------「家规手册」

4.1 原型(Prototype)------「家族遗传(家族基因库)」

想象你出生在一个家族,你的‌原型‌就是你的父母。父母有一些共同特征(比如眼睛颜色、身高基因),这些特征你天生就会继承:

javascript 复制代码
// 父母的特性(原型对象)
const parent = {
  eyeColor: "brown",
  sayHello() { console.log("Hello!"); }
};

// 你继承了父母的特性
const child = Object.create(parent); 
console.log(child.eyeColor); // "brown"(继承自父母)
child.sayHello();           // "Hello!"(调用父母的方法)
4.2 原型链(Prototype Chain)------「家族族谱」

现在扩展到你整个家族的血脉传承:

javascript 复制代码
// 曾祖父
const greatGrandpa = { familyName: "Smith" };

// 祖父继承了曾祖父
const grandpa = Object.create(greatGrandpa);
grandpa.hobby = "fishing";

// 父亲继承了祖父
const father = Object.create(grandpa);
father.job = "engineer";

// 你继承了父亲
const you = Object.create(father);
you.name = "Alice";

console.log(you.familyName); // "Smith"(曾祖父的姓氏)
console.log(you.hobby);      // "fishing"(祖父的爱好)
  • 查找过程 ‌(原型链):

    当你访问you.familyName时,JavaScript会按顺序查找:

    1. 你自己(you)→ 没有
    2. 父亲(father)→ 没有
    3. 祖父(grandpa)→ 没有
    4. 曾祖父(greatGrandpa)→ 找到!返回"Smith"
  • 终止条件 ‌:如果查到族谱顶端(Object.prototype)还没有,返回undefined

4.3 **特殊案例------「DNA突变」**‌

如果你自己定义了和原型相同的属性,会覆盖继承的属性(类似「基因突变」):

javascript 复制代码
const parent = { trait: "kind" };
const child = Object.create(parent);
child.trait = "brave"; // 覆盖原型属性

console.log(child.trait); // "brave"(优先用自己的)
4.4 现代家族(ES6 Class)------「家规手册」

ES6的class相当于把家族继承规则写成了明确的手册:

javascript 复制代码
class Family {
  constructor(lastName) {
    this.lastName = lastName; // 家族姓氏
  }
  sayMotto() { console.log("Family first!"); }
}

// 你的分支家族
class You extends Family {
  constructor(lastName, name) {
    super(lastName); // 必须调用父类构造方法
    this.name = name;
  }
}

const you = new You("Smith", "Alice");
you.sayMotto(); // "Family first!"(继承自Family原型)
总结比喻
  • 原型‌ = 直接父母(提供默认属性和方法)
  • 原型链‌ = 整个家族族谱(逐级向上查找特征)
  • 属性查找 ‌ = 问遍全家族,直到找到答案或确认没有(null
  • 修改原型‌ = 父母学新技能,子女自动受益
  • 覆盖属性‌ = 你决定特立独行,不跟父母一样
javascript 复制代码
// 构造函数(家族)
function Family(lastName) {
  this.lastName = lastName; // 实例独有的属性(如名字)
}

// 基因库(prototype)
Family.prototype = {
  familyName: "Smith", // 所有实例共享的姓氏
  sayMotto() {
    console.log(`${this.lastName} family: Family first!`);
  },
  tradition: "Annual picnic" // 共享的家族传统
};

// 创建实例(后代子孙)
const alice = new Family("Alice");
const bob = new Family("Bob");

// 实例共享基因库中的属性和方法
console.log(alice.familyName); // "Smith"(来自基因库)
console.log(bob.familyName);  // "Smith"(同一个基因库)
alice.sayMotto();             // "Alice family: Family first!"(共享方法)
bob.sayMotto();              // "Bob family: Family first!"(共享方法)

// 修改基因库会影响所有实例
Family.prototype.tradition = "Monthly hiking";
console.log(alice.tradition); // "Monthly hiking"(所有实例同步更新)
关键点‌:
  1. 共享性 ‌:Family.prototype 上的属性和方法会被所有实例共享,避免重复定义。
  2. 动态性 ‌:修改 prototype 会影响所有现有和未来的实例(类似基因库升级)。
  3. 关联性 ‌:实例通过内部 [[Prototype]] 属性(__proto__)指向 prototype 对象。

**prototype__proto__ 的关系------「基因库的访问路径」**‌
  • ‌**prototype**‌:构造函数独有的属性,指向基因库(原型对象)。
  • ‌**__proto__** ‌:实例对象的内部属性,指向构造函数的 prototype 对象(即基因库)。

类比家族‌:

  • prototype 是‌家族基因库的实体‌(由构造函数保管)。
  • __proto__ 是‌子孙后代手中的钥匙‌,指向基因库的位置。
javascript 复制代码
function Family() {}
const alice = new Family(); // 创建子孙后代

console.log(alice.__proto__ === Family.prototype); // true
// 实际关系:alice → [__proto__] → Family.prototype(基因库)
  • prototype ‌ = 家族的‌基因库‌(存储所有后代共享的属性和方法)。
  • 构造函数 ‌ = 家族的‌族谱登记处‌,负责为每个新成员关联基因库。
  • 实例 ‌ = 家族的‌后代个体 ‌,通过 __proto__ 指向基因库,继承共享特性。
  • 原型链 ‌ = 从个体到基因库再到祖先基因库的‌查找路径‌,实现属性继承。

5、打比方解释 constructorprototype__proto__ 的关系

用一个 ‌公司组织架构 ‌ 的比喻来解释 constructorprototype__proto__ 的关系,通俗易懂:

比喻场景:一家科技公司(JavaScript 对象系统)

  1. ‌**constructor → 部门经理**‌

    • 比如公司有个「前端开发部」,部门经理是 Person()(构造函数)。
    • 他的职责是 ‌招聘新员工 ‌(new Person() 创建实例)。
    • 每个员工的名牌上会写:「直属领导:Person()」(即实例的 constructor 属性指向构造函数)。
  2. ‌**prototype → 部门共享资源库(上述比方中的 家族基因库)**‌

    • 经理 Person() 有一个 ‌公共文件柜 ‌(Person.prototype)。
    • 里面放了部门通用的工具,比如《代码规范手册》(共享方法如 greet())。
    • 所有员工都可以用‌,但不需要每人复印一份(节省内存)。
  3. ‌**__proto__ → 员工的权限卡**‌

    • 新员工 alice 入职时,会拿到一张权限卡(alice.__proto__)。
    • 刷卡后,她能访问 ‌公共文件柜 ‌(Person.prototype)里的资源。
    • 如果她需要工具,先找自己的工位(实例属性),找不到就刷卡去文件柜找(原型链查找)。

关键互动流程:

  • 招人 ‌:new Person() → 经理 Person() 招了一个新员工 alice
  • 发权限卡 ‌:alice.__proto__ = Person.prototype(员工默认能访问部门资源)。
  • 查资料 ‌:
    • alice.greet() → 先看自己工位有没有 greet,没有 → 刷卡(__proto__)去文件柜(prototype)找。
    • 如果文件柜也没有,会去总公司(Object.prototype)找,最后没找到就报错。

一句话总结:

  • ‌**constructor**‌:谁创造了我?(构造函数)
  • ‌**prototype**‌:我的共享资源库在哪?(构造函数的公共存储)
  • ‌**__proto__**‌:我的权限能访问哪些资源?(实例的原型链入口)

6、拓展:Object.create()new 的区别

6.1 ‌核心作用不同‌
  • Object.create()

    用于‌基于现有对象创建新对象‌,并可指定新对象的原型链(即设置 [[Prototype]])。

javascript 复制代码
const parent = { name: 'Parent' };
const child = Object.create(parent); // child 的原型是 parent
console.log(child.name); // 输出 "Parent"(继承自原型)
  • new

用于‌调用构造函数创建对象‌,同时绑定 this 到新对象,并返回该对象(若构造函数无显式返回值)。

javascript 复制代码
function Person(name) {
  this.name = name;
}
const person = new Person('Alice'); // 调用构造函数创建实例
console.log(person.name); // 输出 "Alice"
6.2 原型链机制差异
  • ‌**Object.create()** ‌

    直接通过参数指定新对象的原型([[Prototype]]),更灵活但需手动管理属性(需后续赋值或通过属性描述符)。

    javascript 复制代码
    const obj = Object.create(null); // 无原型链的对象(常用于纯净字典)
    obj.key = 'value'; // 手动添加属性
  • ‌**new** ‌

    依赖构造函数的 prototype 属性作为新对象的原型,隐式绑定 this,代码更简洁但需遵循构造函数模式。

    javascript 复制代码
    function Car() {}
    Car.prototype.drive = function() { console.log('Driving...'); };
    const myCar = new Car();
    myCar.drive(); // 输出 "Driving..."(继承自原型)

7、拓展:new操作符都干了什么?

  1. 创建⼀个新对象

  2. 新对象原型指向构造函数原型对象

  3. 将构建函数的this指向新对象

  4. 根据返回值判断

javascript 复制代码
function mynew(Func, ...args) {
     // 1.创建⼀个新对象
const obj = {} 
     // 2.新对象原型指向构造函数原型对象
 obj.__proto__ = Func.prototype 
     // 3.将构建函数的this指向新对象 
 let result = Func.apply(obj, args) 
     // 4.根据返回值判断 
 return result instanceof Object ? result : obj 
}

测试一下:

javascript 复制代码
function mynew(func, ...args) { 
    const obj = {}
    obj. __proto__ = func.prototype 
     let result = func.apply(obj, args)
     return result instanceof Object ? result : obj 
} 


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

Person.prototype.say = function () { 
     console.log(this.name) 
} 
let p = mynew(Person, "huihui", 123)
console.log(p) // Person {name: "huihui", age: 123} 
p.say() // huihui
相关推荐
拉不动的猪26 分钟前
管理不同权限用户的左侧菜单展示以及权限按钮的启用 / 禁用之其中一种解决方案
前端·javascript·面试
西陵37 分钟前
前端框架渲染DOM的的方式你知道多少?
前端·javascript·架构
小九九的爸爸37 分钟前
我是如何让AI帮我还原设计稿的
前端·人工智能·ai编程
海的诗篇_1 小时前
前端开发面试题总结-JavaScript篇(一)
开发语言·前端·javascript·学习·面试
じ☆ve 清风°1 小时前
理解JavaScript中map和parseInt的陷阱:一个常见的面试题解析
开发语言·javascript·ecmascript
江城开朗的豌豆1 小时前
eval:JavaScript里的双刃剑,用好了封神,用不好封号!
前端·javascript·面试
Forever Nore1 小时前
前端技能包
前端
江城开朗的豌豆2 小时前
JavaScript篇:前端定时器黑科技:不用setInterval照样玩转循环任务
前端·javascript·面试
书中自有妍如玉2 小时前
.net 使用MQTT订阅消息
java·前端·.net
江城开朗的豌豆2 小时前
JavaScript篇:自定义事件:让你的代码学会'打小报告'
前端·javascript·面试