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

JavaScript手录17-原型

画板

一、原型

原型

  • 原型的本质:原型(prototype)是JavaScript中实现继承的核心机制,是专门存储由某一方法(构造函数)创建的所有对象实例共有属性的存储空间。
    • 原型就像是“公共仓库”,同一构造函数所创建的所有实例都能共享仓库中的属性和方法,避免重复储存,节省内存。
  • 理解:原型本身是一个对象,属于构造函数的属性(prototype),并不是一种独立概念。

方法与对象的关系

  • 方法的本质:方法即函数(function),可以通过返回对象字面量创建对象实例。
  • 示例
function createPerson() {return { name: '小明' }
}
let person = createPerson() // 由方法创建对象实例
  • 对象的原型属性:所有通过方法创建的对象都包含[[prototype]](隐式原型)属性。该属性指向对象的原型,用于访问共享属性。
  • 查看[[prototype]]属性:展开对象即可在控制台中查看[[prototype]]属性。

对象的[[prototype]]属性

构造函数

  • 普通方法创建对象的局限性:类似上文中示例那样创建对象的普通方法,每次调用只能创建独立对象,无法实现属性共享和方法复用,在需要创建大量相似对象的场景时,创建效率很低。
  • 构造函数:为了提高创建大量相似对象的效率,引入了构造函数。
    • 构造函数的作用:作为对象创建的模板,专门用于批量创建结构相同的对象,提高代码复用率,避免重复劳动。
    • 适用场景:仅适用于需要创建结构相同而属性值不完全相同的对象。例如:需要创建很多“学生”对象,对象结构均为(name和age)。

构造函数的定义和规范

  • 构造函数的本质:构造函数是一种具有特殊用途的函数,但是其本质仍然是函数。构造函数必须通过new关键字调用,并且会隐式返回新对象。
  • 构造函数的通用命名规范:首字母大写以同普通函数区分(例如:Student、Person、Animal)。尽量与JS内置函数(例如:Date、Array、Math)保持一致,提升代码可读性。
  • 构造函数的分类:
    • JS内置构造函数:JS内置,例如Date()函数,创建日期对象;Array()函数创建数组;
    • 自定义构造函数:当JS内置的构造函数无法满足需求时,可以创建自定义构造函数使用。
    • 自定义构造函数示例:
// 创建具有name和age属性的student对象的构造函数
// 构造函数定义
function Student(name, age){// 给实例添加属性this.name = namethis.age = age
}
// 使用new关键字创建实例
let stu1 = new Student('小明', 20)
let stu2 = new Student('小红', 20)
// 注:由构造函数创建的对象称为构造函数的实例

new关键字的底层原理

  • new关键字的核心作用:new是运算符,用于通过构造函数创建对象实例。“有new就一定有对象”。\
  • new关键字与构造函数:构造函数必须使用new关键字调用,否则无法创建对象实例,并且this会指向全局(例如浏览器中的window)而不是自身。(对象实例是由new创建的,而不是构造函数return的结果。构造函数可以理解为用于给想要创建的对象实例构造一个结构。)
  • new关键字的底层原理
    1. 创建空对象:在内存中创建一个空对象。
    2. 建立原型链:将实例的隐式原型(<font style="color:rgb(0, 0, 0);">[[Prototype]]</font>)指向构造函数的 <font style="color:rgb(0, 0, 0);">prototype</font>(建立原型链)。
    3. 添加属性:将构造函数的 <font style="color:rgb(0, 0, 0);">this</font> 指向这个实例,执行构造函数为实例添加属性。
    4. 返回对象:自动返回新对象(即使构造函数没有return语句)。

画板

  • 手动实现new的示例
// 定义一个构造函数
function Person(name, age){this.name = namethis.age = age// 测试返回值情况// return { gender: 'male' };// 如果构造函数有返回对象,myNew会优先使用该对象
}// 手动实现new的功能// ...args用于接收动态参数(即不确定数量的参数;例如构造函数可能有2个参数,也可能有20个参数)
function myNew(constructor, ...args){// 1.创建空对象(实例)let obj = {}// 2.将实例的隐式原型指向构造函数的原型prototype(建立原型链)Object.setPrototypeOf(obj, constructor.prototype)// 等价于: obj.__proto__ = constructor.prototype 但是不推荐直接操作__proto__// 3.调用构造函数,将this指向实例,并传递参数let res = constructor.apply(obj, args)// 4.判断构造函数返回值:如果返回对象则使用返回值;否则返回实例return res instanceof Object ? res : obj}// 用原生的new创建实例
let p1 = new Person('张三', 20)
let p2 = myNew(Person, '李四', 22)// 测试结果
console.log(p1)
console.log(p2)// 原型上的共有方法
Person.prototype.sayHello = function(){return `我是${this.name},我今年${this.age}岁了`
}console.log(p1.sayHello())
console.log(p2.sayHello())// 验证原型关联是否正确
console.log(p1.__proto__ === Person.prototype) // true
console.log(p2.__proto__ === Person.prototype) // true

自有属性与共有属性

类型定义创建方式存储位置特点
自有属性实例独有的属性通过构造函数内
this.***添加;或者在实例上直接添加
实例对象本身仅影响当前实例;访问时优先级高于共有属性
共有属性所有实例共享的属性必须添加到构造函数的prototype上构造函数的prototype修改后影响所有实例;通过原型链访问

示例

function Student(name, age){this.name = namethis.age = age
}
let xm = new Student('小明', 20)
let xh = new Student('小红', 21)
// 共有属性
Student.prototype.car = 'bmw'
// 小明有自由car;小红没有
xm.car = 'benz' // 自由属性
console.log(xm.car) // 'benz' 来自实例添加到自有属性;自有属性优先访问
console.log(xh.car) // 'bmw' 来自prototype上的共有属性

补充:判断自有属性的方式

// 判断方法:使用obj.hasOwnProperty(prop),返回true为自有属性,false为共有属性或不存在。

原型链与属性访问机制

  • 原型链的定义:实例的[[prototype]]指向构造函数的prototype,而prototype本身也是对象,其[[prototype]]指向更上层的原型(例如Object.prototype),形成的链条即原型链
  • 属性访问规则:
    1. 方位属性时,先查找实例自身的自由属性
    2. 若未找到,沿着原型链向上查找共有属性
    3. 直到找到Object.prototype(原型链的终点,其[[prototype]]为null);若仍未找到,则返回undefined

二、继承

实现继承是原型链的核心价值。

继承的本质与实现

  • 继承的定义:通过原型链机制,使“新类型”复用“现有类型”的属性和方法,并可扩展新特性。
  • 核心实现:依赖原型链的链式查找,子类实例的隐式原型指向父类的显示原型
  • 示例:学生继承人类的属性,同时扩展自己的方法
// 父类构造函数
function Person(){this.species = '人类'; // 自有属性
}
// 父类共有方法
Person.prototype.eat = function() {console.log('吃饭')
}// 子类构造函数
function Student(){};// 核心:使子类原型指向父类实例,建立继承关系
Student.prototype = new Person()
// 修复constructor指向,避免继承混乱
Student.prototype.constructor = Student
// 子类拓展自有方法
Student.prototype.study = function(){console.log('学习')
}// 子类实例
let xm = new Student()
xm.eat() // '吃饭' 继承了父类的共有方法
console.log(xm.species) // '人类' 继承了父类的自由属性
sm.study() // '子类' 子类的扩展方法

继承中的属性与方法扩展规则

  • 共有方法扩展:通过 子类构造函数.prototype.方法名 添加,所有子类实例可以共享。
  • 自由属性扩展:通过 实例.属性名 添加,通过这种方式添加的属性仅当前实例拥有
  • :扩展时避免直接覆盖原型对象,例如:Student.prototype = { … }。这样做会导致constructor等原生属性丢失,需要手动修复。
// 错误:直接覆盖原型,丢失constructor
Student.prototype = { study: function() {} };
console.log(Student.prototype.constructor); // 指向Object(错误)// 正确:逐个添加或修复constructor
Student.prototype = {constructor: Student, // 手动绑定constructorstudy: function() {}
};

三、原型链

原型链的概念

  • 原型链的定义:对象的隐式原型__proto__指向创建它的构造函数的显式原型prototype,而显式原型本身也是对象,其__proto__也指向更上层的原型,这种查找形成的链式结构即为原型链。
  • 原型链的核心特征
    • 查找机制:访问属性时,沿__proto__链逐级向上查找,直到找到目标属性,或者到达原型链的终点null;如果到达终点,则返回undefined。
    • 关键节点

画板

原型链的完整结构(以Student实例为例)

// 父类构造函数
function Person(){this.species = '人类'
}function Student(){};
Student.prototype = new Person()
Student.prototype.constructor = Student
Student.prototype.study = function(){console.log('学习')
}// 实例 → Student.prototype → Person.prototype → Object.prototype → null
let xm = new Student();// 验证原型链关系
console.log(xm.__proto__ === Student.prototype); // true
console.log(Student.prototype.__proto__ === Person.prototype); // true(若Student继承Person)
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true(终点)

:如果存在多层继承,原型链会相应延长。

原型链的查找流程

实例访问属性prop时,原型链的查找流程如下:

  1. 检查 实例自身 是否有prop自由属性,如果有则返回;
  2. 如果没有,则通过 实例.proto 查找构造函数的prototype,如果有则返回;
  3. 如果没有,则通过构造 函数.prototype.proto 查找父级原型;重复此过程;
  4. 直到 Object.prototype ;如果仍未找到,则返回 undefined

四、函数与对象的原型链特殊性

函数作为“特殊对象”的原型链

在JavaScript中,函数也是对象,因此同时拥有:

  • prototype:显示原型,以供实例继承;
  • proto:隐式原型,指向创建它的构造函数的原型;

函数的原型链核心规则如下:

  • 所有函数(包括构造函数)都是由Function构造函数创建的,因此函数的__proto__均指向Function.prototype
  • Function是“函数的函数”,其__proto__指向自身的prototype(形成闭环:Function.proto** **=== Function.prototype)
  • 实例:
function Foo() {}
// 函数的__proto__指向Function.prototype
console.log(Foo.__proto__ === Function.prototype); // true
// Function自引用
console.log(Function.__proto__ === Function.prototype); // true

Object 原型链:所有对象的最终源头

  • <font style="color:rgb(0, 0, 0);">Object</font>是所有对象的 “根构造函数”,其<font style="color:rgb(0, 0, 0);">prototype</font>是原型链的顶层节点(除<font style="color:rgb(0, 0, 0);">null</font>外)。
  • 所有对象(包括函数对象)的原型链最终都会指向<font style="color:rgb(0, 0, 0);">Object.prototype</font>,而<font style="color:rgb(0, 0, 0);">Object.prototype.__proto__ = null</font>(原型链的终点)。

关键关系

// 普通对象的原型链
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true// 函数对象的原型链(函数→Function.prototype→Object.prototype→null)
console.log(Function.prototype.__proto__ === Object.prototype); // true

原型链核心关系图

实例对象(xm)↓ __proto__
构造函数.prototype(Student.prototype)↓ __proto__
父级构造函数.prototype(Person.prototype)↓ __proto__
Object.prototype↓ __proto__
null(终点)// 函数对象的原型链
函数(Foo)↓ __proto__
Function.prototype↓ __proto__
Object.prototype↓ __proto__
null(终点)

五、原型链的实际应用与风险

1. 典型应用场景

  • 扩展内置对象方法:给<font style="color:rgba(0, 0, 0, 0.85) !important;">Array.prototype</font>添加自定义方法,使所有数组可用(需谨慎,避免污染)。
// 给数组添加求和方法
Array.prototype.sum = function() {return this.reduce((a, b) => a + b, 0);
};
[1, 2, 3].sum(); // 6(所有数组均可调用)
  • 实现代码复用:将工具方法挂载到构造函数原型,供所有实例共享(如表单验证方法挂载到<font style="color:rgba(0, 0, 0, 0.85) !important;">Form.prototype</font>)。
  • polyfill 实现:为低版本浏览器补充 ES6 + 方法(如<font style="color:rgba(0, 0, 0, 0.85) !important;">Array.prototype.includes</font>),本质是修改内置对象的原型。

2. 风险与注意事项

  • 原型污染:直接修改<font style="color:rgba(0, 0, 0, 0.85) !important;">Object.prototype</font>会影响所有对象(包括函数、数组等),可能导致命名冲突或逻辑异常。
// 危险:污染所有对象
Object.prototype.id = 1;
console.log([].id); // 1(数组也会继承id,不符合预期)
  • 性能影响:过长的原型链会增加属性查找时间(浏览器优化会缓解,但仍需避免不必要的层级)。
  • constructor 指向混乱:覆盖原型时若不修复<font style="color:rgba(0, 0, 0, 0.85) !important;">constructor</font>,可能导致<font style="color:rgba(0, 0, 0, 0.85) !important;">instanceof</font>判断异常或类型识别错误。

3. 原型链相关 API

API作用示例
<font style="color:rgb(0, 0, 0);">Object.getPrototypeOf(obj)</font>获取对象的隐式原型(推荐,替代<font style="color:rgb(0, 0, 0);">__proto__</font>
<font style="color:rgb(0, 0, 0);">Object.getPrototypeOf(xm) === Student.prototype</font>
<font style="color:rgb(0, 0, 0);">Object.setPrototypeOf(obj, proto)</font>修改对象的隐式原型(谨慎使用,影响性能)<font style="color:rgb(0, 0, 0);">Object.setPrototypeOf(xm, Person.prototype)</font>
<font style="color:rgb(0, 0, 0);">obj instanceof Constructor</font>判断实例是否属于某构造函数(检查原型链)<font style="color:rgb(0, 0, 0);">xm instanceof Student</font>
<font style="color:rgb(0, 0, 0);">true</font>

六、ES6 class 与原型链的关系

ES6 的<font style="color:rgba(0, 0, 0, 0.85) !important;">class</font>语法是原型链的 “语法糖”,其底层仍依赖原型链实现继承,但代码更直观:

// 父类
class Person {constructor() {this.species = "人类";}eat() { console.log("吃饭"); } // 等同于Person.prototype.eat
}// 子类继承
class Student extends Person {constructor() {super(); // 对应父类构造函数调用}study() { console.log("学习"); } // 等同于Student.prototype.study
}// 本质与ES5原型链继承一致
const xm = new Student();
console.log(xm.__proto__ === Student.prototype); // true
console.log(Student.prototype.__proto__ === Person.prototype); // true
http://www.xdnf.cn/news/1309033.html

相关文章:

  • 2025年生成式引擎优化(GEO)服务商技术能力评估报告
  • 【Docker】Ubuntu上安装Docker(网络版)
  • [创业之路-550]:公司半年度经营分析会 - 常见差距与根因分析示例
  • linux网络基础
  • 022 基础 IO —— 文件
  • Redis-plus-plus 安装指南
  • 161. Java Lambda 表达式 - 使用工厂方法创建 Predicates
  • 力扣(LeetCode) ——142. 环形链表 II(C语言)
  • OpenShift 4.19安装中的变化
  • Vue 3与React内置组件全对比
  • Hadoop面试题及详细答案 110题 (16-35)-- HDFS核心原理与操作
  • 音视频学习(五十四):基于ffmpeg实现音频重采样
  • 基于单片机的防酒驾系统设计
  • 我的世界Java版1.21.4的Fabric模组开发教程(十八)自定义传送门
  • 《C++进阶之继承多态》【多态:概念 + 实现 + 拓展 + 原理】
  • 超越“调参”:从系统架构师视角,重构 AI 智能体的设计范式
  • 嵌入式硬件篇---电感本质
  • VScode 使用遇到的问题
  • Git Revert 特定文件/路径的方法
  • 设计模式之【快速通道模式】,享受VIP的待遇
  • leetcode_ 739 每日温度
  • AI杀死的第一个仪式:“hello world”
  • C++设计模式:面向对象设计原则
  • B+树索引分析:单表最大存储记录数
  • Day2--滑动窗口与双指针--2090. 半径为 k 的子数组平均值,2379. 得到 K 个黑块的最少涂色次数,2841. 几乎唯一子数组的最大和
  • Windows 基于ACL(访问控制列表)的权限管理
  • Manus AI与多语言手写识别的技术突破与行业变革
  • 数学建模Topsis法笔记
  • 【php反序列化介绍与常见触发方法】
  • Bash常用操作总结