《JavaScript 模式》读书笔记(6)— 代码复用模式1
我们有开始进入新篇章了。这篇内容主要讲代码复用模式,实际上代码复用,就是继承啊,原型啊,构造函数啊等等这一类的内容。对于前端进阶来说,是很重要的基础知识。这一篇内容会对原型、 继承有很深入的讲解。我也会尽我所能的为大家讲清楚、分析透彻。
代码复用是一个非常重要而且有趣的主题,简而言之,这是由于人们很自然的争取编写尽可能少的代码。尤其是那些具有质量优秀、通过测试、可维护、可扩展性、文档化的可复用代码。
在谈及代码复用的时候,首先想到的是代码的继承性(inheritance),而本章中大部分也专门致力于代码复用这个主题。在这里可以看到多种方法都可以实现“基于类特性的(classical)”和“非基于类特性的(nonclassical)”继承特性。但重要的是要记住其最终目标,我们要复用代码。继承性就是程序员用以实现代码复用这个目标的一种方法或手段,而且它也并不是唯一的方法。在本章中,可以看到如何利用其他对象组合成所需的对象,也可以看到如何使用mix-in技术(混入或者渗元技术),还可以看到如何在技术上没有永久继承的情况下仅借用和复用所需的功能。
当开始接触代码复用任务时,请记住GoF(Gang of Four,指《Design Patterns》的四位作者)等人在其著作中提出的有关创建对象的建议原则“优先使用对象组合,而不是类继承”。
一、传统与现代继承模式的比较
经常会在有关JavaScript继承模式的主题讨论中听到术语“传统继承(classical inheritance)”,为此,让我们先阐明“传统(classical)”所代表的意义。该术语从词义上来说,并不是与用于古董、历史沉积、或者广为接受并认定为正确的处理事务的方法这些词义相同。实际上,该术语只是单词“Class(类)”的一种表现形式。
许多编程语言都具有类的概念,并以此作为对象的蓝图。在那些编程语言中,每个对象都是一个类的特定实例(比如,Java语言环境中),并且在不存在某个类的时候并不能创建该类的对象。在JavaScript中,由于没有类的概念,因此实例的概念也就没有多大的意义。JavaScript中的对象是简单的键-值(key-value)对,可以动态的创建和修改这些对象。
但JavaScript具有构造函数,并且new操作符的语法与那些使用类的编程语言在语法上有许多相似之处。
在Java中可以采用下列方式创建对象:
Person adam = new Person();
而在JavaScript中则可以采用下列方式创建对象:
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()的一个例子:
// 父构造函数
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()的第一种用法:
function inherit(C,P) {
C.prototype = new P();
}
重要的是需要记住,原型的属性应该指向一个对象,而不是一个函数,因此它必须指向一个由父构造函数所创建的实例(一个对象),而不是指向构造函数本身。也就是说,要注意使用new操作符来创建新对象,因为需要new才能使用这种模式。
在以后的程序中,当使用new Child()语句创建一个对象时,它会通过原型从Parent()实例中获取它的功能,如下所示:
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”。
最后,让我们更进一步查看原型链的概念,比如说,我们有以下这样的代码:
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()函数的问题在于它并不支持将参数传递到子构造函数中,而子构造函数然后又将参数传递到父构造函数中,考虑以下这个例子:
var s = new Child('Seth');
s.say(); // 输出“Adam”
以上的输出结果可能并不是您所期望的。虽然子构造函数可以将参数传递到父构造函数中,但是那样的话,在每次需要一个新的子对象时都必须重新执行这种继承机制,而且该机制的效率时很低的,其原因在于最终会反复的重新创建父对象。
《JavaScript 模式》读书笔记(6)— 代码复用模式1的更多相关文章
- 《JavaScript 模式》读书笔记(6)— 代码复用模式2
上一篇讲了最简单的代码复用模式,也是最基础的,我们普遍知道的继承模式,但是这种继承模式却有不少缺点,我们下面再看看其它可以实现继承的模式. 四.类式继承模式#2——借用构造函数 本模式解决了从子构造函 ...
- 《JavaScript模式》第6章 代码复用模式
@by Ruth92(转载请注明出处) 第6章:代码复用模式 GoF 在其著作中提出的有关创建对象的建议原则: -- 优先使用对象组合,而不是类继承. 传统模式:使用类继承: 现代模式:"类 ...
- javascript代码复用模式(二)
前面说到,javascript的代码复用模式,可分为类式继承和非类式继承(现代继承).这篇就继续类式继承. 类式继承模式-借用构造函数 使用借用构造函数的方法,可以从子构造函数得到父构造函数传任意数量 ...
- 深入理解JavaScript系列(46):代码复用模式(推荐篇)
介绍 本文介绍的四种代码复用模式都是最佳实践,推荐大家在编程的过程中使用. 模式1:原型继承 原型继承是让父对象作为子对象的原型,从而达到继承的目的: function object(o) { fun ...
- 《你不知道的javascript》读书笔记2
概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. 这篇笔记是这本书的下半部分,上半部分请见<你不知道的java ...
- 《编写可维护的javascript》读书笔记(中)——编程实践
上篇读书笔记系列之:<编写可维护的javascript>读书笔记(上) 上篇说的是编程风格,记录的都是最重要的点,不讲废话,写的比较简洁,而本篇将加入一些实例,因为那样比较容易说明问题. ...
- javascript代码复用模式
代码复用有一个著名的原则,是GoF提出的:优先使用对象组合,而不是类继承.在javascript中,并没有类的概念,所以代码的复用,也并不局限于类式继承.javascript中创建对象的方法很多,有构 ...
- 深入理解JavaScript系列(45):代码复用模式(避免篇)
介绍 任何编程都提出代码复用,否则话每次开发一个新程序或者写一个新功能都要全新编写的话,那就歇菜了,但是代码复用也是有好要坏,接下来的两篇文章我们将针对代码复用来进行讨论,第一篇文避免篇,指的是要尽量 ...
- SQL反模式读书笔记思维导图
在写SQL过程以及设计数据表的过程中,我们经常会走一些弯路,会做一些错误的设计.<SQL反模式>这本书针对这些经常容易出错的设计模式进行分析,解释了错误的理由.允许错误的场景,并给出更好的 ...
- 《图解设计模式》读书笔记2-1 Template Method模式
目录 模板方法模式 类图 思想: 模板方法模式 在父类中定义流程,在子类中实现具体的方法. 类图 代码 //抽象类 public abstract class AbstractDisplay { pu ...
随机推荐
- Parquet.Net: 将 Apache Parquet 移植到 .NET
Parquet.Net 是一个用于读取和写入 Apache Parquet 文件的纯 .NET 库,使用MIT协议开源,github仓库:https://github.com/aloneguid/pa ...
- 【赵渝强老师】MySQL高可用架构:MHA
MHA(Master HA)是一款开源的 MySQL 的高可用程序,它为 MySQL 主从复制架构提供了 automating master failover 功能.MHA 在监控到 master 节 ...
- 【Wing】背后的插件们
wing 作为我们日常开发的命令行开发工具,项目开源以来,陆陆续续接入了多个插件,在这里集中分享给大家. ☞ Github ☜ ☞ Gitee ☜ 01. wing -screen 作为Android ...
- 为Linux安装NVIDIA显卡驱动
Linux如何安装NVIDIA驱动 我使用的是Debian系列的Kali Linux,X11,Gnome,4060 我想要安装的是NVIDIA自家的独显驱动程序 下文展示的是为Debian系列安装NV ...
- Nuxt3+PM2集群模式启动及勘误
起因 之前写过一篇 Nuxt3 的文章,Nuxt3 环境变量配置,用到了 PM2,但是里面的一些配置存在问题,最近有空又验证了一下,这里做一个勘误. 问题 PM2 的启动配置中有一项是exec_mod ...
- Redisson 工作原理-源码分析
时间不在于你拥有多少,而在于你怎样使用. 1:Redisson 是什么 个人理解:一种 可重入.持续阻塞.独占式的 分布式锁协调框架,可从 ReentrantLock 去看它. ①:可重入锁 拿到锁的 ...
- DRF-Version组件源码分析
1. 版本管理组件源码分析 注意点: 不同的versioning_class区别:实例化后得到的对象versioning_scheme里面的方法不同(函数同名,但是处理逻辑不同) def determ ...
- WPF学习-布局
1. Grid布局 ,(Table 布局) 两行两列布局, Border 0 行 0 列默认开始 <Window x:Class="WpfApp.MainWindow" ...
- 从0.1开始学java(2)
string串池 现在都在堆中 "=="号的比较 字符串比较一般调用equals或者equalsignoreCase API来比较(后者忽略大小写) StringBuilder 可 ...
- 1.Kubernetes简介
Kubernetes简介 来源 bilibili尚硅谷K8S视频:https://www.bilibili.com/video/BV1GT4y1A756 中文官网:https://kubernetes ...