当前位置: 首页 > 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.dtcms.com/a/613252.html

相关文章:

  • 电子商务实验网站建设实训过程出售东西的网站怎么做
  • 做明星网站可以做那些子网页建设网站要用到什么语言
  • 《计算机网络:体系结构》
  • musl libc 与 C 运行时文件(`crt*.o`):技术解析及移植报错问题
  • 前端WebSocket教程,实时通信案例
  • 陪诊陪检系统源码,陪诊小程序,陪诊APP,陪诊服务,家政上门系统,居家护理陪护源码
  • Java 9+模块化系统(JPMS)详解:设计与迁移实践
  • 【计算机网络】考研408 | 数据链路层的“安全卫士”:探秘检错编码之奇偶校验码
  • Oracle EBS 调用标准创建供应商地点包报错处理
  • 第40节:AR基础:Marker识别与跟踪
  • 新能源汽车动力系统在环(HIL)半实物仿真测试台架深度解析
  • 企业BI建议--数据治理平台
  • 锒川市住房和城乡建设局网站公告湖北省建设工程质量安全监督网站
  • 从裂变能力竞争到技术水平竞争:开源AI智能名片链动2+1模式S2B2C商城小程序对微商企业竞争格局的重塑
  • 09-mcp-server案例分享-即梦MCP-Server实战教程-让Claude直接调用AI生图视频能力
  • SpringBoot18-redis的配置
  • PHP 表单 - 必需字段
  • python爬虫入门案例day05:Pexels
  • android studio Gradle 打包任务配置
  • 【AI学习-comfyUI学习-1批量抠图换背景工作流+2视频抠图工作流-各个部分学习-第十节】
  • Redis(124)Redis在电商系统中的应用有哪些?
  • [Dify 实战案例] 用 Dify 做一个多语种文档翻译工具:支持 TXT / DOCX / XLSX / PPTX 全格式
  • 自然语言编程:从一段Perl程序说起
  • OpenAI Whisper:技术、实战、生态
  • 【ZeroRange WebRTC】DTLS(Datagram Transport Layer Security)技术深度分析
  • 南京本地网站合肥建网站要多少钱
  • 从丝印判断ESP32-WROOM-32E模组Flash容量
  • react 学习
  • 语言模型(Language Model)介绍
  • 基于协同过滤推荐算法的求职招聘推荐系统u1ydn3f4(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。