JavaScript设计模式(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-18 09:21:43

点击下载

作者:张容铭

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

JavaScript设计模式

JavaScript设计模式试读:

前言

一年前如果有人问我是否会写一本书的话,我会直接而坦诚地说我不会。不过随着团队开发中,对同事编写的代码的阅读中我发现,有的人写的代码难懂且臃肿,很难继续编写下去:有的人写的代码简明而灵活,即使再多的需求也很容易实现。我一直在思考,为何为同一需求编写的代码会有这么大的差别?

随着团队项目的开发,我发现,当对类似的需求以类似的模板去解决时,开发成本会减少许多,而且他人也会轻松介入项目的开发。这样,按照同样的流程去解决问题,开发效率得以提高。而将这些解决问题的模板提炼出来,会发现复杂的问题也会简单许多,书写的代码思路清晰且结构简明,这些模板是一种解决问题的方式,或者说是一种模式。

复杂的问题可以分解成一个个小的模块,然后像拼图一样将这些通过模式解决的模块拼凑成一个完整的需求。同时可将余下的精力用去研究其他烦琐问题的解决模式。积攒的模式越多,在工作中以这些模式来解决问题,工作效率就越高。

于是我将这些模式总结出来,编写成一本JavaScript设计模式书。考虑到对技术的探讨有时是很枯燥的,为了降低读者的学习难度,不至于在阅读中出现倦意,我把工作实战中的角色引入书中,通过他们工作中的情境故事来表达每种模式的内涵与应用,也借此希望更多的人读懂,并领悟更多的设计模式,以便应用到自己的项目中。

目标读者

本书不是一本入门级别的书,本书适用于希望将自身JavaScript经验技巧提升一个层次的读者,所以,本书对JavaScript基础知识点,如数据类型、运算符、语句等未进行讨论。本书将面向如下4类读者。

第一类读者有点JavaScript基础,想要更深入地学习JavaScript,并成为一名标准Web开发人员或者前端工程师。想深入了解面向对象编程思想,并且提高自己模块化开发能力,写出可维护、高效率、可拓展的代码的程序员。

第二类读者主要是以前从事Java或者C++等编程语言的程序员,现如今想转行从事前端开发,他们可能对于JavaScript这种语言比较陌生,但是对于面向对象思想以及设计模式了解较多,只是对于将这些思想运用于前端不是十分清楚,因此,通过阅读本书,对于他们实现前端编程开发很有意义。

第三类读者主要是对设计模式感兴趣,并且想更多了解设计模式在JavaScript高效应用的研发人员。通过阅读本书,他们可以体会JavaScript中设计模式的实现,突破以往面向对象语言中的实现,用更具灵活的方式解决问题。

第四类读者主要是那些从事前端开发的专业人员。他们能熟练应用JavaScript开发,但是还希望提升自己,使自己在团队开发中更具有价值。阅读本书后,他们可以更深入地了解面向对象编程,掌握各种设计模式,使自己的编程技术更灵活,他们会懂得在何种情况下使用那种设计模式解决问题效果会更佳。因此,他们可以自由而熟练地运用设计模式重构现有的代码,使其更灵活、高效、可拓展,即使出现复杂的问题也会编写出一目了然、结构清晰的代码。

诚然会有一些不了解JavaScript以及设计模式的读者。他们可能很难看懂书中示例的代码。因此,本书用一种更通俗易懂的方法编写,力求深入浅出,尽可能让更多不同层面的读者理解。

本书特色

本书突破以往填鸭式著书风格,以生动有趣的故事情节推出一个个精彩的设计模式实践。文中以大学刚毕业的小白同学的编程工作经历为主线,在阅读时可以跟着他的经历来学习这些设计模式的具体应用。对于每种模式我们首先提出该模式的定义,这也就声明了该模式的用途。随后交代应用背景,这往往就是该种设计模式的某种应用场境。随着故事的演进,小白所经历的往往是读者在项目中所经历的,因此,很有可能会遇到小白所遇到的问题,这也正是我们需要学习的地方。最后,通过项目经理、小铭等人的帮助使小白顺利地解决一道道难题,从而使小白从初学者一步步进入了工程师的角色。

本书内容

本书分为6篇,共40章。第一篇主要讲述JavaScript面向对象编程基础知识,章节之间知识点连贯,因此,建议读者顺序阅读,并且该篇也是后续5篇的基础,因此,一定要按顺序阅读。后5篇则是讲述各个设计模式,因此,读者可以根据自己的兴趣选择性阅读。但5篇各自侧重点不同,第二篇主要讲述创建型模式,第三篇主要讲述结构型模式,第四篇主要讲述行为型模式,第五篇主要讲述技巧型模式,第六篇主要讲述架构型模式。

第一篇包括第1章和第2章。

第1章介绍JavaScript基础知识,讨论了几种函数编写方式,让读者体会JavaScript在编程中的灵活性。

第2章介绍面向对象编程,讨论了类的创建、数据的封装以及类之间的继承。

第二篇包括第3章到第8章。

第3章介绍简单工厂模式,讨论了对象创建的几种方式。

第4章介绍工厂方法模式,讨论了创建多类对象以及一种安全的创建方式。

第5章介绍抽象工厂模式,讨论了抽象类以及如何定义一种类簇。

第6章介绍建造者模式,讨论了如何更灵活地创建一种复杂的对象。

第7章介绍原型模式,讨论了JavaScript的核心继承方式——原型式继承。

第8章介绍单例模式,讨论了单例对象及其实现与用途。

第三篇包括第9章到第15章。

第9章介绍外观模式,讨论了如何通过外观模式简化接口的使用。

第10章介绍适配器模式,讨论了几种用途的适配器。

第11章介绍代理模式,讨论了代理思想对于跨域的解决方案。

第12章介绍装饰者模式,讨论了装饰者模式更友好地对于己有功能的拓展。

第13章介绍桥接模式,讨论了桥接模式解决对象之间的依赖。

第14章介绍组合模式,讨论了组合模式如何优化系统的可拓展性。

第15章介绍享元模式,讨论了享元模式如何优化系统、提高性能。

第四篇包括第16章到第26章。

第16章介绍模板方法模式,讨论了基于模板类的拓展与创建。

第17章介绍观察者模式,讨论了观察者模式解决团队开发中的模块间通信的实践。

第18章介绍状态模式,讨论了状态模式中状态在交互中的保存与执行。

第19章介绍策略模式,讨论了策略模式如何丰富交互算法。

第20章介绍职责链模式,讨论了如何实现一个需求链。

第21章介绍命令模式,讨论了如何定义命令集合及运用。

第22章介绍访问者模式,讨论了借助己有对象解决己有问题。

第23章介绍中介者模式,讨论了中介者如何管理对象之间的通信交互。

第24章介绍备忘录模式,讨论了如何更好地处理数据缓存问题。

第25章介绍迭代器模式,讨论了迭代器的易用性及其对性能的优化。

第26章介绍解释器模式,讨论了通过解释器解决规定的需求。

第五篇包括第27章到第34章。

第27章介绍链模式,讨论了一种高效的方法调用模式。

第28章介绍委托模式,讨论了事件委托对性能的优化。

第29章介绍数据访问对象模式,讨论了数据访问对象模式对数据库操作对象的封装。

第30章介绍节流模式,讨论了如何优化页面中的高频事件以及交互动画。

第31章介绍简单模板模式,讨论了一种新的生成页面视图的方法。

第32章介绍惰性模式,讨论了对方法的加载以及执行的优化。

第33章介绍参与者模式,讨论了一种宽松地为对象绑定方法的方式。

第34章介绍等待者模式,讨论了对于异步执行方法回调函数的处理。

第六篇包括第35章到第40章。

第35章介绍同步模块模式,讨论了如何模块化封装代码。

第36章介绍异步模块模式,讨论了一种更适合的前端模块化开发实践方式。

第37章介绍Widget模式,讨论了当今流行的组件式开发,并实现了一个简单的模板引擎。

第38章介绍MVC模式,讨论了如何实现对数据、视图、控制器的分离。

第39章介绍MVP模式,讨论了如何解决数据与视图之间的耦合,并实现了一个模板生成器。

第40章介绍MVVM模式,讨论了双向绑定对MVC的模式演化。

本书约定

感谢

设计模式是工作经验的结晶,如此多的模式是我一个人无法做到的,因此,本书取得的成果是在前人工作经验总结的基础上提出的。能够完成本书需要感谢太多太多的人。

从百度空间,到百度首页,从百度翻译,再到百度图片搜索,期间经历了太多的团队,得到了太多同事的帮助,因此要感谢每一个人。感谢慧总、冬叔、璇姐、辉哥。感谢慧总让我加入百度工作:感谢冬叔对我工作的支持:感谢璇姐对我的关怀与帮助,让我们的团队气氛无比融洽:感谢辉哥为我提供百度的图搜工作机会,你的决策使我更加坚定前端的工作。

百度新首页的重构是我工作中经历的最难忘的一段日子,感谢大家给予的帮助,感谢坤哥、周全、鹏飞、锡月、王晨、亚斌、研婷。

除了自己所在团队同事给予的帮助,还要感谢很多帮助过我的人,他们是王潇、尚实、先烈、王凯、冯振兴等。

感谢我的新团队,王群、尊程、晓晨、乔岳、腾飞、茗名、李毅、佳佳、阳阳、潇潇、琳琳、胜敏、王敏。有你们在,工作和生活变得如此融洽。

感谢我的老师,刘嘉敏老师,是你让我认知了计算机世界。

本书能够出版最该感谢的就是人民邮电出版社,感谢我们这次融洽的合作,尤其要感谢张祷编辑,没有你对我的支持与帮助,本书可能不会这么顺利地出版,你是一名专业的编辑。当然还要感谢本书默默无闻的编辑,你们辛苦的审校才使本书顺利地出版。

最后,在此感谢我的家人,你们培养了我,从一个对计算机一无所知的孩子,到如今的一名工程师,感谢你们,感谢你们对我的忖出。你们虽然对我的工作不是很了解,但每天依旧是那么关心、支持着我。希望你们每天健康而开心地生活。

本书读者答疑QQ群为:471118627。

编辑联系邮箱:zhangtao@ptpress.com.cn。第一篇面向对象编程

面向对象编程(Object-oriented programming,OOP)是一种程序设计范型。它将对象作为程序的基本单元,将程序和数据封装其中,以提高程序的重用性、灵活性和扩展性。

第1章 灵活的语言——JavaScript

第2章 写的都是看到的——面向对象编程第1章灵活的语言——JavaScript

结束了4年的大学学习生活,小白信心满满地来到应聘的M公司。今天是入职的第一天,项目经理分下来一个验证表单功能的任务,内容不多,仅需要验证用户名、邮箱、密码等。1.1 入职第一天

小白接到需求看了看,感觉很简单,于是便写下几个函数。 function checkName(){ // 验证姓名 } function checkEmail(){ // 验证邮箱 } function checkPassword(){ // 验证密码 }

……

于是要把自己的代码提交到团队项目里。

正在此时,一位工作多年的程序员小铭看到小白要提交的代码摇了摇头说:“小白,等一下,先不要提交。”“怎么了?”“你创建了很多全局变量呀。”“变量?我只是写了几个函数而己。”“函数不是变量么?”小铭反问道。

此时小白不知所措,心想:“难道函数是变量?”脸瞬间沉了下来。1.2 函数的另一种形式

小铭见此情形忙笑着说:“别着急,你看,如果我这么声明几个变量来实现你的功能你看可以么?” var checkName = function(){ // 验证姓名 } var checkEmail = function(){ // 验证邮箱 } var checkPassword = function(){ // 验证密码 }“一样的,只不过……”“对,只不过这个在用的时候要提前声明,但是这么看你就会发现你创建了3个函数保存在变量里来实现你的功能,而你写的是将你的变量名放在function后面而己,它也代表了你的变量。所以说你也声明了3个全局变量。”“这有什么问题呢?”“从功能上讲当然没问题,但是今天你加入了我们的团队,在团队开发中你所写的代码就不能只考虑自己了,也要考虑不影响到他人,如果别人也定义了同样的方法就会覆盖掉原有的功能了。如果你定义了很多方法,这种相互覆盖的问题是很不容易察觉到的。”“那我应该如何避免呢?”小白问道。“你可以将它们放在一个变量里保存,这样就可减少覆盖或被覆盖的风险,当然一旦被覆盖,所有的功能都会失效,这种现象也是很明显的,你自然也会很轻易觉察到。”“可是我该如何做呢?”小白迫不及待地追问道。1.3 用对象收编变量“一猜你就会问。”“好吧,请你先简单地说一下。”“对象你知道吧,它有属性和方法,而如果我们要访问它的属性或者方法时,可通过点语法向下遍历查询得到。我们可以创建一个检测对象,然后把我们的方法放在里面。” var checkobject = { checkName : function(){ // 验证姓名 }, checkEmail : function(){ // 验证邮箱 }, checkPassword : function(){ // 验证密码 } }“此时我们将所有的函数作为CheckObject对象的方法,这样我们就只有一个对象,而我们要想使用它们也很简单,比如检测姓名 CheckObject.checkName(),只是在我们原来使用的函数式前面多了一个对象名称。”“哦,这样呀,但是我们既然可以通过点语法来使用方法,我们是不是也可以这么创建呢?”1.4 对象的另一种形式“当然,不过首先你要声明一个对象,然后给它添加方法,当然在JavaScript中函数也是对象,所以你可以这么做:” var checkobject = function(){}; checkobject.checkName = function(){ // 验证姓名 } checkobject.checkEmail = function(){ // 验证邮箱 } checkobject.checkPassword = function(){ // 验证密码 }“使用和前面的方式是一样的,比如CheckObject.checkName(),”小铭接着说,“现在虽然能满足你的需求,但当别人想用你写的对象方法时就有些麻烦了,因为这个对象不能复制一份,或者说这个对象类在用new关键字创建新的对象时,新创建的对象是不能继承这些方法的。”“但是复制又有什么用呢?”小白不解地问道。“给你举个例子吧,假如你喜欢设计模式,你买了这本书,然后回去你的小伙伴看见了,感觉很有用,他们也想要怎么办?书就这一本。但如果你买的是一台打印机,那么好吧,即使你的小伙伴再多,你也有能力给他们每个人打印一本。”“哦,有些明白了,但是我该如何做到呢?”1.5 真假对象

小铭解释说:“如果你想简单地复制一下,你可以将这些方法放在一个函数对象中。”于是小铭将代码写下。 var checkobject = function(){ return { checkName : function(){ // 验证姓名 }, checkEmail : function(){ // 验证邮箱 }, checkPassword : function(){ // 验证密码 } } }

小白看了看代码,思考一下说:“哦,你写的看上去是,当每次调用这个函数的时候,把我们之前写的那个对象返回出来,当别人每次调用这个函数时都返回了一个新对象,这样执行过程中明面上是CheckObject 对象,可实际上是返回的新对象。这样每个人在使用时就互不影响了。比如想检测邮箱可以像这样吧。” var a = checkobject (); a.checkEmail ();1.6 类也可以“嗯,对”小铭接着说,“虽然通过创建了新对象完成了我们的需求,但是他不是一个真正意义上类的创建方式,并且创建的对象a和对象CheckObject没有任何关系(返回出来的对象本身就与CheckObject对象无关),所以我们还要对其稍加改造一下。” var checkobject = function(){ this.checkName = function(){ // 验证姓名 } this.checkEmail = function(){ // 验证邮箱 } this.checkPassword = function(){ // 验证密码 } }“像上面这样的对象就可以看成类了。”小铭继续说。“那么我们使用它还像之前那样创建对象的方法创建么?”小白追问道。“不,既然是一个类,你就要用关键字new来创建了。” var a = new checkobject(); a.checkEmail();“这样你就可以用CheckObject类创建出来的对象了。”“如果我和我的小伙伴们都对类实例化了(用类创建对象),那么我们每个人都会有一套属于自己的方法吧。”小白不解地问道。1.7 一个检测类“当然,你看,我们是把所有的方法放在函数内部了,通过this定义的,所以每一次通过new关键字创建新对象的时候,新创建的对象都会对类的this上的属性进行复制。所以这些新创建的对象都会有自己的一套方法,然而有时候这么做造成的消耗是很奢侈的,我们需要处理一下。” var checkobject = function(){}; checkobject.prototype.checkName = function(){ // 验证姓名 } checkobject.prototype.checkEmail = function(){ // 验证邮箱 } checkobject.prototype.checkPassword = function(){ // 验证密码 }“这样创建对象实例的时候,创建出来的对象所拥有的方法就都是一个了,因为它们都要依赖prototype原型依次寻找,而找到的方法都是同一个,它们都绑定在CheckObject对象类的原型上,”小铭继续说,“这种方式我们要将prototype写很多遍,所以你也可以这样做。” var checkobject = function(){}; checkobject.prototype = { checkName : function(){ // 验证姓名 }, checkEmail : function(){ // 验证邮箱 }, checkPassword : function(){ // 验证密码 } }“但有一点你要记住,这两种方式不能混着用,否则一旦混用,如在后面为对象的原型对象赋值新对象时,那么它将会覆盖掉之前对prototype对象赋值的方法。”小铭补充说。“知道了,不过我们要使用这种方式定义的类是不是要像下面这样呢?”小白问道。 var a = new checkobject(); a.checkName(); a.checkEmail(); a.checkPassword();1.8 方法还可以这样用“没错,但是你发现没,你调用了3个方法,但是你对对象a书写了3遍。这是可以避免的,那就要在你声明的每一个方法末尾处将当前对象返回,在JavaScript中this指向的就是当前对象,所以你可以将它返回。例如我们开始写的第一个对象还记得么?改动它很简单,像下面这样就可以。” var checkobject = { checkName : function(){ // 验证姓名 return this; }, checkEmail : function(){ // 验证邮箱 return this; }, checkPassword : function(){ // 验证密码 return this; } }“此时我们要想使用他就可以这样:” checkobject.checkName().checkEmail().checkPassword();“当然同样的方式还可以放到类的原型对象中。” var checkobject = function(){}; checkobject.prototype = { checkName : function(){ // 验证姓名 return this; }, checkEmail : function(){ // 验证邮箱 return this; }, checkPassword : function(){ // 验证密码 return this; } }“但使用时候也要先创建一下:” var a = new checkobject(); a.checkName().checkEmail().checkPassword();1.9 函数的祖先

小白回顾着这些从未见过的代码方式内心很激动,小铭见小白对JavaScript如此着迷,于是补充了两句。“如果你看过prototype.js的代码,我想你会想到下面的书写方式。”“prototype.js是什么?”小白问道。“一款JavaScript框架,里面为我们方便地封装了很多方法,它最大的特点就是对源生对象(JavaScript语言为我们提供的对象类,如Function、Array、Object等等)的拓展,比如你想给每一个函数都添加一个检测邮箱的方法就可以这么做。” Function.prototype.checkEmail = function(){ // 验证邮箱 }“这样你在使用这个方法的时候就比较方便了,如果你习惯函数形式,那么你可以这么做。” var f = function(){}; f.checkEmail();“如果你习惯类的形式你也可以这么做。” var f = new Function(); f.checkEmail();“但是你这么做在我们这里是不允许的,因为你污染了原生对象 Function,所以别人创建的函数也会被你创建的函数所污染,造成不必要的开销,但你可以抽象出一个统一添加方法的功能方法。” Function.prototype.addMethod = function(name, fn){ this[name] = fn; }“这样如果你想添加邮箱验证和姓名验证方法你可以这样做。” var methods = function(){};

或者 var methods = new Function(); methods.addMethod('checkName', function(){ // 验证姓名 }); methods.addMethod('checkEmail', function(){ // 验证邮箱 }); methods.checkName(); methods.checkEmail();1.10 可以链式添加吗“呀,这种方式很奇特呀。不过我想链式添加方法,是不是在addMethod中将this返回就可以呀,这么做可以么?” Function.prototype.addMethod = function(name, fn){ this[name] = fn; return this; }“当然,所以你再想添加方法就可以这样了:” var methods = function(){}; methods.addMethod('checkName', function(){ // 验证姓名 }).addMethod('checkEmail', function(){ // 验证邮箱 });“那么,小白,我问你,我如果想链式使用你知道该如何做么?”

小白想了想说:“既然添加方法的时候可以将this返回实现,那么添加的每个方法将this返回是不是可以实现呢?”

于是小白这么写下: var methods = function(){}; methods.addMethod('checkName', function(){ // 验证姓名 return this; }).addMethod('checkEmail', function(){ // 验证邮箱 return this; });

然后测试一下: methods.checkName().checkEmail();“真的可以呀!”小白兴奋地说。1.11 换一种方式使用方法“可是在你测试的时候,你用的是函数式调用方式?对于习惯于类式调用方式的同学来说,他们可以这样简单更改一下。” Function.prototype.addMethod = function(name, fn){ this.prototype[name] = fn; }“此时我们还按照上一种方式添加方法。” var Methods = function(){}; methods.addMethod('checkName', function(){ // 验证姓名 }).addMethod('checkEmail', function(){ // 验证邮箱 });“但是我们在使用的时候要注意了,不能直接使用,要通过new关键字来创建新对象了。” var m = new Methods(); m.checkEmail()

小白兴奋地看着这一行行的代码情不自禁地叫了一声“这正是一种艺术”。

小铭笑着说:“JavaScript是一种灵活的语言,当然函数在其中扮演着一等公民。所以使用JavaScript,你可以编写出更多优雅的艺术代码。”下章剧透

在欢乐的学习中小白的第一天工作结束了,兴奋、痴迷、感慨。明天小白将去看看同事们丰富的编程世界。在那时小白将领略封装、继承的魅力。忆之获

小白工作第一天的故事结束,通过对小白与小铭对函数的多样化创建与使用,我们对JavaScript这门语言有了新的认识,“灵活性”是这门语言特有的气质,不同的人可以写出不同风格的代码,这是JavaScript给予我们的财富,不过我们要在团队开发中慎重挥霍,尽量保证团队开发代码风格的一致性,这也是团队代码易开发、可维护以及代码规范的必然要求。我问你答

真假对象一节中如何实现方法的链式调用呢?

试着定义一个可以为函数添加多个方法的addMethod方法。

试着定义一个既可为函数原型添加方法又可为其自身添加方法的addMethod方法。第2章写的都是看到的——面向对象编程

第一天的经历使小白深深认识到校园学到的知识与实际工作中的偏差,所以想见识见识公司团队里大家都是如何书写代码并完成需求的。早晨走进公司的时候恰巧遇见了项目经理。2.1 两种编程风格——面向过程与面向对象“早!小白,今天是你来的第二天,这一周你熟悉一下我们团队的项目吧。”项目经理对小白说。“好呀,项目经理,我也正想跟大家学习学习呢。”于是项目经理带着小白将项目中的代码下载下来。可小白打开一看傻眼了:“函数,昨天探讨的函数呢?”小白想了半天还是没找到自己以前熟悉的代码。于是走去问小铭:“为何大家在解决需求时都不按照需求规定的功能写函数呢?怎么都是一个一个对象呢?”“函数?对象?看来你还是习惯于按照传统流程编写一个一个函数来解决需求的方式。昨天跟你说过,那样做不利于团队开发,比如你昨天写的3个对输入框中输入的数据校验功能方法,用了3个函数,这是一种面向过程的实现方式,然而在这种方式中,你会发现无端地在页面中添加了很多全局变量,而且不利于别人重复使用。一旦别人使用以前提供的方法,你就不能轻易地去修改这些方法,这不利于团队代码维护。因此你现在要接受咱们团队这边的编程风格——面向对象编程。”“面向对象编程?我不太理解,你可以跟我说一说么?”小白问道。“面向对象编程就是将你的需求抽象成一个对象,然后针对这个对象分析其特征(属性)与动作(方法)。这个对象我们称之为类。面向对象编程思想其中有一个特点就是封装,就是说把你需要的功能放在一个对象里。比如你大学毕业你来公司携带的行李物品没有一件一件拿过来,而是要将他们放在一个旅行箱里,这样不论携带还是管理都会更方便一些。遗憾的是对于JavaScript这种解释性的弱类型语言没有经典强类型语言中那种通过class等关键字实现的类的封装方式,JavaScript 中都是通过一些特性模仿实现的,但这也带来了极高的灵活性,让我们编写的代码更自由。”2.2 包装明星——封装

2.2.1 创建—个类“在JavaScript中创建一个类很容易,首先声明一个函数保存在一个变量里。按编程习惯一般将这个代表类的变量名首字母大写。然后在这个函数(类)的内部通过对this(函数内部自带的一个变量,用于指向当前这个对象)变量添加属性或者方法来实现对类添加属性或者方法,例如:” var Book = function(id, bookname, price){ this.id = id; this.bookname= bookname; this.price = price; }“也可以通过在类的原型(类也是一个对象,所以也有原型prototype)上添加属性和方法,有两种方式,一种是一一为原型对象属性赋值,另一种则是将一个对象赋值给类的原型对象。但这两种不要混用。例如:” Book.prototype.display = function(){ // 展示这本书 };

或者 Book.prototype = { display : function(){} };“这样我们将所需要的方法和属性都封装在我们抽象的Book类里面了,当使用功能方法时,我们不能直接使用这个Book类,需要用new关键字来实例化(创建)新的对象。使用实例化对象的属性或者方法时,可以通过点语法访问,例如:” var book = new Book(10, 'Javascript设计模式', 50); console.log(book.bookname) // Javascript设计模式

小白看了看对类添加的属性和方法部分,感觉不是很理解,于是问:“通过this添加的属性和方法同在prototype中添加的属性和方法有什么区别呀?”“通过this添加的属性、方法是在当前对象上添加的,然而 JavaScript 是一种基于原型prototype的语言,所以每创建一个对象时(当然在JavaScript中函数也是一种对象),它都有一个原型prototype用于指向其继承的属性、方法。这样通过prototype继承的方法并不是对象自身的,所以在使用这些方法时,需要通过 prototype 一级一级查找来得到。这样你会发现通过this定义的属性或者方法是该对象自身拥有的,所以我们每次通过类创建一个新对象时,this指向的属性和方法都会得到相应的创建,而通过 prototype 继承的属性或者方法是每个对象通过 prototype 访问到,所以我们每次通过类创建一个新对象时这些属性和方法不会再次创建。(如图2-1所示)。”“哦,对了,解析图中的constructor又是指的什么呀。”“constructor 是一个属性,当创建一个函数或者对象时都会为其创建一个原型对象prototype,在 prototype 对象中又会像函数中创建 this 一样创建一个 constructor 属性,那么constructor属性指向的就是拥有整个原型对象的函数或对象,例如在本例中Book prototype中的constructor属性指向的就是Book类对象。”▲图2-1 原型对象prototype

2.2.2 这些都是我的——属性与方法封装“原来是这样,”小白似乎明白些,“面向对象思想在学校里也学过,说的就是对一些属性方法的隐藏与暴露,比如私有属性、私有方法、共有属性、共有方法、保护方法等等,那么JavaScript中也有这些么?”“你能想到这些很好。说明你有一定面向对象的基础了。不过你说的这些在 JavaScript 中没有显性的存在,但是我们可以通过一些灵活的技巧来实现它。”小铭继续解释说,“面向对象思想你可以想象成一个人,比如一位明星为了在社会中保持一个良好形象,她就会将一些隐私隐藏在心里,然而对于这位明星,她的家人认识她,所以会了解一些关于她的事情。外界的人不认识她,即使外界人通过某种途径认识她也仅仅了解一些她暴露出来的事情,不会了解她的隐私。如果想了解更多关于她的事情怎么办?对,还可以通过她的家人来了解,但是这位明星自己内心深处的隐私是永远不会被别人知道的。”“那么在JavaScript中又是如何实现的呢?”小白问。“由于JavaScript的函数级作用域,声明在函数内部的变量以及方法在外界是访问不到的,通过此特性即可创建类的私有变量以及私有方法。然而在函数内部通过 this 创建的属性和方法,在类创建对象时,每个对象自身都拥有一份并且可以在外部访问到。因此通过this创建的属性可看作是对象共有属性和对象共有方法,而通过this创建的方法,不但可以访问这些对象的共有属性与共有方法,而且还能访问到类(创建时)或对象自身的私有属性和私有方法,由于这些方法权利比较大,所以我们又将它看作特权方法。在对象创建时通过使用这些特权方法我们可以初始化实例对象的一些属性,因此这些在创建对象时调用的特权方法还可以看作是类的构造器。如下面的例子。” // 私有属性与私有方法,特权方法,对象公有属性和对象共有方法,构造器 var Book = function(id, name, price){ //私有属性 var num = 1; //私有方法 function checkId(){ }; //特权方法 this.getName = function(){}; this.getPrice = function(){}; this.setName = function(){}; this.setPrice = function(){}; //对象公有属性 this.id = id; //对象公有方法 this.copy = function(){}; //构造器 this.setName(name); this.setPrice(price); };

小白心中暗喜:“原来是这样呀,通过 JavaScript 函数级作用域的特征来实现在函数内部创建外界就访问不到的私有化变量和私有化方法。通过 new 关键字实例化对象时,由于对类执行一次,所以类的内部this上定义的属性和方法自然就可以复制到新创建的对象上,成为对象公有化的属性与方法,而其中的一些方法能访问到类的私有属性和方法,就像例子中家人对明星了解得比外界多,因此比外界权利大,因而得名特权方法。而我们在通过 new 关键字实例化对象时,执行了一遍类的函数,所以里面通过调用特权方法自然就可以初始化对象的一些属性了。可是在类的外部通过点语法定义的属性和方法以及在外部通过 prototype 定义的属性和方法又有什么作用呢?”“通过new关键字创建新对象时,由于类外面通过点语法添加的属性和方法没有执行到,所以新创建的对象中无法获取他们,但是可以通过类来使用。因此在类外面通过点语法定义的属性以及方法被称为类的静态共有属性和类的静态共有方法。而类通过 prototype 创建的属性或者方法在类实例的对象中是可以通过this访问到的(如图2.1新创建的对象的__proto__指向了类的原型所指向的对象),所以我们将prototype对象中的属性和方法称为共有属性和共有方法,如:” //类静态公有属性(对象不能访问) Book.ischinese = true; //类静态公有方法(对象不能访问) Book.resetTime = function(){ console.log('new Tiem') }; Book.prototype = { //公有属性 isJsBook : false, //公有方法 display : function(){} }“通过 new关键字创建的对象实质是对新对象 this 的不断赋值,并将 prototype 指向类的prototype 所指向的对象,而类的构造函数外面通过点语法定义的属性方法是不会添加到新创建的对象上去的。因此要想在新创建的对象中使用isChinese就得通过Book类使用而不能通过this,如Book.isChinese,而类的原型prototype上定义的属性在新对象里就可以直接使用,这是因为新对象的prototype和类的prototype指向的是同一个对象。”

于是小白半信半疑地写下了测试代码: var b = new Book(11,'Javascript设计模式',50); console.log(b.num); // undefined console.log(b.isJsBook); // false console.log(b.id); // 11 console.log(b.ischinese); // undefined“真的是这样,类的私有属性num以及静态共有属性isChinese在新创建的b对象里是访问不到的。而类的共有属性isJSBook在b对象中却可以通过点语法访问到。”“但是类的静态公有属性isChinese可以通过类的自身访问。” console.log(Book.ischinese); // true Book.resetTime(); // new Tiem

2.2.3 你们看不到我——闭包实现“有时我们经常将类的静态变量通过闭包来实现。” // 利用闭包实现 var Book = (function() { //静态私有变量 var bookNum = 0; //静态私有方法 function checkBook(name) { } //返回构造函数 return function(newId, newName, newPrice) { //私有变量 var name, price; //私有方法 function checkID(id){} //特权方法 this.getName = function(){}; this.getPrice = function(){}; this.setName = function(){}; this.setPrice = function(){}; //公有属性 this.id = newId; //公有方法 this.copy = function(){}; bookNum++ if(bookNum > 100) throw new Error('我们仅出版100本书.'); //构造器 this.setName(name); this.setPrice(price); }})();Book.prototype = { //静态公有属性 isJsBook : false, //静态公有方法 display : function(){}};“小白,你知道闭包么?”“不太了解。你能说说么?”“闭包是有权访问另外一个函数作用域中变量的函数,即在一个函数内部创建另外一个函数。我们将这个闭包作为创建对象的构造函数,这样它既是闭包又是可实例对象的函数,即可访问到类函数作用域中的变量,如bookNum这个变量,此时这个变量叫静态私有变量,并且checkBook()可称之为静态私有方法。当然闭包内部也有其自身的私有变量以及私有方法如price,checkID()。但是,在闭包外部添加原型属性和方法看上去像似脱离了闭包这个类,所以有时候在闭包内部实现一个完整的类然后将其返回,看下面的例子。” // 利用闭包实现 var Book = (function() { //静态私有变量 var bookNum = 0; //静态私有方法 function checkBook(name) {} //创建类 function _book(newId, newName, newPrice) { //私有变量 var name, price; //私有方法 function checkID(id){} //特权方法 this.getName = function(){}; this.getPrice = function(){}; this.setName = function(){}; this.setPrice = function(){}; //公有属性 this.id = newId; //公有方法 this.copy = function(){}; bookNum++ if(bookNum > 100) throw new Error('我们仅出版100本书.'); //构造器 this.setName(name); this.setPrice(price); } //构建原型 _book.prototype = { //静态公有属性 isJsBook : false, //静态公有方法 display : function(){} }; //返回类 return _book; })();“哦,这样看上去更像一个整体。”

2.2.4 找位检察长——创建对象的安全模式“对于你们初学者来说,在创建对象上由于不适应这种写法,所以经常容易忘记使用 new而犯错误。”“可是对于我们来说,这种错误发生也是不可避免的,毕竟不像你们工作了这么多年。但是你有什么好办法么?”“哈哈,那是当然,如果你们犯错误有人实时监测不就解决了么,所以赶快找一位检察长吧。比如JavaScript在创建对象时有一种安全模式就完全可以解决你们这类问题。” // 图书类 var Book = function(title, time, type){ this.title = title; this.time = time; this.type = type; } // 实例化—本书 var book = Book('Javascript', '2014', 'js');“小白,你猜book这个变量是个什么?”“Book类的一个实例吧。”为了验证自己的想法,小白写下测试代码。 console.log(book); // undefined“怎么会是这样?为什么是一个undefined(未定义)?”小白不解。“别着急,你来看看我的测试代码。” console.log(window.title); // Javascript console.log(window.time); // 2014 console.log(window.type); // js“怎么样发现问题了么”,小铭问道。“明明创建了一个Book对象,并且添加了title、time、type3个属性,怎么会添加到window上面去了,而且book这个变量还是undefined。”小白又看了看实例中的代码恍然大悟,“哦,原来是忘记了用new关键字来实例化了,可是为什么会出现这个结果呢?”“别着急,首先你要明白一点,new关键字的作用可以看作是对当前对象的this不停地赋值,然而例子中没有用new,所以就会直接执行这个函数,而这个函数在全局作用域中执行了,所以在全局作用域中 this 指向的当前对象自然就是全局变量,在你的页面里全局变量就是window了,所以添加的属性自然就会被添加到window上面了,而我们这个book变量最终的作用是要得到Book这个类(函数)的执行结果,由于函数中没有return语句,这个Book类自然不会告诉book变量的执行结果了,所以就是undefined(未定义)。”“原来是这样,看来创建时真是不小心呀,可是该如何避免呢?”小白感叹道。“‘去找位检察长’呀,哈哈,使用安全模式吧。” // 图书安全类 var Book = function(title, time, type){ // 判断执行过程中this是否是当前这个对象(如果是说明是用new创建的) if(this instanceof Book){ this.title = title; this.time = time; this.type = type; // 否则重新创建这个对象 }else{ return new Book(title, time, type); } } var book = Book('Javascript', '2014', 'js');“好了小白,测试一下吧。” console.log(book); // Book console.log(book.title); // Javascript console.log(book.time); // 2014 console.log(book.type); // js console.log(window.title); // undefined console.log(window.time); // undefined console.log(window.type); // undefined“真的是这样呀,太好了,再也不用担心创建对象忘记使用new关键字的问题了。”“好了说了很多,你也休息一下,好好回顾一下,后面还有个更重要的面向对象等着你——继承,这可是许多设计模式设计的灵魂。”2.3 传宗接代——继承“小白,看继承呢?”小铭忙完自己的事情走过来。“是呀,刚才学习类,发现每个类都有3个部分,第一部分是构造函数内的,这是供实例化对象复制用的,第二部分是构造函数外的,直接通过点语法添加的,这是供类使用的,实例化对象是访问不到的,第三部分是类的原型中的,实例化对象可以通过其原型链间接地访问到,也是为供所有实例化对象所共用的。然而在继承中所涉及的不仅仅是一个对象。”“对呀,不过继承这种思想却很简单,如千年文明能够流传至今靠的就是传承,将这些有用的文化一年一年地流传下来,又如我们祖先一代一代地繁衍,才有了今天的我们。所以继承涉及的不仅仅是一个对象。如人类的传宗接代,父母会把自己的一些特点传给孩子,孩子具有了父母的一些特点,但又不完全一样,总会有自己的特点,所以父母与孩子又是不同的个体。”“可是JavaScript并没有继承这一现有的机制,它又是如何实现的呢?”

2.3.1 子类的原型对象——类式继承“对呀,也正因为 JavaScript 少了这些显性的限制才使得其具有了一定的灵活性,所以我们可以根据不同的需求实现多样式的继承。比如常见的类式继承。” // 类式继承 // 声明父类 function superclass(){ this.superValue = true; } // 为父类添加共有方法 superclass.prototype.getsuperValue = function(){ return this.superValue; }; // 声明子类 function subclass(){ this.subValue = false; } // 继承父类 subclass.prototype = new superclass(); // 为子类添加共有方法 subclass.prototype.getsubValue = function (){ return this.subValue; };“很像,真的很像!”小白很惊讶。“像什么?”小铭不解地问。“刚才看过的封装呀,不同的是这里声明了2个类,而且第二个类的原型prototype被赋予了第一个类的实例。”小白解释道。“很对,继承很简单,就是声明2个类而己,不过类式继承需要将第一个类的实例赋值给第二个类的原型。但你知道为何要这么做么?”“类的原型对象的作用就是为类的原型添加共有方法,但类不能直接访问这些属性和方法,必须通过原型 prototype 来访问。而我们实例化一个父类的时候,新创建的对象复制了父类的构造函数内的属性与方法并且将原型__proto__指向了父类的原型对象,这样就拥有了父类的原型对象上的属性与方法,并且这个新创建的对象可直接访问到父类原型对象上的属性与方法。如果我们将这个新创建的对象赋值给子类的原型,那么子类的原型就可以访问到父类的原型属性和方法。”小白还有些不自信。“对,你分析得很准确。补充一点,你说的新创建的对象不仅仅可以访问父类原型上的属性和方法,同样也可访问从父类构造函数中复制的属性和方法。你将这个对象赋值给子类的原型,那么这个子类的原型同样可以访问父类原型上的属性和方法与从父类构造函数中复制的属性和方法。这正是类式继承原理。”“原来是这样,但是我们要如何使用子类呢?”小白问道。“使用很简单,像下面这样即可。”小铭说。 var instance = new subclass(); console.log(instance.getsuperValue()); //true console.log(instance.getsubValue()); //false“另外,我们还可以通过 instanceof 来检测某个对象是否是某个类的实例,或者说某个对象是否继承了某个类。这样就可以判断对象与类之间的继承关系了。”小铭补充说。“instanceof?它如何就知道对象与类之间的继承关系呢?”小白不解。“instanceof 是通过判断对象的 prototype 链来确定这个对象是否是某个类的实例,而不关心对象与类的自身结构。”“原来是这样。”于是小白写下测试代码。 console.log(instance instanceof superclass); //true

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载