全局空间的“污染”哲学January 21, 2009

八卦文章,所以标题党下,见笑。期前编写 JavaScript 框架的规则, 玉伯在他的 Blog 已有提到 。而今天看见 Ajaxian 上 收集的目前主流 JavaScript 框架占用的 全局空间变量数目 ,我也忍不住八卦下。

大家或许都知道,使用全局空间要格外的谨慎, 如果滥用全局空间会造成诸多问题 。随着 Javascript 的框架越来越多,不同的设计哲学相互碰撞,形成很有趣的局面:

http://files.gracecode.com/2009_11_05/557076d94e40.jpg

其实,从上面几个图就能看出 Javascript 框架能分成几个流派,就我使用过的几个典型的框架,谈下我的看法:

YUI 看起来像是少林寺,代码调理清晰谨慎也非常容易阅读和理解,但略显庞大和臃肿。其使用全局空间的方式,也 犹如少林寺规一样 ,不敢「越雷池」半步,如数绑定到了 YAHOO 上。

jQuery 犹如丐帮,使用面广、上手容易,但要学得「降龙十八掌」和「打狗棒」并非易事。对于全局空间的态度也虽谨慎(全部绑定到 jQuery),但大家似乎更习惯使用「$」,而忘记还有个 jQuery 变量。

Mootools/Prototype 很逍遥派,代码优雅高效,但真正理解的掌握需要有高深的内功。全局空间上使用也近乎随意,甚至对浏览器内置的功能也有不同程度的扩展。Mootools 的作者也 讨论过相关的设计哲学 ,在这里就不复述。

另外还有个很有趣的现象,就是似乎大家都很喜欢使用美元符「$」变量。但这意味着如果载入不同的框架,就会造成命名空间的相互污染( 详细 )。

避免全局空间的污染,在一定程度上能避免以后自找麻烦,也更有利于代码的模块化。撇开上述框架的八卦,在编写实际代码时更应该注意全局空间的污染问题。

使用 toString 进行类型检测January 13, 2009

toString 方法 返回对象的描述信息,很多时候我们都忘记了还有这属性的存在。 在查看 jQuery 源码时 ,发现其 isArray 竟然令人惊艳的使用 toString 进行类型判断:

597 toString = Object.prototype.toString;
    ...
616 isArray: function(obj) {
617    return toString.call(obj) === "[object Array]";
618 }

Javascript 这种弱类型的脚本语言进行类型检测,是件很头痛的事情。 巧合的是 Perfection Kills 近期也发布了篇同内容的文章 ,并得出相似的结论。

Perfection Kills 还阐述了使用 instanceof 和 typeof 需要格外注意的问题,以及其他如何使用「正统保险」的方法实现 isArray 函数(另,针对 YUI 的 isArray 写法,可以 参考这里 )。

回到上述 jQuery 代码, Mozilla 针对 toString 的描述 道破其中玄机:

If this method is not overridden in a custom object, 
toString returns [object type], where *type* is the object type. 

那么,可以同时 toString 使用针对统一的返回格式进行类型检测。由于 call 的首个参数必须是 Object 类型以及

alert(typeof null);

返回的是 Object ( 标准如此规定 让我很困惑),那么索性针对 undefined 以及 null 独立的判断。根据上述观点实现简单的检测代码如下

var chkType = function (obj) {
    if (obj === null) {
        return 'Null';
    }

    if (obj === undefined) {
        return 'Undefined';
    }

    return Object.prototype.toString.call(obj).match(/s(.+)]$/)[1];
};

最后, 简单得写了个 DEMO

由 IE8 User-Agent 更新想到的January 11, 2009

IE 开发团队更改了 IE8 的 User-agent ,更改的部分信息如下:

IE8 on Windows Vista (Compatibility View)
    Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0) 

IE8 on Windows Vista
    Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)

除了 IE8 因 Compatibility View 功能 造成单浏览器「原生」多个 User-agent 外,还有个情况也变得非常的有趣,就是几乎每个浏览器都将自己渲染引擎的标识加入到了 User-agent 中(Gecko、 WebkitTrident )。

User-agent 信息常被用作检测浏览器类型和版本的最佳途径( YUIjQuery ),而上述的改动是否意味着以后类似的检测脚本会变得更加的复杂?回答这个问题之前,让我们看下 Mootools 如何检测浏览器信息

var Browser = {
    Engine: {name: 'unknown', version: 0},
    Features: {
        xpath: !!(document.evaluate),     // 是否支持 XPath
          air: !!(window.runtime),        // 是否支持 Air 扩展
        query: !!(document.querySelector) // 是否支持 CSS 选择器
    },
    Engines: {
        // 判断 Opera
        presto: function() {
            return (!window.opera) ? 
                false : ((arguments.callee.caller) ? 
                    960 : ((document.getElementsByClassName) ? 950 : 925));
        },
        // 判断 IE,根据 ActiveX 和 特有的 XMLHttpRequest 对象
        trident: function() {
            return (!window.ActiveXObject) ? 
                           false : ((window.XMLHttpRequest) ? 5 : 4);
        },
        // Webkit 核心的浏览器,如 Safari 和 Chrome
        webkit: function() {
            return (navigator.taintEnabled) ? 
                false : ((Browser.Features.xpath) ? 
                    ((Browser.Features.query) ? 525 : 420) : 419);
        },
        // Mozilla Gecko 核心浏览器,如 Firefox
        gecko: function() {
            return (document.getBoxObjectFor == undefined) ?
                false : ((document.getElementsByClassName) ? 19 : 18);
        }
    }
};

Browser.detect = function() {
    for (var engine in this.Engines){
        var version = this.Engines[engine]();
        // 如果具有特定的浏览器对象
        if (version){
            this.Engine = {name: engine, version: version};
            this.Engine[engine] = this.Engine[engine + version] = true;
            break;
        }
    }
    return {name: engine, version: version};
};

Browser.detect();

上述代码让人感到耳目一新,它是根据浏览器功能而非 User-agent 判断浏览器类型。仔细考虑一下, User-agent 信息可以被伪造 ,同时浏览器厂商日后也会更改 User-agent 信息,所以此种情况下根据功能判断浏览器类型会可靠得多。

延伸下此策略,比如我们会编写这样的代码:

if (ie) {
  // ie only
} else {
  // other browsers
}

这样因浏览器差异而编写的「硬代码」,往往会造成维护两套实际相同功能的代码,并造成逻辑上的混乱。何不先抛开浏览器兼容的问题,然后再判断相应的对象是否被浏览器支持。

OK,有关编程思想的问题就不继续了…

-- Split --

PS,目前判断是否是 IE8 可这样编写(来自 舜子 ):

var isIE8 = !!window.XDomainRequest;
Yahoo 统计