现实生活例子[特殊字符] 通俗易懂的解释[特殊字符] JS中的原型和原型链[特殊字符]
目录
1、先理解下什么是构造函数,又与普通函数的区别(可跳过这部分,回头再看)
2、原型对象
3、构造函数与原型
4、打个现实比方解释 原型 Prototype、原型链
4.1 原型(Prototype)——「家族遗传(家族基因库)」
4.2 原型链(Prototype Chain)——「家族族谱」
4.3 特殊案例——「DNA突变」
4.4 现代家族(ES6 Class)——「家规手册」
总结比喻
关键点:
prototype 与 __proto__ 的关系——「基因库的访问路径」
5、打比方解释 constructor、prototype 和 __proto__ 的关系
6、拓展:Object.create() 和 new 的区别
6.1 核心作用不同
6.2 原型链机制差异
7、拓展:new操作符都干了什么?
1、先理解下什么是构造函数,又与普通函数的区别(可跳过这部分,回头再看)
1. 通过
new
调用2. 约定俗成 以大写字母开头(如
Person
、Array
),(JavaScript不强制要求,但遵循约定可提高代码可读性)3. 显式或隐式分配实例属性
- 显式赋值:通过
this.xxx = yyy
为实例添加属性。- 隐式分配:在构造函数中操作
this
的属性(如this.age++
)。// 一 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
)。function Person() {} console.log(Person.prototype); // 输出: { constructor: [Function: Person] }
箭头函数的特殊性
- 箭头函数(
() => {}
)没有自己的prototype
属性,因此不能通过new
调用(即不能作为构造函数)。- 这是为了简化箭头函数的用途(通常用于函数式编程或需要绑定
this
的场景)。const ArrowFunc = () => {}; console.log(ArrowFunc.prototype); // 输出: undefined
原型对象干嘛的?
----共享属性和方法的
3、构造函数与原型
函数的
prototype
属性会在通过new
创建实例时,自动成为实例的原型对象。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+)
// 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)——「家族遗传(家族基因库)」
想象你出生在一个家族,你的原型就是你的父母。父母有一些共同特征(比如眼睛颜色、身高基因),这些特征你天生就会继承:
// 父母的特性(原型对象) 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)——「家族族谱」
现在扩展到你整个家族的血脉传承:
// 曾祖父 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会按顺序查找:
- 你自己(
you
)→ 没有- 父亲(
father
)→ 没有- 祖父(
grandpa
)→ 没有- 曾祖父(
greatGrandpa
)→ 找到!返回"Smith"
终止条件:如果查到族谱顶端(
Object.prototype
)还没有,返回undefined
。4.3 特殊案例——「DNA突变」
如果你自己定义了和原型相同的属性,会覆盖继承的属性(类似「基因突变」):
const parent = { trait: "kind" }; const child = Object.create(parent); child.trait = "brave"; // 覆盖原型属性console.log(child.trait); // "brave"(优先用自己的)
4.4 现代家族(ES6 Class)——「家规手册」
ES6的
class
相当于把家族继承规则写成了明确的手册: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
)- 修改原型 = 父母学新技能,子女自动受益
- 覆盖属性 = 你决定特立独行,不跟父母一样
// 构造函数(家族) 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"(所有实例同步更新)
关键点:
- 共享性:
Family.prototype
上的属性和方法会被所有实例共享,避免重复定义。- 动态性:修改
prototype
会影响所有现有和未来的实例(类似基因库升级)。- 关联性:实例通过内部
[[Prototype]]
属性(__proto__
)指向prototype
对象。
prototype
与__proto__
的关系——「基因库的访问路径」
-
prototype
:构造函数独有的属性,指向基因库(原型对象)。-
__proto__
:实例对象的内部属性,指向构造函数的prototype
对象(即基因库)。类比家族:
prototype
是家族基因库的实体(由构造函数保管)。__proto__
是子孙后代手中的钥匙,指向基因库的位置。function Family() {} const alice = new Family(); // 创建子孙后代console.log(alice.__proto__ === Family.prototype); // true // 实际关系:alice → [__proto__] → Family.prototype(基因库)
prototype
= 家族的基因库(存储所有后代共享的属性和方法)。- 构造函数 = 家族的族谱登记处,负责为每个新成员关联基因库。
- 实例 = 家族的后代个体,通过
__proto__
指向基因库,继承共享特性。- 原型链 = 从个体到基因库再到祖先基因库的查找路径,实现属性继承。
5、打比方解释 constructor
、prototype
和 __proto__
的关系
用一个 公司组织架构 的比喻来解释
constructor
、prototype
和__proto__
的关系,通俗易懂:比喻场景:一家科技公司(JavaScript 对象系统)
constructor
→ 部门经理
- 比如公司有个「前端开发部」,部门经理是
Person()
(构造函数)。- 他的职责是 招聘新员工(
new Person()
创建实例)。- 每个员工的名牌上会写:「直属领导:
Person()
」(即实例的constructor
属性指向构造函数)。
prototype
→ 部门共享资源库(上述比方中的 家族基因库)
- 经理
Person()
有一个 公共文件柜(Person.prototype
)。- 里面放了部门通用的工具,比如《代码规范手册》(共享方法如
greet()
)。- 所有员工都可以用,但不需要每人复印一份(节省内存)。
__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]]
)。const parent = { name: 'Parent' }; const child = Object.create(parent); // child 的原型是 parent console.log(child.name); // 输出 "Parent"(继承自原型)
-
new
用于调用构造函数创建对象,同时绑定
this
到新对象,并返回该对象(若构造函数无显式返回值)。
function Person(name) {this.name = name; } const person = new Person('Alice'); // 调用构造函数创建实例 console.log(person.name); // 输出 "Alice"
6.2 原型链机制差异
Object.create()
直接通过参数指定新对象的原型([[Prototype]]
),更灵活但需手动管理属性(需后续赋值或通过属性描述符)。
const obj = Object.create(null); // 无原型链的对象(常用于纯净字典) obj.key = 'value'; // 手动添加属性
new
依赖构造函数的prototype
属性作为新对象的原型,隐式绑定this
,代码更简洁但需遵循构造函数模式。
function Car() {} Car.prototype.drive = function() { console.log('Driving...'); }; const myCar = new Car(); myCar.drive(); // 输出 "Driving..."(继承自原型)
7、拓展:new操作符都干了什么?
1. 创建⼀个新对象
2. 新对象原型指向构造函数原型对象
3. 将构建函数的this指向新对象
4. 根据返回值判断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 }
测试一下:
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