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


发布时间:2020-05-19 19:54:21

点击下载

作者:司徒正美

出版社:人民邮电出版社

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

JavaScript框架设计

JavaScript框架设计试读:

前言

首先说明一下,本书虽是讲解框架设计,但写个框架不是很深奥的事情,重点是建立更为完整的前端知识树。只有自己尝试写个框架,才有机会接触像原型、作用域、事件代理、缓存系统、定时器等深层知识,也才有机会了解applyElement、swapNode、importNode、removeNode、replaceNode、insertAdjacentHTML、createContextualFragment、runtimeStyle 等偏门 API ,也才会知晓像getElementById、getElementsByTagName、setAttribute、innerHTML 存在大量的 Bug,当然你也可以尝试最近几年浏览器带来的新API(包括ECMA262v5、v6、HTML5或大量误认为是HTML5的新模块),如Object.defineProperty、CSS.supports、WebKitShadowRoot、getDefaultComputedStyle……

虽然这难免落入“造轮子”的怪圈中,但“造轮子”在这世界却是出奇普遍。一般创造性的活动,一开始都是临摹他人的作品。就算不“造轮子”,也要收集一大堆“轮子”,作家有他的素材集,设计师有大量icon与笔刷,普通的“码农”也有个commonjs存放着一些常用的函数。以前的程序员们,经常会为了做一个数据处理程序而自己开发一门编程语言。如Charls Moore,他在美国国家天文台做射电望远镜数据提取程序时开发了 Forth;高德纳为了让自己写的书排版漂亮些,写了TeX;DHH为了做网站写了Rails……如果连写一个控件都要百度或Google查找答案,那水平不容易提高。

当前很少有技术书教你写框架的,即便是众多的 Java 类图书,大多数也是教你如何深入了解SHH的运作机理。

如果你是这两三年才接触JavaScript,那恭喜你了。现在JavaScript的应用洪荒时代已经过去, Portotype.js的幕府“统治”也已结果,且已迎来非常强势的jQuery纪元,有大量现成的插件可用,许多公司都用jQuery,意味着我们的技术有了用武之地。

但事实上还是要通过调试程序获得经验,只从 JavaScript 书上学习的那些知识点没法明白jQuery的源代码。

许多大公司的架构师根据技术发展的情况,他们都有自己一套或几套JavaScript底层库,各个部门视情况还发展针对于自己业务的UI库。而企业开发中,UI库就像流水线那样重要。而底层库只是一个好用的“锤子”或“胶钳”。要想迅速上手这么多公司框架,基础知识无疑是非常重要的。假若之前自己写过框架,那就有了经验。道理是一样的,框架设计的一些“套路”肯定存在的。本书就是把这些“潜规则”公开出来,迅速让自己成长为技术达人。

1.框架与库

下面稍微说一下框架与库的区别。

库是解决某个问题而拼凑出来的一大堆函数与类的集合。例如,盖一个房子,需要有测量的方法、砌砖的方法、安装门窗的方法等。每个方法之间都没什么关联。至于怎么盖房子都由自己决定。

框架则是一个半成品的应用,直接给出一个骨架,还例如盖房子,照着图纸砌砖、铺地板与涂漆就行了。在后端Java的三大框架中,程序员基本上就是与XML打交道,用配置就可以处理80%的编程问题。

从上面描述来看,框架带来的便利性无疑比库好许多。但前端JavaScript由于存在一个远程加载的问题,对JavaScript文件的体积限制很大,因此,框架在几年前都不怎么吃香。现在网速快多了,设计师在网页制造的地位(UED)也不比昔日,因此,集成程度更高的MVC、MVVM框架也相继面世。

不过,无论是框架还是库,只要在浏览器中运行,就要与DOM打交道。如果不用jQuery,就要发明一个半成品jQuery或一个半成品Prototype。对想提升自己能力的人来说,答案其实很明显,写框架还能提升自己的架构能力。

2.JavaScript发展历程

第1时期,洪荒时代。从1995年到2005年,就是从JavaScript发[1]明到Ajax概念 的提出。其间打了第一场浏览器战争,IE VS Netscape。这两者的DOM API出入很大,前端开发人员被迫改进技术,为了不想兼容某一个浏览器,发明UA(navigator.userAgent)嗅探技术。[2]

这个时期的杰出代表是Bindows ,2003年发布,它提供了一个完整的Windows桌面系统,支持能在EXT看到的各种控件,如菜单、树、表格、滑动条、切换卡、弹出层、测量仪表(使用VML实现,现在又支持SVG)。现在版本号是4.x,如下图所示。

其他比较著名的还有Dojo(2004年)、Sarissa(2003年)、JavaScript Remote Scripting(2000年)。

Dojo 有 IBM 做后台,有庞大的开发团队在做,质量有保证,被广泛整合到各大 Java 框架内(struct2、Tapestry、Eclipse ATF、MyFaces)。特点是功能无所不包,主要分为Core、Dijit、DojoX 3大块。Core 提供Ajax、events、packaging、CSS-based querying、animations、JSON 等相关操作API。Dijit 是一个可更换皮肤、基于模板的 Web UI 控件库。DojoX 包括一些新颖的代码和控件,如DateGrid、charts、离线应用和跨浏览器矢量绘图等,如下图所示。

JavaScript Remote Scripting 是较经典的远程脚本访问组件,支持将客户端数据通过服务器做代理进行远程的数据/操作交互。

Sarissa封装了在浏览器端独立调用XML的功能。

第2时期,Prototype“王朝”,2005 年~2008 年。其间打了第2次浏览器战争,交战双方是 IE6、IE7、IE8 VS Firefox 1、Firefox 2、Firefox 3,最后Firefox 3 大胜。浏览器战争中,Prototype积极进行升级,加入诸多 DOM 功能,因此,Jser(JavaScript 程序员)比之前好过多了。加之有 rails、script.aculo.us(一流的特效库)、Rico等助阵,迅速占领了市场。

Prototype时期,面向对象技术发展到极致,许多组件成套推出。DOM特征发掘也有序进行,再也不依靠浏览器嗅探去刻意屏蔽某一个浏览器了。无侵入式 JavaScript 开发得到推崇,所有JavaScript代码都抽离到JavaScript文件,不在标签内“兴风作浪”了。

Prototype的发展无可限量,直到1.5版本对DOM进行处理,这是[3]一个错误 。如它一个很好用的API-getElementsByClassName,由于W3C的标准化,Prototype升级慢了,它对DOM的扩展成为了它的“地雷”。

第3时期,jQuery纪元,2008年到现在(如下图所示)。

jQuery则以包裹方式来处理DOM,而且配合它的选择器引擎,若一下子选到N个元素,那么就处理 N 个元素,是集化操作,与主流的方式完全不一样。此外,它的方法名都起得很特别,人们一时很难接受。

2007年7月1日,jQuery发布了1.1.3版本,它的宣传是。(1)速度改良:DOM的遍历比1.1.2版本快了大概800%。(2)重写了事件系统:对键盘事件用更优雅的方法进行了处理。(3)重写了effects系统:提高了处理速度。

停滞不前的Prototype已经跟不上时代的节奏,jQuery在1.3x版本时更换Sizzle,更纯净的CSS选择器引擎,易用性与性能大大提高,被程序员一致看好的mouseenter、mouseleave及事件代理也被整合进去,jQuery就占据了市场。

3.JavaScript框架分类

如果是从内部架构与理念划分,目前JavaScript框架可以划分为5类。

第1种出现的是以命名空间为导向的类库或框架,如创建一个数组用 new Array(),生成一个对象用new Object(),完全的Java 风格,因此我们就可以以某一对象为根,不断为它添加对象属性或二级对象属性来组织代码,金字塔般地垒叠起来。代表作如早期的YUI与EXT。

第2种出现的是以类工厂为导向的框架,如著名的Prototype,还有mootools、Base2、Ten。它们基本上除了最基本的命名空间,其他模块都是一个由类工厂衍生出来的类对象。尤其是mootoos 1.3把所有类型都封装成Type类型。

第3种就是以jQuery为代表的以选择器为导向的框架,整个框架或库主体是一个特殊类数组对象,方便集化操作——因为选择器通常是一下子选择到N个元素节点,于是便一并处理了。JQuery包含了几样了不起的东西:“无new实例化”技术,$(expr)就是返回一个实例,不需要显式地new出来;get first set all 访问规则;数据缓存系统。这样就可以复制节点的事件了。此外,IIFE (Immediately-Invoked Function Expression)也被发掘出来。

第4种就是以加载器串联起来的框架,它们都有复数个JavaScript文件,每个JavaScript文件都以固定规则编写。其中最著名的莫过于AMD。模块化是JavaScript走向工业化的标志。《Unix 编程艺术》列举的众多“金科玉律”的第一条就是模块,里面有言——“要编写复杂软件又不至于一败涂地的唯一方法,就是用定义清晰的接口把若干简单模块组合起来,如此一来,多数问题只会出现在局部,那么还有希望对局部进行改进或优化,而不至于牵动全身”。许多企业内部框架都基本采取这种架构,如Dojo、YUI、kissy、qwrap和mass等。

第5种就是具有明确分层构架的 MV*框架。首先是 JavaScript MVC(现在叫 CanJS)、backbonejs和spinejs,然后更符合前端实际的MVVM框架,如knockout、ember、angular、avalon、winjs。在MVVM框架中,原有DOM操作被声明式绑定取代了,由框架自行处理,用户只需专注于业务代码。

4.JavaScript框架的主要功能

下面先看看主流框架有什么功能。这里面包含jQuery这个自称为库的东西,但它接近9000行,功能比Prototype还齐备。这些框架类库的模块划分主要依据它们在github中的源代码,基本上都是一个模块一个JavaScript文件。

jQuery

jQuery强在它专注于DOM操作的思路一开始就是对的,以后就是不断在兼容性、性能上进行改进。

jQuery 经过多年的发展,拥有庞大的插件与完善的 Bug 提交渠道,因此,可以通过社区的力量不断完善自身。

Prototype.js

早期的王者,它分为4大部分。

语言扩展。

DOM 扩展。

Ajax 部分。

废弃部分(新版本使用其他方法实现原有功能)。

Prototype.js的语言扩展覆盖面非常广,包括所有基本数据类型及从语言借鉴过来的“类”。其中Enumerable 只是一个普通的方法包,ObjectRange、PeriodicalExecuter、Templat 则是用Class 类工厂生产出来的。Class类工厂来自社区贡献。

mootools

它由于 API 设计得非常优雅,其官方网站上有许多优质插件,因此才没有在原型扩展的反对浪潮中没落。

RightJS

又一个在原型上进行扩展的框架。

MochiKit

一个Python风格的框架,以前能进世界前十名的。

Ten

日本著名博客社区Hatena的JavaScript框架,由amachang开发,受Prototype.js影响,是最早以命名空间为导向的框架的典范。

mass Framework

它是一个模块化,以大模块开发为目标,jQuery式的框架。

经过细节比较,我们很容易得出以下框架特征的结论。

对基本数据类型的操作是基础,如jQuery 就提供了trim、camelCase、each、map 等方法, Prototype.js等侵入式框架则在原型上添加camelize等方法。

类型的判定必不可少,常见形式是isXXX 系列。

选择器、domReady、Ajax 是现代框架的标配。

DOM操作是重中之重,节点的遍历、样式操作、属性操作也属于它的范畴,是否细分就看框架的规模了。

brower sniff 已过时,feature detect 正被应用。不过特性侦测还是有局限性,如果针对于某个浏览器版本的渲染 Bug、安全策略或某些 Bug 的修正,还是要用到浏览器嗅探。但它应该独立成一个模块或插件,移出框架的核心。

现在主流的事件系统都支持事件代理。

数据的缓存与处理,目前浏览器也提供data-*属性进行这面的工作,但不太好用,需要框架的进一步封装。

动画引擎,除非你的框架像Prototype.js 那样拥有像script.aculo.us这样顶级的动画框架做后盾,最好也加上。

插件的易开发和扩展性。

提供诸如Deferred 这样处理异步的解决方案。

即使不专门提供一个类工厂,也应该存在一个名为extend 或mixin 的方法对对象进行扩展。jQuery虽然没有类工厂,但在jQuery UI 中也不得不增加一个,可见其重要性。

自从jQuery出来一个名为noConflict的方法,新兴的框架都带此方法,以求狭缝中生存。

许多框架非常重视Cookie 操作。

最后感谢一下业内一些朋友的帮忙,要不是他们,书不会这么顺利地写出来。以下排名不分先后:玉伯、汤姆大叔、弹窗教主、貘大、linxz 、正则帝 abcd。这些都是专家级人物,在业界早已闻名遐迩。由于本人水平有限,书中难免存有不妥之处,请读者批评指正,源程序和答疑网址:https://github.com/RubyLouvre/jsbook/issues。编辑联系邮箱:zhangtao@ptpress.com.cn。

注释

[1]. http://www.adaptivepath.com/ideas/ajax-new-approach-web-applications

[2]. http://www.bindows.net/

[3]. 详见Prototype核心成员的反思:http://perfectionkills.com/whats-wrong-with-extending-the-dom/

第1章 种子模块

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

种子模块就是其中的急先锋,它里面的方法不一定要求个个神通广大,设计优良,但一定极具扩展性,常用,稳定。扩展性是指通过它们能将其他模块的方法包进来,让种子像大树一样成长;常用是指绝大多数模块都用到它们,防止做重复工作;稳定是指不能轻易在以后版本就给去掉,要信守承诺。

参照许多框架与库的实现,我认为种子模块应该包含如下功能:对象扩展,数组化,类型判定,简单的事件绑定与卸载,无冲突处理,模块加载与domReady。本章的讲解内容以mass Framework的种子模块为范本,可以到以下地址下载:

https://github.com/RubyLouvre/mass-Framework/blob/1.4/mass.js

 

1.1 命名空间

种子模块作为一个框架的最开始部分,除了负责辅建全局用的基础设施外,你有没有想到给读者一个震撼的开场呢?俗话说,好的开头是成功的一半。

时下“霸主”jQuery 就有一个很好的开头——IIFE(立即调用函数表达式),一下子吸引住读者,让读者吃一颗定心丸——既然作者的水平如此高超,还怕什么啊,直接拿来用。

IIFE是现代JavaScript框架最主要的基础设施,它像细胞膜一样包裹自身,防止变量污染。但我们总得在 Windows 里设置一个立足点,这个就是命名空间。基本上我们可以把命名空间等同于框架的名字,不过对于某些框架,它们是没有统一的命名空间,如Prototype.js , mootools。它们就是不想让你感觉到框架的存在,它的意义深透到 JavaScript、DOM、COM 等整个执行环境的每个角落,对原生对象的原型进行扩展。由于道格拉斯(JSON作者)的极力反对,新兴的框架都在命名空间上构建了。

命名空间是干什么用呢?这里就不多说了。我们看怎么在JavaScript模拟命名空间。JavaScript一切基于对象,但只有复合类型的对象才符合这要求,比如function、regexp、object……不过最常用的还是object与function。我们往一个对象上添加一个属性,而这个属性又是一个对象,这个对象我们又可以为它添加一个对象,通过这种方法,我们就可以有条不紊地构建我们的框架。用户想调用某个方法,它就以XXX.YYY.ZZZ()的形式调用。if (typeof(Ten) === "undefined") {Ten = {};Ten.Function = {/*略*/}Ten.Array = {/*略*/}Ten.Class = {/*略*/}Ten.JSONP = new Ten.Class(/*略*/)Ten.XHR = new Ten.Class(/*略*/)}

纵观各大类库的实现,一开始基本都是定义一个全局变量作为命名空间,然后对它进行扩展,如Base2的Base、Ext的Ext、jQuery的jQuery、YUI的YUI、dojo的dojo、MochiKit的MochiKit等。从全局变量的污染程度来看,分为两大类。

Prototype、mootools与Base2归为一类。Prototype的哲学是对JavaScript原生对象进行扩展。早些年,Ptototype差点成为事实的标准,因此基本没有考虑到与其他库的共存问题。基于Prototype,也发展出诸如script.aculo.us、rico、Plotr、ProtoChart、Scripty 2等非常优秀的类库及一大堆收费插件,非jQuery那一大堆垃圾插件所能比拟的。而且,有点渊源的插件几乎都与Prototype有关,如著名的lightbox。mootools是Prototype的升级版,更加OO,全面复制其API。Base则是想修复IE的Bug,让IE拥有标准浏览器的API,因此也把所有原生对象污染一遍。

第二类是jQuery、YUI、EXT这些框架。YUI与EXT就是像上面给出的代码那样,以叠罗汉方式构建的。jQuery则另辟蹊径,它是以选择器为导向的,因此它的命名空间是一个函数,方便用户把CSS表达器字符串传进来,然后通过选择器引擎进行查找,最后返回一个jQuery实例。另外,像jQuery最初也是非常弱小的,它想让人家试用自己的框架,但也想像Prototype那样使用美元符号作为它的命名空间。因此它特意实现了多库共存机制,在$、jQuery与用户指定的新命名空间中任意切换。

jQuery的多库共存原理很简单,因此后来也成为许多小库的标配。首先把命名空间保存到一个临时变量中,注意这时这个对象并不是自己框架的东西,可能是 Prototype.js 等巨头的,然后再搞个noConflict放回去。//jQuery1.2var _jQuery = window.jQuery, _$ = window.$;//先把可能存在的同名变量保存起来jQuery.extend({noConflict: function(deep) {window.$ = _$;//这时再放回去if (deep)window.jQuery = _jQuery;returnjQuery;}})

但jQuery的noConflict只对单文件的类库框架有用,像EXT就不能复制了。因此把命名空间改名后,将EXT置为null,然后又通过动态加载方式引入新的JavaScript文件,该文件再以EXT调用,将会导致报错。

mass Framework 对jQuery的多库共存进行改进,它与jQuery一样拥有两个命名空间,一个是美元符号的短命名空间,另一个是根据URL动态生成的长命名空间(jQuery就是jQuery!)。

namespace = DOC.URL.replace( /(#.+|\W)/g,'');

短的命名空间随便用户改名,长的命名空间则是加载新的模块时用的,虽然用户在模块中使用$做命名空间,但当JavaScript文件加载下来时,我们会对里面的内容再包一层,将$指向正确的对象,具体实现见define方法。

 

1.2 对象扩展

我们需要一种机制,将新功能添加到我们的命名空间上。这方法在JavaScript通常被称做extend或mixin。JavaScript对象在属性描述符(Property Descriptor)没有诞生之前,是可以随意添加、更改、删除其成员的,因此扩展一个对象非常便捷。一个简单的扩展方法实现是这样。function 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还支持深拷贝。下面是mass Framework 的mix 方法,支持多对象合并与选择是否覆写。function mix(target, source) {   //如果最后参数是布尔,判定是否覆写同名属性var args = [].slice.call(arguments), i = 1, key,ride = typeof args[args.length - 1] == "boolean" ? args.pop() : true;if (args.length === 1) {   //处理$.mix(hash)的情形target = !this.window ? this : {};i = 0;}while ((source = args[i++])) {for (key in source) {   //允许对象糅杂,用户保证都是对象if (ride || !(key in target)) {target[ key ] = source[ key ];}}}return target;}

 

1.3 数组化

浏览器下存在许多类数组对象,如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}

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

通常来说,只要[].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;elsewhile (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() {returnisIE ?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的设计比较巧妙,功能也比较强大。它一开始就自动执行自身,以后就不用判定浏览器了。它还有两个可选参数,对生成的纯数组进行操作。

dojo的_toArray和Ext一样,后面两个参数是可选的,只不过第二个是偏移量,最后一个是已有的数组,用于把新生的新组元素合并过去。(function() {var efficient = function(obj, offset, startWith) {return (startWith || []).concat(Array.prototype.slice.call(obj, offset || 0));};var slow = function(obj, offset, startWith) {var arr = startWith || [];for (var x = offset || 0; x > obj.length; x++) {arr.push(obj[x]);}returnarr;};dojo._toArray =dojo.isIE ? function(obj) {return ((obj.item) ? slow : efficient).apply(this, arguments);} :efficient;})();

最后是mass的实现,与dojo一样,一开始就进行区分,W3C方直接[].slice.call,IE自己手动实现一个slice方法。$.slice = window.dispatchEvent ? function(nodes, start, end) {return [].slice.call(nodes, start, end);} : function(nodes, start, end) {var ret = [],n = nodes.length;if (end === void 0 || typeof end === "number" &&

isFinite(end)) {start = parseInt(start, 10) || 0;end = end == void 0 ? n : parseInt(end, 10);if (start < 0) {start += n;}if (end > n) {end = n;}if (end < 0) {end += n;}for (var i = start; i < end; ++i) {ret[i - start] = nodes[i];}}return ret;}

 

1.4 类型的判定

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

让我们看一下这里面究竟有多少陷阱。

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; // false

arr.constructor === Array; // falsewindow.onload = function() {alert(window.constructor);// IE67 undefinedalert(document.constructor);// IE67 undefinedalert(document.body.constructor);// IE67 undefinedalert((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还会返回unknow的情况。if (typeof window.ActiveXObject != "undefined") {var xhr = new ActiveXObject("Msxml2.XMLHTTP");alert(typeof xhr.abort);}

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

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

typeof document.all // undefined

document.all // HTMLAllCollection[728] (728为元素总数)

在判定undefined、null、string、number、boolean、function这6个还算简单,前面两个可以分别与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的实现不[2]下二十种,都是因为JavaScript的鸭子类型 被攻破了。直到Prototype.js把Object.prototype.toString发掘出来,此方法是直接输出对象内部的[[Class]],绝对精准。有了它,可以跳过95%的陷阱了。

isArray早些年的探索:function isArray(arr) {return arr instanceof Array;}function isArray(arr) {return !!arr && arr.constructor == Array;}function isArray(arr) {//Prototype.js1.6.0.3return arr != null && typeof arr === "object" &&'splice' in arr && 'join' in arr;}function isArray(arr) {//Douglas Crockfordreturn typeof arr.sort == 'function'}function isArray(array) {//kriszypvar result = false;try {new array.constructor(Math.pow(2, 32))} catch (e) {result = /Array/.test(e.message)}return result;};function isArray(o) {// kangaxtry {Array.prototype.toString.call(o);return true;} catch (e) {}return false;};function isArray(o) {//kangaxif (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;}

最后要判定的对象是 window,由于 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;

……

好了,至此,所有重要的 isXXX 问题都解决了,剩下的就把它们表达出来。经典做法就是直接罗列。

在Prototype.js中,拥有isElement、isArray、isHash、isFunction、isString、isNumber、isDate、isUndefined方法。

mootools搞了个typeOf判定基本类型,instanceOf判定自定义“类”。

RightJS 有isFunction 、isHash、isString、isNumber、isArray 、isElement 、isNode。

EXT有isEmpty、isArray、isDate、isObject、isSimpleObject、isPrimitive、isPrimitive、isFunction、isNumber、isNumeric、isString、isBoolean、isElement、isTextNode、isDefined、isIterable,应有尽有。最后,还有typeOf判定基本类型。

Underscore.js 有 isElement、isEmpty、isArray、isArguments、isObject、isFunction、isString、isNumber、isFinite、isNaN、isBoolean、isDate、isRegExp、isNull、isUndefined。

isXXX系列就像恶性肿瘤一样不断膨胀,其实你多弄几个isXXX也不能满足用户的全部需求。就像isDate、isRegExp会用到的机率有多高呢?

jQuery 就不与其他框架一样了,在 jQuery 1.4 中只有 isFunction、isArray、isPlainObject、isEmptyObject。IsFunction、isArray 肯定是用户用得最多,isPlainObject 则是用来判定是否为纯净的JavaScript对象,既不是DOM、BOM对象,也不是自定义“类”的实例对象,制造它的最初目的是用于深拷贝,避开像window那样自己引用自己的对象。isEmptyObject是用于数据缓存系统,当此对象为空时,就可以删除它。//jquery2.0jQuery.isPlainObject = function(obj) {//首先排除基础类型不为Object的类型,然后是DOM节点与

window对象if (jQuery.type(obj) !== "object" || obj.nodeType ||

jQuery.isWindow(obj)) {return false;}//然后回溯它的最近的原型对象是否有isPrototypeOf,//旧版本IE的一些原生对象没有暴露constructor、

prototype,因此会在这里过滤try {if (obj.constructor &&!hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {return false;}} catch (e) {return false;}return true;}

在avalon.mobile中有一个更精简的版本,由于它只支持IE10等非常新的浏览器,就没有干扰因素了,可以大胆使用ecma262v5的新API。avalon.isPlainObject = function(obj) {return obj && typeof obj === "object" &&

Object.getPrototypeOf(obj) === Object.prototype}

isArrayLike也是一个常用的方法,但判定一个类数组太难了,唯一的辨识手段是它应该有一个大于或等于零的整型length属性。此外还有一些“共识”,如window与函数和元素节点(如form元素)不算类数组,虽然它们都满足前面的条件。因此至今jQuery没有把它暴露出来。//jquery2.0function isArraylike(obj) {var length = obj.length, type = jQuery.type(obj);if (jQuery.isWindow(obj)) {return false;}if (obj.nodeType === 1 && length) {return true;}return type === "array" || type !== "function" &&(length === 0 ||typeof length === "number" && length > 0 && (length - 1) in obj);}//avalon 0.9function isArrayLike(obj) {if (obj && typeof obj === "object") {var n = obj.lengthif (+n === n && !(n % 1) && n >= 0) { //检测length属性是否为非负整数try {//像Argument、Array、NodeList等原生对象的length属性是不可遍历的if ({}.propertyIsEnumerable.call(obj, 'length') === false) {return Array.isArray(obj) || /^\s?function/.test(obj.item || obj. callee)}return true;} catch (e) { //IE的NodeList直接抛错return true}}}return false}//avalon.mobile更倚重Object.prototoype.toString来判定function isArrayLike(obj) {if (obj && typeof obj === "object") {var n = obj.length,str = Object.prototype.toString.call(obj)if (/Array|NodeList|Arguments|CSSRuleList/.test(str)) {return true} else if (str === "[object Object]" && (+n === n && !(n % 1) && n >= 0)) {return true //由于 ecma262v5 能修改对象属性的 enumerable,因此不能用 propertyIs//Enumerable来判定了}}return false}

补充一句,1.3版本中,Prototype.js的研究成果(Object.prototype.toString.call)就应用于jQuery了。在1.2版本中,jQuery判定一个变量是否为函数非常复杂。

isFunction: function( fn ) {

return !!fn&&typeoffn != "string" && !fn.nodeName&&

fn.constructor != Array && /^[\s[]?function/.test( fn + "" );

}

jQuery1.43引入isWindow来处理makeArray中对window的判定,引入isNaN用于确保样式赋值的安全。同时引入type代替typeof关键字,用于获取数据的基本类型。class2type = {}jQuery.each("Boolean Number String Function Array Date RegExpObject".split(" "),function(i, name) {class2type[ "[object " + name + "]" ] = name.toLowerCase();});jQuery.type = function(obj) {return obj == null ?String(obj) :class2type[toString.call(obj) ] || "object";})

jQuery1.7中添加isNumeric代替isNaN。这是个不同于其他框架的isNumber,它可以是字符串,只要外观上像数字就行了。但 jQuery1.7 还做了一件违背之前提到稳定性的事情,贸然去掉jQuery.isNaN ,因此导致基于旧版本 jQuery 的一大批插件失效。//jquery1.43~1.64jQuery.isNaN = function(obj) {return obj == null || !rdigit.test(obj) || isNaN(obj);})//jquery1.7 就是isNaN的取反版jQuery.isNumeric = function(obj) {return obj != null && rdigit.test(obj) && !isNaN(obj);})//jquery1.71~1.72

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

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载