浅显易懂的原型与原型链知识总结

本文深入探讨JavaScript中的原型和原型链,从简介到概念,再到实例解析,全面阐述了原型对象、显式原型和隐式原型之间的关系,以及原型链在对象属性查找中的作用。此外,还介绍了操作符的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、原型

简介

  • 每个函数对象都有一个 prototype 属性,默认指向一个空的 Object 实例对象(即称为原型对象)
  • 原型对象中有一个 constructor 属性,它指向当前的函数对象

代码示例:

function Test() {}

Test.prototype.say = () => {
    console.log('Say Hello');
}

const t1 = new Test();
const t2 = new Test();

Test.prototype.say = () => {
    console.log('Say Hello');
}

console.log("原型对象:", Object.keys(Test.prototype)); // [],说明函数的原型对象是一个空对象
console.log(Test.prototype.constructor === test);   // true,说明 原型对象的 constructor 属性,它指向当前的函数对象

显式原型和隐式原型

  • 每个函数都有一个 prototype 属性,即显式原型
  • 每个实例对象都有一个 __proto__ 属性,即隐式原型
  • 实例对象的隐式原型的值等于对应函数的显式原型的值
function Test() {}

const t1 = new Test();

console.log(Test.prototype);    // 一个空对象,包含 __proto__ 和 constructor 属性(指向 Test)
console.log(t1.__proto__);      // 一个空对象,包含 __proto__ 和 constructor 属性(指向 Test)
console.log(Test.prototype === t1.__proto__);   // true

显式原型和隐式原型的实现机制

  • 函数的 prototype 属性:在定义函数时自动添加的,默认值是一个空的 Object 对象
  • 对象的 __proto__ 属性:创建对象时自动添加的,默认值为构造函数的 prototype 属性值
  • 开发人员一般都是通过操作函数的 prototype 属性来修改原型

原型补充

  • 函数的显式原型指向的对象默认是空 Object 对象(但是 Object 除外,Object 的原型不是空对象,且没有 __proto__ 属性)
    • 所以 Object 的原型对象是原型链的尽头,Object 的原型对象的原型就是 null
function Test() {}

const o = {};
const t = new Test();

console.log(o.__proto__);       // 包含多个方法的 Object 对象,没有 __proto__ 属性
console.log(t.__proto__);       // 空对象

console.log(o.__proto__.__proto__);     // null
  • 每个函数对象既有一个隐式原型,也有一个显式原型
    • 所有函数都是 Function 的实例(包括 Function 自身)
    • 每个函数的隐式原型都是同一个实例对象
    • 唯一的一个隐式原型等于显式原型的函数对象就是 Function 这个函数对象
function Test() {}

function Hello() {}

console.log(Test.prototype);    // 空对象
console.log(Test.__proto__);    // ƒ () { [native code] }

console.log(Hello.prototype === Test.prototype)         // false
console.log(Hello.__proto__ === Test.__proto__);        // true
console.log(Hello.__proto__ === Function.__proto__);    // true
console.log(Function.prototype === Function.__proto__); // true

二、原型链(别名:隐式原型链)

概念

  • 访问一个对象的属性时

    • 先在自身属性中查找,找到返回
    • 如果没有,再沿着 __proto__ 这条链向上查找,找到返回
    • 如果最终没有找到,返回 undefined
  • 作用:查询对象属性

对象读写

  • 读取对象的属性时,会自动到对象的原型链中查找
function Person() {}

Person.prototype.speak = () => {
    console.log('Speak Chinese!');
}

const p1 = new Person();
const p2 = new Person();

// 每个实例对象都拥有函数原型中的属性/方法
p1.speak();     // Speak Chinese!
p2.speak();     // Speak Chinese!
  • 设置对象的属性时,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置值
function Person() {}

Person.prototype.name = '小明';

const p1 = new Person();
const p2 = new Person();

// 读取属性时,会从当前对象以及原型链中进行查找
console.log(p1.name);    // 小明

// 设置属性时,直接修改当前对象上的属性,当前对象上没有,则直接添加,不会修改原型链上的属性
p2.name = '小红';

console.log(p1.name);   // 小明
console.log(p2.name);   // 小红
  • 方法一般定义在原型上,属性一般通过构造函数定义在对象本身上

扩展:instanceOf 操作符原理

  • a instanceOf C: 如果 C 的显式原型在 a 的隐式原型链上,返回 true,否则返回 false
function A() {};

function B() {};

function C() {}

// 将 C 的原型赋值给 B 的原型,将 B 的原型赋值给 A 的原型,这两行代码位置不能颠倒
B.prototype = C.prototype;
A.prototype = B.prototype;

const a = new A();
console.log(a instanceof C);

三、实例讲解

1、实例一

function A() {}

A.prototype.n = 1;

// 对象的原型在对象初始化时赋值为函数的显示原型,因此 b 的隐式原型为 { n: 1 };
const b = new A();

A.prototype = {
    n: 2,
    m: 3
}

// 原因同上,因此 c 的隐式原型为 { n: 2, m: 3 };
const c = new A();

console.log(b.n, b.m, c.n, c.m);    // 1 undefined 2 3

实例二

function Test() {}

Object.prototype.a = function() {
    console.log('a 方法执行');
}

Function.prototype.b = function() {
    console.log('b 方法执行');
}

var t = new Test();

/**
 * 这里解释一波
 *  1、每个函数对象的显式原型都指向一个空对象,而每个实例对象的隐式原型 === 函数对象的显式原型,
 *      那么 t.a() 会按照这个顺序查找: t.__proto__ === Test.prototype = {}; {}.__proto__ === Object.prototype;
 *      而 Object 的显式原型中有方法 a,因此可以正确执行;
 *  2、t.b() 同理,在原型链中没有找到 b 方法,因此会报错:t.b is not a function;
 *  3、Test.a() 的查找顺序:Test.__proto__ === Function.prototype = { b: function() { console.log('b 方法执行'); } };
 *      {b: fucntion() {console.log('b 方法执行');}}.__proto__ === Object.prototype;
 *      因此:Test.a() 和 Test.b() 都可以正确执行
 */
t.a();          // a(); 
t.b();          // t.b is not a function
Test.a();      // a();
Test.b();      // b();

四、总结

  • 每个函数对象(Object 函数对象除外)都默认有一个显式原型对象,指向一个空的 Object 对象,空对象中有一个 constructor 属性,指向当前函数对象

  • 所有函数对象都有一个隐式原型对象(包括 Function 函数对象),且都默认指向 Function 函数对象的显式原型对象

  • 函数对象的显式原型等于实例对象的隐式原型

  • 访问对象属性时,会先在实例对象本身进行查找,找到则返回,没找到则在隐式原型链中继续查找,找到则返回,没找到返回 undefined

  • 额外补充一点:函数对象的隐式原型和显示原型没有关系(Function 函数对象除外),Function 函数对象的显式原型等于自身的隐式原型(因为所有函数都是 Function 的实例(包括 Function 自身))


五、推荐链接

之前看到这篇博客里有详细的原型链示意图,有兴趣的朋友可以详细去看一下:

一个例子让你彻底明白原型对象和原型链

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值