前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >《JavaScript 模式》读书笔记(6)— 代码复用模式1

《JavaScript 模式》读书笔记(6)— 代码复用模式1

作者头像
全栈程序员站长
发布于 2022-07-21 00:36:46
发布于 2022-07-21 00:36:46
27300
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

  我们有开始进入新篇章了。这篇内容主要讲代码复用模式,实际上代码复用,就是继承啊,原型啊,构造函数啊等等这一类的内容。对于前端进阶来说,是很重要的基础知识。这一篇内容会对原型、 继承有很深入的讲解。我也会尽我所能的为大家讲清楚、分析透彻。

  代码复用是一个非常重要而且有趣的主题,简而言之,这是由于人们很自然的争取编写尽可能少的代码。尤其是那些具有质量优秀、通过测试、可维护、可扩展性、文档化的可复用代码。

  在谈及代码复用的时候,首先想到的是代码的继承性(inheritance),而本章中大部分也专门致力于代码复用这个主题。在这里可以看到多种方法都可以实现“基于类特性的(classical)”和“非基于类特性的(nonclassical)”继承特性。但重要的是要记住其最终目标,我们要复用代码。继承性就是程序员用以实现代码复用这个目标的一种方法或手段,而且它也并不是唯一的方法。在本章中,可以看到如何利用其他对象组合成所需的对象,也可以看到如何使用mix-in技术(混入或者渗元技术),还可以看到如何在技术上没有永久继承的情况下仅借用和复用所需的功能。

  当开始接触代码复用任务时,请记住GoF(Gang of Four,指《Design Patterns》的四位作者)等人在其著作中提出的有关创建对象的建议原则“优先使用对象组合,而不是类继承”。

一、传统与现代继承模式的比较

经常会在有关JavaScript继承模式的主题讨论中听到术语“传统继承(classical inheritance)”,为此,让我们先阐明“传统(classical)”所代表的意义。该术语从词义上来说,并不是与用于古董、历史沉积、或者广为接受并认定为正确的处理事务的方法这些词义相同。实际上,该术语只是单词“Class(类)”的一种表现形式。

许多编程语言都具有类的概念,并以此作为对象的蓝图。在那些编程语言中,每个对象都是一个类的特定实例(比如,Java语言环境中),并且在不存在某个类的时候并不能创建该类的对象。在JavaScript中,由于没有类的概念,因此实例的概念也就没有多大的意义。JavaScript中的对象是简单的键-值(key-value)对,可以动态的创建和修改这些对象。

  但JavaScript具有构造函数,并且new操作符的语法与那些使用类的编程语言在语法上有许多相似之处。

  在Java中可以采用下列方式创建对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Person adam = new Person();

  而在JavaScript中则可以采用下列方式创建对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var adam = new Person();

  除了与Java中强类型限制的情况不同之外,在JavaScript中也必须声明adam是Person类型,其语法与Java看起来是一样的。JavaScript的构造函数在调用时Person看起来似乎是一个类,但重要的是要记住Person仍然只是一个函数。这种语法上的相似性导致了很多程序员按照类的方式考虑JavaScript,并产生了一些假定在类的基础上的开发思路和继承模式。我们将这种实现方式称之为“类式(classical)”继承模式。在这里让我们将“现代(modern)”模式表述为:其他任何不需要以类的方式考虑的模式。

  在项目中,首先建议使用现代模式,除非你或你的团队真的不适应这样。本章先讨论类式继承模式,再讨论其他现代模式。

二、使用类式继承时的预期结果

实现类式继承(classical inheritance)的目标是通过构造函数Child()获取来自于另一个构造函数Parent()的属性,从而创建对象。

注意:虽然这里讨论的是类式继承模式,但是请让我们尽量避免使用“class(类)”这个单词。将其表述为“构造函数(constructor function 或 constructor)”时虽然字数更长一些,但是其表述更为精确且不会产生歧义。一般情况下,在开发团队交流时请努力消除单词“class”的使用,因为当涉及JavaScript时,“class”这个词对于不同的人可能意味着不同的含义。

  下面是定义两个构造函数Parent()和Child()的一个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 父构造函数
function Parent(name) {
    this.name = name || 'Adam';
}

// 向该原型添加功能
Parent.prototype.say = function () {
    return this.name;
};

// 空白的子构造函数
function Child() {}

// 继承的魔力在这里发生
inherit(Child,Parent);

  上面的方法中,存在父、子两个构造函数,say()方法被添加到父构造函数的原型(prototype)中,并且一个名为inherit()的函数调用负责处理它们之间的继承关系。其中,inherit()函数并非由编程语言提供的,为此,程序员必须自己来实现该函数。下面,让我们看看实现该函数的几种方法。

三、类式继承模式#1——默认模式

最常用的一种默认方法是使用Parent()构造函数创建一个对象,并将该对象赋值给Child()的原型。下面是可复用继承函数inherit()的第一种用法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function inherit(C,P) {
    C.prototype = new P();
}

  重要的是需要记住,原型的属性应该指向一个对象,而不是一个函数,因此它必须指向一个由父构造函数所创建的实例(一个对象),而不是指向构造函数本身。也就是说,要注意使用new操作符来创建新对象,因为需要new才能使用这种模式。

在以后的程序中,当使用new Child()语句创建一个对象时,它会通过原型从Parent()实例中获取它的功能,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var kid = new Child();
kid.say();

追溯原型链

使用这种默认的继承模式时,同时继承了自身的属性(即,加入到this的实例相关属性,比如name),以及原型属性和方法【比如say()】。

  让我们回顾一下在这种继承模式下原型链的工作原理。出于讨论的目的,让我们将对象视做存在于内存中某处的块,该内存块可以包含数据以及指向其他块的引用。

  当使用new Parent()语句创建一个对象时,会创建一个这样的块,如下图所示中的#2块。

  在#2块中保存了name属性的数据。如果您尝试访问say()方法,虽然块#2中并不包含say()方法,但是通过使用指向构造函数Parent()的prototype(原型)属性的隐式链接__proto__,便可以访问对象#1(Parent.prototype),而对象#1又确实知道关于say()的地址。所有这一切都在后台发生,并不用为这种复杂的原型链而烦恼,重要的是需要理解它的工作原理以及所需要访问或可能修改的数据位于何处。请注意,这里仅使用__proto__来解释原型链,即使在一些环境中提供了该属性,在程序开发语言中也并不能使用该属性。

现在,让我们来看一下在使用inherit()函数后,当使用var kid = new Child()创建新对象时会发生什么情况,如下图所示:

  从上图可以看出,child()构造函数是空的,并且没有任何属性添加到Child.prototype中。因此,使用new Child()语句所创建的对象除了包含隐式链接__proto__以外,它几乎是空的。在这种情况下,__proto__指向了在inherit()函数中使用new Parent()语句所创建的对象。

  现在,当执行kid.say()时会发生什么情况?对象#3中并没有这样的say()方法,因此它将通过原型链查询到#2.然而,#2中也没有该方法,因此它又顺着原型链查询到对象#1,而对象#1正好具有say()方法。然而,在say()中引用了this.name,该引用仍然还需要解析。因此,查询再次启动。在这种情况下,this指向对象#3,对象#3中没有name属性。为此,将查询对象#2,而对象#2中确实有name属性,其值为“Adam”。

  最后,让我们更进一步查看原型链的概念,比如说,我们有以下这样的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var kid = new Child();
kid.name = 'Patrick'
kid.say();

  下图显示了上述这种情况下原型链的工作过程。

  设置kid.name语句并不会修改对象#2的name属性,但是它却直接在kid对象#3上创建了一个自身的属性。当执行kid.say()时,将依次在对象#3、对象#2中查询say()方法,并且最终在对象#1中找到该方法,这与前面所描述的过程相似。但是,如果这次是查找this.name(这是与kid.name相同的),那么其过程是很快的,这是由于该属性立刻就能够在对象#3中找到,而无需通过原型链。

  如果使用delee kid.name语句删除新属性,那么对象#2的name属性将会“表现出来”,并在连续的查找过程中找到其name属性。

使用模式#1时的缺点

本模式的其中一个缺点在于:同时继承了两个对象的属性,即添加到this的属性以及原型属性。在绝大多数的时候,并不需要这些自身的属性(比如这里的name),因为它们很可能是指向一个特定的实例,而不是复用。

注意,对于构造函数的一般经验法则是:应该将可复用的成员添加到原型中。

  另一个关于使用通用inherit()函数的问题在于它并不支持将参数传递到子构造函数中,而子构造函数然后又将参数传递到父构造函数中,考虑以下这个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var s = new Child('Seth');
s.say(); // 输出“Adam”

  以上的输出结果可能并不是您所期望的。虽然子构造函数可以将参数传递到父构造函数中,但是那样的话,在每次需要一个新的子对象时都必须重新执行这种继承机制,而且该机制的效率时很低的,其原因在于最终会反复的重新创建父对象。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/124384.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年3月2,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
WPF 基于 WER 注册应用崩溃无响应回调和重启方法
本文来告诉大家如何在 Windows 上利用从 Vista 引入的 Windows Error Reporting (WER) 机制来实现,在应用崩溃、无响应等异常的时候收到回调用于处理信息保存
林德熙
2021/08/17
8370
WPF 基于 WER 注册应用崩溃无响应回调和重启方法
.NET/C# 获取一个正在运行的进程的命令行参数
在自己的进程内部,我们可以通过 Main 函数传入的参数,也可以通过 Environment.GetCommandLineArgs 来获取命令行参数。
walterlv
2020/02/10
2.5K0
制作一个极简的 .NET 客户端应用自安装或自更新程序
本文主要说的是 .NET 客户端应用,可以是只能在 Windows 端运行的基于 .NET Framework 或基于 .NET Core 的 WPF / Windows Forms 应用,也可以是其他基于 .NET Core 的跨平台应用。但是不是那些更新权限受到严格控制的 UWP / iOS / Android 应用。
walterlv
2023/10/22
2820
.NET 命令行参数包含应用程序路径吗?
发布于 2018-09-11 13:28 更新于 2018-09-13 03:24
walterlv
2018/09/18
5630
.NET 命令行参数包含应用程序路径吗?
C#/.NET基于Topshelf创建Windows服务的守护程序不显示UI界面的问题分析和解决方案
C#/.NET基于Topshelf创建Windows服务的守护程序作为服务启动的客户端桌面程序不显示UI界面的问题分析和解决方案
Rector
2019/05/25
1.4K0
如何在 .NET/C# 代码中安全地结束掉一个控制台应用程序?通过发送 Ctrl+C 信号来结束
我的电脑上每天会跑一大堆控制台程序,于是管理这些程序的运行就成了一个问题。或者说你可能也在考虑启动一个控制台程序来完成某些特定的任务。
walterlv
2023/10/22
1.6K0
如何在 .NET/C# 代码中安全地结束掉一个控制台应用程序?通过发送 Ctrl+C 信号来结束
C# Windows服务开发
我要开发一个系统服务,服务的作用是定时检测并关闭其他应用的弹窗,但是开发后却发现,服务在运行是压根获取不到任何窗口。
码客说
2022/05/23
1.3K0
C# Windows服务开发
在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)
在上一篇文章《C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载》中,我们了解发C#/.NET创建基于Topshelf Windows服务程序的大致流程,参数配置以及服务的安装和卸载。同时,我们也使用一个简单的定时任务演示了Topshelf服务的执行情况。
Rector
2020/06/19
2.2K0
.NET/C# 阻止屏幕关闭,阻止系统进入睡眠状态
在 Windows 系统中,一段时间不操作键盘和鼠标,屏幕便会关闭,系统会进入睡眠状态。但有些程序(比如游戏、视频和演示文稿)在运行过程中应该阻止屏幕关闭,否则屏幕总是关闭,会导致体验会非常糟糕。
walterlv
2023/10/22
1K0
.NET/C# 阻止屏幕关闭,阻止系统进入睡眠状态
在 Windows 系统上降低 UAC 权限运行程序(从管理员权限降权到普通用户权限)
在 Windows 系统中,管理员权限和非管理员权限运行的程序之间不能使用 Windows 提供的通信机制进行通信。对于部分文件夹(ProgramData),管理员权限创建的文件是不能以非管理员权限修改和删除的。
walterlv
2023/10/23
2.1K0
如何将应用程序与文件类型(文件扩展名)关联起来
自定义一个文件格式,如 .jgrass ,如何将这种文件格式与对应的程序关联起来? 或者,自己编写了一个可以打开 txt 格式的应用程序,怎么能够通过双击 txt 文件,直接打开这个自定义程序?
jgrass
2024/12/25
1200
如何将应用程序与文件类型(文件扩展名)关联起来
使用 PInvoke.net Visual Studio Extension 辅助编写 Win32 函数签名
2018-07-21 14:35
walterlv
2018/09/18
1.1K0
使用 PInvoke.net Visual Studio Extension 辅助编写 Win32 函数签名
将 async/await 异步代码转换为安全的不会死锁的同步代码
发布于 2018-03-16 03:58 更新于 2018-08-19 11:10
walterlv
2018/09/18
1.7K0
将 async/await 异步代码转换为安全的不会死锁的同步代码
如何创建应用程序清单文件 App.Manifest,如何创建不带清单的应用程序
如果你的程序对 Windows 运行权限有要求,那么需要设置应用程序清单。本文介绍如何添加应用程序清单,并解释其中各项权限设置的实际效果。
walterlv
2023/10/22
1.4K0
如何创建应用程序清单文件 App.Manifest,如何创建不带清单的应用程序
(2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
发布于 2018-07-25 13:20 更新于 2018-07-28 11:50
walterlv
2018/09/18
1.3K1
(2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
WPF 使用 AppBar 将窗口停靠在桌面上,让其他程序不占用此窗口的空间(附我封装的附加属性)
本文介绍如何使用 Windows 的 AppBar 相关 API 实现固定停靠在桌面上的特殊窗口。
walterlv
2023/10/22
1.1K0
WPF 使用 AppBar 将窗口停靠在桌面上,让其他程序不占用此窗口的空间(附我封装的附加属性)
如何在 .NET 库的代码中判断当前程序运行在 Debug 下还是 Release 下
发布于 2018-07-05 11:39 更新于 2018-09-01 00:00
walterlv
2018/09/18
1.8K0
WPF 跨应用程序域的 UI(Cross AppDomain UI)
发布于 2017-11-12 16:36 更新于 2017-11-13 15:04
walterlv
2018/09/18
1.2K0
WPF 跨应用程序域的 UI(Cross AppDomain UI)
[C# 开发技巧]如何防止程序多次运行
最近发现很多人在论坛中问到如何防止程序被多次运行的问题的,如: http://social.msdn.microsoft.com/Forums/zh-CN/6398fb10-ecc2-4c03-ab25-d03544f5fcc9, 所以这里就记录下来,希望给遇到同样问题的朋友有所参考的,同时也是对自己的一个积累。在介绍具体实现代码之前,我们必须明确解决这个问题的思路是什么的?下面只要分享我的一个思考的这个问题的方式:
乔达摩@嘿
2020/09/11
2K0
[C# 开发技巧]如何防止程序多次运行
如何在 ASP.NET MVC 中集成 AngularJS(3)
今天来为大家介绍如何在 ASP.NET MVC 中集成 AngularJS 的最后一部分内容。 调试路由表 - HTML 缓存清除 就在我以为示例应用程序完成之后,我意识到,我必须提供两个版本的路由表:一个运行在调试模式的应用程序下和一个运行在发布模式的应用程序下。在调试模式下,JavaScript 文件在未使用压缩功能的情况下会被下载。如果想要调试并在 JavaScript 控制器中设置断点,这是必须的。事实上,路由表的产生版本也出现了一些挑战,由于产生路由代码使用的是 JavaScript 捆绑,但是在
葡萄城控件
2018/01/10
1.9K0
如何在 ASP.NET MVC 中集成 AngularJS(3)
推荐阅读
WPF 基于 WER 注册应用崩溃无响应回调和重启方法
8370
.NET/C# 获取一个正在运行的进程的命令行参数
2.5K0
制作一个极简的 .NET 客户端应用自安装或自更新程序
2820
.NET 命令行参数包含应用程序路径吗?
5630
C#/.NET基于Topshelf创建Windows服务的守护程序不显示UI界面的问题分析和解决方案
1.4K0
如何在 .NET/C# 代码中安全地结束掉一个控制台应用程序?通过发送 Ctrl+C 信号来结束
1.6K0
C# Windows服务开发
1.3K0
在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)
2.2K0
.NET/C# 阻止屏幕关闭,阻止系统进入睡眠状态
1K0
在 Windows 系统上降低 UAC 权限运行程序(从管理员权限降权到普通用户权限)
2.1K0
如何将应用程序与文件类型(文件扩展名)关联起来
1200
使用 PInvoke.net Visual Studio Extension 辅助编写 Win32 函数签名
1.1K0
将 async/await 异步代码转换为安全的不会死锁的同步代码
1.7K0
如何创建应用程序清单文件 App.Manifest,如何创建不带清单的应用程序
1.4K0
(2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
1.3K1
WPF 使用 AppBar 将窗口停靠在桌面上,让其他程序不占用此窗口的空间(附我封装的附加属性)
1.1K0
如何在 .NET 库的代码中判断当前程序运行在 Debug 下还是 Release 下
1.8K0
WPF 跨应用程序域的 UI(Cross AppDomain UI)
1.2K0
[C# 开发技巧]如何防止程序多次运行
2K0
如何在 ASP.NET MVC 中集成 AngularJS(3)
1.9K0
相关推荐
WPF 基于 WER 注册应用崩溃无响应回调和重启方法
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验