【前端面试小册】JS-第10节:面试必会 - 原型和原型链

一、核心概念

1.1 三个关键点

  1. 每个普通函数都有一个 prototype 属性,箭头函数没有 prototype 属性
  2. 每一个 JavaScript 对象(除了 null)都具有隐式原型 __proto__ 属性,这个属性指向构造函数的显式原型 prototype
  3. 对象本身没有的属性,会去它的隐式原型 __proto__(也就是它的构造函数的显式原型 prototype)中寻找

类比理解:原型链就像家族族谱。每个对象都有自己的"父亲"(__proto__),而构造函数定义了"家族基因"(prototype)。当你找不到某个属性时,就会沿着族谱向上查找,直到找到或到达顶层。

二、prototype(显式原型)

2.1 基本概念

每个普通函数都有一个 prototype 属性,这个属性指向一个对象。

function Person() {}

// 函数拥有 prototype 属性
Person.prototype.name = '前端职场圈';

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

console.log(p1.name);  // 前端职场圈
console.log(p2.name);  // 前端职场圈
console.log(Person.prototype);  // { name: '前端职场圈', constructor: Person, ... }

2.2 prototype 的来源

prototype 指向的对象是调用该构造函数而创建的实例的原型。

理解方式

// 等价理解
p1 = Object.create({ name: '前端职场圈' });
p2 = Object.create({ name: '前端职场圈' });

核心理解

  • 每一个 JavaScript 对象(null 除外)在创建的时候就会与之关联另一个对象
  • 这个对象就是我们所说的原型
  • 每一个对象都会从原型"继承"属性

2.3 关系图

graph TD
    A[构造函数 Person] --> B[prototype 属性]
    B --> C[原型对象]
    D[实例 p1] --> E[__proto__]
    E --> C
    F[实例 p2] --> G[__proto__]
    G --> C
    C --> H[共享属性和方法]

三、proto(隐式原型)

3.1 基本概念

每一个 JavaScript 对象(除了 null)都具有一个隐式属性 __proto__,指向该对象的原型。

function Person() {}

Person.prototype.name = '前端职场圈';
const p = new Person();

console.log(p.name);           // 前端职场圈
console.log(p.__proto__);      // { name: '前端职场圈', constructor: Person, ... }
console.log(p.__proto__ === Person.prototype);  // true

3.2 属性查找机制

当你查找对象某个属性不存在时,它会去隐式原型 __proto__ 中寻找,等价于去它的构造函数的显式原型 prototype 中寻找。

查找流程

graph TD
    A[访问 p.name] --> B{p 自身有 name?}
    B -->|有| C[返回 p.name]
    B -->|无| D[查找 p.__proto__]
    D --> E{Person.prototype 有 name?}
    E -->|有| F[返回 Person.prototype.name]
    E -->|无| G[继续向上查找]
    G --> H[Object.prototype]
    H --> I{有 name?}
    I -->|有| J[返回 Object.prototype.name]
    I -->|无| K[返回 undefined]

代码验证

function Person() {}
Person.prototype.name = '前端职场圈';
const p = new Person();

// p 本身没有 name 属性
console.log(p.hasOwnProperty('name'));  // false

// 通过 __proto__ 查找到原型上的 name
console.log(p.name);  // 前端职场圈

// __proto__ 等价于构造函数的 prototype
console.log(p.__proto__ === Person.prototype);  // true

四、原型链

4.1 原型链的概念

原型链是 JavaScript 实现继承的机制。当对象查找属性时,如果自身没有,就会沿着 __proto__ 向上查找,直到找到或到达顶层 null

function Person() {}
Person.prototype.name = '前端职场圈';
var p = new Person();

console.log(p.name);  // 前端职场圈
console.log(p.__proto__);  // Person.prototype

查找路径

graph TD
    A[实例 p] --> B[查找 name]
    B --> C{p 自身有?}
    C -->|无| D[p.__proto__]
    D --> E[Person.prototype]
    E --> F{有 name?}
    F -->|有| G[返回 前端职场圈]
    F -->|无| H[Person.prototype.__proto__]
    H --> I[Object.prototype]
    I --> J{有 name?}
    J -->|有| K[返回 Object.prototype.name]
    J -->|无| L[Object.prototype.__proto__]
    L --> M[null]
    M --> N[返回 undefined]

4.2 完整的原型链

function Person() {}
const p = new Person();

// 完整的原型链
console.log(p.__proto__ === Person.prototype);                    // true
console.log(Person.prototype.__proto__ === Object.prototype);    // true
console.log(Object.prototype.__proto__ === null);                 // true

经典原型链图

实例 p
  ↓ __proto__
Person.prototype
  ↓ __proto__
Object.prototype
  ↓ __proto__
null

五、原型进阶

5.1 面试题一:原型链查找

var foo = {},
    F = function(){};

Object.prototype.a = 'value a';
Function.prototype.b = 'value b';

console.log(foo.a);  // value a
console.log(foo.b);  // undefined
console.log(F.a);    // value a
console.log(F.b);    // value b

分析

graph TD
    A[foo 是对象] --> B[foo.__proto__ === Object.prototype]
    B --> C[找到 a: value a]
    D[foo.__proto__] --> E[Object.prototype]
    E --> F[Object.prototype.__proto__ === null]
    F --> G[找不到 b,返回 undefined]
    
    H[F 是函数] --> I[F.__proto__ === Function.prototype]
    I --> J[找到 b: value b]
    K[F.__proto__] --> L[Function.prototype]
    L --> M[Function.prototype.__proto__ === Object.prototype]
    M --> N[找到 a: value a]

详细解释

  • foo.afoo 是对象,foo.__proto__ === Object.prototype,找到 a
  • foo.bfoo 的原型链中没有 b,返回 undefined
  • F.aF 是函数,F.__proto__ === Function.prototypeFunction.prototype.__proto__ === Object.prototype,找到 a
  • F.bF.__proto__ === Function.prototype,找到 b

5.2 构造器

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

console.log(Person.__proto__ === Function.prototype);  // true

理解:函数的 __proto__ 指向它的构造函数的原型对象,所以函数 Person 的构造函数是 Function

5.3 9 个内置构造器

JavaScript 有 9 个内置构造器:

Number;
Boolean;
String;
Object;
Function;
Array;
RegExp;
Error;
Date;
// ES6 新增
Proxy;

5.4 构造器的构造器是 Function

console.log(Number.__proto__   === Function.prototype);  // true
console.log(Boolean.__proto__  === Function.prototype);  // true
console.log(String.__proto__   === Function.prototype);  // true
console.log(Object.__proto__   === Function.prototype);  // true
console.log(Function.__proto__ === Function.prototype);  // true
console.log(Array.__proto__    === Function.prototype);  // true
console.log(RegExp.__proto__   === Function.prototype);  // true
console.log(Error.__proto__    === Function.prototype);  // true
console.log(Date.__proto__     === Function.prototype);  // true

console.log(Date.__proto__ === Object.prototype);  // false

关键理解

  • 构造器是对象的同时也是特殊的函数
  • 所有构造器都继承自 Function.prototype
  • 所以它们的 __proto__ 都指向 Function.prototype

5.5 内置对象

console.log(Math.__proto__ === Object.prototype);   // true
console.log(JSON.__proto__ === Object.prototype);   // true
console.log(JSON.__proto__ === Function.prototype); // false

原因MathJSON 是以对象形式存在,不用 new,所以它们的构造器是 Object

5.6 独特的 Function.prototype

特殊之处Function.prototype 是函数类型,而不是对象类型!

// 你以为的结论
console.log(typeof Function.prototype);  // "function" ❌ 不是 "object"

// 其他函数的 prototype 都是对象
console.log(typeof Number.prototype);   // "object"
console.log(typeof Boolean.prototype);  // "object"
console.log(typeof String.prototype);   // "object"
console.log(typeof Object.prototype);   // "object"
console.log(typeof Array.prototype);    // "object"
console.log(typeof RegExp.prototype);   // "object"
console.log(typeof Error.prototype);    // "object"
console.log(typeof Date.prototype);      // "object"

原因Function.prototype 是特殊的存在,只有它的 Function.prototype 是函数,其余函数的 prototype 类型都是对象。

5.7 Function.prototype.proto

虽然 Function.prototype 是函数类型,但函数也是对象,所以它也有 __proto__

console.log(Function.prototype.__proto__ === Object.prototype);  // true

理解

  • 函数是一等公民,函数也是对象
  • 所以 Function.prototype 被视为一个对象就有 __proto__
  • Function.prototype.__proto__ 指向 Object.prototype

六、总结

核心知识点

  1. prototype(显式原型)

    • 每个普通函数都有 prototype 属性
    • 指向一个对象,是实例的原型
  2. proto(隐式原型)

    • 每个对象都有 __proto__ 属性
    • 指向构造函数的 prototype
  3. 原型链

    • 对象查找属性时,沿着 __proto__ 向上查找
    • 直到找到或到达 null
  4. 特殊点

    • 所有构造器都来自 Function.prototype
    • 构造器既是函数又是对象
    • Function.prototype 是函数类型(唯一例外)
    • Function.prototype.__proto__ === Object.prototype

常见面试题

Q1: 什么是原型链?

答:原型链是 JavaScript 实现继承的机制。当对象查找属性时,如果自身没有,就会沿着 __proto__ 向上查找,直到找到或到达顶层 null

Q2: prototype 和 proto 的区别?

答:

  • prototype:函数才有的属性,指向原型对象
  • __proto__:对象才有的属性,指向构造函数的 prototype

Q3: 为什么 Function.prototype 是函数类型?

答:这是 JavaScript 的特殊设计。Function.prototype 是唯一一个 prototype 是函数类型的,其他函数的 prototype 都是对象类型。

实战建议

  • ✅ 理解原型链有助于理解 JavaScript 的继承机制
  • ✅ 掌握 prototype__proto__ 的关系
  • ✅ 理解原型链查找机制,有助于调试和性能优化
  • ✅ 注意 Function.prototype 的特殊性
#前端面试##面试题##前端#
前端面试小册 文章被收录于专栏

每天更新3-4节,持续更新中... 目标:50天学完,上岸银行总行!

全部评论

相关推荐

迷茫的大四🐶:那你问他上班之后老实了没
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务