最佳实践

charset

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

XHTML

<meta charset=”utf-8″>

1
<meta charset="utf-8">

html5 之前网页中会这样写:

XHTML

<meta http-equiv=”Content-Type” content=”text/html;
charset=utf-8″>

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

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

“传统”方式

用一种“传统”的思路,我们要更新页面某一个部分的UI,应该这么做:

JavaScript

$.get(‘url’, function(data) { ui.find(‘#name’).html(data.name) })

1
2
3
$.get(‘url’, function(data) {
  ui.find(‘#name’).html(data.name)
})

这个例子应该是一个典型的场景

  • 拉数据
  • 找元素
  • 改属性

为什么核心在于“找元素”呢?由于要尽可能的优化UI的性能,只能做最小更新操作,那么就需要找到发生变化的那个字段所需要的元素,单独对其进行操作。

所以jQuery的核心就在于query,首当其冲就是它能最快捷的帮我们query出需要的元素来,很好的满足了一个JS库的核心需求。当然它的另一个优势就是它的API设计得太简便了,简直是不会JS都能用,入门成本之低令人发指。

离屏绘制

上一节提到,绘制同样的一块区域,如果数据源是尺寸相仿的一张图片,那么性能会比较好,而如果数据源是一张大图上的一部分,性能就会比较差,因为每一次绘制还包含了裁剪工作。也许,我们可以先把待绘制的区域裁剪好,保存起来,这样每次绘制时就能轻松很多。

drawImage 方法的第一个参数不仅可以接收 Image 对象,也可以接收另一个
Canvas 对象。而且,使用 Canvas 对象绘制的开销与使用 Image
对象的开销几乎完全一致。我们只需要实现将对象绘制在一个未插入页面的
Canvas 中,然后每一帧使用这个 Canvas 来绘制。

JavaScript

// 在离屏 canvas 上绘制 var canvasOffscreen =
document.createElement(‘canvas’); canvasOffscreen.width = dw;
canvasOffscreen.height = dh;
canvasOffscreen.getContext(‘2d’).drawImage(image, sx, sy, sw, sh, dx,
dy, dw, dh); // 在绘制每一帧的时候,绘制这个图形
context.drawImage(canvasOffscreen, x, y);

1
2
3
4
5
6
7
8
// 在离屏 canvas 上绘制
var canvasOffscreen = document.createElement(‘canvas’);
canvasOffscreen.width = dw;
canvasOffscreen.height = dh;
canvasOffscreen.getContext(‘2d’).drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
 
// 在绘制每一帧的时候,绘制这个图形
context.drawImage(canvasOffscreen, x, y);

离屏绘制的好处远不止上述。有时候,游戏对象是多次调用 drawImage
绘制而成,或者根本不是图片,而是使用路径绘制出的矢量形状,那么离屏绘制还能帮你把这些操作简化为一次
drawImage 调用。

第一次看到 getImageDataputImageData 这一对
API,我有一种错觉,它们简直就是为了上面这个场景而设计的。前者可以将某个
Canvas 上的某一块区域保存为 ImageData 对象,后者可以将 ImageData
对象重新绘制到 Canvas 上面去。但实际上,putImageData
是一项开销极为巨大的操作,它根本就不适合在每一帧里面去调用。

百度禁止转码

通过百度手机打开网页时,百度可能会对你的网页进行转码,脱下你的衣服,往你的身上贴狗皮膏药的广告,为此可在
head 内添加

XHTML

<meta http-equiv=”Cache-Control” content=”no-siteapp” />

1
<meta http-equiv="Cache-Control" content="no-siteapp" />

相关链接:SiteApp
转码声明

扯扯“Model Driven UI”

2016/02/03 · 基础技术 ·
UI

原文出处:
刘骥(@刘骥-JimLiu)   

为什么我认为对于构建应用程序而言,MVVM/React是比jQuery更容易的方式?

文章比较浅,科普性质,大神们别嫌弃。

视野之外的绘制

有时候,Canvas
只是游戏世界的一个「窗口」,如果我们在每一帧中,都把整个世界全部画出来,势必就会有很多东西画到
Canvas 外面去了,同样调用了绘制
API,但是并没有任何效果。我们知道,判断对象是否在 Canvas
中会有额外的计算开销(比如需要对游戏角色的全局模型矩阵求逆,以分解出对象的世界坐标,这并不是一笔特别廉价的开销),而且也会增加代码的复杂程度,所以关键是,是否值得。

我做了一个实验,绘制一张 320×180 的图片 104
次,当我每次都绘制在 Canvas 内部时,消耗了 40ms,而每次都绘制在 Canvas
外时,仅消耗了 8ms。大家可以掂量一下,考虑到计算的开销与绘制的开销相差
2~3 个数量级,我认为通过计算来过滤掉哪些画布外的对象,仍然是很有必要的。

DOCTYPE

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

DTD(Document Type Definition)
声明以 <!DOCTYPE> 开始,不区分大小写,前面没有任何内容,如果有其他内容(空格除外)会使浏览器在
IE 下开启怪异模式(quirks mode)渲染网页。公共
DTD,名称格式为注册//组织//类型 标签//语言,注册指组织是否由国际标准化组织(ISO)注册,+表示是,-表示不是。组织即组织名称,如:W3C。类型一般是
DTD。标签是指定公开文本描述,即对所引用的公开文本的唯一描述性名称,后面可附带版本号。最后语言
DTD 语言的 ISO 639 语言标识符,如:EN 表示英文,ZH 表示中文。XHTML 1.0
可声明三种 DTD 类型。分别表示严格版本,过渡版本,以及基于框架的 HTML
文档。

  • HTML 4.01 strict
XHTML

&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd"&gt;

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f4a3d4b690825595726-1">
1
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f4a3d4b690825595726-1" class="crayon-line">
 &lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01//EN&quot; &quot;http://www.w3.org/TR/html4/strict.dtd&quot;&gt;
</div>
</div></td>
</tr>
</tbody>
</table>
  • HTML 4.01 Transitional
XHTML

&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd"&gt;

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f4a3d4b699456112895-1">
1
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f4a3d4b699456112895-1" class="crayon-line">
 &lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot; &quot;http://www.w3.org/TR/html4/loose.dtd&quot;&gt;
</div>
</div></td>
</tr>
</tbody>
</table>
  • HTML 4.01 Frameset
JavaScript

&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"http://www.w3.org/TR/html4/frameset.dtd"&gt;

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f4a3d4b69d342863431-1">
1
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f4a3d4b69d342863431-1" class="crayon-line">
&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Frameset//EN&quot; &quot;http://www.w3.org/TR/html4/frameset.dtd&quot;&gt;
</div>
</div></td>
</tr>
</tbody>
</table>
  • 最新 HTML5 推出更加简洁的书写,它向前向后兼容,推荐使用。
JavaScript

&lt;!doctype html&gt;

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f4a3d4b6a1157483452-1">
1
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f4a3d4b6a1157483452-1" class="crayon-line">
&lt;!doctype html&gt;
</div>
</div></td>
</tr>
</tbody>
</table>

在 HTML中 doctype 有两个主要目的。

  • 对文档进行有效性验证。

    它告诉用户代理和校验器这个文档是按照什么 DTD
    写的。这个动作是被动的,每次页面加载时,浏览器并不会下载 DTD
    并检查合法性,只有当手动校验页面时才启用。

  • 决定浏览器的呈现模式

    对于实际操作,通知浏览器读取文档时用哪种解析算法。如果没有写,则浏览器则根据自身的规则对代码进行解析,可能会严重影响
    html 排版布局。浏览器有三种方式解析 HTML 文档。 *
    非怪异(标准)模式 * 怪异模式 * 部分怪异(近乎标准)模式
    关于IE浏览器的文档模式,浏览器模式,严格模式,怪异模式,DOCTYPE
    标签,可详细阅读模式?标准!的内容。

没那么糙的方式

现在有了MVVM和Virtual-DOM了,batch
update也都是标配,Business层可以肆无忌惮的对Model进行任何粒度的CRUD。UI也不需要监听Model上的各种事件了——简单的说来,虽然整个数据流没有变,但是每一个环节都变简单了。

所以MVVM和Virtual-DOM解决的问题是数据绑定/数据展现吗?是,也不全是。更深究地说,它们解决的问题是帮助UI和Model之间“脏活累活谁来干”的问题——都没人干,于是只能让框架干了。从此以后,

对于Model而言:“老子就管写,你爱读不读。反正我的值是对的,用户看到展现不对那都赖你。”

对于UI而言:“老子就歇着,你爱咋样就来弄我两下,但是活儿得好,别让我太累,用户嫌卡那就怪你。”

至于Model如何Drive
UI,Angular(脏检查)、React(Virtual-DOM)用的办法是主动的发现Model的变化,然后去推动UI更新;Avalon、Vue基于property
getter的做法是被动的等Model发生变化。
除了Virtual-DOM以外,都需要对UI进行预处理,解析出一个UI Element ->
property之间的依赖关系,知道每一个Element依赖了Model的哪个字段。把这张图反过来,就知道当一个property被修改时,它会影响那些个Element,从而实现最小更新。
而Virtual-DOM的最小化patch方案是通过tree-diff计算出来的,基于现代浏览器“老子for循环跑的飞快”的霸气,执行tree-diff的速度很理想。于是就直接不需要构建依赖关系,用起来更简单粗暴;进而在需要的时候有一定的优化空间,可以通过immutable这种方式来快速跳过tree-diff当中的某些环节。
所以在精心优化的情况下,Virtual-DOM应该最快的无疑,property
getter有更强的适应性,天生就很快,但从外部去优化它很难。
React另一个优势是它的启动速度,由于不需要构建依赖关系,甚至是连parse模板都不需要(这一步相当于直接在构建JSX的时候已经做好了),它启动步骤就短多了,夸张地说,直接render就出来了。
使用property
getter的方案对于Model层有非常微弱的侵入性(相比Knockout那是低多了),使用脏检查和Virtual-DOM对Model层都几乎没有侵入性。
当然上面所说的性能差异其实都没有那么大啦……只是因为我自己写过virtual-dom玩具,也看了Vue的源码,一点小结而已。

小结

正文就到这里,最后我们来稍微总结一下,在大部分情况下,需要遵循的「最佳实践」。

  1. 将渲染阶段的开销转嫁到计算阶段之上。
  2. 使用多个分层的 Canvas 绘制复杂场景。
  3. 不要频繁设置绘图上下文的 font 属性。
  4. 不在动画中使用 putImageData 方法。
  5. 通过计算和判断,避免无谓的绘制操作。
  6. 将固定的内容预先绘制在离屏 Canvas 上以提高性能。
  7. 使用 Worker 和拆分任务的方法避免复杂算法阻塞动画运行。

    1 赞 5 收藏
    评论

图片 1

优先使用 IE 最新版本和 Chrome

XHTML

<meta http-equiv=”X-UA-Compatible” content=”IE=edge,chrome=1″ />

1
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

这么做的问题

一句话

UI被设计为依赖Model,Model不应该依赖UI。

如果实现成贫血Model层,就会在逻辑代码里面去进行上面的query-update操作,如果是充血Model层那可能就在Model里。不论怎样,这样做都违背了上述依赖关系。

很简单,当UI发生变化(这种变化在迭代当中非常频繁)的时候,不仅需要修改UI本身,也需要去修改逻辑代码或者Model层,比方说#name这个ID换掉了,得换个选择器;比方说span变成了textbox,得把.html()换成.val();比方说整个UI层重新换了一套CSS命名规范,或者上了一个className混淆方案,可能让所有的addClass/removeClass/hasClass全瞎;比方说运营需要“重要的事情说三遍”于是同一个字段要被连续展现3次;比方说相册改版,啥没变,惟独从井字格变成轮播图了……

这些本身应该是UI的事儿——毫无业务逻辑在里面——却需要去改逻辑代码,依赖关系颠倒过来了,形成了anti-pattern。

所以现在流行说“单向数据流”,它是对上面所说的依赖关系的一个形象描述。

Canvas 上下文是状态机

Canvas API 都在其上下文对象 context 上调用。

JavaScript

var context = canvasElement.getContext(‘2d’);

1
var context = canvasElement.getContext(‘2d’);

我们需要知道的第一件事就是,context 是一个状态机。你可以改变 context
的若干状态,而几乎所有的渲染操作,最终的效果与 context
本身的状态有关系。比如,调用 strokeRect 绘制的矩形边框,边框宽度取决于
context 的状态 lineWidth,而后者是之前设置的。

JavaScript

context.lineWidth = 5; context.strokeColor = ‘rgba(1, 0.5, 0.5, 1)’;
context.strokeRect(100, 100, 80, 80);

1
2
3
4
context.lineWidth = 5;
context.strokeColor = ‘rgba(1, 0.5, 0.5, 1)’;
 
context.strokeRect(100, 100, 80, 80);

图片 2

说到这里,和性能貌似还扯不上什么关系。那我现在就要告诉你,对
context.lineWidth
赋值的开销远远大于对一个普通对象赋值的开销,你会作如何感想。

当然,这很容易理解。Canvas 上下文不是一个普通的对象,当你调用了
context.lineWidth = 5
时,浏览器会需要立刻地做一些事情,这样你下次调用诸如 stroke
strokeRect 等 API 时,画出来的线就正好是 5
个像素宽了(不难想象,这也是一种优化,否则,这些事情就要等到下次
stroke 之前做,更加会影响性能)。

我尝试执行以下赋值操作 106
次,得到的结果是:对一个普通对象的属性赋值只消耗了 3ms,而对 context
的属性赋值则消耗了
40ms。值得注意的是,如果你赋的值是非法的,浏览器还需要一些额外时间来处理非法输入,正如第三/四种情形所示,消耗了
140ms 甚至更多。

JavaScript

somePlainObject.lineWidth = 5; // 3ms (10^6 times) context.lineWidth =
5; // 40ms context.lineWidth = ‘Hello World!’; // 140ms
context.lineWidth = {}; // 600ms

1
2
3
4
somePlainObject.lineWidth = 5;  // 3ms (10^6 times)
context.lineWidth = 5;  // 40ms
context.lineWidth = ‘Hello World!’; // 140ms
context.lineWidth = {}; // 600ms

context 而言,对不同属性的赋值开销也是不同的。lineWidth
只是开销较小的一类。下面整理了为 context
的一些其他的属性赋值的开销,如下所示。

属性 开销 开销(非法赋值)
line[Width/Join/Cap] 40+ 100+
[fill/stroke]Style 100+ 200+
font 1000+ 1000+
text[Align/Baseline] 60+ 100+
shadow[Blur/OffsetX] 40+ 100+
shadowColor 280+ 400+

与真正的绘制操作相比,改变 context
状态的开销已经算比较小了,毕竟我们还没有真正开始绘制操作。我们需要了解,改变
context 的属性并非是完全无代价的。我们可以通过适当地安排调用绘图 API
的顺序,降低 context 状态改变的频率。

favicon icon

XHTML

<link rel=”shortcut icon” type=”image/ico” href=”/favicon.ico” />
<!– 添加 favicon icon –>

1
<link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> <!– 添加 favicon icon –>

比较详细的 favicon
介绍可参考

个人的感受

  • 程序怎么写,还得看活儿
  • 做Web App和做Web Page,取舍还是差别大
  • 怎么算Web App怎么算Web Page,还得看老板怎么想
  • 如若无所谓模式,无所谓架构,那一切都是白说,反正It works
  • 面向工资编程,终究还是为了出活儿快、下班早,需求变时别骂娘,早日升职加薪,当上总经理,迎娶白富美,走上人生巅峰

    1 赞 1 收藏
    评论

图片 1

数据源与绘制的性能

由于我们具备「把图片中的某一部分绘制到 Canvas
上」的能力,所以很多时候,我们会把多个游戏对象放在一张图片里面,以减少请求数量。这通常被称为「精灵图」。然而,这实际上存在着一些潜在的性能问题。我发现,使用
drawImage
绘制同样大小的区域,数据源是一张和绘制区域尺寸相仿的图片的情形,比起数据源是一张较大图片(我们只是把数据扣下来了而已)的情形,前者的开销要小一些。可以认为,两者相差的开销正是「裁剪」这一个操作的开销。

我尝试绘制 104 次一块 320×180 的矩形区域,如果数据源是一张
320×180 的图片,花费了 40ms,而如果数据源是一张 800×800
图片中裁剪出来的 320×180 的区域,需要花费 70ms。

虽然看上去开销相差并不多,但是 drawImage 是最常用的 API
之一,我认为还是有必要进行优化的。优化的思路是,将「裁剪」这一步骤事先做好,保存起来,每一帧中仅绘制不裁剪。具体的,在「离屏绘制」一节中再详述。

发表评论

电子邮件地址不会被公开。 必填项已用*标注