当前位置: 首页 > news >正文

原型理解从入门到精通

原型这块知识点,其实在我们工作中的使用非常简单。但是俗话说“面试造火箭,工作拧螺丝”,在面试中,面试官不时就会考察一些花里胡哨的问题,所以,我们只有将这个概念和他的相关知识理解透彻,才能以不变应万变。

  1. 两个容易混淆但要分清的东西

  2. 每个普通对象都有内部隐式属性 ​[[Prototype]]​​(常见访问名 ​proto —— 它指向另一个对象(即原型对象)。

    所以原型对象名字的由来就是,一个对象有一个 prototype 属性,就是原型属性,而这个原型属性本身又是一个对象,所以称之为原型对象。

  3. 函数(作为构造函数)有 ​.prototype​ 属性 —— 当你用 new Fn() 创建实例时,实例的 [[Prototype]] 会被设置为 Fn.prototype

总结:.prototype 是构造函数的属性;[[Prototype]]/__proto__ 是普通对象实例的内部指针,二者在构造/实例化时建立联系,但不是同一个东西。

  1. 原型链:属性查找的核心机制

当你访问 obj.prop 时,JS 的查找流程如下:

  1. 先查看 obj 自身是否有名为 prop 的​自有属性​。有就返回。
  2. 没有则沿着 obj.[[Prototype]](即 obj.__proto__)去找,找到就返回。
  3. 若仍未找到则继续沿着原型的 [[Prototype]](形成链)向上查找,直到 null(查不到返回 undefined)。

这就是所谓的 ​原型链(prototype chain)​。

ES6 class 是语法糖,本质仍用原型。

示例:

const grand = { greet() { return 'hi from grand'; } };
const parent = Object.create(grand);
parent.say = () => 'parent';
const child = Object.create(parent);
child.own = 1;console.log(child.own);           // 1 (own property)
console.log(child.say());         // 'parent' (从 parent 找到)
console.log(child.greet());       // 'hi from grand' (从 grand 找到)
console.log(Object.getPrototypeOf(child)); // parent

我们既可以通过构造函数的方式实现继承,也可以通过纯原型继承(Object.create())的方式实现。

  • Object.getPrototypeOf(obj):安全地获取 [[Prototype]]
  • Object.setPrototypeOf(obj, proto):设置对象的原型。通常优先建议使用 Object.create 在创建时设置原型。
  1. 构造函数与 new 的工作原理

当你写 new F(arg)

  1. 新建一个空对象 obj
  2. 这个空对象的 [[Prototype]] 被设置为 F.prototype
  3. 执行 F,并把 this 指向 obj
  4. F 返回对象,则最终结果为该对象;否则返回 obj

因此,F.prototype 是实例继承的方法/属性的来源。

/*** 模拟实现 new 操作符的函数* @param {Function} Constructor 构造函数* @param {...any} args 传递给构造函数的参数* @return {*} 如果无返回值或者显示返回一个对象,则返回构造函数的执行结果;如果显示返回一个基本类型,则返回构造函数的实例*/
function myNew(Constructor, ...args) {// 1. 创建一个全新的空对象 2. 为这个空对象设置原型(__proto__)// 可以使用 {},但是推荐使用 Object.create() 创建对象并设置原型const instance = Object.create(Constructor.prototype)// 3. 绑定构造函数的this为其新创建的空实例对象,并执行构造函数体const result = Constructor.apply(instance, args)const isObject = typeof result === 'object' && result !== nullconst isFunction = typeof result === 'function'// 4. 如果构造函数返回一个非原始值,则返回这个对象;否则返回创建的新实例对象if (isObject || isFunction) return resultreturn instance
}
  1. hasOwnPropertyinObject.keys 的区别

  • obj.hasOwnProperty('a'):只检查自身属性(不走原型链)。
  • 'a' in obj:检查自身或原型链上是否存在属性(包括不可枚举的)。
  • Object.keys(obj) / for...inObject.keys 返回自身可枚举属性数组;for...in 会枚举自身 + 可枚举的继承属性(可用 hasOwnProperty 过滤)。

示例:

const p = {x:1};
const o = Object.create(p);
o.y = 2;'x' in o // true
o.hasOwnProperty('x') // false
Object.keys(o) // ['y']
for (const k in o) { console.log(k); } // 'y' 'x'
  1. instanceof 如何工作

obj instanceof Constructor 检查的是 Constructor.prototype 是否出现在 obj 的原型链上(通过 Object.getPrototypeOf 递归判断)。

/*** 模拟 instanceOf 的实现* @param object 实例对象* @param Constructor 构造函数(类)* @return {boolean}*/
function myInstanceOf(object, Constructor) {// 初始获取对象的原型let proto = Object.getPrototypeOf(object)while (true) {// 遍历到原型链顶端if (proto === null) return false// 找到匹配的原型if (proto === Constructor.prototype) return true// 继续向上查找原型链proto = Object.getPrototypeOf(proto)}
}
  1. 覆盖与读取顺序

如果对象自身有同名属性,会遮蔽原型上的同名属性:

const proto = {v:1};
const o = Object.create(proto);
o.v = 2;
console.log(o.v); // 2 (自身属性优先)
delete o.v;
console.log(o.v); // 1 (回退到原型)
  1. 修改原型

你可以给原型添加/修改方法,所有继承该原型的对象都会受影响:

Array.prototype.myLog = function(){ console.log(this.length); };
[1,2,3].myLog(); // 3

注意​:

  • 不要随意修改内置对象(如 Object.prototypeArray.prototype)。修改 prototype 会影响所有实例,可能引入难以追踪的副作用。

    这也是非常常见的一种网络安全漏洞:原型污染。指攻击者使用某种

  1. 单独说说 constructor

上面的内容看起来是不是还挺简单的。如果上面内容已经完全理解了,那么再来看 construtor 属性。

JavaScript 每个函数(构造函数)对象天生都会有一个 prototype 属性,而这个 prototype 对象中,默认会有一个指向函数本身的属性 —— constructor

可以理解为:

constructor 是原型对象上一个指针,用来指向创建该实例的构造函数。

function Person(name) { this.name = name; }
console.log(Person.prototype.constructor === Person); // true

上述这段代码还比较好理解,总之就是 prototype 这个对象身上有一个属性叫做 constructor,这个 constructor 刚好指向原 构造函数。

接着这段代码的思路,我们再来看看下面这段代码:

function Person(name) { this.name = name; }
const p = new Person("Tom");
console.log(p.constructor === Person); // true

诶?不儿?constructor 不是 prototype 上的属性吗?实例对象上也有这个属性吗?

如果你能想到这里,那说明之前的内容至少你已经学懂了。接下来让我告诉你为什么 p.constructor === Person

原因其实也很简单,因为:

p.constructor
= p.__proto__.constructor   // 实例上没有 constructor,会去原型 __proto__ 查找
= Person.prototype.constructor
= Person

为什么上面的继承方式我没有说 constructor?

因为原型重写后会丢失 constructor 指向,需要手动补回。看这段代码:

function Animal() {}
Animal.prototype = {eat() {}
};

乍眼一看,我们是为 Animal 构造函数添加了 eat 方法,但其实 ⚠️ 这样做会 ​覆盖原始默认的 prototype 对象​,从而导致 constructor 丢失(变成 Object ==> { eat(){} } )。

console.log(Animal.prototype.constructor); // 此时是 Object,不是 Animal

所以,如果你非要这么写,还得自己补回 constructor:

function Animal() {}
Animal.prototype = {constructor: Animal, // 手动补回构造函数eat() {}
};

这样你是不是明白了,为什么上面的继承方式我没有说 constructor。不是不行,而是不太推荐。​任何人都可以随意改原型​,导致 constructor 变得不可信。

ES6 class 的 constructor 本质也是一样的。

  1. 我是真的不想再谈 Funciton 了

这一节完全可以不看,因为本质上还是上面的内容,但奈何总有面试官喜欢挖坑,也总有同学喜欢上当~

普通函数(非箭头)天然可以作为构造函数。所以上面说的什么 Object、Person 等等所有函数都是 Function 的实例。

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

Function.prototype 自身也是一个函数(内置),它的 prototype 与普通对象不同——记住 Function 本身是一个 constructor:

Function instanceof Function // true
Function.prototype instanceof Function // false (Function.prototype 是个普通函数对象)
Function.prototype.__proto__ === Object.prototype // true
Person (构造函数)│├── prototype → Person.prototype → { constructor: Person, ... }  ✅└── __proto__ → Function.prototype  ✅
Function.__proto__ === Function.prototype // true

Function 自己也是一个函数,它也是自己构造出来的。这就像是先有鸡还是先有蛋的问题 😂。

http://www.jsqmd.com/news/41300/

相关文章:

  • 2025-11-15
  • 2025.11.15博客
  • Pandas - read_html()
  • 实用指南:Linux企业级解决方案架构:字节跳动短视频推荐系统全链路实践
  • 实用指南:PyTorch DataLoader 高级用法
  • 简单做一个舒尔特方格小游戏
  • C语言新手怎么快速掌握
  • RSS and Atom
  • Wi-Fi FTM(Fine Timing Measurement)简介
  • 通用会话控制方案
  • LISTAGG 用于将多行数据聚合为单行字符串(拼接),而与其功能相反的需求是 将单行字符串按指定分隔符拆分为多行数据
  • ESP32 I2S音频总线学习笔记(八):添加按键控制功能 - 详解
  • 2025年8款AI论文写作神器推荐:轻松搞定毕业论文查重
  • 基于python的酒店管理系统_36rhk752(Pycharm Flask Django成品源码LW) - 详解
  • pythontip 从字典中删除一组键
  • Softmax 函数全面而详细的解读,原理、图像、应用 - 详解
  • 中级前端工程师详细技能清单
  • Atcoder FPS 24 记录
  • 扩展单调栈扫描线维护历史信息
  • 酵母单杂交 (Y1H):蛋白质 - DNA 互作研究的 基因解码器
  • ORACLE行记录转字符串用分隔符连接的两个函数:WM_CONCAT、LISTAGG
  • MySQL 8+ 日志管理与数据备份恢复实战指南 - 指南
  • 航运、应急、工业适用,AORO P1100三防平板引领行业数字化变革 - 详解
  • 20232419 2025-2026-1 《网络与系统攻防技术》实验五实验报告
  • 为什么高手写 CSS 都偏爱 rem?这三大优势无法拒绝
  • 完整教程:FPGA 49 ,Xilinx Vivado 软件术语解析(Vivado 界面常用英文字段详解,以及实际应用场景和注意事项 )
  • 前端css中rem的作用
  • 第三十天
  • WinDbg 随笔 001 —— HelloWorld + WinDbg
  • 数据结构2:单链表 - 教程