javascript的垃圾回收机制与内存管理

一、垃圾回收机制—GC

Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。

原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。

JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。

不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后在函数中使用这些变量,直至函数结束,而闭包中由于内部函数的原因,外部函数并不能算是结束。

还是上代码说明吧:

1
2
3
4
5
6
7
8
9
10
11
function fn1() {
 var obj = {name: 'hanzichi', age: 10};
}
 
function fn2() {
 var obj = {name:'hanzichi', age: 10};
 return obj;
}
 
var a = fn1();
var b = fn2();

我们来看代码是如何执行的。首先定义了两个function,分别叫做fn1和fn2,当fn1被调用时,进入fn1的环境,会开辟一块内存存放对象{name: 'hanzichi', age: 10},而当调用结束后,出了fn1的环境,那么该块内存会被js引擎中的垃圾回收器自动释放;在fn2被调用的过程中,返回的对象被全局变量b所指向,所以该块内存并不会被释放。

这里问题就出现了:到底哪个变量是没有用的?所以垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:标记清除和引用计数。引用计数不太常用,标记清除较为常用。

二、标记清除

js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

1
2
3
4
5
function test(){
 var a = 10 ; //被标记 ,进入环境
 var b = 20 ; //被标记 ,进入环境
}
test(); //执行完毕 之后 a、b又被标离开环境,被回收。

  垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。

  到目前为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。

三、引用计数

  引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

1
2
3
4
5
6
function test(){
 var a = {} ; //a的引用次数为0
 var b = a ; //a的引用次数加1,为1
 var c =a; //a的引用次数再加1,为2
 var b ={}; //a的引用次数减1,为1
}

  Netscape Navigator3是最早使用引用计数策略的浏览器,但很快它就遇到一个严重的问题:循环引用。循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。

1
2
3
4
5
6
7
8
function fn() {
 var a = {};
 var b = {};
 a.pro = b;
 b.pro = a;
}
 
fn();

  以上代码a和b的引用次数都是2,fn()执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为a和b的引用次数不为0,所以不会被垃圾回收器回收内存,如果fn函数被大量调用,就会造成内存泄露。在IE7与IE8上,内存直线上升。

我们知道,IE中有一部分对象并不是原生js对象。例如,其内存泄露DOM和BOM中的对象就是使用C++以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数策略。因此,即使IE的js引擎采用标记清除策略来实现,但js访问的COM对象依然是基于引用计数策略的。换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题。

1
2
3
4
var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;

  这个例子在一个DOM元素(element)与一个原生js对象(myObject)之间创建了循环引用。其中,变量myObject有一个名为element的属性指向element对象;而变量element也有一个属性名为o回指myObject。由于存在这个循环引用,即使例子中的DOM从页面中移除,它也永远不会被回收。

看上面的例子,有同学回觉得太弱了,谁会做这样无聊的事情,其实我们是不是就在做

1
2
3
4
window.onload=function outerFunction(){
 var obj = document.getElementById("element");
 obj.onclick=function innerFunction(){};
};

这段代码看起来没什么问题,但是obj引用了document.getElementById(“element”),而document.getElementById(“element”)的onclick方法会引用外部环境中德变量,自然也包括obj,是不是很隐蔽啊。

解决办法

最简单的方式就是自己手工解除循环引用,比如刚才的函数可以这样

1
2
myObject.element = null;
element.o = null;


1
2
3
4
5
window.onload=function outerFunction(){
 var obj = document.getElementById("element");
 obj.onclick=function innerFunction(){};
 obj=null;
};

将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时,就会删除这些值并回收它们占用的内存。

要注意的是,IE9+并不存在循环引用导致Dom内存泄露问题,可能是微软做了优化,或者Dom的回收方式已经改变

四、内存管理

1、什么时候触发垃圾回收?

垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。IE6的垃圾回收是根据内存分配量运行的,当环境中存在256个变量、4096个对象、64k的字符串任意一种情况的时候就会触发垃圾回收器工作,看起来很科学,不用按一段时间就调用一次,有时候会没必要,这样按需调用不是很好吗?但是如果环境中就是有这么多变量等一直存在,现在脚本如此复杂,很正常,那么结果就是垃圾回收器一直在工作,这样浏览器就没法儿玩儿了。

微软在IE7中做了调整,触发条件不再是固定的,而是动态修改的,初始值和IE6相同,如果垃圾回收器回收的内存分配量低于程序占用内存的15%,说明大部分内存不可被回收,设的垃圾回收触发条件过于敏感,这时候把临街条件翻倍,如果回收的内存高于85%,说明大部分内存早就该清理了,这时候把触发条件置回。这样就使垃圾回收工作职能了很多

2、合理的GC方案

1)、Javascript引擎基础GC方案是(simple GC):mark and sweep(标记清除),即:

  • (1)遍历所有可访问的对象。
  • (2)回收已不可访问的对象。

2)、GC的缺陷

和其他语言一样,javascript的GC策略也无法避免一个问题:GC时,停止响应其他操作,这是为了安全考虑。而Javascript的GC在100ms甚至以上,对一般的应用还好,但对于JS游戏,动画对连贯性要求比较高的应用,就麻烦了。这就是新引擎需要优化的点:避免GC造成的长时间停止响应。

3)、GC优化策略

David大叔主要介绍了2个优化方案,而这也是最主要的2个优化方案了:

(1)分代回收(Generation GC)
这个和Java回收策略思想是一致的。目的是通过区分“临时”与“持久”对象;多回收“临时对象”区(young generation),少回收“持久对象”区(tenured generation),减少每次需遍历的对象,从而减少每次GC的耗时。如图:

这里需要补充的是:对于tenured generation对象,有额外的开销:把它从young generation迁移到tenured generation,另外,如果被引用了,那引用的指向也需要修改。

(2)增量GC
这个方案的思想很简单,就是“每次处理一点,下次再处理一点,如此类推”。如图:

这种方案,虽然耗时短,但中断较多,带来了上下文切换频繁的问题。

因为每种方案都其适用场景和缺点,因此在实际应用中,会根据实际情况选择方案。

比如:低 (对象/s) 比率时,中断执行GC的频率,simple GC更低些;如果大量对象都是长期“存活”,则分代处理优势也不大。

参考:

以上就是关于javascript的垃圾回收机制与内存管理的全部内容,希望对大家的学习有所帮助。


移动前端不得不了解的HTML5 head 头标签(2016最新版)

from: http://www.css88.com/archives/6410

HTML的头部内容特别多,有针对SEO的头部信息,也有针对移动设备的头部信息。而且各个浏览器内核以及各个国内浏览器厂商都有些自己的标签元素,有很多差异性。移动端的工作已经越来越成为前端工作的重要内容,除了平常的项目开发,HTML 头部标签功能,特别是meta,link等标签的功能属性显得非常重要。这里整理了一份 <head> 部分的清单,让大家了解每个标签及相应属性的意义,写出满足自己需求的 <head> 头部标签,可以很有效的增强页面的可用性。

注:去年整理过移动前端不得不了解的html5 head 头标签,随着时间和浏览器厂商的升级,现在看起来似乎有些过时了。所以重新整理了一下。增加了新的内容,及过时的一些提示,同时增加了部分桌面端浏览器的一些说明。

HTML基本的头部标签

下面是HTML基本的头部元素:

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <meta http-equiv="x-ua-compatible" content="ie=edge">
  6. <!--移动端的页面这个可以忽略,具体可以查看本文Internet Explorer浏览器部分-->
  7. <meta name="viewport" content="width=device-width, initial-scale=1">
  8. <!--具体可以查看本文 为移动设备添加 viewport 部分-->
  9. <!-- 以上 3 个 meta 标签 *必须* 放在 head 的最前面;其他任何的 head 内容必须在这些标签的 *后面* -->
  10. <title>页面标题</title>
  11. ...
  12. </head>

其中

  1. <meta http-equiv="x-ua-compatible" content="ie=edge">

在桌面开发的时候可以让IE浏览器以最新的模式渲染页面,具体可以查看本文Internet Explorer浏览器部分。
如果你的页面确定只在桌面浏览器中运行,那么

  1. <meta name="viewport" content="width=device-width, initial-scale=1">

也可以省略。

DOCTYPE

DOCTYPE(Document Type),该声明位于文档中最前面的位置,处于 html 标签之前,此标签告知浏览器文档使用哪种 HTML 或者 XHTML 规范。

使用 HTML5 doctype,不区分大小写。

  1. <!DOCTYPE html> <!-- 使用 HTML5 doctype,不区分大小写 -->

charset

声明文档使用的字符编码,

  1. <meta charset="utf-8">

html5 之前网页中会这样写:

  1. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

这两个是等效的,具体可移步阅读:<meta charset='utf-8'> vs<meta http-equiv='Content-Type'>,所以建议使用较短的,易于记忆。

lang属性

更加标准的 lang 属性写法 http://zhi.hu/XyIa

简体中文

  1. <html lang="zh-cmn-Hans"> <!-- 更加标准的 lang 属性写法 http://zhi.hu/XyIa -->

繁体中文

  1. <html lang="zh-cmn-Hant"> <!-- 更加标准的 lang 属性写法 http://zhi.hu/XyIa -->

很少情况才需要加地区代码,通常是为了强调不同地区汉语使用差异,例如:

  1. <p lang="zh-cmn-Hans">
  2. <strong lang="zh-cmn-Hans-CN">菠萝</strong><strong lang="zh-cmn-Hant-TW">鳳梨</strong>其实是同一种水果。只是大陆和台湾称谓不同,且新加坡、马来西亚一带的称谓也是不同的,称之为<strong lang="zh-cmn-Hans-SG">黄梨</strong>
  3. </p>

为什么 lang="zh-cmn-Hans" 而不是我们通常写的 lang="zh-CN" 呢,请移步阅读: 页头部的声明应该是用 lang=”zh” 还是 lang=”zh-cn”

Meta 标签

meta标签是HTML中head头部的一个辅助性标签,它位于HTML文档头部的 <head> 和 <title> 标记之间,它提供用户不可见的信息。虽然这部分信息用户不可见,但是其作用非常强大,特别是当今的前端开发工作中,设置合适的meta标签可以大大提升网站页面的可用性。

桌面端开发中,meta标签通常用来为搜索引擎优化(SEO)及 robots定义页面主题,或者是定义用户浏览器上的cookie;它可以用于鉴别作者,设定页面格式,标注内容提要和关键字;还可以设置页面使其可以根据你定义的时间间隔刷新自己,以及设置RASC内容等级,等等。

移动端开发中,meta标签除了桌面端中的功能设置外,还包括,比如viewport设置,添加到主屏幕图标,标签页颜色等等实用设置。具体可以看后面详细的介绍。

meta标签分类

meta标签根据属性的不同,可分为两大部分:http-equiv 和 name 属性。

http-equiv:相当于http的文件头作用,它可以向浏览器传回一些有用的信息,以帮助浏览器正确地显示网页内容。
name属性:主要用于描述网页,与之对应的属性值为content,content中的内容主要是便于浏览器,搜索引擎等机器人识别,等等。

推荐使用的meta标签

  1. <!-- 设置文档的字符编码 -->
  2. <meta charset="utf-8">
  3. <meta http-equiv="x-ua-compatible" content="ie=edge">
  4. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  5. <!-- 以上 3 个 meta 标签 *必须* 放在 head 的最前面;其他任何的 head 内容必须在这些标签的 *后面* -->
  6.  
  7. <!-- 允许控制资源的过度加载 -->
  8. <meta http-equiv="Content-Security-Policy" content="default-src 'self'">
  9. <!-- 尽早地放置在文档中 -->
  10. <!-- 仅应用于该标签下的内容 -->
  11.  
  12. <!-- Web 应用的名称(仅当网站被用作为一个应用时才使用)-->
  13. Animatable properties

    7. Animatable properties

    The definition of each CSS property defines when the values of that property can be interpolated by referring to the definitions of property types in theprevious section. Values are animatable when both the from and the to values of the property have the type described. (When a composite type such as "length, percentage, or calc" is listed, this means that both values must fit into that composite type.) When multiple types are listed in the form "either A or B", both values must be of the same type to be interpolable.

    For properties that exist at the time this specification was developed, this specification defines whether and how they are animated. However, future CSS specifications may define additional properties, additional values for existing properties, or additional animation behavior of existing values. In order to describe new animation behaviors and to have the definition of animation behavior in a more appropriate location, future CSS specifications should include an "Animatable:" line in the summary of the property's definition (in addition to the other lines described in [CSS21]section 1.4.2). This line should say "no" to indicate that a property cannot be animated or should reference an animation behavior (which may be one of the behaviors in theAnimation of property types section above, or may be a new behavior) to define how the property animates. Such definitions override those given in this specification.

    7.1. Properties from CSS

    The following definitions define the animation behavior for properties in CSS Level 2 Revision 1 ([CSS21]) and in Level 3 of the CSS Color Module ([CSS3COLOR]).

    Property Name Type
    background-color as color
    background-position as repeatable list of simple list of length, percentage, or calc
    border-bottom-color as color
    border-bottom-width as length
    border-left-color as color
    border-left-width as length
    border-right-color as color
    border-right-width as length
    border-spacing as simple list of length
    border-top-color as color
    border-top-width as length
    bottom as length, percentage, or calc
    clip as rectangle
    color as color
    font-size as length
    font-weight as font weight
    height as length, percentage, or calc
    left as length, percentage, or calc
    letter-spacing as length
    line-height as either number or length
    margin-bottom as length
    margin-left as length
    margin-right as length
    margin-top as length
    max-height as length, percentage, or calc
    max-width as length, percentage, or calc
    min-height as length, percentage, or calc
    min-width as length, percentage, or calc
    opacity as number
    outline-color as color
    outline-width as length
    padding-bottom as length
    padding-left as length
    padding-right as length
    padding-top as length
    right as length, percentage, or calc
    text-indent as length, percentage, or calc
    text-shadow as shadow list
    top as length, percentage, or calc
    vertical-align as length
    visibility as visibility
    width as length, percentage, or calc
    word-spacing as length
    z-index as integer

    7.2. Properties from SVG

    All properties defined as animatable in the SVG specification, provided they are one of the property types listed above.


使用CSS Mod Queries控制选择器范围

最近有一个项目需求,希望一个简单的列表在一个网格中完美显示,比如只有一个单元格的时候,列表占据整个容器宽度,但总是不太好控制。因此,有时当你忙着一件事情时,问你有没有办法可以实现,我试图使用可以想到的办法,总是会有空白出现,我就会心烦意乱的说,实现不了。

当时,我正在伦敦一家新闻机构做网站。花了一年多时间把网站从CMS迁移到Adobe AEM平台上,并且UI做了很大的改进。在这个阶段,我开始关注UI的构建以及其新特性。另外整个开发团队划分成了许多个小团队,而我和我的团队主导就是UI的构建。

每个中心页面实际上是一个列表,列表只是用来帮助读者能找到他们更感兴趣的内容。你可以想象,一个新闻网站几乎是由内容列表构成。页面只是垂直排列的话,对于他的读者而言是没有任何吸引力,但我们希望读者在浏览网站的时候能立马找到他们感兴趣的内容。如此一来,就需要对不同的列表划分单独的区域,并且让这些列表能更协调的向读者展示。简而言之,通过视觉的调整,让整个页面更好的显示其可用性和有效性。

我所说的简单列表是指主版面顶部总是高调的突出或强调一个特定的兴趣点。从一个项目开始,随着时间每天增加文章列表数。为了能更好的排列,需要一个网格列表,而不是一个单一的列,如此一来,有的时候总是会有单个列表排在最后面。而我们想要的需求是,不管有多少个子元素,它的布局必须保持整洁和干净。也就是说,不管有多少个列表项,在网格中都能很好的平整显示。第一项宽度是为100%,后面两个50%,另外后续所有列表项宽度是33%。这样一来,所谓的简单列表突然变得不再是那么简单。

不是每个人都想要一个通用的网格或者堆栈相同的列表项。如果你知道一个精确的列表长度,可能通过硬编码的方式构建一个优雅的网格布局,但如果长度是可变的话,那么布局变得就更具挑战性:当最后一排列表项不到三个时,怎么保持最后一个能整洁干净排列

当更多的列表项添加进来之后将会打破我们预期的布局,直接影响视觉效果

在实际操作过程中,发意识到了解列表的长度对于布局并没有起到很有效的帮助。一开始还以为能在@Heydon Pickering分享的《Quantity Queries for CSS》文章中找到相关的解决方案,但最终我以失败而告终。

Quantity Queries for CSS》译文可以点击这里阅读。

我的需求中,列表数量是可以任意长度的,这样一来,我就无法通过数量查询来解决。而且我也不可能预测到列表有多少个数量。加上,有的时候还有“加载更多”的按钮,当用户点击这个按钮之后,会动态加入另一个10个列表项。如此一来,我需要一个不同的解决方案。

崩溃之后的我,静下来问自己,假如@Lea Verou碰到这样的需求会怎么处理?好吧,不恐慌或许这是一个好的开始。此外,它将有助于简化和确定潜在的需求。因为列表从根本上是一行显示三列,这样我只需要知道列表除以3之后的余数是多少。

模查询(Mod Query)

能过通过数量和兄弟选择器来控制样式,这是一个强大的特性,但它有一个致命的缺陷,那就是无法对未知长度的列表做控制。在下面的示例中,通过整除一个数而不是知道列表的长度个数来控制自己的列表项。

不幸的是,在CSS中没有一个取模查询,但我们可以通过结合:nth-child(3n)(以名取模选择器)和:first-child两个选择器来模拟一个CSS取模查询。

比如下面的代码,选择一切能被3整除的列表:

/*选择列表中所有能被3整除的列表*/li:nth-last-child(3n):first-child,li:nth-last-child(3n):first-child ~ li { }

@clanceyp通过这种取模查询的方式,能被3整除的列表项,图标都高亮显示了。效果如上图所示,具体示例如下:

我们一起来讨论一下代码。就拿上面的示例做为讨论的列子。

CSS选择器:

li:nth-last-child(3n):first-child ~ li

选择所有兄弟元素:

... ~ li

第一个子元素(在这个示例中,就是列表中的第一个li):

...:first-child ...

选择列表后面开始选择3的倍数的列表:

...:nth-last-child(3n):...

上面的组合选择器意味第一个子元素是列到倒数第3n个列表项和选择所有的兄弟元素。

如果查询第一个列表的所有兄弟元素,但不包括第一个列表项本身,这样就需要单独的为它添加一个选择器:

li:nth-last-child(3n):first-child,li:nth-last-child(3n):first-child ~ li { }

来看个简单的示例:

点击“add list item”按钮,当列表数分别能被2345整除时,具有不同的样式风格。

余数呢

通过取模查询(Mod Query),如果列表项能被3整除的列表都能选中,但如果列表不能被3整除时,也就是有余数的时候,需要给列表一个不同的样式。(如果余数为1的时候,只需要从倒数第二个开始计数,而不是最后一个。如果一来只需要在查询中简单的+1就可以。)

li:nth-last-child(3n+1):first-child,li:nth-last-child(3n+1):first-child ~ li {     … 列表数除以3的余数是1的列表 …  }

同上,如果余数是2,只需要换成+2即可:

li:nth-last-child(3n+2):first-child,li:nth-last-child(3n+2):first-child ~ li {     … 列表数除以3的余数是2的列表 …  }

创建选择器范围

现在有方法确定列表长度能否被任何给定的数字整除,有没有余数,但很多时候仍然需要一个选择范围。和取模查询一样,原始的CSS选择器是没有这样的,但同样的,可以结合:nth-child(n)(一切大于n)和:nth-child(-n)(一切小于n)两个选择器实现。

假设要选择列表中的第35这几个列表项,可以这样使用:

li:nth-child(n+3):nth-child(-n+5){ }

事实上,可以使用:nth-child(n)选择器和具体的列表项目数实现上面的的效果,也就是直接使用:

li:nth-child(3),li:nth-child(4),li:nth-child(5) {    ...}

但是定义一个开始和结束来确定是一个选择器范围,这功能更强大。接下来,我们来看看这样能做些什么?

从第五个列表项向前选择,包括第五个列表项:

li:nth-child(-n+5){ … }

从第三个列表项向后选择,包括第三个列表项:

li:nth-child(n+3){ … }

两个选择器组合在一起,就创建了一个选择器范围:

li:nth-child(n+3):nth-child(-n+5){ … }

来看一个示例,所有的列表都放在一个网格中,每个列表都包括了图片、标题和描述。第一行只需要显示图片(标题和描述都隐藏),第二行和第三行,显示图片、标题和描述,而从第四行开始隐藏图片,而且标题和描述显示在一行。

具体的可以查看@clanceyp写的一个示例:

示例还做了响应式处理,缩小你的视窗,可以看到不同的效果,但始终保持网格第一行只显示缩略图,第二行和第三行显示缩略图、标题和描述,第四行开始隐藏缩略图,而且标题和描述都在同一行显示。

通过范围选择器,第一个选择器选择前三个,第二个选择器是从第4个开始到第9个结束,选择49的一个范围,第三个选择器是从第10个选择器开始向后选择。正如上述,使用了CSS媒体查询在不同的断点改变选择器的范围,这样可以让我们网格布局保持一个不错的效果。

SCSS Mixin

我们一直都在使用CSS处理器,可以通过使用预处理函数来简化我们的代码。下面的代码就是通过SCSS创建了选择器范围和取模查询范围的混合宏:

// 选择器范围的混合宏@mixin select-range($start, $end){  &:nth-child(n+#{$start}):nth-child(-n+#{$end}){   @content;   }}// 取模查询的混合宏@mixin mod-list($mod, $remainder){  &:nth-last-child(#{$mod}n+#{$remainder}):first-child,  &:nth-last-child(#{$mod}n+#{$remainder}):first-child ~ li {    @content;    }}

实际使用的时候,可以像下面的代码一样在li的嵌套中调用声明好的混合宏:

li {    @include mod-list(3, 0){        @include select-range(3, 5){            // styles for items 3 to 5 in a list mod 3 remainder = 0        }    }}

有关于SCSS更多实用的混合宏,可以查询SassMagic,里面提供了常用的SCSS混合宏。如果你有类似常用的混合宏,欢迎给SassMagic提交PR。

有效组织在一起

现在我有一个小的工具来帮助我处理取模、范围或者范围中取模,更好的脱离固定长度或固定的列表布局。创造性的得用取模查询和范围选择器让我们应用样式改变元素的布局。

回到最初的原始需求,让列表的表现行为变得很明显。如果假设列表样式是三的倍数,那么只会有两个其他情况:

  • 列表数被三整除,余数是一
  • 列表数被三整除,余数是二

如果有一个剩余的列表项,我会让第二行显示三个列表项,而不是默认的两个列表项,但如果剩余的列表项是两个,可以让第三行显示两个列表项,第四和第五个列表项宽度为50%

最后,我不需要大量的查询,这让我实际操作起来变得相当的简单。但有一个特殊情况:如果列表只有两个列表项呢? 解决办法就是查询第二个列表项,而且同时其也是最后一个列表项:

li:nth-child(2):last-child { }

查询最终并不像我预期想的那么困难,只需要把取模和范围选择器组合在一起。

li:nth-last-child(3n):first-child /* mod query */ ~ li:nth-child(n+3):nth-child(-n+5){ /* range selector */    /*给第三到第五个元素设置样式,而且列表能被三整除*/}

总之,针对文章开头提到的需求,我们的CSS代码如下所示:

/*  默认列表项能被3整除,余数为0,    每个列表项的宽度为33%,    第一个列表项宽度为100%,    第二个列表项和第三个列表项宽度为50%*/li {  width: 33.33%;}li:first-child {  width: 100%;}/* 选择器范围,第2至第3列表项 */li:nth-child(n+2):nth-child(-n+3){  width: 50%;}/* 覆盖前面的样式 *//* 列表项被3整除时,余数为1 */li:nth-last-child(3n+1):first-child ~ li:nth-child(n+2):nth-child(-n+3) {  width: 33.33%; /* 覆盖第2和第3个列表项宽度*/}/* 列表项被3整除时,余数为2 */li:nth-last-child(3n+2):first-child ~ li:nth-child(n+4):nth-child(-n+5) {  width: 50%; /* 覆盖第4和第5个列表项宽度 */}/* 特殊情况,列表项只有两个的时候 */li:nth-child(2):last-child {  margin-left: 25%;}

经验之谈,注意浏览器的支持范围

取模查询和范围选择器依赖于CSS3选择器,所以支持CSS3的现代浏览器都支持,包括IE9+。

我创建了一个取模或范围选择器的工具,你可以直接使用这个工具来实现相同的功能。

当我第一次遇到QQs时,从理论中说我觉得他是强大而又有趣的,但并没有实战过。然而随着移动应用超过桌面运用以及响应式设计规范需要列表在不同的断点下列表项宽度不一样变得更为常见。我发现这样的特性比以往任何时候都显得更为重要,为了方便UI开发人员更好的使用这些特性,UI开发人员开发类似的工具也变成日常工作的一部分。

扩展阅读

  • “Quantity Queries for CSS”
  • modqueries.com
  • :nth Tester
  • quantityqueries.com/
  • Nth-Test
  • CSS3 structural pseudo-class selector tester
  • Can I use… Support tables for HTML5, CSS3, etc.
  • Magic randomisation with nth-child and Cicada Principle

    The combination of nth-child and cicada principle is a clever technique used for faking the randomisation of patterns. Since computers can’t generate random numbers (e.g. math.random() in Javascript isn’t completely random), the best we can do is give it a pseudo random seed value, which is usually based on a variable that can't be predicted (like exact time, for example). The key to making a sequence seem random is to introduce so many variables that it becomes too difficult to predict.

    Why is it called the cicada principle?

    A cicada is a rather grim looking little bug. You may have heard of them. There is a kind called the Periodical Cicada, which simultaneously emerge in masses every7, 11, 13 or 17 years; they find a mate and then they die. It's not much of a life.

    However, the interesting thing is that these numbers are all prime numbers. The reason for emerging at these particular times is to avoid clashing with the cycles of their predators.

    So, the principle in relation to websites is all about prime numbers.

    Random blobbiness

    I've used it on the UX London site to make it look like the images on the Speakers page get a distorted, circular, blobbiness added to them at random.

    uxlondon-grid.png#asset:444

    How does it work?

    nth-child is how we can target all items in a prime number space.

    .Item:nth-child(2n) {} .Item:nth-child(3n) {} .Item:nth-child(5n) {} .Item:nth-child(7n) {} .Item:nth-child(11n) {}

    However, I found that just using prime numbers didn't quite target enough items to make it look really random, so I added another layer of nth-child using prime numbers like this:

    .Item:nth-child(2n+1) {} .Item:nth-child(3n+2) {} .Item:nth-child(5n+3) {} .Item:nth-child(7n+5) {} .Item:nth-child(11n+7) {}

    The number that goes before n is the next prime number and the number  added onto it is the previous prime number.

    So for each nth-child rule, the image has four different border-radius values and some have a very slight rotation. Then I’ve tweaked the blobbines on the hover states.

    .Item:nth-child(2n+1) .Item-image {  border-top-left-radius: 59%;  border-top-right-radius: 52%;  border-bottom-left-radius: 59%;  border-bottom-right-radius: 56%;  transform: rotate(-6deg);}.Item:nth-child(2n+1) .Item-image:hover {  border-top-left-radius: 51%;  border-top-right-radius: 67%;  border-bottom-left-radius: 64%;  border-bottom-right-radius: 56%;  transform: rotate(-4deg); }

    You can visit the UXLondon site to see it in action.