三月沙

三月沙 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

左手我的世界,右手代码的世界,而你走在中间

个人动态

三月沙 收藏了文章 · 2014-08-28

通用 CSS 笔记、建议与指导

在参与规模庞大、历时漫长且参与人数众多的项目时,所有开发者遵守如下规则极为重要:

  • 保持 CSS 易于维护
  • 保持代码清晰易懂
  • 保持 CSS 的可拓展性

为了实现这一目标,我们要采用诸多方法。

本文档第一部分将探讨语法、格式以及分析 CSS 结构;第二部分将围绕方法论、思维框架以及编写与规划 CSS 的看法。


CSS 文档分析

无论编写什么文档,我们都应当维持统一的风格,包括统一的注释、统一的语法与统一的命名规范。

总则

将行宽控制在 80 字节以下。渐变(gradient)相关的语法以及注释中的 URL 等可以算作例外,毕竟这部分我们也无能为力。

我倾向于用 4 个空格而非 Tab 缩进,并且将声明拆分成多行。

单一文件与多文件

有人喜欢在一份文件文件中编写所有的内容,而我在迁移至 Sass 之后开始将样式拆分成多个小文件。这都是很好的做法。无论你选择哪种,下文的规则都将适用,而且如果你遵守这些规则的话你也不会遇到什么问题。这两种写法的区别仅仅在于目录以及区块标题:

目录

在 CSS 的开头,我会维护一份目录,就像这样:

/*------------------------------------*\
    $CONTENTS
\*------------------------------------*/
/**
 * CONTENTS............You’re reading it!
 * RESET...............Set our reset defaults
 * FONT-FACE...........Import brand font files
 */

这份目录可以告诉其他开发者这个文件中具体含有哪些内容。这份目录中的每一项都与其对应的区块标题相同。

如果你在维护一份单文件 CSS,对应的区块将也在同一文件中。如果你是在编写一组小文件,那么目录中的每一项应当对应相应的 @include 语句。

区块标题

目录应当对应区块的标题。如下:

/*------------------------------------*\
    $RESET
\*------------------------------------*/

区块标题前缀 $ 可以让我们使用 Cmd|Ctrl + F 命令查找标题名时将搜索范围限制在区块标题中

如果你在维护一份大文件,那么在区块之间空 5 行,如下:

/*------------------------------------*\
    $RESET
\*------------------------------------*/
[Our
reset
styles]





/*------------------------------------*\
    $FONT-FACE
\*------------------------------------*/

在大文件中快速翻动时这些大块的空档有助于区分区块。

如果你在维护多份以 include 连接的 CSS 的话,在每份文件头加上标题即可,不必这样空行。

代码顺序

尽量按照特定顺序编写规则,这将确保你充分发挥 CSS 中第一个 C 的意义:cascade,层叠。

一份规划良好的 CSS 应当按照如下排列:

  1. Reset 万物之根源
  2. 元素类型 没有 class 的 h1ul
  3. 对象以及抽象内容 最一般、最基础的设计模式
  4. 子元素 由对象延伸出来的所有拓展及其子元素
  5. 修补 针对异常状态

如此一来,当你依次编写 CSS 时,每个区块都可以自动继承在它之前区块的属性。这样就可以减少代码相互抵消的部分,减少某些特殊的问题,组成更理想的 CSS 结构。

关于这方面的更多信息,强烈推荐 Jonathan Snook 的 SMACSS

CSS 样式集分析

[selector]{
    [property]:[value];
    [<- Declaration ->]
}

[选择器]{
    [属性]:[值];
    [<- 声明 ->]
}

编写 CSS 样式时,我习惯遵守这些规则:

  • class 名称以连字符(-)连接,除了下文提到的 BEM 命名法;
  • 缩进 4 空格;
  • 声明拆分成多行;
  • 声明以相关性顺序排列,而非字母顺序;
  • 有前缀的声明适当缩进,从而对齐其值;
  • 缩进样式集从而反映 DOM;
  • 保留最后一条声明结尾的分号。

例如:

.widget{
    padding:10px;
    border:1px solid #BADA55;
    background-color:#C0FFEE;
    -webkit-border-radius:4px;
       -moz-border-radius:4px;
            border-radius:4px;
}
    .widget-heading{
        font-size:1.5rem;
        line-height:1;
        font-weight:bold;
        color:#BADA55;
        margin-right:-10px;
        margin-left: -10px;
        padding:0.25em;
    }

我们可以发现,.widget-heading.widget 的子元素,因为前者的样式集比后者多缩进了一级。这样通过缩进就可以让开发者在阅读代码时快速获取这样的重要信息。

我们还可以发现 .widget-heading 的声明是根据其相关性排列的:.widget-heading 是行间元素,所以我们先添加字体相关的样式声明,接下来是其它的。

以下是一个没有拆分成多行的例子:

.t10    { width:10% }
.t20    { width:20% }
.t25    { width:25% }       /* 1/4 */
.t30    { width:30% }
.t33    { width:33.333% }   /* 1/3 */
.t40    { width:40% }
.t50    { width:50% }       /* 1/2 */
.t60    { width:60% }
.t66    { width:66.666% }   /* 2/3 */
.t70    { width:70% }
.t75    { width:75% }       /* 3/4*/
.t80    { width:80% }
.t90    { width:90% }

在这个例子(来自inuit.css’s table grid system)中,将 CSS 放在一行内可以使得代码更紧凑。

命名规范

一般情况下我都是以连字符(-)连接 class 的名字(例如 .foo-bar 而非 .foo_bar.fooBar),不过在某些特定的时候我会用 BEM(Block, Element, Modifier)命名法。

BEM 命名法可以使得选择器更规范,更清晰,更具语义。

该命名法按照如下格式:

.block{}
.block__element{}
.block--modifier{}

其中:

  • .block 代表某个基本的抽象元素;
  • .block__element 代表构成 .block 的一个子元素;
  • .block--modifier 代表 .block 的某个不同状态或版本。

打个比方:

.person{}
.person--woman{}
    .person__hand{}
    .person__hand--left{}
    .person__hand--right{}

这个例子中我们描述的基本元素是一个人,然后这个人可能是一个女人。我们还知道人拥有手,这些是人体的一部分,而手也有不同的状态,如同左手与右手。

这样我们就可以根据亲元素来划定选择器的命名空间并传达该选择器的职能,例如根据这个选择器是一个子元素(__)还是其亲元素的不同状态(--)。

由此,.page-wrapper 是一个独立的选择器。这是一个符合规范的命名,因为它不是其它元素的子元素或其它状态;然而 .widget-heading 则与其它对象有关联,它应当是 .widget 的子元素,所以我们应当将其重命名为 .widget__heading

BEM 命名法虽然不太好看,而且相当冗长,但是它使得我们可以通过名称快速获知元素的功能和元素之间的关系。与此同时,BEM 语法中的重复部分非常有利于 gzip 的压缩算法。

无论你是否使用 BEM 命名法,你都应当确保 class 命名得当,力保一字不多、一字不少;将元素命名抽象化以提高复用性(例如 .ui-list.media)。子元素的命名则要尽量精准(例如 .user-avatar-link)。不用担心 class 名的数量或长度,因为写得好的代码 gzip 也能有效压缩。

HTML 中的 class

为了确保易读性,在 HTML 标记中用两个空格隔开 class 名,例如:

<div class="foo--bar  bar__baz">

增加的空格应当可以使得在使用多个 class 时更易阅读与定位。

JavaScript 钩子

切勿将标记 CSS 样式的 class 用作 JavaScript 钩子。把 JS 行为与样式混在一起将无法对其分别处理。

如果你要把 JS 和某些标记绑定起来的话,写一个 JS 专用的 class。简单地说就是划定一个前缀 .js- 的命名空间,例如 .js-toggle.js-drag-and-drop。这意味着我们可以通过 class 同时绑定 JS 和 CSS 而不会因为冲突而引发麻烦。

<th class="is-sortable  js-is-sortable">
</th>

上面的这个标记有两个 class,你可以用其中一个来给这个可排序的表格栏添加样式,用另一个添加排序功能。

I18n

虽然我(该 CSS Guideline 文档原作者 Harry Roberts)是个英国人,而且我一向拼写 colour 而非 color,但是为了追求统一,我认为在 CSS 中用美式拼法更佳。CSS 以及其它多数语言都是以美式拼法编写,所以如果在 .colour-picker{} 中写 color:red 就缺乏统一性。我以前主张同时用两种拼法,例如:

.color-picker,
.colour-picker{
}

但是我最近参与了一份规模庞大的 Sass 项目,这个项目中有许多的颜色变量(例如 $brand-color$highlight-color 等等),每个变量要维护两种拼法实在辛苦,要查找并替换时也需要两倍的工作量。

所以为了统一,把所有的 class 与变量都以你参与的项目的惯用拼法命名即可。

注释

我使用行宽不超过 80 字节的文档块风格注释:

/**
 * This is a docBlock style comment
 * 
 * This is a longer description of the comment, describing the code in more
 * detail. We limit these lines to a maximum of 80 characters in length.
 * 
 * We can have markup in the comments, and are encouraged to do so:
 * 
   <div class=foo>
       <p>Lorem</p>
   </div>
 * 
 * We do not prefix lines of code with an asterisk as to do so would inhibit
 * copy and paste.
 */


/**
 * 这是一个文档块(DocBlock)风格的注释。
 *
 * 这里开始是描述更详细、篇幅更长的注释正文。当然,我们要把行宽控制在 80 字节以内。
 *
 * 我们可以在注释中嵌入 HTML 标记,而且这也是个不错的办法:
 *
    <div class=foo>
        <p>Lorem</p>
    </div>
 *
 * 如果是注释内嵌的标记的话,在它前面不加星号,以免被复制进去。
 */

在注释中应当尽量详细描述代码,因为对你来说清晰易懂的内容对其他人可能并非如此。每写一部分代码就要专门写注释以详解。

注释的拓展用法

注释有许多很高级的用法,例如:

  • 准修饰选择器(Quasi-qualified selectors)
  • 代码标签
  • 继承标记

准修饰选择器(Quasi-qualified selectors)

你应当避免过分修饰选择器,例如如果你能写 .nav{} 就尽量不要写 ul.nav{}。过分修饰选择器将影响性能,影响 class 复用性,增加选择器私有度。这些都是你应当竭力避免的。

不过有时你可能希望告诉其他开发者 class 的使用范围。以 .product-page 为例,这个 class 看起来像是一个根容器,可能是 html 或者 body 元素,但是仅凭 .product-page 则无法判断。

我们可以在选择器前加上准修饰(即将前面的类型选择器注释掉)来描述我们规划的 class 作用范围:

/*html*/.product-page{}

这样我们就能准确获知该 class 的作用范围而不会影响复用性。

其它例子如:

/*ol*/.breadcrumb{}
/*p*/.intro{}
/*ul*/.image-thumbs{}

这样我们就能在不影响代码私有度的前提下获知 class 作用范围。

代码标签

如果你写了一组新样式的话,可以在它上面加上标签,例如:

/**
 * ^navigation ^lists
 */
.nav{}

/**
 * ^grids ^lists ^tables
 */
.matrix{}

这些标签可以使得其他开发者快速找到相关代码。如果一个开发者需要查找和列表相关的部分,他只要搜索 ^lists 就能快速定位到 .nav.matrix 以及其它相关部分。

继承标记

将面向对象的思路用于 CSS 编写的话,你经常能找到两部分 CSS 密切相关(其一为基础,其一为拓展)却分列两处。我们可以用继承标记来在原元素和继承元素之间建立紧密联系。这些在注释中的写法如下:

在元素的基本样式中:

/**
 * Extend `.foo` in theme.css
 */
 .foo{}

在元素的拓展样式中:

/**
 * Extends `.foo` in base.css
 */
 .bar{}

这样一来我们就能在两块相隔很远的代码间建立紧密联系。


编写 CSS

之前的章节主要探讨如何规划 CSS,这些都是易于量化的规则。本章将探讨更理论化的东西,也将探讨我们的态度与方法。

编写新组件

编写新组件时,要在着手处理 CSS 之前写好 HTML 部分。这可以令你准确判断哪些 CSS 属性可以继承,避免重复浪费。

先写标记的话,你就可以关注数据、内容与语义,在这之后再添加需要的 class 和 CSS 样式。

面向对象 CSS

我以面向对象 CSS 的方式写代码。我把组件分成结构(对象)与外观(拓展)。正如以下分析(注意此处并非示例):

.room{}

.room--kitchen{}
.room--bedroom{}
.room--bathroom{}

我们在屋子里有许多房间,它们都有共同的部分:地板、天花板、墙壁和门。这些共享的部分我们可以放到一个抽象的 .room{} class 中。不过我们还有其它与众不同的房间:一个厨房可能有地砖,卧室可能有地毯,洗手间可能没有窗户但是卧室会有,每个房间的墙壁颜色也许也会不一样。面向对象 CSS 的思路使得我们把相同部分抽象出来组成结构部分,然后用更具体的 class 来拓展这些特征并添加特殊的处理方法。

所以比起编写大量各自不同的模块,应当努力找出这些模块中重复的设计模式并将其抽象出来,写成一个可以复用的 class,将其用作基础然后编写其它拓展模块的特殊情形。

当你要编写一个新组件时,将其拆分成结构和外观。编写结构部分时用最通用 class 以保证复用性,编写外观时用更具体的 class 来添加设计方法。

布局

所有组件都不要声明宽度,而由其亲元素或格栅系统来决定。

坚决不要声明高度。高度应当仅仅用于尺寸已经固定的东西,例如图片和 CSS Sprite。在 puldiv 等元素上不应当声明高度。如果需要的话可以使用更加灵活的 line-height

格栅系统应当当作书架来理解。是它们容纳内容,而不是把它们本身当成内容装起来,正如你先搭起书架再把东西放进去。比起声明它们的尺寸,把格栅系统和元素的其它属性分来开处理更有助于布局,也使得我们的前端工作更高效。

你在格栅系统上不应当添加任何样式,它们仅仅是为布局而用。在格栅系统内部再添加样式。在格栅系统中任何情况下都不要添加盒模型相关属性。

UI 尺寸

我用很多方法设定 UI 尺寸,包括百分比,pxemrem 以及干脆什么都不用。

理想情况下,格栅系统应当用百分比设定。如上所述,因为我用格栅系统来固定栏宽和页宽,所以我可以不用理会元素的尺寸。

我用 rem 定义字号,并且辅以 px 以兼容旧浏览器。这可以兼具 em 和 px 的优势。下面是一个非常漂亮的 Sass Mixin,假设你在别处声明了基本字号(base-font-size)的话,用它就可以生成 rem 以及兼容旧浏览器的 px。

@mixin font-size($font-size){
    font-size:$font-size +px;
    font-size:$font-size / $base-font-size +rem;
}

我只在已经固定尺寸的元素上使用 px,包括图片以及尺寸已经用 px 固定的 CSS Sprite。

字号

我会定义一些与格栅系统原理类似的 class 来声明字号。这些 class 可以用于双重标题分级,关于这点请阅读 Pragmatic, practical font-sizing in CSS

简写

CSS 简写应当谨慎使用。

编写像 background: red; 这样的属性的确很省事,但是你这么写的意思其实是同时声明 background-image: none; background-position: top left; background-repeat: repeat; background-color: red;。虽然大多数时候这样不会出什么问题,但是哪怕只出一次问题就值得考虑要不要放弃简写了。这里应当改为 background-color: red;

类似的,像 margin: 0; 这样的声明的确简洁清爽,但是还是应当尽量写清楚。如果你只是想修改底边边距,就要具体一些,写成 margin-bottom: 0;

与此同时你需要声明的属性也要写清楚,不要因为简写而波及其它属性。例如如果你只想改掉底部的 margin,那就不要用会把其它边距也清零的 margin: 0

简写虽然好,但也很容易滥用。

ID

在我们开始处理选择器之前,牢记这句话:

在 CSS 里坚决不要用 ID。

在 HTML 里 ID 可以用于 JS 以及锚点定位,但是在 CSS 里只要用 class,一个 ID 也不要用。

Class 的优势在于复用性,而且私有度也并不高。在项目中私有度非常容易导致问题,所以将其降低就尤为重要。ID 的私有度是 class 的 255 倍,所以在 CSS 中坚决不要使用。

选择器

务必保持选择器简短高效。

通过页面元素位置而定位的选择器并不理想。例如 .sidebar h3 span{} 这样的选择器就是定位过于依赖相对位置,如果把 span 移到 h3 和 sidebar 外面时就很难保持其样式。

结构复杂的选择器将会影响性能。选择器结构越复杂(如 .sidebar h3 span 为三层,.content ul p a 是四层),浏览器的开销就越大。

尽量使得样式不依赖于其定位,尽量保持选择器简洁清晰。

作为一个整体,选择器应当尽量简短(例如只有一层结构),但是 class 名则不应当过于简略,例如 .user-avatar 就远比 .usr-avt 好。

牢记:class 无所谓是否语义化;应当关注它们是否合理。不要强调 class 名要符合语义,而要注重使用合理且不会过时的名称。

过度修饰的选择器

由前文所述,过度修饰的选择器并不理想。

过度修饰的选择器是指像 div.promo 这样的。很可能你只用 .promo 也能得到相同的效果。当然你可能偶尔会需要用元素类型来修饰 class(例如你写了一个 .error 而且想让它在不同的元素类型中显示效果不一样,例如 .error { color: red; }div.error { padding: 14px; }),但是大多数时候还是应当尽量避免。

再举一个修饰过度的选择器例子,ul.nav li a{}。如前文所说,我们马上就可以删掉 ul 因为我们知道 .nav 是个列表,然后我们就可以发现 a 一定在 li 中,所以我们就能将这个选择器改写成 .nav a{}

选择器性能

虽然浏览器性能日渐提升,渲染 CSS 速度越来越快,但是你还是应当关注效率。使用简短、没有嵌套的选择器,不使用全局选择器(* {})作为核心选择器,避免使用日渐复杂的 CSS3 新选择器可以避免这样的问题。

译注,核心选择器:浏览器解析选择器为从右向左的顺序,最右端的元素是样式生效的元素,是为核心选择器。

使用 CSS 选择器的目的

比起努力运用选择器定位到某元素,更好的办法是给你想要添加样式的元素直接添加一个 class。我们以 .header ul {} 这样一个选择器为例。

假定这个 ul 就是这个网站的全站导航,它位于 header 中,而且目前为止是 header 中唯一的 ul 元素。.header ul{} 的确可以生效,但是这样并不是好方法,它很容易过时,而且非常晦涩。如果我们在 header 中再添加一个 ul 的话,它就会套用我们给这个导航部分写的样式,哪怕我们设想的不是这个效果。这意味着我们要么要重构许多代码,要么给后面的 ul 新写许多样式来抵消之前的影响。

你的选择器必须符合你要给这个元素添加样式的原因。思考一下,「我定位到这个元素,是因为它是 .header 下的 ul,还是因为它是我的网站导航?」这将决定你应当如何使用选择器。

确保你的核心选择器不是类型选择器,也不是高级对象或抽象选择器。例如你在我们的 CSS 中肯定找不到诸如 .sidebar ul {} 或者 .footer .media {} 这样的选择器。

表达清晰:直接找到你要添加样式的元素,而非其亲元素。不要想当然地认为 HTML 不会改变。用 CSS 直接命中你需要的元素,而非投机取巧。

完整内容请参考我的文章 Shoot to kill; CSS selector intent

!important

只在起辅助作用的 class 上用 !important。用 !important 提升优先级也可以,例如如果你要让某条规则一直生效的话,可以用 .error { color:red!important; }

避免主动使用 !important。例如 CSS 写得很复杂时不要用它来取巧,要好好整理并重构之前的部分,保持选择器简短并且避免用 ID 将效果拔群。

魔数与绝对定位

魔数(Magic Number)是指那些「凑巧有效果」的数字,使用魔数非常不好,因为它们只是治标不治本而且缺乏拓展性。

例如使用 .dropdown-nav li:hover ul { top: 37px; } 把下拉菜单移动下来远非良策,因为这里的 37px 就是个魔数。37px 会生效的原因是因为这时 .dropbox-nav 碰巧高 37px 而已。

这时你应该用 .dropdown-nav li:hover ul { top: 100%; },也即无论 .dropbox-down 多高,这个下拉菜单都会往下移动 100%。

每当你要在代码中放入数字的时候,请三思而行。如果你能用一个关键字(例如 top: 100% 意即「从上面拉到最下面」)替换之,或者有更好的解决方法的话,就尽量避免直接出现数字。

你在 CSS 中留下的每一个数字,都是你许下而不愿遵守的承诺。

条件判断

专门为 IE 写的样式基本上都是可以避免的,唯一需要为 IE 专门处理的是为了处理 IE 不支持的内容(例如 PNG)。

简而言之,如果你重构 CSS 的话,所有的布局和盒模型都不用额外兼容 IE。也就是说你基本上不用 <!--[if IE 7]> element{ margin-left:-9px; } < ![endif]--> 或者类似的兼容 IE 的写法。

Debugging

如果你要解决 CSS 问题的话,先把旧代码拿掉再写新的。如果旧的 CSS 中有问题的话,写新代码是解决不了的。

把 CSS 代码和 HTML 部分删掉,直到没有 BUG 为止,然后你就知道问题出在哪里了。

有时候写上一个 overflow: hidden 或者其它能把问题藏起来的代码的确效果立竿见影,但是 overflow 方面可能根本就没问题。所以要治本,而不是单纯治标

CSS 预处理器

我用 Sass。使用时应当灵活运用。用 Sass 可以令你的 CSS 更强大,但是不要嵌套得太复杂。在 Vanilla CSS 中,只在必要的地方用嵌套即可,例如:

.header{}
.header .site-nav{}
.header .site-nav li{}
.header .site-nav li a{}

这样的写法在普通 CSS 里完全用不到。以下为不好的 Sass 写法:

.header{
    .site-nav{
        li{
            a{}
        }
    }
}

如果你用 Sass 的话,尽量这么写:

.header{}
.site-nav{
    li{}
    a{}
}


via CSS-Guidelines

查看原文

三月沙 关注了用户 · 2014-05-13

关注 39

三月沙 收藏了文章 · 2014-05-13

在js中的深复制实现方法

针对本话题,我在2015年5月发布了新的文章:深入剖析 JavaScript 的深复制

要实现深复制有很多办法,比如最简单的办法有:

var cloneObj = JSON.parse(JSON.stringify(obj));

上面这种方法好处是非常简单易用,但是坏处也显而易见,这会抛弃对象的constructor,也就是深复制之后,无论这个对象原本的构造函数是什么,在深复制之后都会变成Object。另外诸如RegExp对象是无法通过这种方式深复制的。

所以这里我将介绍一种,我自认为很优美的深复制方法,当然可能也存在问题。如果你发现了我的实现方法有什么问题,请及时让我知道~

先决条件:
1. 对于任何对象,它可能的类型有Boolean, Number, Date, String, RegExp, Array 以及 Object(所有自定义的对象全都继承于Object
2. 我们必须保留对象的构造函数信息(从而使新对象可以使用定义在prototype上的函数)

最重要的一个函数:

Object.prototype.clone = function () {
    var Constructor = this.constructor;
    var obj = new Constructor();

    for (var attr in this) {
        if (this.hasOwnProperty(attr)) {
            if (typeof(this[attr]) !== "function") {
                if (this[attr] === null) {
                    obj[attr] = null;
                }
                else {
                    obj[attr] = this[attr].clone();
                }
            }
        }
    }
    return obj;
};

定义在Object.prototype上的clone()函数是整个方法的核心,对于任意一个非js预定义的对象,都会调用这个函数。而对于所有js预定义的对象,如Number,Array等,我们就要实现一个辅助clone()函数来实现完整的克隆过程:

/* Method of Array*/
Array.prototype.clone = function () {
    var thisArr = this.valueOf();
    var newArr = [];
    for (var i=0; i<thisArr.length; i++) {
        newArr.push(thisArr[i].clone());
    }
    return newArr;
};

/* Method of Boolean, Number, String*/
Boolean.prototype.clone = function() { return this.valueOf(); };
Number.prototype.clone = function() { return this.valueOf(); };
String.prototype.clone = function() { return this.valueOf(); };

/* Method of Date*/
Date.prototype.clone = function() { return new Date(this.valueOf()); };

/* Method of RegExp*/
RegExp.prototype.clone = function() {
    var pattern = this.valueOf();
    var flags = '';
    flags += pattern.global ? 'g' : '';
    flags += pattern.ignoreCase ? 'i' : '';
    flags += pattern.multiline ? 'm' : '';
    return new RegExp(pattern.source, flags);
};

可能直接定义在预定义对象的方法上,让人感觉会有些问题。但在我看来这是一种优美的实现方式。

同时我也在开发一个插件,主要的思想也就是扩展预定义对象的方法。
这个插件叫JustJSGithub项目地址
有以下一些特性:
1. 同时支持Web前端和node.js使用。
2. 直接对预定义对象的方法进行扩展
3. 使用 J(function(){...}) 语句块,决不污染全局命名空间。
目前只写了一小部分,同时也写了些简单的文档,有兴趣的同学可以看一下,也可以加入我,Fork我的项目,喜欢的同学还可以给Star

查看原文

三月沙 收藏了文章 · 2014-05-05

少有人知的 GitHub 使用技巧

GitHub 大家常上吧?可是使用 GitHub 的各种小窍门你就不一定知道了。本文将各种使用 GitHub 的小窍门分享给大家。

diff时忽略空格

有些修改只是增减了空格,在URL中添加?w=1就可以忽略。

查看某个作者的提交历史

在URL中添加?author=username,例如:

https://github.com/rails/rails/commits/master?author=dhh

比较版本

使用类似如下的URL比较分支:

https://github.com/rails/rails/compare/master...4-1-stable

同样可以使用一下格式:

https://github.com/rails/rails/compare/master@{1.day.ago}...master
https://github.com/rails/rails/compare/master@{2014-10-04}...master

如果想和派生仓库比较,加上派生仓库名作前缀即可:

https://github.com/rails/rails/compare/byroot:master...master

通过 HTML 方式嵌入 Gist

Gists是 GitHub 推出的基于 Git 的代码片段服务。Gists页面提供JavaScript代码,可以将 Gist 嵌入到其他站点。但是很多站点粘贴 JavaScript 无效,这时候你可以在 Gist URL 后附加.pibb,得到一个纯 HTML 的版本,然后就可以复制粘贴 HTML 源码到其他网站了。例如 https://gist.github.com/tiimgreen/10545817.pibb

Git.io

Git.io 是适用于 GitHub 的短网址服务。

当然,为了逼格方便,也可以使用Curl访问:

$ curl -i http://git.io -F "url=https://github.com/..."
HTTP/1.1 201 Created
Location: http://git.io/abc123

$ curl -i http://git.io/abc123
HTTP/1.1 302 Found
Location: https://github.com/...

你甚至可以指定短网址的字段:

$ curl -i http://git.io -F "url=https://github.com/technoweenie" \
    -F "code=t"
HTTP/1.1 201 Created
Location: http://git.io/t

高亮行

例如,在 URL 中加上 #L52 可以高亮第52行。或者你也可以直接点击行数。

多行高亮同样支持。你可以使用类似#L53-L60格式,或者在按住shift的同时点击。

https://github.com/rails/rails/blob/master/activemodel/lib/active_model.rb#L53-L60

快速引用

你可以选中别人的评论文字,然后按r,这些内容会以引用的形式被复制在文本框中:

任务列表

在工单或合并请求中,你可以使用任务列表语法:

- [ ] Be awesome
- [ ] Do stuff
- [ ] Sleep

勾选之后,会更新 Markdown:

- [x] Be awesome
- [x] Do stuff
- [ ] Sleep

合并请求的 diff 和 patch

可以在 URL 后添加 .diff.patch,以对应的模式查看合并请求:

https://github.com/tiimgreen/github-cheat-sheet/pull/15
https://github.com/tiimgreen/github-cheat-sheet/pull/15.diff
https://github.com/tiimgreen/github-cheat-sheet/pull/15.patch

结果是纯文本的:

diff --git a/README.md b/README.md
index 88fcf69..8614873 100644
--- a/README.md)
+++ b/README.md
@@ -28,6 +28,7 @@ All the hidden and not hidden features of Git and GitHub. This cheat sheet was i
 - [Merged Branches](#merged-branches)
 - [Quick Licensing](#quick-licensing)
 - [TODO Lists](#todo-lists)
+- [Relative Links](#relative-links)
 - [.gitconfig Recommendations](#gitconfig-recommendations)
     - [Aliases](#aliases)
     - [Auto-correct](#auto-correct)
@@ -381,6 +382,19 @@ When they are clicked, they will be updated in the pure Markdown:
 - [ ] Sleep

(...)

编撰 SegmentFault
参考 github-cheat-sheet

查看原文

三月沙 收藏了文章 · 2014-05-05

for循环中到底发生了什么--Python提高班

我们都知道for in在代码中出现的次数相当的频繁, 那么你知道for in循环中到底发生了什么吗?

答: 当我们调用一个for x in dataContainer的时候, 实际上是先调用了dataContainer__iter__()方法来获得它的iterator(迭代器), 然后不断的调用next()方法, Python3.x里面是__next__(), 直到迭代器抛出StopIteration的异常, 停止

什么是iterable和iterator

上一篇如何理解yield中已经对interable已经略有介绍, 我们可以简单的认为可以使用for in的都是iterable的, 但是这只是从使用场景来说的, 下面就从它内部来说下

  • iterable: 如果一个数据容器定义了一个__iter__()方法, 那么它就是可迭代的
  • iterator: 如果一个object支持迭代协议, 也就是: 1. 定义一个__iter__返回它自身 2. 定义一个next()方法, 每次被调用的时候返回下一个值

很明显listdictiterable的, 但它不是iterator

>>> a = [1,2,3]
>>> a.__iter__
<method-wrapper '__iter__' of list object at 0x2ad2cf8>
>>> a.next()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'list' object has no attribute 'next'

但是通过__iter__获得的就是它们的iterator迭代器

>>> ai = a.__iter__()
>>> ai
<listiterator object at 0x2dfe310>
>>> ai.next()
1
>>> ai.__iter__() is ai
True

如何构建iterable的容器

class MyList(list):
    def __iter__(self):
        return MyListIter(self)

class MyListIter(object):
    """一个实现List的Iterator的 Demo Class"""
    def __init__(self, lst):
        self.lst = lst
        self.i = -1
    def __iter__(self):
        return self
    def next(self):
        if self.i<len(self.lst)-1:
            self.i += 1         
            return self.lst[self.i]
        else:
            raise StopIteration

if __name__ == '__main__':
    a = MyList([1, 2, 3, 4])
    ia = iter(a)
    print 'type(a): %r, type(ia): %r' %(type(a), type(ia))
    for i in a: 
        print i,

上面的一段代码中 MyListIter 就实现了 MyList的迭代器, 运行结果应该是

type(a): <class '__main__.MyList'>, type(ia): <class '__main__.MyListIter'>
1 2 3 4

如何把我们的变量iterable

比如我们有一个需求, 需要返回所有0~4中数字和a~e中字母组合情况, 一般我们可能会这样写

class Combinations:
    def __init__(self):
        self.combs = []
        for x in range(5):
            for y in ['a', 'b', 'c', 'd', 'e']:
                self.combs.append("%s%s" % (x, y))

for c in Combinations().combs: print c

这样的话, 我们每次都要调用Combinations的'combs'才能拿到所有的组合情况, 显然每次暴露combs出来非常的不优雅, 为什么不能for c in Combinations()这样呢?

当然可以, 定义一个__iter__方法返回combs的迭代器就可以了

class Combinations:
    def __init__(self):
        self.combs = []
        for x in range(5):
            for y in ['a', 'b', 'c', 'd', 'e']:
                self.combs.append("%s%s" % (x, y))
    def __iter__(self):
        return iter(self.combs)

for c in Combinations(): print c

参考: Understanding Python Iterables and Iterators

查看原文

三月沙 收藏了文章 · 2014-04-16

开启iOS/Mac开发之旅,过来人告诉你16件事

我曾向iOS开发者推荐了Twitter上最值得关注的30个人,收到了不少开发者的反馈,受此鼓舞,我向知名iOS开发者和设计师询问了这样一个问题–回到你开始iOS/Mac app开发的时候,你以现在的角度会给“最初的你”哪些建议。

Matt Gemmell:每种app都已经做过几百遍了,但是仍有空间留给那些更好的app。

Instinctive Code 创始人

总有空间留给更好的软件。iOS平台上每种app都已经做过几百遍了,但是仍有空间留给那些更简洁、更出色以及更易于使用的应用。即便完全饱和的类别,比如清单和文本编辑器。如果你认为你的创意是独一无二的,并非常有价值,那你就放心去开发、发布。如果有一些功能是你想要的,那么其他人也会想要。

推特:http://www.twitter.com/mattgemmell

博客:http://mattgemmell.com/2013/09/20/nets/

Ray Wenderlich:不要期待一夜成名

iPhone开发工作室–Razeware的创办人,Raywenderlich.com的管理员。

不要期望一夜成名,持续学习成长以及开发app–下个总比上个好。

推特:https://www.twitter.com/rwenderlich

博客:http://www.raywenderlich.com/

Mattt Thompson:做一款令人惊讶的app变得前所未有的便捷–go for it!

热门开源项目AFNetworking & NSHipster的作者。

当我开始iOS开发时,我是一个有着4年Ruby & Rails开发经历的程序员。我在语言语法方面花了大量时间,并且努力掌握所有的系统框架。那时候一些基本任务的教程都是非常稀少的,也几乎没有什么开源项目可言,我不得不依赖苹果的示例代码。

现在,我很高兴的一点是iOS开发者有大量优秀资源和开源项目库可以使用。对于教程和示例代码,我推荐Ray Wenderlich的网站和NSScreencast。对于那些希望学习更高级别技术的开发者而言,也可以参看NSHipster,我每周会在上边写一些Objective-C和Cocoa相关的文章。开源方面,CocoaPods是基本的。不仅是因为CocoaPods可以让你毫不费力地进行依赖关系管理,它还是款令人惊讶的用以发现新库的工具。自从第一次使用这个工具,我就发现了很多改变。做一款令人惊讶的app变得前所未有的便捷,所以我对那些开始进行iOS开发或者考虑iOS开发的人的建议就是“go for it!”。

GitHub:https://github.com/mattt

推特:https://www.twitter.com/mattt

博客:http://mattt.me/

Dan Rowinski:对设计和开发要同等重视

ReadWriteWeb移动领域编辑。

在开发规划过程中,对设计和开发要同等重视。app开发者常常过分关注应用能做什么,而不想想应用看起来怎么样,以及用户如何与应用进行交互。如果你创建的杀手级功能非常难用,那么用户将很难再返回你的应用中。另一方面,如果你创建的应看起来非常了不起,但却总是崩溃,或者不能做到承诺中的那样,用户也会选择“逃跑”。看看Path和Instagram,它们在平衡设计和功能方面做的非常好。

推特:http://clicktotweet.com/Vebl9

Jeremy Olson:不要把目标锁定在仅能支付99美分的利基市场

Tapity创始人。

不要忽略理念。我们经常会听过创意无关紧要这样的说法,关键是执行力。我过去也这样坚定地认为,但后来发生了一些事情。我的第二款app–Languages一天赚的钱比我第一款app在两年内赚钱的钱还多,到底怎么回事?这两款应用都是执行力非常好的应用,Grades甚至还获得了苹果的设计大奖。这两款应用都进行了非常好的市场推广,都被苹果和新闻媒体推荐过,但是差别在哪里呢?–理念。

Grades被局限在它所服务的狭小的利基市场–那些关心他们分数的大学生(比我们想象中的利基市场要小)。Languages用户要更加普遍些,几乎大部分人对离线翻译类app都比较感兴趣。不要误解,正如下边这个图表所列举的,利基市场app肯定可以获得收益,但是它们所处的利基市场必须十分关注这个app,并且愿意为它付费。不幸的是,大部分app犯的一个错误是,它们把目标锁定在仅能支付99美分的利基市场,导致了一个令人失望的商业等式。

推特:http://www.twitter.com/jerols

Marco Arment:雇一个设计师

Marco Arment(出生于1982年6月11日)是一个美国籍的iOS和web开发者,同样也是一个科技写手和杂志编辑,居住在纽约韦斯切斯特。Marco Arment是Tumblr的联合创办人,并开发了Instapaper。

推特:http://www.twitter.com/marcoarment

博客:http://www.marco.org/

Peter Steinberger:不要失去信心,多冒险。

PSPDFKit的创办者 。

在2009年,我写了一个从网上抓取内容的社交app,从中我体会到了很多艰难的事情。这款社交app非常成功,我也为这个平台着迷,但就在参加我的第一届WWDC前不久,苹果关闭了这个平台。我当时非常崩溃,花了很长时间才摆脱这种情况,并鼓起勇气放弃了自己的日常工作,做一名自由职业者。再后来,我同样艰难地放弃了自由职业者,选择做自己的事情。这是一场炼狱般的经历,把我带到了现在所处的位置,并带给我一份可持续发展的业务。

推特:http://clicktotweet.com/b6m6t

Marc Edwards:学习的最好方法是分享和讨论

Bjango首席设计师。

合作关系至关重要。找到优秀的人并使之和你一同工作,他们可以补充你的技能。尊重他们并学习他们的技术,从而能使用相同的语言进行交谈。对iOS设计师来说,这意味着需要熟悉Xcode和苹果在UI元素方面使用的命名规则。还需要你参加开发者的相关会议,遇到不错的开发者。

最快的学习方法是分享和讨论技巧和技术。写作有助于形成固体意见,并作为一个伟大的方式来检查你是否以正确的方式工作(如果你的工作方式不正确,你得确保有人能让你知道)。策略很重要,我喜欢读Asymco.com, ben-evans.com以及收听The Critical Path。这一点在最初并不是显而易见的,但对行业趋势的深入理解可以让你洞察设计的发展方向。当事情更可预见时,你也可以制定相应的计划。

推特:http://www.twitter.com/marcedwards

Aaron Hillegass:不要“爱上”你的想法,多找找哪些方式可以证明你是错的。

Big Nerd Ranch的首席学习官

成为一名开发者需要一定的智慧和信心。对于刚入行的开发者来说,智慧和信心反倒会成为缺点,新手开发者会“爱上”他们的想法。经验丰富的程序员经过多年的试错,会更怀疑他们的假定。所以如果你刚开始了程序员生涯,那你需要寻找可以证明你错误想法的简单方法,不要相信你想到的一切。

推特:http://www.twitter.com/aaronhillegass

博客:http://www.bignerdranch.com/instructors/hillegass.shtml

Dave Verwer:先确定用户,再开发app以满足用户的需求。

iOS Dev Weekly的创办者,是一位iPhone和iPad开发者以及培训师。

推特:http://www.twitter.com/daveverwer

Daniel Jalkut:把成功的人和其他人区别开来的方法就是不断尝试

Red Sweater Software创始人,Core Intuition协办者

把成功的人和其他人区别开来的方法就是不断尝试,即便在别人放弃的时候。知识和聪明是达成目标的关键因素,但与毅力和不服输的精神相比,知识和聪明相形失色,

推特:http://www.twitter.com/danielpunkass

博客:http://www.red-sweater.com/blog/

Robin Raszka:要专注为真实用户设计最佳的体验

Tapmates和Pttrns.com的联合创始人。

不要努力为了做下一个Jony Ive,要专注为真实用户设计最佳的体验。不要停止学习新知识,忘掉线框图和把截图上传至Dribbble,相反学习如何做自己的作品原型,尽快让它在设备上运行。

博客:http://lkd.to/robinraszka

推特:http://www.twitter.com/robinraszka

David Smith:为了让这个过程变得令人愉快,我们要享受这个过程,喜欢这个过程中遇到的人。

FeedWrangler创办人, 主办Developing Perspective

确保你了解你自己定义的成功看起来是什么样子的,如果这唯一跟金融财务相关,那么这是一个残酷的市场。为了让这个过程变得令人愉快,我们要享受这个过程,喜欢这个过程中遇到的人。

博客:http://david-smith.org/

推特:http://www.twitter.com/X75XDavidSmith

Dan Counsell:有时候更少的功能可以是你最大的竞争优势

Realmac Software 创始人,知名应用clear的开发者。

通过移除所有不必要的元素和功能来让产品保持专注。有时候更少的功能可以是你最大的竞争优势。

推特:http://www.twitter.com/dancounsell

博客:http://dancounsell.com/about

Craig Hockenberry:不要害怕遇见他人,要克服自己内向的性格。

他开发的Twitterific是Twitter最漂亮的iPhone客户端之一,也是Iconfactory网站的负责人之一,曾写过多本开发相关的书籍。

当我第一次为Mac开发软件时,我非常幸运地和一些天才设计师一起工作,比如Jeffrey Zeldman和Iconfactory上的一些人。后来我和Jeffrey分道扬镳,但是我仍不能想象没有Iconfactory上同事的帮忙我如何做产品。在你开发产品的过程中,有一位合作者非常重要。再从深层次考虑,不要害怕遇见他人,要克服自己内向的性格,并花时间建立和同事设计师、开发者之间的友好关系,你可以从别人身上学习到很多东西。在开始一个谈话之前,先弄清楚有哪些是你知道而别人不知道的东西。

推特:http://www.twitter.com/chockenberry

博客:http://furbo.org/

Rene Ritchie:开发、设计以及市场营销是不同的,但都需要同样重要的技能要求来确保成功。

iMore专栏作者 @MobileNations

给予编码和设计,产品和营销同样程度的重视。开发、设计以及市场营销是不同的,但都需要同样重要的技巧来确保成功。如果你不会编码,那就雇佣最好的程序员。这一点对设计和市场营销也一样。任何人都有获得幸运的可能,但是你越聪明,就会越幸运。

推特:http://www.twitter.com/reneritchie


原文 13 Things You Must Know When Starting Out in iOS/Mac Development
翻译 cocoachina

查看原文

三月沙 收藏了文章 · 2014-01-04

9个助力CSS开发的网站

CSS3 是对 CSS 规范的一个很大的改善和增强,它使得 Web 开发人员可以很容易的在网站中加入时尚的效果。本文收集了9款有用的开发工具推荐给大家。

CSS3 Pie

css3 pie

使用CSS3 Pie可以让IE6至IE9版本实现大多数的CSS3修饰特性,如圆角、阴影、渐变等等。

CSS3 Pie

CSS3 Builder

CSS3 Builder

使用这款工具可以制作出漂亮的CSS3盒子,像是Photoshop制作出来的效果。

CSS3 Builder

CSS3 Drop shadow generator

CSS3 Drop shadow generator

一款很棒的阴影效果生成工具,自动生成相应的代码。

CSS3 Drop shadow generator

Border Radius

Border Radius

一款在线CSS3圆角工具,四个角输入值就能生成对应的效果和代码。

Border Radius.com

Button Maker

Button Maker

一款非常不错的CSS3按钮制作工具。

Button Maker

CSS3 Generator

CSS3 Generator

非常不错的CSS3代码生成器,带预览效果。

CSS3 Generator

Modernizr

Modernizr

一个非常有用的用于 CSS3 和 HTML5 特性检测的JS库

Modernizr

HTML5 & CSS3 Support

HTML 5 & CSS3 Support

一个非常不错的HTML5 & CSS3 兼容性在线检测网站。

HTML5 & CSS3 Support

CSS3 Gradient Generator

CSS3 Gradient Generator

一款非常棒的CSS3渐变效果制作工具。

CSS3 Gradient Generator

CSS3 Please

CSS3 Please

非常帅的一款CSS3工具,可修改代码,即时预览。

CSS3 Please

原文 10+ useful tools to simplify CSS3 development
编译 梦想天空

查看原文

三月沙 收藏了文章 · 2014-01-04

像老大一样优化 Python

我们应该忘掉一些小的效率问题,在 97% 的情况下是这么说的:过早优化是万恶之源。—— Donald Knuth

如果不首先想想这句Knuth的名言,就开始进行优化工作是不明智的。可是,你很快写出来加入一些特性的代码,可能会很丑陋,你需要注意了。这篇文章就是为这时候准备的。

那么接下来就是一些很有用的工具和模式来快速优化Python。它的主要目的很简单:尽快发现瓶颈,修复它们并且确认你修复了它们。

写一个测试

在你开始优化前,写一个高级测试来证明原来代码很慢。你可能需要采用一些最小值数据集来复现它足够慢。通常一两个显示运行时秒的程序就足够处理一些改进的地方了。

有一些基础测试来保证你的优化没有改变原有代码的行为也是很必要的。你也能够在很多次运行测试来优化代码的时候稍微修改这些测试的基准。

那么现在,我们来来看看优化工具把。

简单的计时器

计时器很简单,这是一个最灵活的记录执行时间的方法。你可以把它放到任何地方并且副作用很小。运行你自己的计时器非常简单,并且你可以将其定制,使它以你期望的方式工作。例如,你个简单的计时器如下:

import time

def timefunc(f):
    def f_timer(*args, **kwargs):
        start = time.time()
        result = f(*args, **kwargs)
        end = time.time()
        print f.__name__, 'took', end - start, 'time'
        return result
    return f_timer

def get_number():
    for x in xrange(5000000):
        yield x

@timefunc
def expensive_function():
    for x in get_number():
        i = x ^ x ^ x
    return 'some result!'

# prints "expensive_function took 0.72583088875 seconds"
result = expensive_function()

当然,你可以用上下文管理来让它功能更加强大,添加一些检查点或者一些其他的功能:

import time

class timewith():
    def __init__(self, name=''):
        self.name = name
        self.start = time.time()

    @property
    def elapsed(self):
        return time.time() - self.start

    def checkpoint(self, name=''):
        print '{timer} {checkpoint} took {elapsed} seconds'.format(
            timer=self.name,
            checkpoint=name,
            elapsed=self.elapsed,
        ).strip()

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.checkpoint('finished')
        pass

def get_number():
    for x in xrange(5000000):
        yield x

def expensive_function():
    for x in get_number():
        i = x ^ x ^ x
    return 'some result!'

# prints something like:
# fancy thing done with something took 0.582462072372 seconds
# fancy thing done with something else took 1.75355315208 seconds
# fancy thing finished took 1.7535982132 seconds
with timewith('fancy thing') as timer:
    expensive_function()
    timer.checkpoint('done with something')
    expensive_function()
    expensive_function()
    timer.checkpoint('done with something else')

# or directly
timer = timewith('fancy thing')
expensive_function()
timer.checkpoint('done with something')

计时器还需要你做一些挖掘。包装一些更高级的函数,并且确定瓶颈在哪,然后深入的函数里,能够不停的重现。当你发现一些不合适的代码,修复它,然后测试一遍以确认它被修复了。

一些小技巧:不要忘了好用的timeit模块!它对小块代码做基准测试而不是实际调查更加有用。

  • Timer 优点:很容易理解和实现。也非常容易在修改后进行比较。对于很多语言都适用。
  • Timer 缺点:有时候对于非常复杂的代码有点过于简单,你可能会花更多时间放置或移动引用代码而不是修复问题!

内建优化器

启用内建的优化器就像是用一门大炮。它非常强大,但是有点不太好用,使用和解释起来比较复杂。

你可以了解更多关于profile模块的东西,但是它的基础是非常简单的:你能够启用和禁用优化器,而且它能打印所有的函数调用和执行时间。它能给你编译和打印出输出。一个简单的装饰器如下:

import cProfile

def do_cprofile(func):
    def profiled_func(*args, **kwargs):
        profile = cProfile.Profile()
        try:
            profile.enable()
            result = func(*args, **kwargs)
            profile.disable()
            return result
        finally:
            profile.print_stats()
    return profiled_func

def get_number():
    for x in xrange(5000000):
        yield x

@do_cprofile
def expensive_function():
    for x in get_number():
        i = x ^ x ^ x
    return 'some result!'

# perform profiling
result = expensive_function()

在上面代码的情况下,你应该看到有些东西在终端打印出来,打印的内容如下:

5000003 function calls in 1.626 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
  5000001    0.571    0.000    0.571    0.000 timers.py:92(get_number)
        1    1.055    1.055    1.626    1.626 timers.py:96(expensive_function)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

你可以看到,它给出了不同函数的调用次数,但它遗漏了一些关键的信息:是哪个函数让运行这么慢?

可是,这对于基础优化来说是个好的开始。有时候甚至能用更少的精力找到解决方案。我经常用它来在深入挖掘究竟是哪个函数慢或者调用次数过多之前来调试程序。

内建优点:没有额外的依赖并且非常快。对于快速的高等级检查非常有用。
内建缺点:信息相对有限,需要进一步的调试;报告有点不太直接,尤其是对于复杂的代码。

Line Profiler

如果内建的优化器是一门大炮,那么line profiler可以看作是一门离子加农炮。它非常的重量级和强大。

在这个例子里,我们会用非常棒的line_profiler库。为了容易使用,我们会再次用装饰器包装一下,这种简单的方法也可以防止把它放在生产代码里。

try:
    from line_profiler import LineProfiler

    def do_profile(follow=[]):
        def inner(func):
            def profiled_func(*args, **kwargs):
                try:
                    profiler = LineProfiler()
                    profiler.add_function(func)
                    for f in follow:
                        profiler.add_function(f)
                    profiler.enable_by_count()
                    return func(*args, **kwargs)
                finally:
                    profiler.print_stats()
            return profiled_func
        return inner

except ImportError:
    def do_profile(follow=[]):
        "Helpful if you accidentally leave in production!"
        def inner(func):
            def nothing(*args, **kwargs):
                return func(*args, **kwargs)
            return nothing
        return inner

def get_number():
    for x in xrange(5000000):
        yield x

@do_profile(follow=[get_number])
def expensive_function():
    for x in get_number():
        i = x ^ x ^ x
    return 'some result!'

result = expensive_function()

如果你运行上面的代码,你就可以看到一下的报告:

Timer unit: 1e-06 s

File: test.py
Function: get_number at line 43
Total time: 4.44195 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    43                                           def get_number():
    44   5000001      2223313      0.4     50.1      for x in xrange(5000000):
    45   5000000      2218638      0.4     49.9          yield x

File: test.py
Function: expensive_function at line 47
Total time: 16.828 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    47                                           def expensive_function():
    48   5000001     14090530      2.8     83.7      for x in get_number():
    49   5000000      2737480      0.5     16.3          i = x ^ x ^ x
    50         1            0      0.0      0.0      return 'some result!'

你可以看到,有一个非常详细的报告,能让你完全洞悉代码运行的情况。和内置的 cProfiler 不同,它能计算话在语言核心特性的时间,比如循环和导入并且给出在不同的行花费的时间。

这些细节能让我们更容易理解函数内部。如果你在研究某个第三方库,你可以直接将其导入并加上装饰器来分析它。

一些小技巧:只装饰你的测试函数并将问题函数作为接下来的参数。

  • Line Profiler 优点:有非常直接和详细的报告。能够追踪第三方库里的函数。
  • Line Profiler 缺点:因为它会让代码比真正运行时慢很多,所以不要用它来做基准测试。这是额外的需求。

总结和最佳实践

你应该用更简单的工具来对测试用例进行根本的检查,并且用更慢但能显示更多细节的line_profiler来深入到函数内部。

九成情况下,你可能会发现在一个函数里循环调用或一个错误的数据结构消耗了90%的时间。一些调整工具是非常适合你的。

如果你仍然觉得这太慢,而是用一些你自己的秘密武器,如比较属性访问技术或调整平衡检查技术。你也可以用如下的方法:

1.忍受缓慢或者缓存它们

2.重新思考整个实现

3.更多使用优化的数据结构

4.写一个C扩展

注意了,优化代码是种罪恶的快感!用合适的方法来为你的Python代码加速很有意思,但是注意不要破坏了本身的逻辑。可读的代码比运行速度更重要。先把它缓存起来再进行优化其实更好。


原文: Profiling Python Like a Boss
转载翻译自: 伯乐在线 - 贱圣OMG

查看原文

三月沙 收藏了文章 · 2014-01-04

Device.js——检测设备平台、操作系统的Javascript 库

Device.js 是一个可以让你检测设备的平台,操作系统和方向 JavaScript 库,它会自动在 <html> 标签添加一些设备平台,操作系统,方向相关的 CSS class,这样就能让你针对不同设备撰写不同的 CSS,并且还提供一些 Javascript 函数来判断设备。

Device.js

Device.js 通过操作系统(比如 iOS,安卓,黑莓,Windows,Firefox OX),方向(横屏或者竖屏),类型(平板或者移动设备),如下面在 iPhone 上的浏览的时候在 <html> 添加的 CSS Class:

Device.js 添加的 CSS Class

支持的设备

  • iOS: iPhone, iPod, iPad
  • Android: Phones & Tablets
  • Blackberry: Phones & Tablets
  • Windows: Phones & Tablets
  • Firefox OS: Phones & Tablets

如何使用

Device.js 使用非常简单,只需要在页面的 head 载入相关的 JS 库即可:

<script data-original="device.js"></script>

生成的 CSS Class:

DeviceCSS Classes
iPadios ipad tablet
iPhoneios iphone mobile
iPodios ipod mobile
Android Phoneandroid mobile
Android Tabletandroid tablet
BlackBerry Phoneblackberry mobile
BlackBerry Tabletblackberry tablet
Windows Phonewindows mobile
Windows Tabletwindows tablet
Firefox OS Phonefxos mobile
Firefox OS Tabletfxos tablet
Desktopdesktop
OrientationCSS Classes
Landscapelandscape
Portraitportrait

相关的 Javascript 函数

DeviceJavaScript Method
Mobiledevice.mobile()
Tabletdevice.tablet()
iOSdevice.ios()
iPaddevice.ipad()
iPhonedevice.iphone()
iPoddevice.ipod()
Androiddevice.android()
Android Phonedevice.androidPhone()
Android Tabletdevice.androidTablet()
BlackBerrydevice.blackberry()
BlackBerry Phonedevice.blackberryPhone()
BlackBerry Tabletdevice.blackberryTablet()
Windowsdevice.windows()
Windows Phonedevice.windowsPhone()
Windows Tabletdevice.windowsTablet()
Firefox OSdevice.fxos()
Firefox OS Phonedevice.fxosPhone()
Firefox OS Tabletdevice.fxosTablet()
OrientationJavaScript Method
Landscapedevice.landscape()
Portraitdevice.portrait()

项目主页:Device.js


via wpjam

查看原文

三月沙 收藏了文章 · 2014-01-04

45 个实用的 JavaScript 技巧、窍门和最佳实践

keep-calm-and-learn-javascript

如你所知,JavaScript是世界上第一的编程语言(编者注:2013年最后的一天就不要起圣战了=_=),它是Web的语言,是移动混合应用(mobile hybrid apps)的语言(比如 PhoneGap或者 Appcelerator),是服务器端的语言(比如 NodeJS或者 Wakanda),并且拥有很多其他的实现。同时它也是很多新手的启蒙语言,因为它不但可以在浏览器上显示一个简单的alert信息,而且还可以用来控制一个机器人(使用 nodebot,或者 nodruino)。掌握JavaScript并且能够写出组织规范并性能高效的代码的开发人员,已经成为人才市场上的猎寻对象。

在这篇文章中,我将分享一组JavaScript的技巧、窍门和最佳实践,这些都是JavaScript程序员应该知晓的,不管他们是使用在浏览器/引擎上,还是服务器端(SSJS——Service Side JavaScript)JavaScript解释器上。

译者注:原文作者总共写了44条(漏写了第3条),译者自己补了一条觉得比较重要的技巧。

需要注意的是,这篇文章中的代码片段都在最新的Google Chrome(版本号30)上测试过,它使用V8 JavaScript引擎(V8 3.20.17.15)

1. 第一次给一个变量赋值的时候不要忘记使用 var 关键字

给一个未定义的变量赋值会导致创建一个全局变量。要避免全局变量。

2. 使用===,而不是==

==(或!=)操作符在需要的时候会自动执行类型转换。===(或!==)操作不会执行任何转换。它将比较值和类型,而且在速度上也被认为优于==

[10] === 10    // is false
[10]  == 10    // is true
'10' == 10     // is true
'10' === 10    // is false
 []   == 0     // is true
 [] ===  0     // is false
 '' == false   // is true but true == "a" is false
 '' ===   false // is false

3. 使用闭包实现私有变量(译者添加)

function Person(name, age) {
    this.getName = function() { return name; };
    this.setName = function(newName) { name = newName; };
    this.getAge = function() { return age; };
    this.setAge = function(newAge) { age = newAge; };
 
    //未在构造函数中初始化的属性
    var occupation;
    this.getOccupation = function() { return occupation; };
    this.setOccupation = function(newOcc) { occupation = 
                         newOcc; };
}

4. 在语句结尾处使用分号

在语句结尾处使用分号是一个很好的实践。如果你忘记写了你也不会被警告,因为多数情况下 JavaScript 解释器会帮你加上分号。

5. 创建对象的构造函数

function Person(firstName, lastName){
    this.firstName =  firstName;
    this.lastName = lastName;
}
 
var Saad = new Person("Saad", "Mousliki");

6. 小心使用typeofinstanceofconstructor

var arr = ["a", "b", "c"];
typeof arr;   // return "object"
arr  instanceof Array // true
arr.constructor();  //[]

7. 创建一个自调用函数(Self-calling Funtion)

这个经常被称为自调用匿名函数(Self-Invoked Anonymous Function)或者即时调用函数表达式(IIFE-Immediately Invoked Function Expression)。这是一个在创建后立即自动执行的函数,通常如下:

(function(){
    // some private code that will be executed automatically
})();
(function(a,b){
    var result = a+b;
    return result;
})(10,20)

8. 从数组中获取一个随机项

var items = [12, 548 , 'a' , 2 , 5478 , 'foo' , 8852, , 'Doe' , 2145 , 119];
 
var  randomItem = items[Math.floor(Math.random() * items.length)];

9. 在特定范围内获取一个随机数

这个代码片段在你想要生成测试数据的时候非常有用,比如一个在最小最大值之间的一个随机薪水值。

var x = Math.floor(Math.random() * (max - min + 1)) + min;

10. 在 0 和设定的最大值之间生成一个数字数组

var numbersArray = [] , max = 100;
 
for( var i=1; numbersArray.push(i++) &lt; max;);  // numbers = [0,1,2,3 ... 100]

11. 生成一个随机的数字字母字符串

function generateRandomAlphaNum(len) {
    var rdmstring = "";
    for( ; rdmString.length &lt; len; rdmString  += Math.random().toString(36).substr(2));
    return  rdmString.substr(0, len);
}

【译者注:特意查了一下Math.random()生成 0 到 1 之间的随机数,number.toString(36) 是将这个数字转换成36进制(0-9,a-z),最后`substr 去掉前面的“0.”字符串】

12. 打乱一个数字数组

var numbers = [5, 458 , 120 , -215 , 228 , 400 , 122205, -85411];
numbers = numbers.sort(function(){ return Math.random() - 0.5});
/* the array numbers will be equal for example to [120, 5, 228, -215, 400, 458, -85411, 122205]  */

13. String的trim函数

在Java、C#、PHP和很多其他语言中都有一个经典的 trim 函数,用来去除字符串中的空格符,而在JavaScript中并没有,所以我们需要在String对象上加上这个函数。

String.prototype.trim = function(){return this.replace(/^\s+|\s+$/g, "");};

译者注:去掉字符串的前后空格,不包括字符串内部空格

14. 附加(append)一个数组到另一个数组上

var array1 = [12 , "foo" , {name: "Joe"} , -2458];
 
var array2 = ["Doe" , 555 , 100];
Array.prototype.push.apply(array1, array2);
/* array1 will be equal to  [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */

译者注:其实concat可以直接实现两个数组的连接,但是它的返回值是一个新的数组。这里是直接改变array1

15. 将arguments对象转换成一个数组

var argArray = Array.prototype.slice.call(arguments);

译者注:arguments 对象是一个类数组对象,但不是一个真正的数组

16. 验证参数是否是数字(number)

function isNumber(n){
    return !isNaN(parseFloat(n)) && isFinite(n);
}

17. 验证参数是否是数组

function isArray(obj){
    return Object.prototype.toString.call(obj) === '[object Array]' ;
}

注意:如果 toString() 方法被重写了(overridden),你使用这个技巧就不能得到想要的结果了。或者你可以使用:

Array.isArray(obj); // 这是一个新的array的方法

如果你不在使用多重frames的情况下,你还可以使用 instanceof 方法。但如果你有多个上下文,你就会得到错误的结果。

var myFrame = document.createElement('iframe');
document.body.appendChild(myFrame);
 
var myArray = window.frames[window.frames.length-1].Array;
var arr = new myArray(a,b,10); // [a,b,10]
 
// instanceof will not work correctly, myArray loses his constructor
// constructor is not shared between frames
arr instanceof Array; // false

译者注:关于如何判断数组网上有不少讨论,大家可以google一下。这篇就写的挺详细的。

18. 获取一个数字数组中的最大值或最小值

var  numbers = [5, 458 , 120 , -215 , 228 , 400 , 122205, -85411];
var maxInNumbers = Math.max.apply(Math, numbers);
var minInNumbers = Math.min.apply(Math, numbers);

译者注:这里使用了Function.prototype.apply方法传递参数的技巧

19. 清空一个数组

var myArray = [12 , 222 , 1000 ];
myArray.length = 0; // myArray will be equal to [].

20. 不要使用 delete 来删除一个数组中的项。

使用 splice 而不要使用 delete 来删除数组中的某个项。使用 delete 只是用 undefined 来替换掉原有的项,并不是真正的从数组中删除。

不要使用这种方式:

var items = [12, 548 ,'a' , 2 , 5478 , 'foo' , 8852, , 'Doe' ,2154 , 119 ];
items.length; // return 11
delete items[3]; // return true
items.length; // return 11
/* items will be equal to [12, 548, "a", undefined × 1, 5478, "foo", 8852, undefined × 1, "Doe", 2154,       119]   */

而使用:

var items = [12, 548 ,'a' , 2 , 5478 , 'foo' , 8852, , 'Doe' ,2154 , 119 ];
items.length; // return 11
items.splice(3,1) ;
items.length; // return 10
/* items will be equal to [12, 548, "a", 5478, "foo", 8852, undefined × 1, "Doe", 2154,       119]   */

delete 方法应该被用来删除一个对象的某个属性。

21. 使用 length 来截短一个数组

跟上面的清空数组的方式类似,我们使用 length 属性来截短一个数组。

var myArray = [12 , 222 , 1000 , 124 , 98 , 10 ];
myArray.length = 4; // myArray will be equal to [12 , 222 , 1000 , 124].

此外,如果你将一个数组的 length 设置成一个比现在大的值,那么这个数组的长度就会被改变,会增加新的 undefined 的项补上。 数组的 length 不是一个只读属性。

myArray.length = 10; // the new array length is 10
myArray[myArray.length - 1] ; // undefined

22. 使用逻辑 AND/OR 做条件判断

var foo = 10;
foo == 10 && doSomething(); // 等价于 if (foo == 10) doSomething();
foo == 5 || doSomething(); // 等价于 if (foo != 5) doSomething();

逻辑 AND 还可以被使用来为函数参数设置默认值

function doSomething(arg1){
    Arg1 = arg1 || 10; // 如果arg1没有被设置的话,Arg1将被默认设成10
}

23. 使用 map() 方法来遍历一个数组里的项

var squares = [1,2,3,4].map(function (val) {
    return val * val;
});
// squares will be equal to [1, 4, 9, 16]

24. 四舍五入一个数字,保留N位小数

var num =2.443242342;
num = num.toFixed(4);  // num will be equal to 2.4432

25 – 浮点数问题

0.1 + 0.2 === 0.3 // is false
9007199254740992 + 1 // is equal to 9007199254740992
9007199254740992 + 2 // is equal to 9007199254740994

为什么会这样? 0.1+0.2 等于 0.30000000000000004。你要知道,所有的 JavaScript 数字在内部都是以 64 位二进制表示的浮点数,符合IEEE 754 标准。更多的介绍,可以阅读这篇博文。你可以使用 toFixed()toPrecision() 方法解决这个问题。

26. 使用for-in遍历一个对象内部属性的时候注意检查属性

下面的代码片段能够避免在遍历一个对象属性的时候访问原型的属性

for (var name in object) {
    if (object.hasOwnProperty(name)) {
        // do something with name
    }
}

27. 逗号操作符

var a = 0;
var b = ( a++, 99 );
console.log(a);  // a will be equal to 1
console.log(b);  // b is equal to 99

28. 缓存需要计算和查询(calculation or querying)的变量

对于jQuery选择器,我们最好缓存这些DOM元素。

var navright = document.querySelector('#right');
var navleft = document.querySelector('#left');
var navup = document.querySelector('#up');
var navdown = document.querySelector('#down');

29. 在调用 isFinite()之前验证参数

isFinite(0/0) ; // false
isFinite("foo"); // false
isFinite("10"); // true
isFinite(10);   // true
isFinite(undifined);  // false
isFinite();   // false
isFinite(null);  // true  !!!

30. 避免数组中的负数索引(negative indexes)

var numbersArray = [1,2,3,4,5];
var from = numbersArray.indexOf("foo") ;  // from is equal to -1
numbersArray.splice(from,2);    // will return [5]

确保调用 indexOf 时的参数不是负数。

31. 基于JSON的序列化和反序列化(serialization and deserialization)

var person = {name :'Saad', age : 26, department : {ID : 15, name : "R&D"} };
var stringFromPerson = JSON.stringify(person);
/* stringFromPerson is equal to "{"name":"Saad","age":26,"department":{"ID":15,"name":"R&D"}}"   */
var personFromString = JSON.parse(stringFromPerson);
/* personFromString is equal to person object  */

32. 避免使用 eval()Function 构造函数

使用 eval 和 Function 构造函数是非常昂贵的操作,因为每次他们都会调用脚本引擎将源代码转换成可执行代码。

var func1 = new Function(functionCode);
var func2 = eval(functionCode);

33. 避免使用 with()

使用 with() 会插入一个全局变量。因此,同名的变量会被覆盖值而引起不必要的麻烦。

34. 避免使用 for-in 来遍历一个数组

避免使用这样的方式:

var sum = 0;
for (var i in arrayNumbers) {
    sum += arrayNumbers[i];
}

更好的方式是:

var sum = 0;
for (var i = 0, len = arrayNumbers.length; i < len; i++) {
    sum += arrayNumbers[i];
}

附加的好处是,i 和 len 两个变量的取值都只执行了一次,会比下面的方式更高效:

for (var i = 0; i < arrayNumbers.length; i++)

为什么?因为 arrayNumbers.length 每次循环的时候都会被计算。

35. 在调用 setTimeout()setInterval() 的时候传入函数,而不是字符串。

如果你将字符串传递给 setTimeout() 或者 setInterval(),这个字符串将被如使用 eval 一样被解析,这个是非常耗时的。
不要使用:

setInterval('doSomethingPeriodically()', 1000);
setTimeOut('doSomethingAfterFiveSeconds()', 5000)

而用:

setInterval(doSomethingPeriodically, 1000);
setTimeOut(doSomethingAfterFiveSeconds, 5000);

36. 使用 switch/case 语句,而不是一长串的 if/else

在判断情况大于2种的时候,使用 switch/case 更高效,而且更优雅(更易于组织代码)。但在判断的情况超过10种的时候不要使用 switch/case
译者注:查了一下文献,大家可以看一下这篇介绍

37. 在判断数值范围时使用 switch/case

在下面的这种情况,使用 switch/case 判断数值范围的时候是合理的:

function getCategory(age) {
    var category = "";
    switch (true) {
        case isNaN(age):
            category = "not an age";
            break;
        case (age >= 50):
            category = "Old";
            break;
        case (age <= 20):
            category = "Baby";
            break;
        default:
            category = "Young";
            break;
    };
    return category;
}
getCategory(5);  // will return "Baby"

译者注:一般对于数值范围的判断,用 if/else 会比较合适。 switch/case 更适合对确定数值的判断

38. 为创建的对象指定 prototype 对象

写一个函数来创建一个以指定参数作为 prototype 的对象是有可能:

function clone(object) {
    function OneShotConstructor(){};
    OneShotConstructor.prototype= object;
    return new OneShotConstructor();
}
clone(Array).prototype ;  // []

39. 一个HTML转义函数

function escapeHTML(text) {
    var replacements= {"<": "<", ">": ">","&": "&", "\"": """};
    return text.replace(/[<>&"]/g, function(character) {
        return replacements[character];
    });
}

40. 避免在循环内部使用 try-catch-finally

在运行时,每次当 catch 从句被执行的时候,被捕获的异常对象会赋值给一个变量,而在 try-catch-finally 结构中,每次都会新建这个变量。

避免这样的写法:

var object = ['foo', 'bar'], i;
for (i = 0, len = object.length; i <len; i++) {
    try {
        // do something that throws an exception
    }
    catch (e) {
        // handle exception
    }
}

而使用:

var object = ['foo', 'bar'], i;
try {
    for (i = 0, len = object.length; i <len; i++) {
        // do something that throws an exception
    }
}
catch (e) {
    // handle exception
}

41. 为 XMLHttpRequests 设置超时。

在一个XHR请求占用很长时间后(比如由于网络问题),你可能需要中止这次请求,那么你可以对XHR调用配套使用 setTimeout()。

var xhr = new XMLHttpRequest ();
xhr.onreadystatechange = function () {
    if (this.readyState == 4) {
        clearTimeout(timeout);
        // do something with response data
    }
}
var timeout = setTimeout( function () {
    xhr.abort(); // call error callback
}, 60*1000 /* timeout after a minute */ );
xhr.open('GET', url, true);  
 
xhr.send();

此外,一般你应该完全避免同步的 Ajax 请求。

42. 处理 WebSocket 超时

通常,在一个 WebSocket 连接创建之后,如果你没有活动的话,服务器会在30秒之后断开(time out)你的连接。防火墙也会在一段时间不活动之后断开连接。

为了防止超时的问题,你可能需要间歇性地向服务器端发送空消息。要这样做的话,你可以在你的代码里添加下面的两个函数:一个用来保持连接,另一个用来取消连接的保持。通过这个技巧,你可以控制超时的问题。

使用一个 timerID:

var timerID = 0;
function keepAlive() {
    var timeout = 15000;
    if (webSocket.readyState == webSocket.OPEN) {
        webSocket.send('');
    }
    timerId = setTimeout(keepAlive, timeout);
}
function cancelKeepAlive() {
    if (timerId) {
        cancelTimeout(timerId);
    }
}

keepAlive() 方法应该被添加在webSOcket连接的 onOpen() 方法的最后,而 cancelKeepAlive() 添加在 onClose() 方法的最后。

43. 牢记,原始运算符始终比函数调用要高效。使用 VanillaJS

举例来说,不使用:

var min = Math.min(a,b);
A.push(v);

而用:

var min = a < b ? a b;
A[A.length] = v;

44. 编码的时候不要忘记使用代码整洁工具。在上线之前使用JSLint和代码压缩工具(minification)(比如JSMin)。《省时利器:代码美化与格式化工具

45. JavaScript是不可思议的。最好的JavaScript学习资源

总结

我知道还有很多其他的技巧,窍门和最佳实践,所以如果你有其他想要添加或者对我分享的这些有反馈或者纠正,请在评论中指出。

引用

在这篇文章中,我使用了一些我自己的代码片段,也有一些代码片段来自别人的文章或者论坛:


原文:45 Useful JavaScript Tips, Tricks and Best Practices
转载翻译自: 伯乐在线 - Owen Chen

查看原文

认证与成就

  • 获得 0 次点赞
  • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-01-04
个人主页被 50 人浏览