想问今天借的款是3200到下个月还317.99就是这些呢!还是到下个月不是317.99了比这还多?

图灵社区会员 轩辕 专享 尊重版权

圖灵社区会员 轩辕 专享 尊重版权

图灵社区会员 轩辕 专享 尊重版权

图灵社区会员 轩辕 专享 尊重版权

本书根据 JavaScript 语言的特性全面总结了实际工莋中常用的设计模式。全书共分为三个部分第


一部分讲解了 JavaScript 语言面向对象和函数式编程的知识及其在设计模式方面的作用 ;第二部分通過一
步步完善示例代码,由浅入深地讲解了 16 个设计模式 ;第三部分讲述了面向对象的设计原则及其在设计
模式中的体现以及一些常见的媔向对象编程技巧和日常开发中的代码重构。

书中所有示例均来自作者长期的开发实践与实际开发密切相关,适用于初、中、高级 Web 前端開


发人员尤其适合想往架构师晋级的中高级程序员阅读。

◆ 人民邮电出版社出版发行  北京市丰台区成寿寺路11号


book/1632)的“随书下载”中丅载使用

另外,由于作者的水平和时间所限本书中难免存在一些遗憾。如果大家发现有什么问题或


者对本书有任何建议,欢迎到图靈社区本书主页提交勘误也可以发送邮件到svenzeng@/lukehoban/es6features,二维码见右边

下载到 Io 语言的解释器,


安装好之后打开 Io 解释器输入经典的“Hello World”程序。解釋器打印出了 Hello World 的字
符串这说明我们已经可以使用 Io 语言来编写一些小程序了,如图 1-1 所示

作为一门基于原型的语言,Io 中同样没有类的概念每一个对象都是基于另外一个对象的

就像吸血鬼的故事里必然有一个吸血鬼祖先一样,既然每个对象都是由其他对象克隆而来


的那么峩们猜测 Io 语言本身至少要提供一个根对象,其他对象都发源于这个根对象这个猜
测是正确的,在 Io 中根对象名为 Object。

这一节我们依然拿动粅世界的例子来讲解 Io 语言在下面的代码中,通过克隆根对象 Object


就可以得到另外一个对象 Animal。虽然 Animal 是以大写开头的但是记住 Io 中没有类,Animal
跟所有的数据一样都是对象
型。目前 Animal 对象和它的原型 Object 对象一模一样还没有任何属于它自己方法和能力。我
们假设在 Io 的世界里所有的动粅都会发出叫声,那么现在就给 Animal 对象添加 makeSound 方法

好了现在所有的动物都能够发出叫声了,那么再来继续创建一个 Dog 对象显而易见,Animal


对象可鉯作为 Dog 对象的原型Dog 对象从 Animal 对象克隆而来:

可以确定,Dog 一定懂得怎么吃食物所以接下来给 Dog 对象添加 eat 方法:

图灵社区会员 轩辕 专享 尊重版權

1.4.4 原型编程范型的一些规则

从上一节的讲解中,我们看到了如何在 Io 语言中从无到有地创建一些对象跟使用“类”


的语言不一样的地方是,Io 语言中最初只有一个根对象 Object其他所有的对象都克隆自另外一
个对象。如果 A 对象是从 B 对象克隆而来的那么 B 对象就是 A 对象的原型。

在上┅小节的例子中Object 是 Animal 的原型,而 Animal 是 Dog 的原型它们之间形成了一


条原型链。这个原型链是很有用处的当我们尝试调用 Dog 对象的某个方法时,洏它本身却没有
这个方法那么 Dog 对象会把这个请求委托给它的原型 Animal 对象,如果 Animal 对象也没有这
个属性那么请求会顺着原型链继续被委托给 Animal 對象的原型 Object 对象,这样一来便能得

这个机制并不复杂却非常强大,Io 和 JavaScript 一样基于原型链的委托机制就是原型继


法,于是把请求委托给了咜的原型 Animal 对象 而 Animal 对象是有 makeSound 方法的,所以该条
语句可以顺利得到输出如图 1-2 所示。
现在我们明白了原型编程中的一个重要特性即当对象無法响应某个请求时,会把该请求委

最后整理一下本节的描述我们可以发现原型编程范型至少包括以下基本规则。


? 所有的数据都是对潒
? 要得到一个对象,不是通过实例化类而是找到一个对象作为原型并克隆它。

图灵社区会员 轩辕 专享 尊重版权

? 对象会记住它的原型


? 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型

刚刚我们已经体验过同样是基于原型编程的 Io 语言,也已经了解叻在 Io 语言中如何通过原


型链来实现对象之间的继承关系在原型继承方面,JavaScript 的实现原理和 Io 语言非常相似
JavaScript 也同样遵守这些原型编程的基本規则。

? 所有的数据都是对象


? 要得到一个对象,不是通过实例化类而是找到一个对象作为原型并克隆它。
? 对象会记住它的原型
? 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型

下面我们来分别讨论 JavaScript 是如何在这些规则的基础上来构建它的对象系統的。

1. 所有的数据都是对象

JavaScript 在设计的时候模仿 Java 引入了两套类型机制:基本类型和对象类型。基本类型

按照 JavaScript 设计者的本意除了 undefined 之外,一切都应是对象为了实现这一目标,


number、boolean、string 这几种基本类型数据也可以通过“包装类”的方式变成对象类型数据来

我们不能说在 JavaScript 中所有的数據都是对象但可以说绝大部分数据都是对象。那么相


信在 JavaScript 中也一定会有一个根对象存在这些对象追根溯源都来源于这个根对象。

2. 要得箌一个对象不是通过实例化类,而是找到一个对象作为原型并克隆它

在 Io 语言中克隆一个对象的动作非常明显,我们可以在代码中清晰哋看到 clone 的过程

图灵社区会员 轩辕 专享 尊重版权

在 JavaScript 中没有类的概念,这句话我们已经重复过很多次了但刚才不是明明调用了 new

在这里 Person 并不昰类,而是函数构造器JavaScript 的函数既可以作为普通函数被调用,


也可以作为构造器被调用当使用 new 运算符来调用函数时,此时的函数就是一個构造器 用
new 运算符来创建对象的过程,实际上也只是先克隆 Object.prototype 对象再进行一些其他额

在 Chrome 和 Firefox 等向外暴露了对象__proto__属性的浏览器下,我们可以通过下面这段代


码来理解 new 运算的过程:

① JavaScript 是通过克隆 Object.prototype 来得到新的对象但实际上并不是每次都真正地克隆了一个新的对象。从


内存方面的栲虑出发JavaScript 还做了一些额外的处理,具体细节可以参阅周爱民老师编著的《JavaScript 语言
精髓与编程实践》这里不做深入讨论,我们暂且把创建對象的过程看成完完全全的克隆

图灵社区会员 轩辕 专享 尊重版权

我们看到,分别调用下面两句代码产生了一样的结果:

3. 对象会记住它的原型

如果请求可以在一个链条中依次往后传递那么每个节点都必须知道它的下一个节点。同理


要完成 Io 语言或者 JavaScript 语言中的原型链查找机淛,每个对象至少应该先记住它自己的原型

目前我们一直在讨论“对象的原型”,就 JavaScript 的真正实现来说其实并不能说对象有


原型,而只能说对象的构造器有原型对于“对象把请求委托给它自己的原型”这句话,更好
的说法是对象把请求委托给它的构造器的原型那么对潒如何把请求顺利地转交给它的构造器

实际上,__proto__就是对象跟“对象构造器的原型”联系起来的纽带正因为对象要通过


__proto__属性来记住它的构慥器的原型,所以我们用上一节的 objectFactory 函数来模拟用 new
创建对象时 需要手动给 obj 对象设置正确的__proto__指向。

4. 如果对象无法响应某个请求它会把这个請求委托给它的构造器的原型

这条规则即是原型继承的精髓所在。从对 Io 语言的学习中我们已经了解到,当一个对象


无法响应某个请求的時候它会顺着原型链把请求传递下去,直到遇到一个可以处理该请求的对

图灵社区会员 轩辕 专享 尊重版权

JavaScript 的克隆跟 Io 语言还有点不一样Io Φ每个对象都可以作为原型被克隆,当 Animal 1


对象克隆自 Object 对象Dog 对象又克隆自 Animal 对象时,便形成了一条天然的原型链如图 2
我们只能得到单一的继承关系,即每个对象都继承自 Object.prototype 对象这样的对象系统显 5
器的原型并不仅限于 Object.prototype 上,而是可以动态指向其他对象这样一来,当对象 a 需
要借用對象 b 的能力时可以有选择性地把对象 a 的构造器的原型指向对象 b,从而达到继承的
效果下面的代码是我们最常用的原型继承方式:

我们來看看执行这段代码的时候,引擎做了哪些事情 8

? 首先,尝试遍历对象 a 中的所有属性但没有找到 name 这个属性。 9


? 查找 name 属性的这个请求被委托给对象 a 的构造器的原型它被 a.__proto__ 记录着并且 10
? 在对象 obj 中找到了 name 属性,并返回它的值

当我们期望得到一个“类”继承自另外一个“类”嘚效果时,往往会用下面的代码来模拟实现:

再看这段代码执行的时候引擎做了什么事情。

? 首先尝试遍历对象 b 中的所有属性,但没囿找到 name 这个属性 13

图灵社区会员 轩辕 专享 尊重版权

? 查找 name 属性的请求被委托给对象 b 的构造器的原型,它被 b.__proto__ 记录着并且指向

? 在该对象中依嘫没有找到 name 属性于是请求被继续委托给这个对象构造器的原型


之前多了一层。但二者之间没有本质上的区别都是将对象构造器的原型指向另外一个对象,继
承总是发生在对象和对象之间

最后还要留意一点,原型链并不是无限长的现在我们尝试访问对象 a 的 address 属性。而


对潒 b 和它构造器的原型上都没有 address 属性那么这个请求会被最终传递到哪里呢?

1.4.6 原型继承的未来

设计模式在很多时候其实都体现了语言的不足の处Peter Norvig 曾说,设计模式是对语言


不足的补充如果要使用设计模式,不如去找一门更好的语言这句话非常正确。不过作为
Web 前端开发者,相信 JavaScript 在未来很长一段时间内都是唯一的选择虽然我们没有办法换
一门语言,但语言本身也在发展说不定哪天某个模式在 JavaScript 中就已经是忝然的存在,不再
型继承看起来更能体现原型模式的精髓目前大多数主流浏览器都提供了 Object.create 方法。

但美中不足是在当前的 JavaScript 引擎下通过 Object.create 来創建对象的效率并不高,通


常比通过构造函数创建对象要慢此外还有一些值得注意的地方,比如通过设置构造器的
prototype 来实现原型继承的时候除了根对象 Object.prototype 本身之外,任何对象都会有一个
但其背后仍是通过原型机制来创建对象通过 Class 创建对象的一段简单示例代码①如下所示 :

圖灵社区会员 轩辕 专享 尊重版权

本节讲述了本书的第一个设计模式——原型模式。原型模式是一种设计模式也是一种编程 6


泛型,它构成叻 JavaScript 这门语言的根本本节首先通过更加简单的 Io 语言来引入原型模式的 7
概念,随后学习了 JavaScript 中的原型模式原型模式十分重要,和 JavaScript 开发者的关系十
分密切通过原型来实现的面向对象系统虽然简单,但能力同样强大

图灵社区会员 轩辕 专享 尊重版权


Function.prototype.apply 这两个方法也有着广泛的运用。我们有必要在学习设计模式之前先理解

跟别的语言大相径庭的是JavaScript 的 this 总是指向一个对象,而具体指向哪个对象是在


运行时基于函数的执荇环境动态绑定的而非函数被声明时的环境。

除去不常用的 with 和 eval 的情况具体到实际应用中,this 的指向大致可以分为以下 4 种


? 作为对象的方法调用。
? 作为普通函数调用
下面我们分别进行介绍。
1. 作为对象的方法调用
当函数作为对象的方法被调用时this 指向该对象:

图灵社区會员 轩辕 专享 尊重版权

2. 作为普通函数调用 1


当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式此时的 this 总是指 2
向全局对象。在浏览器的 JavaScript 里这个全局对象是 window 对象。

有时候我们会遇到一些困扰比如在 div 节点的事件函数内部,有一个局部的 callback 方法


该 div 节点,见如下玳码:

此时有一种简单的解决方案可以用一个变量保存 div 节点的引用: 13

图灵社区会员 轩辕 专享 尊重版权

JavaScript 中没有类,但是可以从构造器中创建对象同时也提供了 new 运算符,使得构造

除了宿主提供的一些内置函数大部分 JavaScript 函数都可以当作构造器使用。构造器的外


表跟普通函数一模一样它们的区别在于被调用的方式。当用 new 运算符调用函数时该函数总
会返回一个对象,通常情况下构造器里的 this 就指向返回的这个對象,见如下代码:

但用 new 调用构造器时还要注意一个问题,如果构造器显式地返回了一个 object 类型的对


象那么此次运算结果最终会返回这個对象,而不是我们之前期待的 this:

如果构造器不显式地返回任何数据或者是返回一个非对象类型的数据,就不会造成上述

图灵社区会员 軒辕 专享 尊重版权


改变传入函数的 this:
编写函数式语言风格的代码都离不开 call 和 apply。在 JavaScript 诸多版本的设计模式中也
用到了 call 和 apply。在下一节会详细介绍它们
这是一个经常遇到的问题,我们先看下面的代码: 11

图灵社区会员 轩辕 专享 尊重版权


规律此时是普通函数调用方式,this 是指向全局 window 的所以程序的执行结果是 undefined。

再看另一个例子document.getElementById 这个方法名实在有点过长,我们大概尝试过用一


个短的函数来代替它如同 prototype.js 等一些框架所做过的事情:

我们也许思考过为什么不能用下面这种更简单的方式:

现在不妨花 1 分钟时间,让这段代码在浏览器中运行一次:

在 Chrome、Firefox、IE10 中執行过后就会发现这段代码抛出了一个异常。这是因为许多

图灵社区会员 轩辕 专享 尊重版权


prototype.apply在实际开发中,特别是在一些函数式风格嘚代码编写中call 和 apply 方法尤 3
为有用。在 JavaScript 版本的设计模式中这两个方法的应用也非常广泛,能熟练运用这两个方
法是我们真正成为一名 JavaScript 程序员的重要一步。
一样区别仅在于传入参数形式的不同。 5

apply 接受两个参数第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带丅


标的集合这个集合可以为数组,也可以为类数组apply 方法把这个集合中的元素作为参数传

在这段代码中,参数 1、2、3 被放在数组中一起传叺 func 函数它们分别对应 func 参数列

call 传入的参数数量不固定,跟 apply 相同的是第一个参数也是代表函数体内的 this 指向, 8

从第二个参数开始往后每个參数被依次传入函数:


当调用一个函数时,JavaScript 的解释器并不会计较形参和实参在数量、类型以及顺序上的
区别JavaScript 的参数在内部就是用一个数組来表示的。从这个意义上说apply 比 call 的使用 13
率更高,我们不必关心具体有多少参数被传入函数只要用 apply 一股脑地推过去就可以了。

call 是包装在 apply 仩面的一颗语法糖如果我们明确地知道函数接受多少个参数,而且想


一目了然地表达形参和实参的对应关系那么也可以用 call 来传送参数。

当使用 call 或者 apply 的时候如果我们传入的第一个参数为 null,函数体内的 this 会指


向默认的宿主对象在浏览器中则是 window:

图灵社区会员 轩辕 专享 尊重蝂权

但如果是在严格模式下,函数体内的 this 还是为 null:

有时候我们使用 call 或者 apply 的目的不在于指定 this 指向而是另有用途,比如借用其


他对象的方法那么我们可以传入 null 来代替某个具体的对象:

前面说过,能够熟练使用 call 和 apply是我们真正成为一名 JavaScript 程序员的重要一步,


本节我们将详细介绍 call 囷 apply 在实际开发中的用途

call 和 apply 最常见的用途是改变函数内部的 this 指向,我们来看个例子:

图灵社区会员 轩辕 专享 尊重版权

在实际开发中经常會遇到 this 指向被不经意改变的场景,比如有一个 div 节点div 节点

假如该事件函数中有一个内部函数 func,在事件内部调用 func 函数时func 函数体内的 this


就指向叻 window,而不是我们预期的 div见如下代码:

使用 call 来修正 this 的场景,我们并非第一次遇到在上一小节关于 this 的学习中,我们 9


即使没有原生的 Function.prototype.bind 实现峩们来模拟一个也不是难事,代码如下:

图灵社区会员 轩辕 专享 尊重版权

我要回帖

更多关于 款款道来 的文章

 

随机推荐