【编程向导】JavaScript-创建对象一期讲解

2024-03-15 17:52

本文主要是介绍【编程向导】JavaScript-创建对象一期讲解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

工厂模式

工厂模式 是用来创建对象的一种最常用的设计模式。工厂模式不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂。工厂模式常见于大型项目,例如 jQuery 的 $ 对象,我们创建选择器对象之所以没有 new selector 就是因为 $() 已经是一个工厂方法,其他例子例如 React.createElement()Vue.component() 都是工厂模式的实现。

工厂模式根据抽象程度的不同可以分为三种:

  • 简单工厂:通过第三方的类完成松耦合的任务
  • 复杂工厂:通过把实例化的任务交给子类来完成的,用以到达松耦合的目的
  • 超级工厂:通过 eval() 来完成智能工厂

工厂的目的:在于判断接口最终用哪个类实例化(故与接口密不可分)。

使用工厂最终达到的效果是:多态,和类与类之间的松耦合。

应用场景

ES5 实现工厂模式

function createPerson(name, age, job) {let person = new Object();person.name = name;person.age = age;person.job = job;person.sayNam = function () {console.log(`I'm ${name}`);};return person;
}const person1 = createPerson('Ben', 21, 'student');
const person2 = createPerson('Gray', 25, 'Doctor');

函数 createPerson() 能够根据接受的参数来构建一个包含所有必要信息的 Person 对象。可以无数次调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

ES6 实现工厂模式

class User {constructor(name, auth) {this.name = name;this.auth = auth;}
}
class UserFactory {static createUser(name, auth) {//工厂内部封装了创建对象的逻辑://权限为 admin 时,auth=1;权限为 user 时,auth 为 2//使用者在外部创建对象时,不需要知道各个权限对应哪个字段, 不需要知道赋权的逻辑,只需要知道创建了一个管理员和用户if (auth === 'admin') return new User(name, 1);if (auth === 'user') return new User(name, 2);}
}
const admin = UserFactory.createUser('cxk', 'admin');
const user = UserFactory.createUser('xz', 'user');

原型模式

我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由 特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,那么 prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。

function Person(){}Person.prototype.name = 'Uzi';
Person.prototype.age = 22;
Person.prototype.job = 'E-Sports Player';
Person.prototype.sayName = function(){console.log(this.name);
}const uzi1 = new Person();
uzi1.sayName();
// 'Uzi'const uzi2 = new Person();
uzi2.sayName();
// 'Uzi'// 共用公用方法
console.log(person1.sayName == person2.sayName);
// true

与构造函数不同,新对象的这些属性和方法是由所有实例共享的。

理解原型对象

无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性是一个指向 prototype 属性所在函数的指针。

创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都是从 Object 继承而来的。当调用构造函数创建一个新的实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMAScript 5 中管这个指针叫做 [[Prototype]]。虽然在脚本中没有标准的方式访问 [[Prototype]],但 Firefox、Safari 和 Chrome 在每个对象上都支持一个属性 __proto__;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型之间,而不是存在于实例与构造函数之间。

原型最初只包含 constructor 属性,而该属性也是共享的,因此可以通过对象实例访问。

虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的属性。

function Person(){}Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function(){
console.log(this.name);
};const person1 = new Person();
const person2 = new Person();person1.name = 'Greg';
console.log(person1.name);
// 'Greg' 		// from instance
console.log(person2.name);
// 'Nicholas' 	// from prototype

两个实例访问 name 属性的过程:

  • person1 ==> 实例中读取 name 属性 ==> 在实例中读取 name 属性成功
  • person2 ==> 实例中读取 name 属性 ==> 实例中无 name 属性 ==> 从原型链中读取 name 属性 ==> 读取成功

当为对象实例添加一个属性时,这个属性就会 屏蔽 原型对象中保存的同名属性。换句话说,添加这个属性只会阻止我们访问原型中的那个属性值,但不会修改那个属性。即使这个属性设置为 null,也只会在实例中设置这个属性,而不会恢复其指向原型的连接。不过,使用 delete 操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性。

ECMAScript5 的 Object.getOwnPropertyDescriptor() 方法只能用于实例属性,要取得原型属性的描述符,必须直接在原型对象上调用 Object.getOwnPropertyDescriptor() 方法。

原型与实例属性检测

有两种方式使用 in 操作符:单独使用和在 for-in 循环中使用。在单独使用时,in 操作符会在通过对象能够访问给定属性时返回 true无论该属性存在于实例中还是原型中

同时使用 hasOwnProperty() 方法和 in 操作符,就可以确定该属性到底是存在于对象中,还是存在于原型中。

由于 in 操作符只要通过对象能够访问到属性就返回 truehasOwnProperty() 只在属性存在于实例中时才返回 true,因此只要 in 操作符返回 truehasOwnProperty() 返回 false,就可以确定属性是原型中的属性。

更简单的原型语法

前面的例子中每添加一个属性和方法就要输入一遍 Person.prototype,为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。

function Person(){}Person.prototype = {name: 'Nicholas',age: 29,job: 'Software Engineer',sayName: function (){console.log(this.name);}
}

前面介绍过,没创建一个函数,就会同时创建它的原型对象,这个对象自动获得构造函数。而这里的语法,这里相当于重写了实例的原型对象,相应地原型对象中的构造函数 constructor 亦被覆盖,不再指向 Person 函数。此时,尽管 instanceof 操作符还能返回正确的结果,但通过 constructor 已经无法确定对象的类型了。

当然,我们可以手动为它设置回适当的值。但是,以这种方式重设 constructor 属性回导致它的 [[Enumerable]] 特性被设置为 true。默认情况下,原生的 constructor 属性是不可枚举的。

function Person(){}Person.prototype = {constructor: Person,name: 'Nicholas',age: 29,job: 'Software Engineer',sayName: function (){console.log(this.name);}
}

重设构造函数,只适用于 ECMAScript5 兼容的浏览器。

Object.defineProperty(Person, 'constructor', {enumerable: false,value: Person
})

原型的动态性

由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来,即使是先创建了实例后修改原型也照样如此。

实例与原型之间的关系是松散的,

function Person(){}const friend = new Person();Person.prototype = {constructor: Person,name: 'Nicholas',age: 29,job: 'Software Engineer',sayName: function (){console.log(this.name);}
};friend.sayName();
// error

重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系,它们引用的仍然是最初的原型。

原型对象的原型

原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建的。所有原生引用类型(Object、Array、String 等等)都在其构造函数的原型上定义了方法。

通过原生对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法。可以像修改自定义对象的原型一样修改原生对象的原型,因此可以随时添加方法。

尽管可以这样做,但我们不推荐在产品化的程序中修改原生对象的原型。如果因某个实现中缺少某个方法,就在原生对象的原型中添加这个方法,那么当在另一个支持该方法的实现中运行代码时,就可能会导致命名冲突。而且,这样做也可能会意外地重写原生方法。

原型对象的问题

原型模式省略了为构造函数传递初始参数的环节,结果所有实例在默认情况下都将取得相同的属性值。

原型中的所有属性是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性倒也说得过去,毕竟,通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然而,对于包含引用类型值的属性来说,问题就比较突出了。

function Person(){}Person.prototype = {name: 'Nicholas',age: 29,job: 'Software Engineer',friends: ['Shelby', 'Court'],sayName: function (){console.log(this.name);}
}const person1 = new Person();
const person2 = new Person();person1.friends.push('Van');console.log(person1.friends);
// 'Shelby,Court,Van'
console.log(person2.friends);
// 'Shelby,COurt,Van'
console.log(person1.friends == person2.friends);
// true

这篇关于【编程向导】JavaScript-创建对象一期讲解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/812768

相关文章

springboot集成easypoi导出word换行处理过程

《springboot集成easypoi导出word换行处理过程》SpringBoot集成Easypoi导出Word时,换行符n失效显示为空格,解决方法包括生成段落或替换模板中n为回车,同时需确... 目录项目场景问题描述解决方案第一种:生成段落的方式第二种:替换模板的情况,换行符替换成回车总结项目场景s

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

SpringBoot中@Value注入静态变量方式

《SpringBoot中@Value注入静态变量方式》SpringBoot中静态变量无法直接用@Value注入,需通过setter方法,@Value(${})从属性文件获取值,@Value(#{})用... 目录项目场景解决方案注解说明1、@Value("${}")使用示例2、@Value("#{}"php

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

基于 Cursor 开发 Spring Boot 项目详细攻略

《基于Cursor开发SpringBoot项目详细攻略》Cursor是集成GPT4、Claude3.5等LLM的VSCode类AI编程工具,支持SpringBoot项目开发全流程,涵盖环境配... 目录cursor是什么?基于 Cursor 开发 Spring Boot 项目完整指南1. 环境准备2. 创建

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

springboot中使用okhttp3的小结

《springboot中使用okhttp3的小结》OkHttp3是一个JavaHTTP客户端,可以处理各种请求类型,比如GET、POST、PUT等,并且支持高效的HTTP连接池、请求和响应缓存、以及异... 在 Spring Boot 项目中使用 OkHttp3 进行 HTTP 请求是一个高效且流行的方式。

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq