摘要: 纵观当下的软件工程界,最热门的话题莫过于并行计算,为此C#早在4.0版本中就已经引入了Parallel Linq扩展,简化并行的开发。但是这远远不够,即便Parallel Linq已经提供了极大的便利,但其执行-回调模型依旧打破了编码人员以往对代码就是一行一行顺序执行的习惯思维。因此,C# vNext的主导思想是在这之上再给予更多的进化阅读全文
posted @ 2011-09-07 17:52 Gray Zhang 阅读(3073) 评论(27) 编辑

教条

根据各种Web性能优化手册,“减少HTTP请求”这一条始终被放在显眼的位置,其中就包括著名的YSlowGoogle Page Speed,两者对这一教条的解释分别是:

80% of the end-user response time is spent on the front-end. Most of this time is tied up in downloading all the components in the page: images, stylesheets, scripts, Flash, etc.

YSlow表示,前端的多数时间是用在下载图片、样式表、脚本、Flash等,所以要减少HTTP请求。

RTT is the major contributing factor to latency on "fast" (broadband) connections.

Page Speed则表示,RTT(请求往返时间)是导致连接快不起来的主要原因,所以要减少,即减少HTTP的请求数。

疑惑

很少人会对这2大优化守则产生怀疑,因为它们即真理、即教条、即必须遵守之则,如有违逆,虽远必诛……

但是如果认真地去解读这2条规则,其他他们都表达了一个意思:网络上的往返越多,响应的速度就越慢。

但是他们却忽略了一个很重要的事情,那就是工作总用时多,并不代表任务完成时间也多,因为这里有一个概念,叫作并行

试想一个任务,用一个线程跑,和用100个线程跑,哪一个总用时更少?答案其实是一个线程,因为100个线程之间有线程切换的开销、有结果join的时间、有任务分割的时间……但是从结论上来看,哪一个能更快地跑完任务?答案是多线程,这就是并行计算的理论来源。

事实上,资源的下载也是如此,如果并行的话,多个资源的下载时间不见得会小于一个资源,如下图是一个合并后的资源的下载示意:

大资源下载示意

上图用来表示一个较大的资源的下载过程,其中不同的颜色分别对应:

  • 蓝色:TCP链接建立时间
  • 绿色:请求头发送时间
  • 紫色:服务器处理时间
  • 橙色:响应头发送时间
  • 红色:文件传输时间
  • 灰色:文件解析执行时间

如果将这个大资源分解成3个相等的小资源的话,那么他们的下载就可能是这样的:

小资源下载示意

由于浏览器只有一个线程可以对文件进行解析和执行,因此灰色的解析部分必定是串行的,需要相互等待。

比较2张图,假设其中的每一小块的时间为t,可以发现这样一个结论:一个大资源的加载,使用了13t的时间,而将大资源分解后,虽然总共用了21t的时间,但是客户端真正等待的时间却只有9t,比合并资源的方式节约了4t。

这就是并行的效果,当然要实现这个效果,是有前提的:

  • 并行的连接数没有超过浏览器的限制,这里假设4并行是一个比较合理地、照顾到各浏览器的值。
  • 服务器能顶得住,不会因为并发连接过多而导致处理时间变长。
  • 网络足够稳定,这一点将保证TCP建立、请求头发送、响应头发送这3段时间是稳定的。
  • 浏览器的资源加载不会阻塞,如IE6-7在下载js文件时会阻塞后续资源的请求,则不可能实现并行。

缓存的考虑

合并资源有另一个好处,就是在缓存之后,只需向服务器验证一次即可。但是再和拆分资源的加载过程作一个比较,如果采用带验证的缓存,不难得出下图:

服务器缓存的情况

而如果使用客户端的缓存,则是以下情况:

客户端缓存的情况

从上面2张图中可以看到,即便在有缓存的情况下,如果满足一定的条件,可以进行并发的话,若干个小资源的加载情况下客户端的等待时间和合并为一个大资源后是相同的,并没有多余的消耗。

总结

一个最基础的结论是,加载资源总用时客户端等待时间是2个完全不同的概念,他们之间并不存在正比的关系,而在宽带普及的当前时代,多数网络资源并不是按流量收费的,因此前端的优化应该更关注于客户端等待时间,而不是加载资源总用时和流量之上。

事实上,一个最经典的应用就是下载软件,从90年代的NetAnt开始,到其后的网际快车、迅雷,无一不具有“多线程下载”的能力。事实上多线程下载同样会因为线程的切换、文件分段的空间分配、最后多段碎片的拼接等导致总耗时更多,但也确确实实极大地缩短了下载的时间。在对页面的资源加载作优化时,是不是也可以参考一下这个模型呢?

由此,对资源的切分将会从单纯的“合并”的级别,提升到一种完全艺术的程度,综合考虑不同浏览器对资源加载的策略,最大限度利用浏览器的并行下载能力,从而正确、最优地分配静态资源域名,切分静态资源,压榨浏览器全部的能力,进一步地提升页面加载的速率。这虽然大大提高了资源管理、分解的复杂度,但是对于追求极限而言,本人认为这样才是真正的最佳实践。

最后,本文完全没有全部否定减少HTTP请求数这一优化原则的意思,只是希望从另外一个角度看问题,保持着一定的怀疑的心态来重新审视这一原则,拒绝不经过思考的无意义的遵循,从而寻找到在特定环境下,最适合、最优化的方案。

本文永久地址:http://www.otakustay.com/doubt-on-fewer-http-requests/

posted @ 2011-05-19 13:54 Gray Zhang 阅读(3131) 评论(50) 编辑

近期群里常有人提一些简单的问题,比如发一段代码乱七八糟的代码,然后说里面某个变量是什么,比如这里就有个很好的例子:

function fn(arg) {
    alert(this.arg);
    alert(this);
}
fn(123);
var o = { fn: fn };
o.fn(123);

然后就可能有这样的问题:

为什么this.arg是undefined?为什么2次调用fn的this是不一样的?

为此,我觉得自己作为一个虽然不成熟的前端,对于一些自己力所能及的事情,还是应该传道授业解惑的。所以,这篇文章,计划从非常肤浅的层面上,来解释一下javascript中的对象查找是如何进行的。

注意,本篇文章只是从表象上来介绍对象查找这一行为的过程,文章中的观点并不全正确,甚至存在着一些谬误,但是这也是为了让初学者更好地理解对象查找这一过程。相信如果说得太过抽象、深入,反而会引起一些负面效果。如果有一天,你再回过来,发现这个文章说得并不那么正确,那么恭喜你,那个时候的你已经可以找到正确前进的道路,这篇中的错误也不会再对你有任何影响。

对象的分类

所谓对象查找,即在一段可执行代码的作用域内,找到一个当前需要的对象。在javascript中,需要进行查找的对象大致可以分为3种类型:

  • 变量查找,如foo++;,这里就会去查找一个叫作foo的变量。
  • 属性查找,如foo.bar++;,这里会去查找foo这个变量下的一个叫作bar的属性。
  • this查找,即针对this关键字的处理。

区分这3种类型的对象查找是首先要完成的任务,你可以基于以下原则进行判断:

  • 变量仅由变量名组成,即单独的foo、bar等。
  • 属性永远由2种形式去访问,即foo.barfoo['bar'],因此看到有“.”或“[]”即可当成是属性查找。
  • this就不用说了,好好的关键字。

看一下这段代码:

var foo = this;
foo.bar();

这2行的代码就体现了3种对象查找,分别为:

  1. 查找this对象,并赋值给foo变量。
  2. 查看foo变量。
  3. 查找foo变量下的bar属性,并将之作为一个函数进行调用。

变量的查找

当确定一个对象的查找为变量查找后,可以按照变量查找的规则来查看。

变量查找,即在作用域链上进行查找,作用域链是javascript非常著名的2条链之一,以下代码体现一个标准的作用域链:

var foo = 1;
function a() {
    var bar = 2;
    function b() {
        foo = 3;
        function c() {
            alert(foo + ',' + bar); // 注意这一行
        }
        c();
    }
    b();
}
a();

在c函数中,一共进行了2个变量的查找,分别为foo和bar。

变量的查找可以简单地遵守“从下向上”的原则,即:

  1. 在函数c的范围内查找foo和bar,显然在c里面并没有foo和bar的声明,查找失败。
  2. 在包含c的函数,即函数b的范围内查找foo和bar,可以看到b里面只有对foo的赋值,并没有声明,查找失败。
  3. 在包括b的函数,即函数a的范围内查找foo和bar,可以找到bar的声明,因此确定bar为2。
  4. 由于a不被任何函数包含,那么就在全局作用域内查找foo,发现有foo的声明,因此确定foo的值为1。但是由于在函数b中,对这个foo有赋值,所以foo的值被修改为3。
  5. 完确定foo的值为3,bar的值为2,因此输出"3,2"

总结一下,变量的查找是延着作用域链进行的,作用域链可以简单地看成函数间的包含关系,被包含的函数中不存在某个变量时,在包含他的函数中查找,直到全局作用域

属性的查找

当确定一个对象的查找为属性查找后,可以按照属性查找的规则来查看。

属性查找,即在原型链上进行查找,原型链是javascript双链的另一条,以下可以表示出一个原型链:

var a = function() {};
var b = function() {};
var c = function() {};
b.prototype = new a();
c.prototype = new b();
a.prototype.foo = 1;
b.prototype.bar = 2;
c.prototype.foo = 3;
var o = new c();
alert(o.foo + ',' + o.bar); // 这一行进行查找

属性查找是一个不断寻找prototype的过程,即:

  1. 查找c.prototype中,有没有显示定义foo和和bar,发现定义了foo,其值为3。
  2. 发现c.prototype就是new b()得到的对象,那么查找b.prototype中,有没有显示定义bar,发现定义了,其值为2。
  3. 因此确定foo的值为3,bar的值为2,输出"3,2"

总结一下,属性查找是延着原型链进行的,原型链的具体知识这里不作详细解释,可以另找文章进行参考。所有的对象,其原型链最终会是Object.prototype

this的查找

this的查找是很多人迷茫的一点,也似乎有很多人抱有this不稳定这样的看法,实在令人无语。this的查找可以说是3种对象查找中最为简单的,因为其实this对象的确定根 本没有一个“查找”的过程。

首先,this对象只会在一个函数中需要确定,如果是在全局域下,this永远为Global对象,在浏览器中通常就是window对象。而在javascript中,函数的调用一共有4种方式:

Function Invocation Pattern

诸如`foo()`的调用形式被称为Function Invocation Pattern,是函数最直接的使用形式,注意这里的foo是作为单独的变量出现,而不是属性。

在这种模式下,foo函数体中的this永远为Global对象,在浏览器中就是window对象。

Method Invocation Pattern

诸如`foo.bar()`的调用形式被称为Method Invocation Pattern,注意其特点是被调用的函数作为一个对象的属性出现,必然会有“.”或者“[]”这样的关键符号。

在这种模式下,bar函数体中的this永远为“.”或“[”前的那个对象,如上例中就一定是foo对象。

Constructor Pattern

`new foo()`这种形式的调用被称为Constructor Pattern,其关键字`new`就很能说明问题,非常容易识别。

在这种模式下,foo函数内部的this永远是new foo()返回的对象。

Apply Pattern

`foo.call(thisObject)`和`foo.apply(thisObject)`的形式被称为Apply Pattern,使用了内置的`call`和`apply`函数。

在这种模式下,`call`和`apply`的第一个参数就是foo函数体内的this,如果thisObject是`null`或`undefined`,那么会变成Global对象。

应用以上4种方式,确定一个函数是使用什么样的Pattern进行调用的,就能很容易确定this是什么。

另外,this是永远不会延作用域链或原型链出现一个“查找”的过程的,只会在函数调用时就完全确认。

总结

对于一个对象的查找:

  1. 确定是变量查找、属性查找还是this查找。
  2. 如果是变量查找,则延作用域链找,找不到就是ReferenceError。
  3. 如果是属性查找,就延原型链找,找歪以就是undefined。
  4. 如果是this查找,去找调用函数的代码,根据调用的形式来确定this是什么。
  5. 注意把一次查找过程拆分开来,比如this.foo.bar.yahoo(),可以拆分成这样的代码,就能更清楚了:

    var o = this; // this查找
    var foo = o.this; // 属性查找
    var bar = foo.bar; // 属性查找
    bar.yahoo(); // 属性查找,加Method Invocation Pattern
    

最后,如果有一天你可以了解这些东西,这篇文章对你用户也就不大了:

  • 为什么延作用域查找不到会有ReferenceError。
  • 其实变量也是属性,一个特殊对象的属性。
  • this也许不是Global,也许会是undefined。

本文永久地址:http://www.otakustay.com/object-lookup-in-javascript/

posted @ 2011-04-20 17:04 Gray Zhang 阅读(1644) 评论(6) 编辑

经典动画

话不多说,首先来个经典的动画函数:

function animate(element, name, from, to, time) {
    time = time || 800; //默认0.8秒

    var style = element.style,
        latency = 60, // 每60ms一次变化
        count = time / latency, //变化的次数
        step = Math.round((to - from) / count), //每一步的变化量
        now = from;

    function go() {
        count--;
        now = count ? now + step : to;
        style[name] = now + 'px';

        if (count) {
            setTimeout(go, latency);
        }
    }

    style[name] = from + 'px';
    setTimeout(go, latency);
}

姑且不论这个函数的设计存在局限性,如只能对以px为单位的样式进行修改。仅从函数的实现上来看,这可以是一个非常经典的动画理念,其基本逻辑由以下部分组成:

  1. 获取起点值from和终点值to,通过动画需要进行的时间time,以及每侦间隔latency的要求,计算出值的改变次数count和每次改变的量step
  2. 开启setTimeout(fn, latency);来步进到下一侦。
  3. 在下一侦中,设置属性步进一次,如果动画还没结束,再回到第2步继续下一侦。

这个函数工作得很好,服务了千千万万的站点和系统,事实上jQuery的animate函数的核心也无非是setInterval函数。

但是,随着现在系统复杂度的稳步上升,动画效果也越来越多,同时对动画的流畅度也有了更多的重视,这导致上面的函数会出现一些问题。例如同时打开100个动画效果,根据上面的函数,很明显会有100个定时器在同时运行,这些定时器之间的调度会对性能有轻微的影响。虽然在正常的环境中,这些许的影响并不会有什么关系,但是在动画这种对流畅度有很高要求的环境下,任何细微的影响都可能产生出不好的用户体验。

在这样的情况下,有一些开发者就发明了一种基于统一帧管理的动画框架,他使用一个定时器触发动画帧,不同的动画来注册这些帧,在每一帧上处理多个动画的属性变化。这样的好处是减少了定时器调度的开销,但是对于动画框架的开发者来说,统一帧管理、提供监听帧的API等,都是需要开发和维护的。

浏览器的直接支持

最终,浏览器厂商们发现这件事其实可以由他们来做,并且基于浏览器层面,还可以有更多的优化,比如:

  • 对于一个侦中对DOM的所有操作,只进行一次Layout和Paint。
  • 如果发生动画的元素被隐藏了,那么就不再去Paint。

于是,浏览器开始推出一个API,叫做requestAnimationFrame,关于这个函数,MDC的相关页面有比较详细的介绍,简单来说,这个函数有2种使用方法:

  1. 调用requestAnimationFrame函数,传递一个callback参数,则在下一个动画帧时,会调用callback。
  2. 不传递参数地直接调用该函数,启动动画帧,下一个帧触发时,会同时触发window.onmozbeforepaint事件,可以通过注册该事件来进行动画。

第2种方法由于依赖于Firefox自己的事件,且beforepaint事件还没进入到标准中,所以不推荐使用,还是使用第1种方式比较好。此时,我们的动画逻辑可以变成这样:

  1. 记录当前时间startTime,作为动画开始的时间。
  2. 请求下一帧,带上回调函数。
  3. 下一帧触发时,回调函数的第一个参数为当前的时间,再与startTime进行比较,确定时间间隔ellapseTime
  4. 判断ellapseTime是否已经超过事先设定的动画时间time,如果超过,则结束动画。
  5. 计算动画属性变化的差值differ = to - from,再确定在ellapseTime的时候应该变化多少step = differ / time * ellapseTime
  6. 计算出现在应该变化到的位置Math.round(from + step),并重新对样式赋值。
  7. 继续请求下一帧。

新的动画函数

下面就是一个全新的动画函数:

function animate(element, name, from, to, time) {
    time = time || 800; // 默认0.8秒
    var style = element.style,
        startTime = new Date;

    function go(timestamp) {
        var progress = timestamp - startTime;
        if (progress >= duration) {
            style[name] = to + 'px';
            return;
        }

        var now = (to - from) * (progress / duration);
        style[name] = now.toFixed() + 'px';
        requestAnimationFrame(go);
    }

    style[name] = from + 'px';

    requestAnimationFrame(go);
}

到这一步,还剩一个问题,那就是并不是每个浏览器都支持requestAnimationFrame函数的,所以再做一个简单的修正。

根据Firefox的特性来看,其mozRequestAnimationFrame提供的最高FPS为60,并且会根据每一帧的计算的耗时来进行调整,比如每一帧计算用了1s,那他只会提供1FPS的动画效果。

而Chrome的高版本同样也实现了这个函数,叫webkitRequestAnimationFrame,可以预见未来还会有Opera的oRequestAnimationFrame和IE的msRequestAnimationFrame,所以这里一并做一个简单的兼容处理:

requestAnimationFrame = window.requestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.msRequestAnimationFrame || 
    window.oRequestAnimationFrame ||
    function(callback) { setTimeout(callback, 1000 / 60); };

参考资料

本文永久地址:http://www.otakustay.com/animation-and-requestanimationframe/

posted @ 2011-04-18 17:03 Gray Zhang 阅读(1315) 评论(5) 编辑

相信每一个前端工程师都或多或少遇上过“乱码”这位仁兄,无论你的基础有多么扎实,在生产的过程中都免不了偶尔和“乱码”兄弟喝上几杯茶吧。作为一个前端工程师,你是如何指定一个页面的编码的呢?你知道浏览器是怎么识别编码的吗?

首先,一个很简单的例子,用遇简的HTML页面来看看各浏览器下有什么不同:

<!DOCTYPE html>

最简HTML,<head><body>都没有内容,服务器也不给出具体的编码声明,直接从本地打开,各个浏览器下查看页面的编码:

浏览器 显示编码 备注
IE6 UTF-8  
IE8 UTF-8  
IE9 GB2312 系统默认字符集
Firefox3.5 GBK2312 系统默认字符集
Firefox4.0 ISO-8859-1 西欧语言,英语默认编码
Chrome GBK 系统默认字符集
Opera 中文-自动检测 应该也是GB2312

从表格中可以看出,对于没有使用任何手段声明编码的页面,各浏览器有着不同的解析。当然在最简的页面中,无论用什么编码(当然前提是ASCII的超集)都没有影响,但足以表现出正确设置编码的重要性。

编码声明

HTML4和HTML5分别采用了一个章节来阐述编码声明的方法,可以点击这里查看HTML4的相关章节点击这里查看HTML5的相关章节

首先,何为编码?编码即是通过一定的方式,指定浏览器(或称用户代理)以一种特殊的算法来解析字节流,以得到真正正确的内容。在HTML的标准中,编码可以使用别名来表示。编码的别名来自于IANA的定义,只有在该列表中出现的编码才可以被浏览器识别。因此如果把UTF-8写成UTF8,浏览器就有可能完全不予理睬。另外,编码别名是大小写不敏感的。

在HTML4中,提出有3种方法指定页面的编码,根据优先级高低依次是:

  1. HTTP头里的Content-Type字段后跟随字符集。
  2. 使用<meta http-equiv="Content-Type">标签来声明。
  3. 对于部分外部资源,如<script>标签加载的js文件,可以通过标签上的charset属性声明。

这个自然没有什么疑问,需要注意的是,通过<meta http-equiv="Content-Type">标签来声明页面的话,当浏览器遇上该标签时,如果发现自己使用的编码与标签声明的不符,是会回到头里重新解析页面的。这会导致页面的一部分被重新解析,因此如果试图使用标签的方式声明编码的话,建议将标签尽可能地写在前面。一个最佳实践是写在<head>标签之后,任何其他标签之前。关于这一点,Google PageSpeed也有相应的介绍

时代演进

但是随着时间的推移,开发者渐渐发现了一件事。就如同DOCTYPE的最简声明一样,其实浏览器在读取<meta>标签的编码的时候,并不是严格地按照标准进行的。总而言之,由于在HTML的解析阶段,基于在Tokenizer阶段之前就必须确定好页面的编码,因此浏览器不可能像分析DOM树一样,在DOM树构建的时候再分解<meta>标签的结构,取出其中的http-equivcontent属性,再确定编码。

现实中,浏览器做了一件非常简单的事,来读取<meta>标签定义的编码:

  1. 确定这是一个<meta>标签,这根据HTML解析的状态机,由"<"字符加上"meta"字符串就能确定。
  2. 查找该字符串(此处还没有标签的概念,只是个字符串),找到一个子字符串"charset"。
  3. 再向后读,忽略掉所有的空格字符,找到第一个有意义的字符c。
    • 如果c不是"="这个字符,则回到第2步继续找。
    • 如果c是"="这个字符,继续向下走。
  4. 再跳掉所有的空格字符和单引号、双引号等,向后扫描,直到遇上单引号、双引号、空格字符、结束标签等不应该出现的字符为上,截取其中扫描得出的字符串s。
  5. 分析s,得到编码别名。

从上面的算法,不难发现,下面几种写法,其实都能让浏览器正确地识别出编码:

  • <meta http-equiv="Cotnent-Type" content="text/html; charset=utf-8" />
  • <meta charset="utf-8" />
  • <meta charset=utf-8 />
  • ……以及其他很多古怪的写法。

于是,随着历史的推进,终于有一天,各浏览器厂商们坐在了一起,开始讨论这个问题……最终他们惊奇地发现各自的实现非常相似(也许根本就是相互借鉴),所以他们决定将这种方式变成一个标准……最后,再经过漫长的讨论,HTML5中广为人爱的编码声明方式就诞生了。在HTML5中,称其为“meta charset元素”,其最简形式如下:

<meta charset=utf-8>

当然这是HTML的语法,如果遵从XHTML并觉得XHTML更加亲切地话,写成<meta charset="utf-8" />也是没问题的。

而前文所述的具体获取编码的算法也被详细地记录在案,可以在这里看到

到了HTML5时代,标准再次对编码的声明方式做了修正和细化,总得来说有以下的区别:

  • HTML5允许使用BOM来决定编码,但仅支持UTF-16的BOM(即U+FEFF),且没有说明BOM指定编码的优先级如何。
  • HTML5添加了meta charset标签。
  • HTML5规定如果一个页面没有指定编码,则使用ASCII作为其编码,而HTML4则规定浏览器可以根据所处的环境自行选择。

其他杂项

除了编码的基本声明方式外,标准中还有不少需要注意的细节:

  • 如果使用<meta>标签声明编码的话,该编码只能是ASCII的超集编码。可以简单地认为ASCII超集就是支持ASCII的256个字符的编码。
  • HTML5非常推荐使用UTF-8编码。
  • 标准中提出不要使用UTF-32、JIS_C6226-1983、JIS_X0212-1990、HZ-GB-2312、JOHAB等字符集,并禁止使用CESU-8、UTF-7、BOCU-1和SCSU字符集。但事实上浏览器却至少能识别UTF-7。
  • 对于想要严格遵守XHTML的开发者,应当使用XML声明来指定编码,即<?xml version="1.0" encoding="UTF-8" standalone="no" ?>。但是这个在IE6下会影响到DOCTYPE,所以开发者也不得在这一点上给予妥协,乖乖地去用HTML的声明方式。
  • 关于现实中各编码声明方式的优先级,以及一些其他需要注意的细节,这篇文章值得一读。

最佳实践

  • 尽可能使用HTTP头指定编码。
  • 尽可能使用UTF-8,或者至少全站所有资源使用统一编码。
  • 如果想使用UTF-16,就给文件加上BOM,以确定是Little Endian还是Big Endian的。
  • 如果使用<meta>标签指定编码,可以不使用http-equiv的形式,但尽可能让标签出现在前面,至少保证在任何非ASCII字符之前。
  • 链接外部的脚本,如果无法确定编码相同的话,加上charset属性。

本文永久地址:http://www.otakustay.com/learning-html5-charset/

posted @ 2011-04-11 17:38 Gray Zhang 阅读(1656) 评论(6) 编辑
摘要: 上一篇文章主要讲述了HTML文档的构成,同时肤浅地接触了“标签省略”这一概念,本文会从概念上介绍HTML文档中第一个出现的重要元素 - DOCTYPE。所谓DOCTYPE,最初是XML的概念,即通过一种特定的语法,作为一种元数据,来描述XML文档中允许出现的元素,以及各元素的组成、嵌套规则等。具体的概念可以在WIKI中中得到一个更详细的结果。但是在HTML中,DOCTYPE又有着一些不同的效果,其...阅读全文
posted @ 2011-03-31 18:34 Gray Zhang 阅读(2208) 评论(3) 编辑
摘要: 说起HTML的结构,很多人都能说得头头是道,一般来说答案可能是这样的:一个DOCTYPE,一个html,里面有head和body元素。这当然不能说是不正确的,但是如果问到一个最小的HTML源文件必须有哪一些东西的话,恐怕很少有人能正确地做出回答。先来回答一下这个问题,一个最简的HTML5源码文件需要的内容如下:是的,就这样,一个字符不多,一个字符不少,除了大小写可任意变化外,其他的任何内容都是不能...阅读全文
posted @ 2011-03-28 16:27 Gray Zhang 阅读(2668) 评论(11) 编辑
摘要: 最近前端的群都蛮热闹的,但我发现多数讨论的是javascript和css相关的问题,仿佛大家在努力创建各种交互、样式的时候,忘却了这一切的基础 - HTML。其实我很喜欢HTML,觉得这个语言远比XML来得有趣,其灵活、轻便远非极端规范的XML可以比拟。同时又因为HTML的作用范围极小,规定的标签有限等说不上优点还是缺点的特色,使得HTML有着自己的确定性。本系列的前面很大一部分会以非常短小的篇幅...阅读全文
posted @ 2011-03-25 12:40 Gray Zhang 阅读(2735) 评论(8) 编辑
摘要: 这个总结来源于一次优化的请求,最初某个页面的加载十分缓慢,load事件迟迟无法触发,因此希望可以通过对静态文件分域名等方式对页面的外部资源进行优化,拿得load事件尽可能早地触发。于是我查看了页面的源码,并对外部资源进行了整理,基于下面2个理念画出了一个推测的瀑布图:浏览器对同一个域只能并发2个HTTP请求 - 网上盛传已久。 javascript文件的加载会阻塞浏览器其他资源的加载 - 同样网上盛传已久。 然而,当我看到各浏览器中实际的瀑布图时,我知道自己又犯了一个简单的错误:太过相信所谓的权威和大众的声音,而没有更早地进行实践来检验理论的正确性……本篇文章就使用几种流行的浏览器,针对同一个阅读全文
posted @ 2011-03-08 17:33 Gray Zhang 阅读(1560) 评论(25) 编辑
摘要: 超级玛丽奥,一个无比经典的游戏,在红白机上的受欢迎程度无出其右,游戏的设计必有其出色之处,才导致那么多人的痴迷。本篇文章试图将超级玛丽的游戏设计的部分理念和细节转换为项目管理的方案,使用游戏的方式去管理项目,找寻一条快乐的管理之道。游戏的组成超级玛丽的游戏组成非常简单,只有几个必要的概念,但是可以玩出无数的花样:主角一个水管工,名叫玛丽奥,某天他的公主被邪恶的大魔王抓走了,于是开始了拯救公主的征途……在项目中,主角无疑是整个团队,首先保证整个团队的一致性和不可分割性,使其成为一个单独的个体,而非若干个个体组合起来的松散的组织。当团队拿到项目的这一刻,就如同站在屏幕左边的玛丽奥,一段征程就此开始阅读全文
posted @ 2011-03-02 15:07 Gray Zhang 阅读(2374) 评论(7) 编辑