JavaScript框架设计(第2版)(txt+pdf+epub+mobi电子书下载)


发布时间:2020-11-28 05:18:37

点击下载

作者:司徒正美

出版社:人民邮电出版社

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

JavaScript框架设计(第2版)

JavaScript框架设计(第2版)试读:

前言

距本书前一版出版已经3年了,这3年来前端技术也发生了很大变化。因为这3年中,出现了团购、P2P、O2O、直播等几大创业热潮,对前端的技术需求更强,也要求更高。许多公司为了迎合用户需求,也由PC端转移到移动端,全新的交互方式,及之前不曾遇到的性能问题,这些都需要全新的思路与框架来解决。旧的jQuery已经跟不上时代的步伐,因此一些新库如雨后春笋般出来,如fastclick、iscroll、react、fetch-polyfill、es5-shim、babel、rollup、rxjs …… 在react新版刚完成之际,react的版本号已经飙升到15,nodejs已经发展到8.0,这么多新东西,这么快的更新换代,一方面反映了前端技术的欣欣向荣,另一方面说明这个市场还不成熟。市场不成熟,正是框架高手“称雄一方”的好时机。远的不说,就说国内的玉伯,由于适时推出seajs,与国外的requirejs竞争,国内舆论哗然,贬褒不一,但不管你说什么,时势造英雄,为什么英雄不是自己呢。说到底,实力很重要。

在前端最能反映威力的技术就是框架,其中一个评价标准是在GitHub上拿到很多星星的开源框架(网友的评论)。这是一本讲JavaScript框架的书,初版发布时,是国内唯一一本深入研究前端框架的书。新的一版,也是市面上唯一能把框架研究得较深入的书。深入并不代表难,但肯定有门槛。因为从2015年起,JavaScript就加速添加新特征、新语法、新功能,框架也变得越来越庞大,越来越复杂,因为对应的行业需求总是比我们的框架更复杂。是我们的框架适应现实,不是我们的框架无端变得如此“不可理喻”,学习门槛越来越高,要读懂已有框架的难度系数越来越陡峭。

当然,你也可以尝试无师自通,在武侠小说中,无师自通的主角不在少数,但无书自通者恐怕寥寥无几。我觉得有效的方法是多看几本进阶的书。有人说,书买十本看一本也值的,我觉得有待商榷。因此我推荐钱穆的另一个说法。当年,李敖向他请教治学方法,他回答说:并没有具体方法,要多读书、多求解,当以古书原文为底子、为主,免受他人成见的约束。书要看第一流的,一遍一遍读。与其十本书读一遍,不如一本书读十遍。不要怕大部头的书,养成读大部头书的习惯,则普通书就不怕了。从这里面得到许多启示,一是读好书,二是读原版,三是精读,四是能读厚书。

第1点读好书。在知乎,总是有新人请教学习前端该看什么书的问题。其实前端的好书也有不少,如红皮书、蝴蝶书、犀牛书、乌龟书、时钟书、鸽子书……《你不知道的JavaScript》,也很有特色。

第2点读原版,在IT行业,特指英文原版。大凡有新技术出来,它们几乎都是以英文为载体出现,然后隔一年半载,才翻译到国内,所以学习新技术还是先看原版书。

第3点精读,就是重复看。多看书是一定的,但如果发现书上说的东西你始终看不进去时,一定不要坚持,一般来说看不下去是因为:一、你还没有达到看本书的水平,不适合你看;二、你看的书的确是本“烂书”,遇到这种情况赶紧换书。如果找到一本适合自己的书一定要坚持看下去,看到不懂的东西多搜索,搜索不到就多问人。当初,我阅读jQuery源码也是如此,最初看的是1.4.3版本,看得一头雾水,一气之下,从最初的1.0版本开始看。看完所有版本,了解其迭代过程,明白了一些代码后来为什么重构成这样。在那本书中,有相当多的源码,里面着着实实比较了程序的演化过程。

第4点看厚书,一些集大成者的书,比如你创作文学,就看看《文心雕龙》;研究哲学,就得看黑格尔的《哲学全书》。在JavaScript领域,所谓的大部头莫过于《JavaScript权威指南》等。厚达千页,许多细节都涉及了。

看了这样的书,也不能保证你能写出漂亮的代码,更别说创造一个热门的框架。这情形就好像你学了些单词和语法,你就想听懂不带字幕的英文电影一样,那是不可能的。不同阶段,你需要掌握的技能是不一样的。框架是另一个层次的技术。但别灰心,本书就是这些知识应用的指南,从高人的博客中、浏览器的更新日志中、GitHub的ISSUE中,辛辛苦苦整理来的知识,加上自己的实践分享给大家。

第一版的《JavaScript框架设计》是完全按照一个框架的编写顺序来编排,导致阅读难度太大,让许多读者反馈说无法坚持下去。这次大改版,将一些难点挪到后面,并加上大量的流程图与示意图来降低学习难度。并且随着浏览器的升级,许多东西也该更新换代,因此本书俨然是一本新书,无论是目录结构与内容上都焕然一新。最开始的知识是偏底层,讲授一些工具方法,一些常出现在各种框架的模块,里面会大量列举实现相同功能的不同实现,比如说isArray、trim、一个迷你的事件系统、获取元素样式、如何跨域……但越到后面,代码就越少,却能通过提供的网上链接,到相关博客里去观看相关的内容。后面几章都是非常前沿的东西,许多技术方案还没有定案(至少在我成书时),没有最佳实践,只能提供一些抽象的方法论,让读者们自行悟道。有些东西不是死记硬背就能理解的。并且,本书也不是让你造一个avalon/angular的框架。每个时代对框架的需求都不一样。引用《从0到1》里的话——商业世界的每一刻都不会重演。下一个比尔·盖茨不会再开发操作系统,下一个拉里·佩奇或是谢尔盖·布林不会再研发搜索引擎,下一个马克·扎克伯格也不会去创建社交网络。如果你照搬这些人的做法,你就不是向他们学习了。

最后,感谢那些帮我审稿的朋友们(以下排名不分先后):

王方磊、曹学进、张劲松、刘可、贺文榜、方正、索平、熊小涛、周静文、余永鹏、杨忠业、刘伟、曹学进、黄建新。

还有经常伴随我的可口可乐、香飘飘奶茶、旺旺仙贝、大白兔奶糖、肉松饼、徐福记、牛轧糖、牛肉粒、鱿鱼丝、小黄鱼、曲奇饼、德芙巧克力……它们也不分先后!

本书答疑QQ群为(471118627),本书编辑和投稿邮箱为zhangtao@ptpress.com.cn。InfoQ访谈

InfoQ:请您介绍一下自己目前的工作职责,负责的项目情况。据说您是写小说出身的,能否简单介绍一下自己的工作经历?为什么选择进入前端领域?为什么对前端框架抱有极大的热情?

笔者:现在我在“去哪儿网”任前端架构师一职,带领大家搞react开发。react,特别是react native是现时最好的移动框架。而公司的重点也转移到移动端了,因此我们小组负责将移动端相关设置全部搭建起来,各种“坑”踩一遍,努力为业务线提供最坚实的支撑。

每个人的经历都很曲折,尤其是前端人员,许多人都觉得前端比较容易学,或者赚钱比较多,才进入这行业。而我比较幸运,编代码也是我大学时的一个乐趣。因此不会像其他人,会出现动力不足的情况。当然我的兴趣还是很多的,比如写小说、建筑、考古、学日语、学动画、看科幻小说、陶艺……会找乐子的人,不会轻易泄气。

总的来说,我的兴趣都有个特点,就是缺少与他人的互动。编代码也一样,一个人静静地编就好了。像PM,需要与别人不断沟通,我可能做不过来。每个人的性格都不一样,因此选择行业要符合自己的性格。

至于为什么进入前端,纯粹是偶然。我的一个弟弟是做这行的。我在小县城呆着赚不了几个钱,他说带我去深圳见识见识,便一下子介绍到他公司做前端了。我在小县城时也用前端接些小活过日子,因此不会觉得一下子跳跃太大。更重要的是,我特别能写程序,我没进入这行时,就写了三百多篇有关前端的博文,那时大家都以为我在大公司任职了。

出于这样的误解,公司一开始就把一些很重要的事让我做,我要确保代码的质量,因为我的组件是被许多人复用的,就从那时起,我就一直搞框架、搞组件、搞各种工具。

infoQ:请您跟大家讲一下前端框架的发展历史、前端框架的起源和发展如何?现在的前端框架很多,其背后的原因是什么?国内的前端框架又是怎样起步的、发展现状如何?

笔者:这是一个老生常谈的话题,基本每本JavaScript书都会聊一下这个话题。主要原因是,JavaScript没有自己的SDK(核心库),需要依赖“民间”的力量。最开始是一些大公司有能力开发这些框架,如Prototype.js,也是作为ROR的次要项目开发出来的。此外还有dojo、closure、YUI这巨大的框架,也是大公司搞的。后来突然出现jQuery这样由天才开发的框架。事实证明,大公司那一套管理方式,以KPI驱动的框架有着致命的缺憾,虽然面面俱到,但不能迅速吸收IT社区的新东西,使用起来不够方便灵活。

再后来,大家都知道是jQuery的天下,大家都争先恐后地为它做插件。jQuery也大大解放了程序员的生产力,让我们有时间做一些更有意义的事。在后jQuery时代,最有意义的两件事是RequireJS的诞生与nodejs的出世。前者试图解决JavaScript模块化问题,后者让我们能从后端那里“抢”走一些活儿,那些活儿本来就是前端做比较合适。比如说做模板、套模板、传数据、JavaScript的语法检测、风格检测、理点等。

这个时间里,产生了像backbone这样的MVC框架。但立即被knockout、angular、react等MVVM框架占去份额了。要知道,后端从MVC进化到MVVM,需要大概10年时间,而前端则不到2年。前端框架发展实在太迅猛了!

我想其背后最大的动力是需求!源源不断的需求!原来由后端做的活儿,放在前端做更合适、更快,用户体验更好。这是趋势使然,挡也挡不住。

目前国内的发展历程其实与国外一模一样。最开始是公司牵头,后来就涌现大量出色的个人项目。阿里的前端技术之所以这么强,是因为他们不断地研制自己的“轮子”,“轮子”会越造越好。那些绝不重复造“轮子”的人默默无名,而框架作者们则开创自己一片新天地了。中国拥有世界上最庞大的互联网市场,面临的挑战很大,光是满足国内用户的需求,国内框架的研发人员就需要比国外同行更加努力。经过这几年的磨练,国内的框架也渐渐走出国门(如我的avalon,在澳大利亚、德国都有人在用,又如百度的ECharts,这个也非常抢眼)。

infoQ:avalon的起源与发展是如何的?avalon 2的架构如何?采用这样的架构有什么好处?与其他框架相比,avalon更加“接地气”的点体现在哪些地方?

笔者:avalon当初只是我另一个早期的框架mass Framework的一个插件。mass Framework类似于jQuery与Prototypejs的结合体。没什么特色,被埋没也是必然的。但我说过,“轮子”会越造越好的。当我将这个插件介绍到博客园——国内一个非常著名的IT社区,反映很不错。于是独立出来搞。经过5年的发展,它渐渐拥有自己的论坛与社区。不过,由于年龄的增长,我也开始抗拒一些新东西(比如说社区上一些自动化工具,总是想自己全部实现),导致avalon一度发展缓慢。发展到1.5版时试图奋起直追,效果不明显。avalon2决定使用一个更吸引眼球的东西扭转局面——这就是虚拟DOM,带来了性能上的飞跃。MVVM虽然非常方便,但很容易出现性能瓶颈。出自于谷歌之手的angular,也有2000个指令(即一个页面超过2000个指令,页面更新就慢得令人发指)。Facebook的react带来了“虚拟DOM”这个新概念,使用轻量对象代替重型对象来承担绝大多数的页面重绘工作,解决了所谓的“性能墙”问题。

原来MVVM架构分3层,M、V、VM三层,我们只需要关注VM。VM通过各种手段得知外界对它的操作,然后它智能地通知M与V进行变更。VM承受太多职责,导致不堪重负。而虚拟DOM的导入,让avalon2拥有4层架构。虚拟DOM位于V与VM之间,复杂的视图计算由虚拟DOM计算好,然后diff出差异点实现最小化刷新。这是算法的伟大胜利。为了实现虚拟DOM,前端框架作者也接触编译原理等高深的东西了。

现在主流的MVVM也结合虚拟DOM进行性能优化。基本上它们是基于Object.defineProperty这个API。而这个API在IE8中有bug,只能用于IE9+。因此它们的兼容性都比较差。而avalon的优势在于其作者精通各种兼容性问题与“魔法”。在IE6~IE8下,我找到了VBScript实现对VM的自省机制,在较新的浏览器使用Object.defineProperty,在更新锐的浏览器,则使用Proxy(动态代理)这个划时代的东西,从此我们可以动态监听对象是否被添加删除了某个属性,或调用了某个方法,而不像Object.defineProperty只能监听读写操作(Proxy对象被用于定义自定义基本操作的行为,如属性查找、分配、枚举、函数调用等,在Firefox的定义中一共有14个属性)。

从上面的描述来看,avalon走在时代的前列,但它不忘初心,还继续支持IE6,让大家用MVVM或虚拟DOM时没有后顾之忧。并且大家也不用担心avalon为了兼容IE6,会变得非常冗长。因为avalon是一份源码编译出好几个版本,每个版本根据浏览器的支持程度合并对应的模板。如果只想运行于IE10,其会相当小。

infoQ:在选择前端框架时,大家的建议很多,例如结合自己的业务等。您也曾提到,选择前端框架应综合考虑框架本身与团队情况。要考虑的点这么多,究竟怎样来综合考虑呢?具体的步骤应该是怎样的?

笔者:的确如此,技术本来是为业务服务的,单纯“玩”技术是没有前途,也找不到方向。前端框架之所以这么多,也是因为大家的业务侧重点各不相同。选择合适的框架,比选择一个时髦的框架重要多了。千万别让手下人员自行决定。他们玩不转可以“拍拍屁股走人”,留下一个烂摊子。我们要考虑到业务的可持续性、代码的可交接性及团队的普遍接受能力。比如一个公司,没有前端开发人员,都是后端开发人员顺手做前端的活,早期许多公司都是招PHP实现前端开发。他们的设计模式比较好,可以上手angular。如果一个团队新人够多,不稳定,则只能用jQuery与bootstrap。如果是一个创业公司,急着做出原型来拉投资,可以尝试vue、avalon、react等短平快的框架。但我所说的还是核心框架,涉及图表、UI库,这些需要架构师见多识广,自己“趟过坑”,才让团队“集中过河”。

infoQ:有人说前端编程标准和方法渐渐出现稳定的趋势,您怎么看待这一观点?在之后的发展过程中,有没有可能标准完全统一?有没有可能某个前端框架“一统江湖”?

笔者:这个观点前半段是对的。像jQuery带来一系列便捷的操作DOM的方式,append、prepend、remove等方法已经在DOM4中实现了。其最著名的选择器引擎,也有了原生替代品。因为浏览器商之间也存在竞争关系,将一些公认的好东西内置可以讨好用户。但浏览器厂商之间没怎么沟通,W3C给出的规范也模糊,出现差异是在所难免的。因此不要相信浏览器,要使用框架!至于框架,框架之争是不会停息的,好的框架会不断涌现,它们可能以某个神奇的设计一下推翻前面的技术。就像jQuery“灭掉”prototype,gulp“灭掉”grunt,webpack“灭掉”browserfy,react“灭掉”angular ……

并且没有一个框架覆盖所有需求,每个领域都有领头羊。因此想一个框架“一统江湖”,这个不可能也不实际。只要做好自己的擅长之处,开发就会比较顺利。

infoQ:您认为,前端开发人员学习框架设计应具备哪些能力?应从哪些方面着手进行设计?哪些地方有“坑”,需要注意避开?

笔者:这个问题比较笼统,我也只能笼统地回答。就像你问怎么挣大钱,有许多东西,人家说出来你也不能复制。首先,基础很重要,如计算机科班出来的人,搞前端就很容易上手。其次是设计模式,这是Java十多年积累的精华,是我们构建巨型工程的利器。现在前端框架的程序很多是上万行了。像过去那样,全是方法+全局变量在堆砌,在生产环境中找bug是恶梦。最后是好好看高手们的框架,阅读源码是进步最快的方式之一。只有看了足够多的源码,你才能博采众长。再最后,就是宣传与测试了,宣传确保你拥有第一批用户,成为你继续维护与升级的动力。需要提供一系列便捷的下载渠道,因为酒香也怕巷子深。测试是确保你能留住用户。目前社区上有大量的测试工具,你可以将它们全部绑定在webpack,在用户build工程时,把所有测试运行一遍。

最后说“坑”,其实没什么“坑”,所有浏览器兼容性问题与技术难点,许多技术高人都提供现成方案。但如果你做出框架,不再维护,对使用者来说这才是“大坑”。就是你不想维护了,就要找一个接盘者来主持。就像jQuery、nodejs等著名项目,原作者早早交给他人维护。

我今天的分享就到这里,谢谢大家!第1章种子模块1.1  模块化

最近几年,JavaScript得到飞速发展,一些框架越来越大,已经不像过去那样全部写进一个JS文件中。但拆到多个JS文件时,就要决定哪个是入口文件,哪个是次要文件,而这些次要文件也不可能按1、2、3、4的顺序组织起来,可能1依赖于2、3,3依赖于4、5,每个文件的顶部都像其他语言那样声明其依赖,最后在结束时说明如何暴露出那些变量或方法给外部使用。

就算你的框架只有几千行,在开发时将它们按功能拆分为10多个文件,维护起来也非常方便。加之Node.js的盛行,ES2016许多语法不断被浏览器支持,我们更应该拥抱模块化。

本书所介绍的所有模块,都以Node.js提倡的CommonJS方式组织起来。[1][2]

时下流行3种定义模块的规范:AMD、CommonJS与ES6 module。它们都被webpack所支持。以AMD定义JS模块通过RequireJS能直接运行于浏览器;CommonJS则需要browserfy等Node.js打包后才能运行于浏览器;ES6 module在我写书时,还没有浏览器支持,需要webpack、rollup等Node.js工具打包才能运行于浏览器。

下面简略演示一下这3种模块的定义方式。//AMDdefine(['./aaa', './bbb'], function(a, b){ return { c: a + b }})// CommonJSvar a = require('./aaa')var b = require('./bbb')module.exports = { c: a + b}//es6 moduleimport a form './aaa'import b form './bbb'var c = a + bexport {c}

有关这3种模块的用法,后面的章节会详述,这里暂时略过。

本人是比较倾向使用CommonJS的,因为其相关的打包工具非常成熟,并且以这种方式编写的框架,可以与大量Node.js编写的测试框架无缝配合使用。

再说回来,刚才提到的入口文件。整个程序就是引入其他子模块,然后导出代表命名空间的JavaScript对象就行了。var avalon = require('./seed/index')require('./filters/index')require('./vdom/index')require('./dom/index')require('./directives/index')require('./strategy/index')require('./component/index')require('./vmodel/index')module.exports = avalon

我们编写一个框架时,可能拆成上百个JS文件。为了方便寻找,这时需要按照功能或层次放进不同的子文件夹。然后每个子文件都有它的入口文件(index.js),由它来组织其依赖。本章的种子模块,就放到seed这个文件夹下了。1.2 功能介绍

种子模块也叫核心模块,是框架的最先执行的部分。即便像jQuery那样的单文件函数库,它的内部也分许多模块,必然有一些模块冲在前面立即执行;有一些模块只有用到才执行;也有一些模块(补丁模块)可有可无,存在感比较弱,只在特定浏览器下才运行。

既然是最先执行的模块,那么就要求其里面的方法是历经考验、千锤百炼的,并且能将这个模块变得极具扩展性、高可用、稳定性。(1)扩展性,是指方便将其他模块的方法或属性加入进来,让种子迅速成长为“一棵大树”。(2)高可用,是指这里的方法是极其常用的,其他模块不用重复定义它们。(3)稳定性,是指不能轻易在以后版本中删除,要信守承诺。

参照许多框架与库的实现,笔者认为种子模块应该包含如下功能:对象扩展、数组化、类型判定、无冲突处理、domReady。本章讲解的内容以avalon种子模块为范本,可以在下面的地址中阅览。https://github.com/RubyLouvre/avalon/tree/master/src/seed1.3 对象扩展

我们需要一种机制,将新功能添加到我们的命名空间上。命名空间,是指我们这个框架在全局作用域暴露的唯一变量,它多是一个对象或一个函数。命名空间通常也就是框架名字。我们可以看一下别人是如何为框架起名字的。https://www.zhihu.com/question/46804815

回到主题,对象扩展这种机制,我们一般做成一个方法,叫做[3]extend或mixin。JavaScript对象在属性描述符(Property Descriptor)没有诞生之前,是可以随意添加、更改、删除其成员的,因此扩展一个对象非常便捷。一个简单的扩展方法实现如下。//Prototype.jsfunction extend(destination, source) { for (var property in source) destination[property] = source[property]; return destination;}

不过,旧版本IE在这里有个问题,它认为像Object的原型方法就是不应该被遍历出来,因此for in循环是无法遍历名为valueOf、toString的属性名。这导致,后来人们模拟Object.keys方法实现时也遇到了这个问题。Object.keys = Object.keys || function(obj){ var a = []; for(a[a.length] in obj); return a ;}

在不同的框架中,这个方法还有不同的实现,如EXT分为apply与applyIf两个方法,前者会覆盖目标对象的同名属性,而后者不会。dojo允许多个对象合并在一起。jQuery还支持深拷贝。下面是avalon.mix方法,几乎是照搬jQuery.extend方法,只是在VBscript上做了一些防御处理。avalon.mix = avalon.fn.mix = function () { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false // 如果第一个参数为布尔,判定是否深拷贝 if (typeof target === 'boolean') { deep = target target = arguments[1] || {} i++ } //确保接受方为一个复杂的数据类型 if (typeof target !== 'object' && !avalon.isFunction(target)) { target = {} } //如果只有一个参数,那么新成员添加于mix所在的对象上 if (i === length) { target = this i-- } for (; i < length; i++) { //只处理非空参数 if ((options = arguments[i]) != null) { for (name in options) { try { src = target[name] copy = options[name] //当options为VBS对象时报错 } catch (e) { continue } // 防止环引用 if (target === copy) { continue } if (deep && copy && (avalon.isPlainObject(copy) || (copyIsArray = Array. isArray(copy)))) { if (copyIsArray) { copyIsArray = false clone = src && Array.isArray(src) ? src : [] } else { clone = src && avalon.isPlainObject(src) ? src : {} } target[name] = avalon.mix(deep, clone, copy) } else if (copy !== void 0) { target[name] = copy } } } } return target}

代码里面用到了avalon.isFunction, avalon.isPlainObject,它们也属于种子模块的一部分,下面有解说。

由于此功能这么常用,到后来ES6就干脆支持它了,于是有了 Object.assgin。如果要低端浏览器直接用它,可以使用以下[4]polyfill。function ToObject(val) { if (val == null) { throw new TypeError('Object.assign cannot be called with null or undefined'); } return Object(val);}module.exports = Object.assign || function (target, source) { var from; var keys; var to = ToObject(target); for (var s = 1; s < arguments.length; s++) { from = arguments[s]; keys = Object.keys(Object(from)); for (var i = 0; i < keys.length; i++) { to[keys[i]] = from[keys[i]]; } } return to;};1.4 数组化

浏览器下存在许多类数组对象,如function内的arguments,通过document.forms、form.elements、doucment.links、select.options、document.getElementsByName、document.getElementsBy TagName、childNodes、children等方式获取的节点集合(HTMLCollection、NodeList),或依照某些特殊写法的自定义对象。var arrayLike = { 0: "a", 1: "1", 2: "2", length: 3}

类数组对象是一个很好的存储结构,不过功能太弱了,为了享受纯数组的那些便捷方法,我们在处理它们前都会做一下转换。

通常来说,使用Array.prototype.slice.call就能转换我们的类数组对象了,但旧版本IE下的HTMLCollection、NodeList不是Object的子类,采用如上方法将导致IE执行异常。我们看一下各大库怎么处理的。//jQuery的makeArrayvar makeArray = function(array) { var ret = []; if (array != null) { var i = array.length; // The window, strings (and functions) also have 'length' if (i == null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval) ret[0] = array; else while (i) ret[--i] = array[i]; } return ret;}

jQuery对象是用来储存与处理dom元素的,它主要依赖于setArray方法来设置和维护长度与索引,而setArray的参数要求是一个数组,因此makeArray的地位非常重要。这方法保证就算没有参数也要返回一个空数组。

Prototype.js的$A方法如下:function $A(iterable) { if (!iterable) return []; if (iterable.toArray) return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results;};

mootools的$A方法如下:function $A(iterable) { if (iterable.item) { var l = iterable.length, array = new Array(l); while (l--) array[l] = iterable[l]; return array; } return Array.prototype.slice.call(iterable);};

Ext的toArray方法如下:var toArray = function() { return isIE ? function(a, i, j, res) { res = []; Ext.each(a, function(v) { res.push(v); }); return res.slice(i || 0, j || res.length); } : function(a, i, j) { return Array.prototype.slice.call(a, i || 0, j || a.length); }}()

Ext的设计比较巧妙,功能也比较强大。它一开始就自动执行自身,以后就不用判定浏览器了。它还有两个可选参数,对生成的纯数组进行操作。

但纵观这些方法,其实有一个特点,就是优化考虑使用Array.prototype.slice来转换类数组对象。而Array.prototype.slice碰到的唯一障碍就是旧版本IE下的节点集合。那么,我们设法让IE下的Array.prototype.slice能切割节点集合就一帆风顺了。以下是avalon的解决方案,不过大部分代码是来自firefox官方的。//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice/*** Shim for "fixing" IE's lack of support (IE < 9) for applying slice* on host objects like NamedNodeMap, NodeList, and HTMLCollection* (technically, since host objects have been implementation-dependent,* at least before ES6, IE hasn't needed to work this way).* Also works on strings, fixes IE < 9 to allow an explicit undefined* for the 2nd argument (as in Firefox), and prevents errors when* called on other DOM objects.*/var _slice = Array.prototype.slicetry { // Can't be used with DOM elements in IE < 9 _slice.call(document.documentElement)} catch (e) { // Fails in IE < 9 // This will work for genuine arrays, array-like objects, // NamedNodeMap (attributes, entities, notations), // NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes), // and will not fail on other DOM objects (as do DOM elements in IE < 9) Array.prototype.slice = function (begin, end) { // IE < 9 gets unhappy with an undefined end argument end = (typeof end !== 'undefined') ? end : this.length // For native Array objects, we use the native slice function if (Array.isArray(this) ) { return _slice.call(this, begin, end) } // For array like object we handle it ourselves. var i, cloned = [], size, len = this.length // Handle negative value for "begin" var start = begin || 0 start = (start >= 0) ? start : len + start // Handle negative value for "end" var upTo = (end) ? end : len if (end < 0) { upTo = len + end } // Actual expected size of the slice size = upTo - start if (size > 0) { cloned = new Array(size) if (this.charAt) { for (i = 0; i < size; i++) { cloned[i] = this.charAt(start + i) } } else { for (i = 0; i < size; i++) { cloned[i] = this[start + i] } } } return cloned }}avalon.slice = function (nodes, start, end) { return _slice.call(nodes, start, end)}

上面的Array.prototype.slice polyfill可以放到另一个补丁模块,这样确保我们的框架在升级时非常轻松地抛弃这些历史包袱。1.5 类型的判定

JavaScript存在两套类型系统:一套是基本数据类型,另一套是对象类型系统。基本数据类型在ES5中包括6种,分别是undefined、string、null、boolean、function和object。基本数据类型是通过typeof来检测的。对象类型系统是以基础类型系统为基础的,通过instanceof来检测。然而,JavaScript自带的这两套识别机制非常不靠谱,于是催生了isXXX系列。就拿typeof来说,它只能粗略识别出string、number、boolean、function、undefined和object这6种数据类型,无法识别Null、RegExp和Argument等细分对象类型。

让我们看一下这里面究竟有多少陷阱。typeof null// "object"typeof document.childNodes //safari "function"typeof document.createElement('embed')//ff3-10 "function"typeof document.createElement('object')//ff3-10 "function"typeof document.createElement('applet')//ff3-10 "function"typeof /\d/i //在实现了ecma262v4的浏览器返回 "function"typeof window.alert //IE678 "object""var iframe = document.createElement('iframe');document.body.appendChild(iframe);xArray = window.frames[window.frames.length - 1].Array;var arr = new xArray(1, 2, 3); // [1,2,3]arr instanceof Array; // falsearr.constructor === Array; // falsewindow.onload = function() { alert(window.constructor);// IE67 undefined alert(document.constructor);// IE67 undefined alert(document.body.constructor);// IE67 undefined alert((new ActiveXObject('Microsoft.XMLHTTP')).constructor);// IE6789 undefined}isNaN("aaa") //true

上面分4组,第一组是typeof的坑。第二组是instanceof的陷阱,只要原型上存在此对象的构造器它就返回true,但如果跨文档比较,iframe里面的数组实例就不是父窗口的Array的实例。第三组是有关constructor的陷阱,在旧版本IE下,DOM与BOM对象的constructor属性是没有暴露出来的。最后有关NaN,NaN对象与null、undefined一样,在序列化时是原样输出的,但isNaN这方法非常不靠谱,把字符串、对象放进去也返回true,这对我们序列化非常不利。

另外,在IE下typeof还会返回unknown的情况。if (typeof window.ActiveXObject != "undefined") { var xhr = new ActiveXObject("Msxml2.XMLHTTP"); alert(typeof xhr.abort);}

基于这IE的特性,我们可以用它来判定某个VBscript方法是否存在。

另外,以前人们总是以document.all是否存在来判定IE,这其实是很危险的。因为用document.all来取得页面中的所有元素是不错的主意,这个方法Firefox、Chrome觊觎好久了,不过人们都这样判定,于是有了在Chrome下的这出“闹剧”。typeof document.all // undefineddocument.all // HTMLAllCollection[728] (728为元素总数)

在判定undefined、null、string、number、boolean和function这6个还算简单,前面2个可以分别与void(0)、null比较,后面4个直接typeof也可满足90%的情形。这样说是因为string、number、boolean可以包装成“伪对象”,typeof无法按照我们的意愿工作了,虽然它严格执行了Ecmascript的标准。typeof new Boolean(1);//"object"typeof new Number(1);//"object"typeof new String("aa");//"object"

这些还是最简单的,难点在于RegExp与Array。判定RegExp类型的情形很少,不多讲了,Array则不一样。有关isArray的实现方法不下20种,都是因为JavaScript的鸭子类型被攻破了。

以下代码是isArray早些年的探索。function isArray(arr) { return arr instanceof Array;}function isArray(arr) { return !!arr && arr.constructor == Array;}function isArray(arr) {//Prototype.js1.6.0.3 return arr != null && typeof arr === "object" && 'splice' in arr && 'join' in arr;}function isArray(arr) {//Douglas Crockford return typeof arr.sort == 'function'}function isArray(array) {//kriszyp var result = false; try { new array.constructor(Math.pow(2, 32)) } catch (e) { result = /Array/.test(e.message) } return result;};function isArray(o) {// kangax try { Array.prototype.toString.call(o); return true; } catch (e) { } return false;};function isArray(o) {//kangax if (o && typeof o == 'object' && typeof o.length == 'number' && isFinite(o.length)) { var _origLength = o.length; o[o.length] = '__test__'; var _newLength = o.length; o.length = _origLength; return _newLength == _origLength + 1; } return false;}

至于null、undefined、NaN则比较好对付。function isNaN(obj) { return obj !== obj}function isNull(obj) { return obj === null;}function isUndefined(obj) { return obj === void 0;}

这一切烦恼,直到Prototype.js把Object.prototype.toString发掘出来才被彻底克服。此方法是直接输出对象内部的[[Class]],绝对精准。有了它,可以跳过95%的陷阱了。当然,toString 方法只能针对原生数据类型,像如何检测是否为window、是否为纯净JavaScript对象,就有点力不从心。

由于ECMA是不规范Host对象,window对象属于Host,所以也没有被约定,就算Object. prototype.toString也对它无可奈何。[object Object] IE6[object Object] IE7[object Object] IE8[object Window] IE9[object Window] firefox3.6[object Window] opera10[object DOMWindow] safai4.04[object global] chrome5.0.3.22

不过根据window.window和window.setInterval去判定更加不靠谱,用一个技巧我们可以完美识别IE6、IE7、IE8的window对象,其他还是用toString。这个神奇的hack(技巧)就可以,window与document互相比较,如果顺序不一样,其结果是不一样的!window == document // IE678 true;document == window // IE678 false;

当然,如果细数起来,JavaScript匪夷所思的事比比皆是。存在a !== a的情况;存在a == b && b != a的情况;存在a == !a的情况;存在a === a+100的情况;1 < 2 < 3为true,3 > 2 > 1为false;0/0为NaN;……

另一个比较常用的判定isArrayLike方法,这用于遍历方法,通常叫做each,它可能循环对象与类数组对象。

好了,我们看一下各大框架有多少is×××方法,哪些是最常用的,方便我们的框架也能包含它们,如表1-1所示。表1-1框架方法Prototype.jsisElement、isArray、isHash、isFunction、isString、isNumber、isDate、isUndefinedmootoolstypeOf判定基本类型,instanceOf判定自定义“类”RightJSisFunction、isHash、isString、isNumber、isArray、isElement、isNodeEXTisEmpty、isArray、isDate、isObject、isSimpleObject、isPrimitive、isFunction、isNumber、isNumeric、isString、isBoolean、isElement、isTextNode、isDefined、isIterableUnderscore.jsisElement、isEmpty、isArray、isArguments、isObject、isFunction、isString、isNumber、isFinite、isNaN、isBoolean、isDate、isRegExp、isNull、isUndefinedjQueryisFunction、isArray、isPlainObject、isEmptyObject、isWindow、isNumeric、isNaN、isXMLavalonisFunction、isObject、isPlainObject、isWindow、isVML1.5.1 type

我们比较一下,发现jQuery针对基础类型的isXXX是很少的,只是isFunction与isArray。其他都是很特别的业务判定。究其原因,是因为jQuery发明type方法,这个方法就囊括了isBoolean、isNumber、isString、isFunction、isArray、isDate、isRegExp、isObject及isError。//jquery2.0var class2type// Populate the class2type mapjQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase();});jQuery.type = function( obj ) { if ( obj == null ) { return String( obj ); } // Support: Safari <= 5.1 (functionish RegExp) return typeof obj === "object" || typeof obj === "function" ? class2type[ core_toString.call(obj) ] || "object" : typeof obj;}

这个方法也不断进化,因为ES6也加入了新的数据类型Symbol。

此外,isXXX系列随着框架版本的提升,也会就像恶性肿瘤一样不断膨胀,其实你多弄几个isXXX也不能满足用户的全部需求。就像isDate、isRegExp会用到的概率有多高呢?

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

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载