对象创建的4种模式

1. 工厂模式

这种模式抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节

缺点:没有解决对象识别的问题(即怎样知道一个对象的类型)

function createPerson(name, age, job) {var o = new Object();o.name = name;o.age = age;o.job = job;o.sayName = function() {alert(this.name)};return o;
}
const personOne = createPerson('lee', 29, 'Software Engineer')
const personTwo = createPerson('Gred', 24, 'Doctor')

2. 构造函数模式

 (1)与工厂模式相比构造函数模式

  • 没有显示地创建对象
  • 直接将属性和方法赋值给了this对象
  • 没有return语句
  • 区别于非构造函数,构造函数以大写字母开头

(2)要创建Person的新实例,必须使用new操作符

         以这种方式调用构造函数实际上会经历以下4个步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
  3. 执行构造函数的代码(为这个新对象添加属性) 
  4. 返回新对象
function Person(name, age, job) {this.name = name;this.age = age;this.job = job;this.sayName = function() {alert(this.name)};
}
const personOne = new Person('lee', 29, 'Software Engineer')
const personTwo = new Person('Gred', 24, 'Doctor')

(3)personOne和personTwo分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person

console.log(personOne.constructor == Person) // true
console.log(personTwo.constructor == Person) // true

(4)以上例子中创建的所有对象既是Object的实例,同时也是Person的实例,这一点通过instanceof操作符可以得到验证。

console.log(personOne instanceof Person) // true
console.log(personOne instanceof Object) // true
console.log(personTwo instanceof Person) // true
console.log(personTwo instanceof Object) // true

(5)构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那它跟普通函数也不会有什么两样。

// 当作构造函数使用
const person = new Person('lee', 29, 'Software Engineer');
person.sayName(); // 'lee'// 作为普通函数调用
Person('Gred', 24, 'Doctor') // 添加到window
window.sayName(); // 'Gred'// 在另外一个对象的作用域中调用
const o = new Object();
Person.call(o, 'Alice', 27, 'Nurse');
o.sayName(); // 'Alice'

缺点:每个方法都要在每个实例上重新创建一遍。

解决方法:把函数转移到构造函数外。

function Person(name, age, job) {this.name = name;this.age = age;this.job = job;this.sayName = sayName
}
function sayName() {alert(this.name)};
const personOne = new Person('lee', 29, 'Software Engineer')
const personTwo = new Person('Gred', 24, 'Doctor')

如上,两个实例的sayName共享了一个全局的sayName方法。解决了2个函数做同一件事的问题。但是新问题又来了

在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实
如果对象需要定义很多方法,那么就要定义很多全局函数,导致我们这个自定义的引用类型丝毫没有封装性可言。

3. 原型模式

(1) 针对以上构造函数的痛点,原型模式可以解决这些问题。

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的作用是包含可以由特定类型的所有实例共享的属性和方法。prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处就是不用在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。

function Person() {}
Person.prototype.name = 'lee';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function() {alert(this.name)
};
const personOne = new Person();
personOne.sayName(); // 'lee'
const personTwo = new Person();
personTwo.sayName(); // 'lee'
console.log(personOne.sayName == personTwo.sayName); // true//简写
function Person(){}
Person.prototype = {constructor: Person,name: 'lee',age: 29,job: 'Software Engineer',sayName: function(){alert(this.name)}
};

(2)默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性这个属性是一个指向prototype属性所在函数的指针。如上例子,Person.prototype.constructor指向Person。

Person.prototype.constructor === Person // true

(3)当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。这个内部属性[ [ Prototype ] ],虽然无法访问到。但是可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。如果[ [ Prototype ] ]指向调用isPrototypeOf()方法的对象(Person.prototype),那么这个方法就返回true。

console.log(Person.prototype.isPrototypeOf(personOne)); // true
console.log(Person.prototype.isPrototypeOf(personTwo)); // true

(4)Object.getPrototypeOf() 这个方法返回[ [ Prototype ] ]的值

console.log(Object.getPrototypeOf(personOne) == Person.prototype); // true
console.log(Object.getPrototypeOf(personOne).name); // 'lee'
console.log(Object.getPrototypeOf(personTwo) == Person.prototype); // true
console.log(Object.getPrototypeOf(personTwo).name); // 'lee'

(5)使用delete可以删除实例中的属性

personOne.name = 'Alice';
console.log(personOne.name) // 'Alice'  来自实例
console.log(personTwo.name) // 'lee'  来自原型 
delete personOne.name;
console.log(personOne.name) // 'lee'  来自原型 

(6)hasOwnProperty()方法可以检测一个属性是存在于实例中还是存在于原型中。如果该属性存在于实例中,则返回true。

personOne.name = 'Alice';
console.log(personOne.hasOwnProperty('name')) // true
console.log(personTwo.hasOwnProperty('name')) // false

(7)in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。

personOne.name = 'Alice';
console.log('name' in personOne) // true name在实例中
console.log('name' in personTwo) // true name在原型中

(8)getOwnPropertyNames()方法得到所有实例属性,无论它是否可枚举。

const keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys); // ['constructor','name','age','job','sayName']

(9)可以随时给原型添加属性和方法,但是如果是重写整个原型对象,就相当于把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。

function Person(){}
const friend = new Person();
Person.prototype = {name: 'lee',age: 29,job: 'Software Engineer',sayName: function(){alert(this.name)}
};
friend.sayName(); // error
//因为这时的friend指向的是以前的Person,而以前的是没有sayName方法的,所以报错。
//而且这里旧的原型对象和新的原型对象的指针都指向Person

缺点:

(1)所有实例在默认情况下都将取得相同的属性值。(上面例子中定义的name,age等)(2)原型中存在引用类型,会导致每一个实例共享这个引用类型。如下例子:每一个实例中的friends会因为其中一个实例的修改,都共享了这个修改后的结果。

function Person(){}
Person.prototype = {constructor: Person,name: 'lee',age: 29,friends: ['Jack', 'Bob'],job: 'Software Engineer',sayName: function(){alert(this.name)}
};
const personOne = new Person();
const personTwo = new Person();
personOne.friends.push('Ivan');
console.log(personOne.friends); // ['Jack', 'Bob', 'Ivan']
console.log(personTwo.friends); // ['Jack', 'Bob', 'Ivan']
console.log(personOne.friends === personTwo.friends); // true

4. 组合使用构造函数模式和原型模式

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。

function Person(name, age, job) {this.name = name;this.age = age;this.job = job;this.friends = ['Jack', 'Bob'];
}
Person.prototype = {constructor: Person,sayName: function() {alert(this.name);}}
const personOne = new Person('lee', 29, 'Software Engineer')
const personTwo = new Person('Gred', 24, 'Doctor')
personOne.friends.push('Van');
console.log(personOne.friends); // ['Jack', 'Bob', 'Ivan']
console.log(personTwo.friends); // ['Jack', 'Bob']
console.log(personOne.friends === personTwo.friends); // false
console.log(personOne.sayName === personTwo.sayName); // true

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/25833.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MATLAB实现磷虾算法(Krill herd algorithm)

1.算法介绍 磷虾算法(Krill Herd Algorithm, KH)是一种基于生物启发的优化算法,其原理模拟了南极磷虾(Euphausia superba)群体的聚集行为。该算法旨在通过模拟磷虾个体间的相互作用、觅食行为和随机扩散,来…

glm-4-9b-chat-1m模型结构解读

glm-4-9b-chat-1m模型结构解读 模型代码文件下载 glm-4-9b-chat-1m模型总体结构 ChatGLMForConditionalGeneration((transformer): ChatGLMModel((embedding): Embedding((word_embeddings): Embedding(151552, 4096))(rotary_pos_emb): RotaryEmbedding()(encoder): GLMTran…

No ‘ChromeSansMM’ font 错误解决

No ‘ChromeSansMM’ font 错误解决 错误 class com.spire.ms.System.Exception: No ChromeSansMM font found! 背景 在使用 Spire.PDF 库处理 PDF 文件时,遇到了以下错误: class com.spire.ms.System.Exception: No ChromeSansMM font found! com.…

L2-002 链表去重(C++)

给定一个带整数键值的链表 L,你需要把其中绝对值重复的键值结点删掉。即对每个键值 K,只有第一个绝对值等于 K 的结点被保留。同时,所有被删除的结点须被保存在另一个链表上。例如给定 L 为 21→-15→-15→-7→15,你需要输出去重后…

【数据结构与算法 | 堆篇】力扣295

1. 力扣295 (1). 题 中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。 例如 arr [2,3,4] 的中位数是 3 。例如 arr [2,3] 的中位数是 (2 3) / 2 2.5 。 实现 MedianFinder 类: MedianFinder() …

10进制与二、八、十六进制的转换

x进制转10进制 1、如八进制数123,通过把每一位数字和8的指数级进行相乘 1 * 8^2 2 * 8^1 3 * 8^01 * 64 2 * 8 3 * 164 16 383 2、十六进制1A3 1 * 16^2 A(即10) * 16^1 3 * 16^01 * 256 10 * 16 3 * 1256 160 3419 3、二进制1010 1 * 2^3 0 * 2…

基于机器学习的锂电池RUL SOH预测

数据集为NASA锂电池数据集。 import datetimeimport numpy as npimport pandas as pdfrom scipy.io import loadmatfrom sklearn.preprocessing import MinMaxScalerfrom sklearn.metrics import mean_squared_errorfrom sklearn import metricsimport matplotlib.pyplot as p…

C# MES通信从入门到精通(11)——C#如何使用Json字符串

前言 我们在开发上位机软件的过程中,经常需要和Mes系统进行数据交互,并且最常用的数据格式是Json,本文就是详细介绍Json格式的类型,以及我们在与mes系统进行交互时如何组织Json数据。 1、在C#中如何调用Json 在C#中调用Json相关…

【Golang】Go语言中defer与return的精妙交织:探索延迟执行与返回顺序的微妙关系

【Golang】Go语言中defer与return的精妙交织:探索延迟执行与返回顺序的微妙关系 大家好 我是寸铁👊 总结了一篇defer 和 return 返回值 的执行顺序探讨的文章✨ 喜欢的小伙伴可以点点关注 💝 前言 在Go语言中,defer 和return是两…

二进制文件的膨胀策略和使用 debloat 消除膨胀测试

在恶意软件的分析中有的 Windows 可执行文件(PE 文件)会通过膨胀策略来绕过防病毒一些防病毒的检查,比如上传云进行分析,因为文件太大了所以无法进行一些防病毒分析。一般的可执行文件有很多的膨胀策略,一般简单的膨胀…

ovs网络配置命令

ovs 查看 OVS 版本查看当前配置的所有交换机查看交换机的详细信息创建和删除桥接添加和删除端口配置端口 Open vSwitch是一种生产质量的多层虚拟交换机,根据开源Apache 2.0许可证进行许可。它旨在通过编程扩展实现大规模网络自动化,同时仍然支持标准管理…

NettyのBufferChannelSelector用法

这一篇介绍Buffer&Channel&Selector的常见API使用案例 1、Buffer 1.1、从Buffe中读取/写入 以ByteBuffer为例。Buffer需要和Channel结合使用(在上一篇中提到,通道是数据传输的载体,缓冲区是数据的临时存储区)。 那么如何…

OSFP 1类LSA详解

概述 上图为1类LSA的实际报文结构 , 在开始之前一定需要说明 , 1类LSA是OSPF中最复杂的LSA类型 , 在LSA头部的文章中详细介绍了 LS Type / Link State ID / Adv Router 3种头部字段 , 在1类LSA的主体内容中还存在类似的字段十分的相似 , 很多网络从业者难以理解的点就在于此 , …

orbslam2代码解读(2):tracking跟踪线程

书接上回,mpTracker->GrabImageMonocular(im,timestamp)函数处理过程: 如果图像是彩色图,就转成灰度图如果当前帧是初始化的帧,那么在构建Frame的时候,提取orb特征点数量为正常的两倍(目的就是能够在初…

vue3中$attrs与inheritAttrs的使用

Vue 3 引入了一些新特性和改进,其中之一就是对 $attrs 的处理方式。在 Vue 2 中,$attrs 包含了父组件传递给子组件的属性,但不包括子组件已经声明的 props。在 Vue 3 中,$attrs 的行为有所变化: 默认情况下&#xff0…

14. RTCP 协议

RTCP 协议概述 RTCP(Real-time Transport Control Protocol 或 RTP Control Protocol 或简写 RTCP),实时传输控制协议,是实时传输协议(RTP)的一个姐妹协议。 注:RTP 协议和 RTP 控制协议&#…

Postgresql源码(135)生成执行计划——Var的调整set_plan_references

1 总结 set_plan_references主要有两个功能: 拉平:生成拉平后的RTE列表(add_rtes_to_flat_rtable)。调整:调整前每一层计划中varno的引用都是相对于本层RTE的偏移量。放在一个整体计划后,需要指向一个统一…

Material-UI create-react-app 创建移动端 H5

当使用 create-react-app 和 Material-UI 来创建移动端 H5 页面时,你需要考虑几个关键点来确保页面在移动设备上表现良好。以下是一些步骤和最佳实践: 创建 React 项目 使用 create-react-app 快速创建一个新的 React 项目: npx create-react-app my-mobile-app cd my-mobil…

京东一面测开(KPI)

京东一面测开凉经(笔试ak) 3.8 面试官:你很优秀啊,你不用谦虚 没问技术相关,问了如何设计测试用例步骤一些理论: 什么是软件测试?其目的是什么? 软件测试有哪些类型?请列…

架构设计-全局异常处理器404、405的问题

java web 项目中经常会遇到异常处理的问题,普遍的做法是使用全局异常处理,这样做有以下几种原因: 集中化处理:全局异常处理允许你在一个集中的地方处理整个应用程序中的异常。这有助于减少代码重复,因为你不必在每个可…