<译>jQuery事件绑定的最佳实践

原文:Effective Event Binding with jQuery

如果你经常使用jQuery,那么你也许很熟悉事件绑定。这是很基本的东西,但是深入一点,你就能够找到机会让你事件驱动的代码变得不太零碎,并且更容易管理。

更好的选择器策略

让我们从基础的例子开始。下面的HTML代码表示的是可以开合的导航菜单。

<button class="nav-menu-toggle">Toggle Nav Menu</button>
<nav>
    <ul>
        <li><a href="/">West Philadelphia</a></li>
        <li><a href="/cab">Cab Whistling</a></li>
        <li><a href="/throne">Throne Sitting</a></li>
    </ul>
</nav>

下面这个是点击按钮之后控制导航菜单开合的javascript代码

$('.nav-menu-toggle').on('click',function(){
    $('nav').toggle();
});

这可能是最常用的实现方式。它能够使用,但是比较脆。javascript代码依赖了按钮的类名nav-menu-toggle。很可能在未来其他开发者或者健忘的你在重构代码的时候会删除或者重命名这个类名。

问题的核心是我们同时在表现和交互中使用了CSS的类名。这违反了关注点分离的原则,让维护更容易出错。

让我们用一个不同的方法来实现

<button data-hook="nav-menu-toggle">Toggle Nav Menu</button>
<nav data-hook="nav-menu">
    <ul>
        <li><a href="/">West Philadelphia</a></li>
        <li><a href="/cab">Cab Whistling</a></li>
        <li><a href="/throne">Throne Sitting</a></li>
    </ul>
</nav>

这次我们使用这个data属性(data-hook)来选择元素。任何对CSS类的改变将不会影响到javascript,让我们能够实现关注点分离以及更加稳定的代码。

下面我们用data-hook属性来选择对应的元素:

$('[data-hook="nav-menu-toggle"]').on('click',function(){
    $('[data-hook="nav-menu"]').toggle();
});

需要注意的是,我也使用data-hook作为nav元素的选择器。你不一定需要,但是我喜欢这里面包含的思想:任何使用你看到data-hook,你会知道这个元素在javascript中引用到啦。

一些语法糖

我必须承认data-hook选择器并不是很漂亮。让我们通过扩展jQuery实现一个自定义的函数:

$.extend({
    hook:function(hookName){
        var selector;
        if(!hookName || hookName === '*'){
            // select all data-hooks
            selector='[data-hook]'
        }else{
            // select specific data-hook
            selector='[data-hook*="'+hookName+'"]';
        }
        return $(selector);
    }
});

上面准备完毕,我们来重写一下javascript。

$.hook('nav-menu-toggle').on('',function(){
    $.hook('nav-menu').toggle();
});

更好的是,我们甚至可以把一系列以空格分开的hook名字放在一个元素上。

<button data-hook="nav-menu-toggle video-pause click-track">Toggle Nav Menu</button>

我们可以找到里面的任意个hook名字:
$.hook(‘click-track’); // returns the button as expected

我们也能够找到页面上所有的hook元素

// both are equivalent
$.hook();
$.hook('*');

防止函数表达式

到目前为止,我们在事件处理中使用的都是匿名函数。让我们重写一下,使用声明的函数来代替它。

function toggleNavMenu(){
    $.hook('nav-menu').toggle();
}

$.hook('nav-menu-toggle').on('click',toggleNavMenu);

这让事件绑定的代码更加易读。这个toggleNavMenu函数名表达了意图,是代码自我注释的好例子。

我们同时也获得了可复用的能力,因为其他地方可能需要使用toggleNavMenu函数。

最后,这对于自动化测试来说是意见大喜事,因为声明的函数的单元测试要比匿名函数单元测试容易的多。

同时使用多个事件

jQuery提供了一个简单方便的语法来处理多事件的问题。比如,你可以为一系列空格隔开的事件列表绑定同一个事件处理函数。

$.hook('nav-menu-toggle').on('click keydown mouseenter',trackAction);

如果你需要为不同的事件绑定不同的处理函数,你可以使用对象表达方式:
$.hook(‘nav-menu-toggle’).on({
‘click’:trackClick,
‘keydown’:tranckKeyDown,
‘mouseenter’:trackMouseEnter
});

反过来,你可以同时取消多个事件的绑定:

// unbinds keydown and mouseenter
$.hook('nav-menu-toggle').off('keydown mouseenter');

// nuclear options:unbinds everything
$.hook('nav-menu-toggle').off();

你可以想象到的是,不小心的取消事件绑定可能会导致严重的我们不想要的副作用。继续看我们可以通过哪些技巧来减轻这个问题。

小心的取消事件绑定

一般情况下我们不会在一个元素的同一事件类型绑定多个事件处理函数。让我们再看一下之前的那个按钮:

<button data-hook="nav-menu-toggle video-pause click-track">Toggle Nav Menu</button>

不同的代码区域可能会在同一个元素的同一事件绑定不同的事件处理函数:

// somewhere in the nav code
$.hook('nav-menu-toggle').on('click',toggleNavMenu);

// somewhere in the video playback code
$.hook('video-pause').on('click',pauseCarltonDanceVideo);

// somewhere in the analytics code
$.hook('click-track').on('click',trackClick);

尽管我们使用了不同的选择器,但是这个元素现在有三个事件处理函数啦。假如我们的分析代码不在关心这个按钮:

// no good
$.hook('click-track').off('click');

糟糕的是,上面的代码实际上回删除所有的点击事件处理函数,不仅仅是trackClick。我们应该实用更加有辨别力的方式来指定我们需要删除的事件处理函数:

$.hook('click-track').off('click',trackClick);

另一种方式是使用命名空间。任何事件都有资格使用一个命名空间来实现绑定和取消绑定,这样你就可以更好的控制事件绑定和取消绑定。

// binds a click event in the "analytics" namespace
$.hook('click-track').on('click.analytics', trackClick);

// unbinds only click events in the "analytics" namespace
$.hook('click-track').off('click.analytics');

你也可以使用多个命名空间:

// binds a click event in both the "analytics" and "usability" namespaces
$.hook('click-track').on('click.analytics.usability',trackClick);

// unbinds any events in either the "analytics" OR "usability" namespaces
$.hook('click-track').off('.usability .analytics');

// unbinds any events in both the "analytics" AND "usability" namespaces
$.hook('click-track').off('.usability.analytics');

需要注意的是,命名空间的顺序是没有关系的,因为命名空间不是层级式的。

如果你有一个复杂的功能需要多个元素绑定多个事件,那么使用命名空间是一种简单的把他们组织起来然后快速清除的方式:

// free all elements on the page of any "analytics" event handling
$('*').off('.analytics');

命名空间在写插件的时候尤其有用,因为这样你就能保证只会取消自己命名空间范围内的事件处理函数的绑定。

评论

Web字体的初探

一,字体基本概念的介绍

1.1 字体的分类


1.1.1 Serif(衬线体)
Serif(衬线):在印刷的文字中衬线字体对于人眼的辨识更轻松,阅读更舒服横细竖粗,开始和结束的地方有装饰。在web上的字体,衬线字体比无衬线字体的辨识度更低,因为屏幕像素有限,不能很好地渲染出衬线体的效果。
1.1.2 Sans-Serif(无衬线体)
Sans-Serif(无衬线体):在印刷的文字中,无衬线体比较醒目,在小字体场合比衬线体更加清晰,但是辨识度没有衬线体高。在web字体中,无衬线字体比衬线字体更易读。
1.1.3 Monospace(等宽字体)
Monospace(等宽字体):每个宽度都一致的字体,看起来比较整齐,比较适合用于显示代码。比较著名的有Courier New字体。
1.1.4 Cursive(草书)
Cursive(草书):相当于印刷中的手写体,看起来比较流畅,像手写一样。

1.2 常用的字体

Serif,Sans-Serif,Monospace属于标准字体,Cursive,Fantasy属于非标准字体
1.2.1 衬线体(Serif)
常用的中文衬线体:宋体(Simsun),仿宋(FangSong),楷体(KaiTi),华文仿宋(STFangSong),华文楷体(STKaiTi)。
常见的英文衬线体:Times new Roman,Times
1.2.2 无衬线体(Sans-Serif)
常见的中文无衬线体:微软雅黑(Microsoft YaHei),黑体(SimHei),华文细黑(STXiHei)
常见的英文无衬线体:Tahoma,Arial,Helvetica,Verdana
1.2.3 等宽字体(monospace)
常见的等宽字体:Courier New,Courier
1.2.4 草书(Cursive)
常见的草书:Comic Sans MS
1.2.5 Fantasy
常见的Fantasy:Impact

二,网站中使用的字体

2.1 英文网站中使用的默认字体

font:12px/1.5 Tahoma,Helvetica,Arial,sans-serif
Tahoma:英文windows操作系统默认的字体
Helvetica:Mac OS X系统的系统默认字体
Arial:早期windows英文系统的默认字体。XP和Vista都是Tahoma
Sans-serif是针对linux的,linux默认只有kernel,字体由用户自定义。
无论是XP还是Vista下,不指定网页的中文字体时,默认的就是宋体。因此在font-family中使用”宋体“是多余的,可以省去。

2.2 Windows操作系统提供的中文字体

黑体:SimHei
宋体:SimSun
新宋体:NSimSun
仿宋:FangSong
楷体:KaiTi
仿宋GB2312:FangSongGB2312
楷体GB2312:KaiTiGB2312
微软雅黑:Microsoft YaHei(Windows 7开始提供)

2.3 OS X操作系统提供的中文字体

冬青黑体:Hiragino Sans GB(Snow Leopard开始提供)
华文细黑:STHeiti Light(又名STXiHei)
华文黑体:STHeiTi
华文楷体:STKaiTi
华文宋体:STSong
华文仿宋:STFangsong

2.4 更多有趣的字体使用

上面介绍的字体属于常见的字体,也就是我们所说的Web safe font。其在大部分网站是可以正常显示的。下面介绍的是比较有趣特殊的字体的使用方式。

2.5 使用web font的方法

2.5.1 使用link标签
通过link导入样式,然后直接通过font-family使用,如:

<link href='https://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>

font-family:"lobster"

参考Google Fonts

2.5.2 使用@import导入
通过@import导入字体的样式,如:

@import url(https://fonts.googleapis.com/css?family=Candal);

font-family:"Candal"

参考Google Fonts

2.5.3 使用javascript
通过javascript获取字体样式,如:
<script type="text/javascript">
     WebFontConfig = {
        google: { families: [ 'Shadows+Into+Light::latin' ] }
    };
    (function() {
        var wf = document.createElement('script');
        wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
              '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
        wf.type = 'text/javascript';
        wf.async = 'true';
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(wf, s);
    })(); 
</script>

font-family:"Shadows Into Light"

参考Google Fonts

2.5.4 使用font-face
首先需要从网站下载对应的字体,然后url填入文件路径,如:

@font-face{ font-family:"saucer"; src:url("fonts/SaucerBB.ttf") format("truetype"); }
font-family:saucer;

2.5.5 使用这些特殊字体的弊端
使用这些特殊字体可以产生很炫酷的文字,但是也存在很大的弊端:
1,不同的环境显示的内容可能不一样
2,显示的内容不可靠
3,需要把字体包含到网站(有时可能有100kb大)中需要消耗大量的下载时间

三,大型网站上的字体实践

1.淘宝:font-family: tahoma, arial, 'Hiragino Sans GB', 宋体, sans-serif;
2.百度:font-family: arial, 宋体, 'Hiragino Sans GB', 'Microsoft Yahei', 微软雅黑, 宋体, Tahoma, Arial, Helvetica, STHeiti;
3.京东:font-family: Arial, Verdana, 宋体;
4.Youtube:font-family: Roboto, arial, sans-serif;
5.github:font-family: Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol';

参考的网站:

creating good websites

serif和sans-serif的区别

中文字体网页开发指南

评论