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

JavaScript-原型、原型链详解

一、构造函数

在 JavaScript 中,构造函数是一种特殊的函数,用于创建和初始化对象,它就像一个 “对象模板”。通过 new 关键字调用构造函数时,会创建一个新对象,并将构造函数中的属性和方法 “绑定” 到这个新对象上。

1.构造函数基础示例

// 定义一个构造函数 Star
function Star(uname, age) {// 通过 this 为新创建的对象添加属性this.uname = uname; this.age = age; 
}// 使用 new 关键字创建对象实例
const ldh = new Star('刘德华', 18); 
const zxy = new Star('张学友', 19); console.log(ldh.uname); // 输出: 刘德华
console.log(zxy.age); // 输出: 19

在这个例子中,Star 是构造函数,每次通过 new Star(...) 创建实例(ldhzxy)时,都会为实例添加 uname 和 age 属性。

2.构造函数的 “方法重复” 问题

如果尝试为构造函数添加方法,会出现内存浪费的问题:

function Star(uname, age) {this.uname = uname;this.age = age;this.sing = function() { // 每个实例都会新建一个 sing 函数console.log('我会唱歌');};
}const ldh = new Star('刘德华', 18);
const zxy = new Star('张学友', 19);console.log(ldh.sing === zxy.sing); // 输出: false(两个不同的函数)

每个实例(ldhzxy)都有独立的 sing 函数,在内存中重复创建相同逻辑的函数,造成浪费。此时,** 原型(prototype)** 机制就可以解决这个问题 —— 将方法定义在构造函数的原型上,让所有实例共享,避免重复创建。

二、原型(原型对象)的引入

JavaScript 规定,每个构造函数都有一个 prototype(原型)属性,指向另一个对象(即原型对象)。我们可以把不变的方法挂载到这个原型对象上,让所有实例共享这些方法,避免重复创建。

修改代码如下:

function Star(uname, age) {this.uname = uname;this.age = age;
}// 将 sing 方法定义在 Star 的原型对象上
Star.prototype.sing = function() {console.log('我会唱歌');
};const ldh = new Star('刘德华', 18);
const zxy = new Star('张学友', 19);console.log(ldh.sing === zxy.sing); // true,现在共享同一个函数

此时,所有通过 Star 构造函数创建的实例(ldh、zxy)都会通过原型链共享 Star.prototype.sing 方法,不再重复创建,节约了内存。

1.构造函数this的指向

结论:构造函数里面的this就是实例对象

function Star(uname) {console.log(this)this.uname = uname
}
const ldh = new Star('刘德华')

验证:

2.构造函数里的原型对象的this指向

结论:原型对象里面的函数this指向还是实例对象

    <script>let thatfunction Star(uname) {this.uname = uname}Star.prototype.sing = function () {that = thisconsole.log('唱歌')}const ldh = new Star('刘德华')ldh.sing()console.log(that === ldh)</script>

注意:

<script>let that; // 全局变量,初始值为 undefinedfunction Star(uname) {this.uname = uname; // 构造函数中的 this 指向实例,但未赋值给 that}Star.prototype.sing = function () {that = this; // 只有调用 sing 方法时,this 才会指向调用它的实例console.log('唱歌');};const ldh = new Star('刘德华');// ldh.sing(); // 注释掉这行时,sing 方法未被调用,that 保持初始值 undefinedconsole.log(that === ldh); // 输出 false 的原因:
</script>

当 ldh.sing() 被注释时(未调用):

  • sing 方法从未执行,其中的 that = this 代码块没有机会运行
  • 全局变量 that 保持初始值 undefined(声明时未赋值)
  • 最终比较 undefined === ldh 为 false(类型和值都不相等)

3.原型对象的constructor 属性

注意:每个原型对象里面都有个construtor属性,该属性指向该原型对象的构造函数

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>function Star() {}const ldh = new Star();console.log(Star.prototype)console.log(Star.prototype.constructor === Star);</script>
</body></html>

⑴.constructor使用场景

如果有多个对象的方法,我们可以给原型对象采取对象形式赋值。但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。

原型对象的定义方式

第一个示例(逐个添加原型方法)

第一个看成方法

此方法不会丢失 constructor 指向

<script>function Star() { }Star.prototype.sing = function () {console.log('唱歌');};Star.prototype.dance = function () {console.log('跳舞');};console.log(Star.prototype);
</script>

  • 原型对象引用不变:通过 prototype 属性逐个添加方法时,始终操作的是构造函数默认的原型对象(初始包含 constructor 属性)。
  • 保留 constructor 指向:原型对象的 constructor 仍然指向 Star 构造函数,因为未覆盖整个原型对象。
  • 打印结果:Star.prototype 会包含 constructor、sing、dance 三个属性。
第二个示例(整体替换原型对象)

第二个看成对象

<script>console.log(Star.prototype);  // ①function Star() { }Star.prototype = {sing: function () {console.log('唱歌');},dance: function () {console.log('跳舞');}};console.log(Star.prototype);  // ②
</script>

  • 原型对象引用改变:直接将 prototype 赋值为一个全新的对象,覆盖了默认的原型对象。
  • 丢失 constructor 指向:新对象没有 constructor 属性,此时 Star.prototype.constructor 会指向 Object(默认构造函数),除非手动添加 constructor: Star。

三、对象原型

答:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>function Star() {}const ldh = new Star()// 对象原型__proto__ 指向 改构造函数的原型对象console.log(ldh.__proto__)console.log(ldh.__proto__ === Star.prototype)// 对象原型里面也有constructor 指向 构造函数 Starconsole.log(ldh.__proto__.constructor === Star)</script>
</body></html>

注意区分原型对象和对象原型

四、原型继承

1.引入

看以下代码

   function Woman() {this.eyes = 2this.head = 1}const red = new Woman()console.log(red)function Man() {this.eyes = 2this.head = 1}const pink = new Man()console.log(pink)

它们除了名字不同,里面都有相同的属性,可以抽取出来

2.封装-抽取公共部分

把男人和女人公共的部分抽取出来放到Person里面,然后公共的部分放原型对象上

        const Person = {eyes: 2,head: 1}function Woman() {}Woman.prototype = Personconst red = new Woman()console.log(red)console.log(Woman.prototype)function Man() {}Man.prototype = Personconst pink = new Man()console.log(pink)



存在一个问题:

解决:

3.Woman.prototype和Man.prototype都指向堆中同一块地址

在 JavaScript 中,对象是按引用传递的。当将同一个对象 Person 直接赋值给 Woman.prototype 和 Man.prototype 时,两者会共享同一个对象的引用,因此它们的原型确实指向堆内存中的同一块地址。以下是对代码的详细分析:

⑴. 原型赋值的本质是引用传递

const Person = { eyes: 2, head: 1 }; // 定义一个对象(堆内存中创建)// 原型赋值(传递引用而非复制对象)
Woman.prototype = Person; // Woman 的原型指向 Person 对象
Man.prototype = Person;   // Man 的原型也指向同一个 Person 对象
  • Person 是一个普通对象,存储在堆内存中。
  • Woman.prototype 和 Man.prototype 本质上是两个指针,它们被赋值为 Person 对象的内存地址。
  • 结论:两者的原型指向同一块堆内存地址,可通过 === 验证:
console.log(Woman.prototype === Man.prototype); // 输出 true

⑵.实例与原型的关系

const red = new Woman(); // red 的 __proto__ 指向 Woman.prototype(即 Person)
const pink = new Man();  // pink 的 __proto__ 指向 Man.prototype(即 Person)console.log(red.__proto__ === pink.__proto__); // 输出 true(均指向 Person)
  • 两个实例的原型链最终都会追溯到同一个 Person 对象,因此它们会共享 Person 中定义的所有属性(eyeshead)。

⑶.潜在问题:共享原型对象的副作用

由于 Woman.prototype 和 Man.prototype 指向同一个对象,对其中一个原型的修改会影响另一个:

// 为 Woman 的原型添加新方法(实际修改的是 Person 对象)
Woman.prototype.speak = function() { return "Hello"; };// Man 的原型也会自动拥有该方法
console.log(pink.speak()); // 输出 "Hello"(意外共享)

这种行为可能导致预期外的继承关系污染,尤其是在需要独立原型的场景下(如 Woman 和 Man 应属于不同类别)。

⑷.解决方法

把上面的Person写成构造函数,然后类似Java多态的写法;Woman.prototype = new Person()

子类的原型=new 父类

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>function Person() {this.eyes = 2this.head = 1}function Woman() {}Woman.prototype = new Person()Woman.prototype.constructor = Womanconst red = new Woman()console.log(red)console.log(Woman.prototype)// 为 Woman 的原型添加新方法(实际修改的是 Person 对象)Woman.prototype.speak = function () { return "Hello"; };// Man 的原型也会自动拥有该方法function Man() {}Man.prototype = new Person()const pink = new Man()console.log(pink)console.log(pink.speak()); // 输出 "Hello"(意外共享)</script>
</body></html>

此时,再用Man调用speak方法就报错了

五、原型链

http://www.xdnf.cn/news/967.html

相关文章:

  • Kubernetes相关的名词解释POD(13)
  • Spring Boot+Mybatis设置sql日志打印
  • 视频分析设备平台EasyCVR安防视频小知识:安防监控常见故障精准排查方法
  • leetcode 516. Longest Palindromic Subsequence
  • 开关电源实战(六)STM32数控电源BuckBoost
  • 【Tips】统一论文中的公式格式
  • 算法导论第3章思考题
  • 【Device|顶刊】突破衍射极限!20纳米光电探测器开启光学传感新时代
  • Flutter路由模块化管理方案
  • 组件是怎样写的(1):虚拟列表-VirtualList
  • 第 6 篇:衡量预测好坏 - 评估指标
  • 实现侧边栏点击标题列表,和中间列表区域联动效果
  • 《P3029 [USACO11NOV] Cow Lineup S》
  • 代码随想录算法训练营day8(栈与队列)
  • 个性化的配置AndroidStudio
  • MySQL-存储过程--游标
  • 腾讯IMA深度使用指南:从下载安装到高效应用
  • 安全协议分析概述
  • 10天学会嵌入式技术之51单片机-day-3
  • CSS文本属性
  • Java 泛型使用教程
  • 力扣第446场周赛
  • 时序逻辑入门指南:LTL、CTL与PTL的概念介绍与应用场景
  • Typescript中的泛型约束extends keyof
  • 速查手册:TA-Lib 超过150种量化技术指标计算全解 - 7. Pattern Recognition(模式识别)
  • ubuntu学习day4
  • SaltStack远程协助工具
  • Oracle for Linux安装和配置(11)——Linux配置
  • Franka机器人ROS 2来袭:解锁机器人多元应用新可能
  • SpringBoot + Vue 实现云端图片上传与回显(基于OSS等云存储)