MAC下编译动态链接库函数的可见性

编译的时候加上 -fvisibility=hidden,可以隐藏一般函数的可见性,如果要使得函数可见,则要加上__attribute__((visibility(“default”)))

代码如下:

 

保存成a.c,然后用命令行编译:

gcc -shared -fPIC a.c -fvisibility=hidden

此时生成a.out,然后用nm查看,可以看到函数a前面是小t,说明是隐藏的,函数b前面是大T,说明是可见的

数组读取性能比较

1~255的数组,如何组织起来,读取比较快?

1)用BYTE数组
2)用int数组(32位)
3)用long long数组(64位)

 

 

 

结论:64位的最快,但比32位只快了一点点,比BYTE数组快很多(一倍以上)

浅谈document.domain

原文:http://yanni4night.com/2014/04/23/setting-document-domain/

在需要主子域跨域技术的应用场景中,父 frame 和子 frame 设置相同的 document.domain 是一种特别常用的方式,我们可以看见腾讯公司的页面中很多都会有一句:

qq.com 域页面的登录行为很多都是依赖这种方式与iframe结合来实现的。

事实上,W3C 的 DOM 2 HTML标准将 document.domain定义为只读的:

domain of type DOMString, readonly The domain name of the server that served the document, or null if the server cannot be identified by a domain name.

HTML5 草案 中有关于对 document.domain赋值的内容。

在 Webkit 的 Document.idl 源码中对 domain 有这样的定义:

这也说明了 domain 设置为“writable” 仅用于页面脚本:即允许主子域脚本进行通信,但不涉及 localStorageindexedDB 和 XMLHttpRequest 的共享。目前市场上主流浏览器都支持 domain可写,可以满足几乎所有主子域脚本通信需求,但在特殊情况下也有些许不同。

所有浏览器都不支持设置 domain 为当前域子域或不完整的超域,比如当前域为 “abc.google.com” ,那么设置domain 为 “www.abc.google.com” 或”ogle.com” 都是不允许的。现在测试各个浏览器在 host 为以下情形下设置不同domain的反应:

  • 为IP地址
  • 单节域名
  • 多节域名

测试 host 为 “google”、“www.google.com”、“10.11.202.231”。由子域名向父域名测试,因此前面的测试不会对后面的测试造成干扰。

UA/host google www.google.com 10.11.202.231
Firefox(Mac/Windows/Android) s.ff

 

m.ff ip.ff
Safari(iOS/Mac/Windows) s.safari m.safari ip.safari
IE6~7 s.ie67 m.ie67 ip.ie67
Chrome(Mac,Windows)
IE8~10
Opera(presto内核,Mac  / Windows )
s.chrome-ie810-opera m.chrome-ie810-opera ip.chrome-ie810-opera
IE(WP8) 无法打开 m.chrome-ie810-opera ip.chrome-ie810-opera

由上表可得出以下结论:

  • Firefox可以接受带 port 的父域名,但是任意 port 都会被忽略,其它浏览器则会报错;
  • 对于IP地址,IE6、IE7和Safari简单地将其当做为域名;
  • 仅Safari允许将 domain 设置为最后一节域名。Safari 以及 国内几乎所有带 webkit 内核的浏览器 使用了一种相对简单的方式,即在字符串层面上新的domain 是当前 domain 的“父域名”即可,可以从 webkit 中 Document.cpp 文件的源代码中看出:

因此即使是IP地址或是最后一节 domain 也会被允许设置。Internet Explorer 不开源,但可以猜测其对多节域名进行了最后一节域名限制,在 IE8+ 上增加了IP地址限制。Firefox 在3.0版本增加了此限制。对于单节域名如 http://hello/,所有浏览器都一致性地允许设置,当然,这相当于设置 domain 为自身。

Firefox浏览器忽略 port 的行为初衷不得而知,但可以测试该特性是在3.0版本上增加的。

值得一提的是,chromium 项目对 webkit 进行了一些修改,从而带来了一些新的特性,观察 Document.cpp 文件的源代码:

可见除了支持HTML5的 sandbox 之外,还增加了IP地址检测,因此 Chrome 才不会像Safari那样允许IP地址的一段设置为 domain。特别地,Chrome 还进行了顶级域名检测, chromium 项目会生成一个effective_tld_names.gperf 文件,提供了很多域名列表,末尾标记不为0的域名将不能设置为document.domain。比如,一个域名为www.english.uk的页面,在 Chrome 下将不能设置 document.domain为 english.uk,因为 english.uk 被认为是顶级域名从而报错:

SecurityError: Failed to set the ‘domain’ property on ‘Document’: ‘english.uk’ is a top-level domain.

这部分逻辑的一些代码来自 Mozilla ,因此Firefox (3.0+)也具有同样的特性。

截止今天(2014-04-24),这个列表至少包含472个顶级域名,包括孟加拉国、文莱、库克群岛、塞浦路斯、厄立特里亚、埃塞俄比亚、斐济、马尔维纳斯群岛、关岛、以色列、牙买加、肯尼亚、柬埔寨、科威特、缅甸、莫桑比克、尼加拉瓜、尼泊尔、新西兰、巴布亚新几内亚、土耳其、英国、也门、南非、赞比亚、津巴布韦等国家和地区的顶级域名。想必这些国家的网站在设置 document.domain 时会遇到一些困难了:)。

在对IE浏览器进行测试时,也发现了一些奇怪的事情。实验“aa.bb.cc.dd”域名,发现在 IE8+ 下将不能设置document.domain 为“cc.dd”。经过反复测试发现 IE8+在多节域名下允许设置 的双节域名中,两节单词中要至少有一个大于2个字母,换言之,下列域名都是不允许的:

  • sa.cn
  • o.jp
  • x.m

但下列是允许的:

  • sax.cn
  • o.com
  • xxx.k

暂不知微软用意为何,但可以联想到,新浪微博的短域名 t.cn在有下一级域名的情形下,将不能设置document.domain为 t.cn

即便拥有上面的诸多问题,不过都属于特例,除了 IE8+ 的短域名问题,其它基本都不会在日常的开发中遇到。

参考
  1. http://javascript.info/tutorial/same-origin-security-policy
  2. http://msdn.microsoft.com/en-us/library/ie/ms533740(v=vs.85).aspx
  3. https://developer.mozilla.org/en-US/docs/Web/API/document.domain

Object properties in JavaScript

[译]JavaScript中对象的属性

src: http://www.2ality.com/2012/10/javascript-properties.html
中文: http://www.cnblogs.com/ziyunfei/archive/2012/10/30/2745786.html

Update 2013-08-25: Blog post “Protecting objects in JavaScript” (Object.preventExtensions()Object.seal()Object.freeze()).

Properties determine the state of an object in JavaScript. This blog post examines in detail how they work.

Kinds of properties

JavaScript has three different kinds of properties: named data properties, named accessor properties and internal properties.

Named data properties (“properties”)

“Normal” properties of objects map string names to values. For example, the following objectobj has a data property whose name is the string "prop" and whose value is the number 123.

You can get (read) a property:

And you can set (write) a property:

Named accessor properties

Alternatively, getting and setting a property value can be handled via functions. Those functions are called accessor functions. A function that handles getting is called a getter. A function that handles setting is called a setter.

Let’s interact with obj:

Internal properties

Some properties are only used by the specification. They are called “internal”, because they are not directly accessible via the language, but they do influence its behavior. Internal properties have special names that are written in double square brackets. Two examples:

  • The internal property [[Prototype]] points to the prototype of an object. It can be read viaObject.getPrototypeOf(). Its value can only be set by creating a new object that has a given prototype, e.g. via Object.create() or __proto__ [1].
  • The internal property [[Extensible]] determines whether or not one can add properties to an object. It can be read via Object.isExtensible(). It can be set false viaObject.preventExtensions(). Once false, it cannot be become true again.

Property attributes

All of the state of a property, both its data and its meta-data, is stored in attributes. They are fields that a property has, much like an object has properties. Attribute keys are often written in double brackets.

The following attributes are specific to named data properties:

  • [[Value]] hold the property’s value, its data.
  • [[Writable]] holds a boolean indicating whether the value of a property can be changed.

The following attributes are specific to named accessor properties:

  • [[Get]] holds the getter, a function that is called when a property is read. That function computes the result of the read access.
  • [[Set]] holds the setter, a function that is called when a property is set to a value. The function receives that value as a parameter.

All properties have the following attributes:

  • [[Enumerable]] holds a boolean. Making a property non-enumerable hides it from some operations (see below).
  • [[Configurable]] holds a boolean. If false, you cannot delete a property, change any of its attributes (except [[Value]]) or convert between data property and accessor property. In other words, [[Configurable]] controls the writability of a property’s meta-data.

Default values

If you don’t specify attributes, the following defaults are used:

 

Attribute key Default value
[[Value]] undefined
[[Get]] undefined
[[Set]] undefined
[[Writable]] false
[[Enumerable]] false
[[Configurable]] false

These defaults are especially important for property descriptors (see below).

Property descriptors

A property descriptor encodes the attributes of a property as an object. Each of the properties of that object corresponds to an attribute. For example, the following is the descriptor of a read-only property whose value is 123:

You can achieve the same goal, immutability, via accessors. Then the descriptor looks as follows:

Functions that use property descriptors

The following functions allow you to work with property descriptors:

  • Object.defineProperty(obj, propName, propDesc)
    Create or change a property on obj whose name is propName and whose attributes are specified via propDesc. Return the modified object. Example:
  • Object.defineProperties(obj, propDescObj)
    The batch version of Object.defineProperty(). Each property of propDescObj holds a property descriptor. The names of the properties and their values tellObject.defineProperties what properties to create or change on obj. Example:
  • Object.create(proto, propDescObj?)
    First, create an object whose prototype is proto. Then, if the optional parameter propDescObjhas been specified, add properties to it – in the same manner as Object.defineProperties. Finally, return the result. For example, the following code snippet produces the same result as the previous snippet:
  • Object.getOwnPropertyDescriptor(obj, propName)
    Returns the descriptor of the own (non-inherited) property of obj whose name is propName. If there is no such property, undefined is returned.

Enumerability

This section explains which operations are influenced by enumerability and which aren’t. Below, we are assuming that the following definitions have been made:

Note that objects (including proto) normally have at least the prototype Object.prototype [2]:

Object.prototype is where standard methods such as toString and hasOwnProperty are defined.

Operations affected by enumerability

Enumerability only affects two operations: The for-in loop and Object.keys().

The for-in loop iterates over the names of all enumerable properties, including inherited ones (note that none of the non-enumerable properties of Object.prototype show up):

Object.keys() returns the names of all own (non-inherited) enumerable properties:

If you want the names of all own properties, you need to use Object.getOwnPropertyNames()(see example below).

Operations that ignore enumerability

All other operations ignore enumerability. Some read operations take inheritance into consideration:

Other read operations only work with own properties:

Creating, deleting and defining properties only affects the first object in a prototype chain:

Best practices

The general rule is that properties created by the system are non-enumerable, while properties created by users are enumerable:

That especially holds for the methods in prototype objects:

Thus, for your code, you should ignore enumerability. You normally shouldn’t add properties to built-in prototypes and objects, but if you do, you should make them non-enumerable to avoid breaking code.

As we have seen, non-enumerability mostly benefits for-in and ensures that legacy code using it won’t break. The non-enumerable properties create the illusion that for-in only iterates over the user-created own properties of an object. In your code, you should avoid for-in if you can [3].

If you use objects as maps from strings to values, you should only work with own properties and ignore enumerability. But there are more pitfalls for this use case [4].

Conclusion

In this post, we have examined the nature of properties, which is defined via so-called attributes. Note that actual JavaScript implementations do not necessarily organize properties via attributes, they are mainly an abstraction used by the ECMAScript specification. But one that is sometimes visible in the language itself, for example in property descriptors.

Further reading on 2ality:

References

  1. JavaScript: __proto__
  2. What object is not an instance of Object?
  3. Iterating over arrays and objects in JavaScript
  4. The pitfalls of using objects as maps in JavaScript

HTML5 cavas

导航


  • 前言
  • 基本知识
  • 绘制矩形
  • 清除矩形区域
  • 圆弧
  • 路径
  • 绘制线段
  • 绘制贝塞尔曲线
  • 线性变
  • 径向渐变(发散)
  • 图形变形(平移、旋转、缩放)
  • 矩阵变换(图形变形的机制)
  • 图形组合
  • 给图形绘制阴影
  • 绘制图像(图片平铺、裁剪、像素处理[不只图像、包括其他绘制图形])
  • 绘制文字
  • 保存和恢复状态(context)
  • 保存文件
  • 结合setInterval制作动画
  • 结语、demo下载

前言


<canvas></canvas>是html5出现的新标签,像所有的dom对象一样它有自己本身的属性、方法和事件,其中就有绘图的方法,js能够调用它来进行绘图 ,最近在研读《html5与css3权威指南》下面对其中最好玩的canvas的学习做下读书笔记与实验。

温馨提示:以下所有实验请使用最新版的opera

基本知识


    context:一直觉得这个翻译成“上下文”真够蛋疼的,context是一个封装了很多绘图功能的对象,获取这个对象的方法是  

        var context =canvas.getContext(“2d”);

也许这个2d勾起了大家的无限遐想,但是很遗憾的告诉你html5还只是个少女,不提供3d服务。

       canvas元素绘制图像的时候有两种方法,分别是

context.fill()//填充

context.stroke()//绘制边框

style:在进行图形绘制前,要设置好绘图的样式

context.fillStyle//填充的样式

context.strokeStyle//边框样式

context.lineWidth//图形边框宽度

颜色的表示方式:

直接用颜色名称:”red” “green” “blue”

十六进制颜色值: “#EEEEFF”

rgb(1-255,1-255,1-255)

rgba(1-255,1-255,1-255,透明度)

和GDI是如此的相像,所以用过GDI的朋友应该很快就能上手

绘制矩形  context.fillRect(x,y,width,height)  strokeRect(x,y,width,height)


     x:矩形起点横坐标(坐标原点为canvas的左上角,当然确切的来说是原始原点,后面写到变形的时候你就懂了,现在暂时不用关系)

     y:矩形起点纵坐标

     width:矩形长度

     height:矩形高度

清除矩形区域 context.clearRect(x,y,width,height)


     x:清除矩形起点横坐标

     y:清除矩形起点纵坐标

     width:清除矩形长度

     height:清除矩形高度

 圆弧context.arc(x, y, radius, starAngle,endAngle, anticlockwise)


    x:圆心的x坐标

    y:圆心的y坐标

    straAngle:开始角度

    endAngle:结束角度

    anticlockwise:是否逆时针(true)为逆时针,(false)为顺时针

ps:经过试验证明书本上ture是顺时针,false是逆时针是错误的,而且无论是逆时针还是顺时针,角度都沿着顺时针扩大,如下图:

 

一不小心画了小日本的国旗…赶紧调下颜色和大小,绿色倒是挺合适的~

路径  context.beginPath()    context.closePath()


细心的朋友会发现上面的画圆并不单单是直接用arc还用到了context的 beginPath   和closePath方法,参考书不愧是参考书,例子给得太简单了,实话说一开始我凌乱了,耐心下来做了几个实验才舒缓蛋疼的心情

实验代码如下,通过分别注释closePath 和beginPath看fill stoke 和fill stroke结合下画出来的两个1/4弧线达到实验效果

实验结果如下:

得出的结论有:*号为重点

1、系统默认在绘制第一个路径的开始点为beginPath

*2、如果画完前面的路径没有重新指定beginPath,那么画第其他路径的时候会将前面最近指定的beginPath后的全部路径重新绘制

3、每次调用context.fill()的时候会自动把当次绘制的路径的开始点和结束点相连,接着填充封闭的部分

ps:书本的结论是   如果没有closePath那么前面的路劲会保留,实验证明正确的结论是 如果没有重新beginPath那么前面的路劲会保留

ps1:如果你真心凌乱了,那么记住每次画路径都在前后加context.beginPath()   和context.closePath()就行

 绘制线段 context.moveTo(x,y)  context.lineTo(x,y)


    x:x坐标

    y:y坐标

每次画线都从moveTo的点到lineTo的点,

如果没有moveTo那么第一次lineTo的效果和moveTo一样,

每次lineTo后如果没有moveTo,那么下次lineTo的开始点为前一次lineTo的结束点

下面给出书本的例子,一朵绿色的菊花,涉及数学,不多解析,有兴趣的自己研究

绘制贝塞尔曲线(贝济埃、bezier) context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y) 

绘制二次样条曲线 context.quadraticCurveTo(qcpx,qcpy,qx,qy)


    cp1x:第一个控制点x坐标

    cp1y:第一个控制点y坐标

    cp2x:第二个控制点x坐标

    cp2y:第二个控制点y坐标

    x:终点x坐标

    y:终点y坐标

     qcpx:二次样条曲线控制点x坐标

    qcpy:二次样条曲线控制点y坐标

    qx:二次样条曲线终点x坐标

    qy:二次样条曲线终点y坐标

下面给出书本的例子,一朵扭曲的绿色菊花…编书这哥们对菊花情有独钟啊- –

 线性渐变 var lg= context.createLinearGradient(xStart,yStart,xEnd,yEnd)

 线性渐变颜色lg.addColorStop(offset,color)


    xstart:渐变开始点x坐标

    ystart:渐变开始点y坐标

    xEnd:渐变结束点x坐标

    yEnd:渐变结束点y坐标

     offset:设定的颜色离渐变结束点的偏移量(0~1)

    color:绘制时要使用的颜色

给出书本偏移量的解析图,从图可以看出线性渐变可以是两种以上颜色的渐变

径向渐变(发散)var rg=context.createRadialGradient(xStart,yStart,radiusStart,xEnd,yEnd,radiusEnd)

径向渐变(发散)颜色rg.addColorStop(offset,color)


    xStart:发散开始圆心x坐标

    yStart:发散开始圆心y坐标

    radiusStart:发散开始圆的半径

    xEnd:发散结束圆心的x坐标

    yEnd:发散结束圆心的y坐标

    radiusEnd:发散结束圆的半径

     offset:设定的颜色离渐变结束点的偏移量(0~1)

    color:绘制时要使用的颜色

书本并没有给出发散偏移量的图,特地画了幅:

下面给出两个实验

图形变形


1、平移context.translate(x,y)

    x:坐标原点向x轴方向平移x

    y:坐标原点向y轴方向平移y

2、缩放context.scale(x,y)

    x:x坐标轴按x比例缩放

    y:y坐标轴按y比例缩放

3、旋转context.rotate(angle)

    angle:坐标轴旋转x角度(角度变化模型和画圆的模型一样)

由于(平移,缩放,旋转)和(平移,旋转,缩放)一样

(缩放,选装,平移)和(旋转,缩放,平移)一样

所以实验结果只能看到“4”中情况,其实是有两种情况被覆盖了

下面给出平移,缩放,旋转先后顺序不同,坐标轴的变化图

矩阵变换 context.transform(m11,m12,m21,m22,dx,dy)


所谓的矩阵变换其实是context内实现平移,缩放,旋转的一种机制

他的主要原理就是矩阵相乘

额,要讲解这个可以另开一个篇幅,庆幸的是已经有人做了总结,可以参考下面这篇文章

http://hi.baidu.com/100912bb_bb/item/90c4a7489518b0fa1281daf1

我们需要了解的是

context.translate(x,y) 等同于context.transform (1,0,0,1,x,y)或context.transform(0,1,1,0.x,y)

context.scale(x,y)等同于context.transform(x,0,0,y,0,0)或context.transform (0,y,x,0, 0,0);

context.rotate(θ)等同于

context.transform(Math.cos(θ*Math.PI/180),Math.sin(θ*Math.PI/180),

-Math.sin(θ*Math.PI/180),Math.cos(θ*Math.PI/180),0,0)

context.transform(-Math.sin(θ*Math.PI/180),Math.cos(θ*Math.PI/180),

Math.cos(θ*Math.PI/180),Math.sin(θ*Math.PI/180), 0,0)

 图形组合 context.globalCompositeOperation=type


    图形组合就是两个图形相互叠加了图形的表现形式,是后画的覆盖掉先画的呢,还是相互重叠的部分不显示等等,至于怎么显示就取决于type的值了
    type:

        source-over(默认值):在原有图形上绘制新图形

        destination-over:在原有图形下绘制新图形

        source-in:显示原有图形和新图形的交集,新图形在上,所以颜色为新图形的颜色

        destination-in:显示原有图形和新图形的交集,原有图形在上,所以颜色为原有图形的颜色

        source-out:只显示新图形非交集部分

        destination-out:只显示原有图形非交集部分

        source-atop:显示原有图形和交集部分,新图形在上,所以交集部分的颜色为新图形的颜色

        destination-atop:显示新图形和交集部分,新图形在下,所以交集部分的颜色为原有图形的颜色

        lighter:原有图形和新图形都显示,交集部分做颜色叠加

        xor:重叠飞部分不现实

        copy:只显示新图形

文字看得人眼花缭乱,特意画图一张:回头一看惊觉打错字,图片的原型为圆形,你懂的- –

以下为实验代码

结果是动态的切换各种组合

给图形绘制阴影


    context.shadowOffsetX :阴影的横向位移量(默认值为0)
    context.shadowOffsetY :阴影的纵向位移量(默认值为0)
    context.shadowColor :阴影的颜色
    context.shadowBlur :阴影的模糊范围(值越大越模糊)

先来个简单的例子

再来个书本上的五角星的例子

绘制图像 

绘图:context.drawImage
图像平铺:context.createPattern(image,type)

图像裁剪:context.clip()

像素处理:var imagedata=context.getImageData(sx,sy,sw,sh)


绘图 context.drawImage

context.drawImage(image,x,y)

        image:Image对象var img=new Image(); img.src=”url(…)”;

        x:绘制图像的x坐标

        y:绘制图像的y坐标

context.drawImage(image,x,y,w,h)

        image:Image对象var img=new Image(); img.src=”url(…)”;

        x:绘制图像的x坐标

        y:绘制图像的y坐标

        w:绘制图像的宽度

        h:绘制图像的高度

context.drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh):选取图像的一部分矩形区域进行绘制

image:Image对象var img=new Image(); img.src=”url(…)”;

        sx:图像上的x坐标
        sy:图像上的y坐标
        sw:矩形区域的宽度
        sh:矩形区域的高度
        dx:画在canvas的x坐标
        dy:画在canvas的y坐标
        dw:画出来的宽度
        dh:画出来的高度

    最后一个方法可能比较拗,还是上图吧

三个方法的运行结果如下:

图像平铺 context.createPattern(image,type)

    type:
        no-repeat:不平铺

        repeat-x:横方向平铺

        repeat-y:纵方向平铺

        repeat:全方向平铺

类似图形组合,给出动态的切换平铺类型代码

图像裁剪:context.clip()

context.clip()只绘制封闭路径区域内的图像,不绘制路径外部图像,用的时候

先创建裁剪区域

再绘制图像(之后绘制的图形都会采用这个裁剪区域,要取消这个裁剪区域就需要用到保存恢复状态,下面有讲)

给出圆形和星形的裁剪代码

像素处理:

获取像素颜色数组: var imagedata=context.getImageData(sx,sy,sw,sh)

    sx:cavas的x轴坐标点

    sy:canvas的y轴坐标点

    sw:距离x的宽度

    sh:距离y的高度

可以利用context.getImageData返回的一个像素颜色数组,顺序是所取像素范围的从左到右,从上到下,数组的元素是(所有图形,包括图片,和绘制的图形)每个像素的rgba
[r1,g1,b1,a1,r2,g2,b2,a2…]

设置像素颜色:context.putImageData(imagedata,dx,dy,dirtyX,dirtyY,dirtyWidth,dirtyHeight)
    对imagedata数组中的各个像素的r、g、b、a值进行修改,再调用putImageData方法进行绘制

        imagedata:修改后的imagedata

        dx:重绘图像的起点横坐标(重绘的起点和原来的图像一致的话就会把原来的图形覆盖掉,看起来就像是原来的图像变成现在的图像一样)

        dy:重绘图像的起点纵坐标

        //以下可选参数,设置重绘的矩形范围,如果缺省,默认会重绘所有的imegedata

        dirtyX:矩形左上角x轴坐标

        dirtyY:矩形左上角y轴坐标

        dirtyWidth:矩形长度

        dirtyHeight:矩形高度

 

绘制文字

填充文字:context.fillText(text,x,y)  

绘制文字轮廓 context.strokeText(text,x,y)


     text:要绘制的文字

     x:文字起点的x坐标轴

     y:文字起点的y坐标轴

     context.font:设置字体样式

     context.textAlign:水平对齐方式

          start、end、right、center

     context.textBaseline:垂直对齐方式

          top、hanging、middle、alphabetic、ideographic、bottom

     var length=context.measureText(text):计算字体长度(px)那么能不能计算高度啊,很遗憾,不能

保存和恢复状态
保存:context.save()
恢复:context.restore()


在上面的裁剪图片提过,一旦设定了裁剪区域,后来绘制的图形都只显示裁剪区域内的内容,要“取消”这个裁剪区域才能正常绘制其他图形,其实这个“取消”是利用save()方法和restore()方法来实现的。

    context.save():调用该方法,会保存当前context的状态、属性(把他理解成游戏存档)

    context.restore():调用该方法就能恢复到save时候context的状态、属性(游戏回档)

保存文件  canvas.toDataURL(MIME)


      在canvas中绘出的图片只是canvas标签而已,并非是真正的图片,是不能右键,另存为的,我们可以利用canvas.toDataURL()这个方法把canvas绘制的图形生成一幅图片,生成图片后,就能对图片进行相应的操作了。

结合setInterval制作动画


基本原理就是定时清除整个canvas重新绘制,下面给出“我弹、我弹、我弹弹弹”的代码 (额、名字而已)

小矩形在矩形区域移动,碰到矩形区域的边缘反弹

结语


历时一天半,本来以为可以玩转的,写下来才发现要玩转canvas还需要很多的实践,这个道理应该是适用所有的技术的,做人啊,就得谦虚点。本文如有错误,请及时留言给我纠正,希望能给正在学canvas绘图的童鞋有所帮助

Run .NET and Node.js code in-process with Edge.js

Edge.js:让.NET和Node.js代码比翼齐飞

通过Edge.js项目,你可以在一个进程中同时运行Node.js和.NET代码。在本文中,我将会论述这个项目背后的动机,并描述Edge.js提供的基本机制。随后将探讨一些Edge.js应用场景,它在这些场景中可以为你开发Node.js程序提供帮助。

为何要使用Edge.js?

虽然许多应用程序只能用Node.js编写,不过有些情况下又需要综合Node.js和.NET两者的优点。基于以下几个理由,你想要在程序中使用.NET和Node.js:.NET框架和NuGet包提供了一个丰富的功能生态系统,它很好地补充了Node.js和NPM模块;可能你希望在Node.js程序中重用某些现成的.NET组件;也可能想使用多线程CLR运行CPU密集型的计算,而这绝非是单线程的Node.js所擅长的;又或者你可能优先选择使用.NET框架和C#而不是使用C/C++编写原生的Node.js扩展来访问那些尚未通过Node.js暴露的操作系统机制。

一旦你决定在程序中使用Node.js和.NET,那么你必须将Node.js和.NET的组件用进程壁垒将两者分离开来,并建立某种形式的进程间通信的机制,比如说HTTP:

Edge.js提供另一种类似的组建异构系统的方式。它允许你在单一进程中同时运行Node.js和.NET代码,并且提供了V8和CLR之间的互操作机制。

使用Edge.js可以在一个进程中运行Node.js和.NET,而不用将其分割为两个进程,这样有两个主要的好处:更好的性能和更低的复杂性。

某个场景的性能测试显示,从Node.js向C#发出的进程内Edge.js请求比两个进程间通过HTTP发送的相同请求快32倍。与两个进程和进程间的通信信道相比,只处理一个单独的进程,明显降低了你需要解决的部署和维护的复杂性。

.NET欢迎Node.js

接下来我将用一个基础实例讲解Edge.js的关键概念,这个例子是从Node.js向C#发送请求。

第1行引入事先从NPM安装的edge模块。Edge.js是一个原生的Node.js组件。Edge.js的特殊之处在于,它被加载的时候便在node.exe进程内部开始代管CLR。

edge模块暴露了一个名为func的单函数。在高层次上,该函数以CLR代码为参数,然后返回一个JavaScript函数作为CLR代码的代理。func函数接受多种格式的CLR代码,从源代码,文件名,到预编译的CLR都可以。在上面的3-8行中,程序指定了一个异步的Lambda表达式作为C#文本代码。Edge.js提取出那段代码并将其编译为内存中的CLR程序集。然后它围绕着第3行的CLR代码(分配给hello变量的)创建并返回了一个JavaScript代理函数。需要注意的是,这个编译过程在每次调用edge.func函数时都会执行一次并将结果缓存。此外,如果你用同样的字符串变量调用edge.func函数两次,那么就会从缓存中获得相同的Func<object,Task<object>>实例。

Edge.js创建的hello函数是C#代码的代理函数,它在第10行由标准的Node.js异步模式调用。这个函数接收一个单独参数(Node.js字符串),并且还有一个接收错误和返回结果的回调函数。输入的参数在第4行被传递到C#异步Lambda表达式中,这个表达式在第6行将传入值附加到“.NET welcomes”字符串之后。当调用第10行的JavaScript回调函数的时候,这个C#中新构造的字符串被Edge.js作为result参数传递进去。JavaScript回调函数则将其打印在控制台上:“.NET welcomes Node.js”。

Edge.js提供了一套进程内Node.js和.NET代码之间规范的互操作模型。它不允许JavaScript直接调用任何CLR函数。CLR函数必须是一个Func<object,Task<object>> 委托。这种机制为Node.js和.NET互相传递数据提供了足够的灵活性。同时,它需要.NET代码异步执行,以便于和单线程的Node.js代码自然地集成在一起。这是Func<object,Task<object>>委托如何映射于Node.js异步模型概念:

互操作模式并不禁止你访问.NET framework的任何部分,但是它往往会要求你额外编写一个适配器层以暴露所需的.NET功能如同Func<object,Task<object>> 委托。这个适配器层要求你明确地定位.NET中的阻塞APIs的问题所在,它可能将这些运算运行在CLR线程池中以避免阻塞Node.js事件循环。

数据和功能

虽然Edge.js仅仅允许你在Node.js和.NET之间传递一个参数,但是这个参数可能是个复杂类型的。当从Node.js请求.NET代码的时候,Edge.js可以封送(marshal)所有标准的JavaScript类型:从基类型到对象和数组。当从.NET向Node.js传递数据的时候,Edge.js不但可以封送所有的基本CLR类型,而且还可以处理CLR对象实例、列表、集合和字典类型。从概念上讲,你可以认为在V8和CLR之间的数据传递就像是在一个环境中将数据序列化为JSON,而在另一个环境中对JSON进行反序列化。但是,Edge.js并没有在进程中进行实际的JSON序列化过程。相反,它直接在内存中进行V8和CLR类型系统之间的数据封送,而省略了字符串型中间代码,这个过程远比JSON序列化和反序列化更加高效。

Edge.js通过值进行数据封送,所以当执行过程跨越V8/CLR边界时,它会在V8或者CLR的堆中另外创建一份数据拷贝。这个规则有一处显著的例外:与通过值进行数据封送不同,Edge.js通过引用来封送函数。让我们通过下面这个例子来说明这个强有力的概念:

在这个例子中,Node.js调用addAndMultiplyBy2的C#中运行的函数。这个函数获取两个数字,而后返回它们总和的2倍。鉴于这个例子的目的,我们假设C#知道如何做加法但是却并不清楚如何做乘法。C#代码在计算和之后需要回调至JavaScript以进行乘法运算。

为了实现这个场景,Node.js应用程序在第18-20行定义一个multiplyBy2函数,并在第23行调用addAndMultiplyBy2函数时将其随同两个运算对象传递至C#代码。注意multiplyBy2函数是如何满足Edge.js规范的互操作模式的。这使得Edge.js可以在给multiplyBy2这个JavaScript函数创建.NET代理,就像是.NET中的Func<object,Task<object>>委托。这个JavaScript函数代理接下来被C#代码在第10行调用,用于对第8-9行中得到的和执行乘法运算。

遵守规范的互操作模式的函数也可以从.NET被封送到Node.js。能够在V8和CLR中双向封送函数是很强有力的概念,尤其是当掺杂着闭包的时候更是如此。请看下面这个例子:

在第1-7行,Edge.js创造了一个JavaScript函数createCounter,这个是C# Lambda表达式的代理。第9行中传给createCounter函数的的参数在第3行被强制转化为一个C#的本地变量。第4-5行的代码比较有趣:C#异步Lambda表达式的结果是一个Func<object,Task<object>>型的委托实例,它(第5行)的实现包含了第3行在闭包中定义的本地变量。当Edge.js将这个Func<object,Task<object>>实例封送为JavaScript函数回传给Node.js,并将其分配给第9行的counter变量的时候,这个JavaScript的counter函数有效的涵盖了CLR状态下的闭包。这点在第10-11行得到了充分的证明。这两行两次调用counter函数,结果返回的是一个不断增加的值。这是由于每次调用第5行实现的Func<object,Task<object>>都会使得第3行的本地变量的数值增加。

在V8和CLR之间封送函数的能力加上闭包的概念是个很强有力的机制。这样.NET代码就能够暴露CLR对象的功能给Node.js。第三行的本地变量在最后的例子中是一个Person类的实例。

让我们一起动手

我们来看几个实际的例子以便了解如何在Node.js应用程序中使用Edge.js。

Node.js是单线程的架构。如果要保持响应性,那么应用程序中就不能执行阻塞的代码。大部分Node.js程序都是在进程外执行CPU密集型的运算。外部进程通常使用的技术并不是Node.js。Edge.js使得这种场景非常容易实现。它允许你的Node.js程序在Node.js进程内部的CLR线程池中执行CPU密集型的逻辑运算。当CPU密集型的计算在CLR线程池的线程中运行时,V8线程上的Node.js程序仍然是可响应的。一旦CPU密集型操作结束,Edge.js同步线程就在V8线程上执行JavaScript回调函数。请看这个使用.NET功能转换图片格式的例子:

convertImageToJpg函数使用了.NET中的System.Drawing的功能将PNG图片转换为JPG格式。这是计算密集型的操作,因此第6行创建的C#实现(implementation)调用了Task.Run在CLR线程池中运行这个转换。当计算执行的时候,进程中的单例(singleton)V8线程可以处理后续的事件。C#代码随第6行的await关键字而等待图片转换的完成。只有在图片转换完成之后,convertImageToJpg在V8线程上执行第14-15行JavaScript回调代码,整个函数才算完成。

另一个让Edge.js大显身手的例子是在MS SQL中读取数据。现在Node.js开发者还没有什么读取MS SQL数据的方法可以比.NET Framework中的ADO.NET更加完善和成熟。Edge.js提供给你一个简单的在Node.js程序中利用ADO.NET的方法。请看下这个Node.js程序:

在第1行中,Edge.js通过编译sql.csx文件中的ADO.NET代码创建了sql函数。这个sql函数接受一个T-SQL命令构成的字符串,并使用ADO.NET异步执行它,然后将结果返回给Node.js。sql.csx文件用C#编写了不到100行的ADO.NET代码,它支持对MS SQL数据库执行CRUD四种操作:

在sql.csx文件中的实现(implementation)使用异步ADO.NET的API来访问MS SQL数据并执行Node.js传给它的T-SQL命令。

上面的两个例子仅仅代表了Edge.js帮你编写Node.js程序的一小部分场景。更多的例子可以参见Edge.js的GitHub站点。

路线图

Edge.js是一个遵循Apache 2.0协议的开源项目。它目前的开发很活跃,欢迎前来贡献代码。你可以用你的时间和经验来检查工作项目列表。

尽管本文中所有的例子都是使用C#写的,Edge.js支持在Node.js程序中运行任何CLR语言的代码。目前的扩展提供了对脚本语言F#PythonPowerShell的支持。通过语言扩展模型你能很容易的添加其他CLR语言的编译器。

Edge.js目前需要.NET Framework环境,因此只能运行在Windows上。但是对Mono的支持也在积极的开发中,不久就可以在MacOS和*nix上运行Edge.js程序了。

关于作者

Tomasz Janczuk是微软的一名软件工程师。他目前主要关注Node.js和Windows Azure。在此之前他从事.NET Framework和网络服务(web services)方面的工作。业余时间里,他在太平洋等地参加了很多户外活动。你可以在Twitter上关注他,@tjanczuk,也可以访问他的GitHub页面或者阅读他的博客以获得更多的资讯。

查看英文原文:Run .NET and Node.js code in-process with Edge.js

php: websocket server

下面我画了一个图演示 client 和 server 之间建立 websocket 连接时握手部分,这个部分在 node 中可以十分轻松的完成,因为 node 提供的 net 模块已经对 socket 套接字做了封装处理,开发者使用的时候只需要考虑数据的交互而不用处理连接的建立。而 php 没有,从 socket 的连接、建立、绑定、监听等,这些都需要我们自己去操作,所以有必要拿出来再说一说。

看了我写的上一篇文章的同学应该是对上图有了比较全面的理解。① 和 ② 实际上就是一个 HTTP 的请求和响应,只不过我们在处理的过程中我们拿到的是没有经过解析的字符串。如:

我们往常看到的请求是这个样子,当这东西到了服务器端,我们可以通过一些代码库直接拿到这些信息。

一、php 中处理 websocket

WebSocket 连接是由客户端主动发起的,所以一切要从客户端出发。第一步是要解析拿到客户端发过来的 Sec-WebSocket-Key 字符串。

前文中也提到了 client 请求的格式(如上),首先 php 建立一个 socket 连接,监听端口的信息。

1. socket 连接的建立

关于 socket 套接字的建立,相信很多大学修过计算机网络的人都知道了,下面是一张连接建立的过程:

相比 node,这个地方的处理实在是太麻烦了,上面几行代码并未建立连接,只不过这些代码是建立一个 socket 套接字必须要写的东西。由于处理过程稍微有复杂,所以我把各种处理写进了一个类中,方便管理和调用。

 demo.php 握手连接测试代码

 

上面这段代码是经过我调试了的,没太大的问题,如果想测试的话,可以在 cmd 命令行中键入 php /path/to/demo.php;当然,上面只是一个类,如果要测试的话,还得新建一个实例。

客户端代码可以稍微简单点:

运行服务器代码,当客户端连接的时候,我们可以看到:

通过上面的代码可以清晰的看到整个交流的过程。首先是建立连接,node 中这一步已经封装到了 net 和 http 模块,然后判断是否握手,如果没有的话,就 shakeHands。这里的握手我直接就 echo 了一个单词,表示进行了这个东西,前文我们提到过握手算法,这里就直接写了。

2. 提取 Sec-WebSocket-Key 信息

这里比较简单,直接正则匹配,websocket 信息头一定包含 Sec-WebSocket-Key,所以我们匹配起来也比较快捷~

3. 加密 Sec-WebSocket-Key

将 SHA-1 加密后的字符串再进行一次 base64 加密。如果加密算法错误,客户端在进行校检的时候会直接报错:

4. 应答 Sec-WebSocket-Accept

这里千万要注意,每一个请求和相应的格式,最后有一个空行,也就是 \r\n,开始测试的时候把这东西给弄丢了,纠结了半天。

当客户端成功校检key后,会触发 onopen 函数:

5. 数据帧处理

这里涉及的编码问题在前文中已经提到过了,这里就不赘述,php 对字符处理的函数太多了,也记得不是特别清楚,这里就没有详细的介绍解码程序,直接把客户端发送的数据原样返回,可以算是一个聊天室的模式吧。

客户端代码:

在连通之后发送数据,服务器原样返回:

 

二、注意问题

1. websocket 版本问题

客户端在握手时的请求中有Sec-WebSocket-Version: 13,这样的版本标识,这个是一个升级版本,现在的浏览器都是使用的这个版本。而以前的版本在数据加密的部分更加麻烦,它会发送两个key:

如果是这种版本(比较老,已经没在使用了),需要通过下面的方式获取

只能无限吐槽这种验证方式!相比 nodeJs 的 websocket 操作方式:

多么简洁,多么方便!有谁还愿意使用 php 呢。。。。

2. 数据帧解析代码

本文没有给出 decodeFrame 这样数据帧解析代码,前文中给出了数据帧的格式,解析纯属体力活。

3. 代码下载

对这部分感兴趣的同学,可以再去深究。提供了参考代码下载

4. 相关开源库参考

http://socketo.me Ratchet 为 php 封装的一个 WebSockets 库。 ]

Google 上搜索 php+websoket+class,也能找到不少相关的资料。

三、参考资料

deferred object in jQuery

作者: 阮一峰

日期: 2011年8月16日

jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本。

每个版本都会引入一些新功能。今天我想介绍的,就是从jQuery 1.5.0版本开始引入的一个新功能—-deferred对象

这个功能很重要,未来将成为jQuery的核心方法,它彻底改变了如何在jQuery中使用ajax。为了实现它,jQuery的全部ajax代码都被改写了。但是,它比较抽象,初学者很难掌握,网上的教程也不多。所以,我把自己的学习笔记整理出来了,希望对大家有用。

本文不是初级教程,针对的读者是那些已经具备jQuery使用经验的开发者。如果你想了解jQuery的基本用法,请阅读我编写的《jQuery设计思想》《jQuery最佳实践》

======================================

jQuery的deferred对象详解

作者:阮一峰

一、什么是deferred对象?

开发网站的过程中,我们经常遇到某些耗时很长的javascript操作。其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们都不是立即能得到结果的。

通常的做法是,为它们指定回调函数(callback)。即事先规定,一旦它们运行结束,应该调用哪些函数。

但是,在回调函数方面,jQuery的功能非常弱。为了改变这一点,jQuery开发团队就设计了deferred对象

简单说,deferred对象就是jQuery的回调函数解决方案。在英语中,defer的意思是”延迟”,所以deferred对象的含义就是”延迟”到未来某个点再执行。

它解决了如何处理耗时操作的问题,对那些操作提供了更好的控制,以及统一的编程接口。它的主要功能,可以归结为四点。下面我们通过示例代码,一步步来学习。

二、ajax操作的链式写法

首先,回顾一下jQuery的ajax操作的传统写法:

  $.ajax({

url: “test.html”,

success: function(){
alert(“哈哈,成功了!”);
},

error:function(){
alert(“出错啦!”);
}

});

(运行代码示例1

在上面的代码中,$.ajax()接受一个对象参数,这个对象包含两个方法:success方法指定操作成功后的回调函数,error方法指定操作失败后的回调函数。

$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5.0版本,返回的是deferred对象,可以进行链式操作。

现在,新的写法是这样的:

  $.ajax(“test.html”)

.done(function(){ alert(“哈哈,成功了!”); })

.fail(function(){ alert(“出错啦!”); });

(运行代码示例2

可以看到,done()相当于success方法,fail()相当于error方法。采用链式写法以后,代码的可读性大大提高。

三、指定同一操作的多个回调函数

deferred对象的一大好处,就是它允许你自由添加多个回调函数。

还是以上面的代码为例,如果ajax操作成功后,除了原来的回调函数,我还想再运行一个回调函数,怎么办?

很简单,直接把它加在后面就行了。

  $.ajax(“test.html”)

.done(function(){ alert(“哈哈,成功了!”);} )

.fail(function(){ alert(“出错啦!”); } )

.done(function(){ alert(“第二个回调函数!”);} );

(运行代码示例3

回调函数可以添加任意多个,它们按照添加顺序执行。

四、为多个操作指定回调函数

deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数,这是传统写法做不到的。

请看下面的代码,它用到了一个新的方法$.when()

  $.when($.ajax(“test1.html”), $.ajax(“test2.html”))

.done(function(){ alert(“哈哈,成功了!”); })

.fail(function(){ alert(“出错啦!”); });

(运行代码示例4

这段代码的意思是,先执行两个操作$.ajax(“test1.html”)和$.ajax(“test2.html”),如果都成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。

五、普通操作的回调函数接口(上)

deferred对象的最大优点,就是它把这一套回调函数接口,从ajax操作扩展到了所有操作。也就是说,任何一个操作—-不管是ajax操作还是本地操作,也不管是异步操作还是同步操作—-都可以使用deferred对象的各种方法,指定回调函数。

我们来看一个具体的例子。假定有一个很耗时的操作wait:

  var wait = function(){

var tasks = function(){

alert(“执行完毕!”);

};

setTimeout(tasks,5000);

};

我们为它指定回调函数,应该怎么做呢?

很自然的,你会想到,可以使用$.when():

  $.when(wait())

.done(function(){ alert(“哈哈,成功了!”); })

.fail(function(){ alert(“出错啦!”); });

(运行代码示例5

但是,这样写的话,done()方法会立即执行,起不到回调函数的作用。原因在于$.when()的参数只能是deferred对象,所以必须对wait()进行改写:

  var dtd = $.Deferred(); // 新建一个deferred对象

var wait = function(dtd){

var tasks = function(){

alert(“执行完毕!”);

dtd.resolve(); // 改变deferred对象的执行状态

};

setTimeout(tasks,5000);

return dtd;

};

现在,wait()函数返回的是deferred对象,这就可以加上链式操作了。

  $.when(wait(dtd))

.done(function(){ alert(“哈哈,成功了!”); })

.fail(function(){ alert(“出错啦!”); });

(运行代码示例6

wait()函数运行完,就会自动运行done()方法指定的回调函数。

六、deferred.resolve()方法和deferred.reject()方法

如果仔细看,你会发现在上面的wait()函数中,还有一个地方我没讲解。那就是dtd.resolve()的作用是什么?

要说清楚这个问题,就要引入一个新概念”执行状态”。jQuery规定,deferred对象有三种执行状态—-未完成,已完成和已失败。如果执行状态是”已完成”(resolved),deferred对象立刻调用done()方法指定的回调函数;如果执行状态是”已失败”,调用fail()方法指定的回调函数;如果执行状态是”未完成”,则继续等待,或者调用progress()方法指定的回调函数(jQuery1.7版本添加)。

前面部分的ajax操作时,deferred对象会根据返回结果,自动改变自身的执行状态;但是,在wait()函数中,这个执行状态必须由程序员手动指定。dtd.resolve()的意思是,将dtd对象的执行状态从”未完成”改为”已完成”,从而触发done()方法。

类似的,还存在一个deferred.reject()方法,作用是将dtd对象的执行状态从”未完成”改为”已失败”,从而触发fail()方法。

  var dtd = $.Deferred(); // 新建一个Deferred对象

var wait = function(dtd){

var tasks = function(){

alert(“执行完毕!”);

dtd.reject(); // 改变Deferred对象的执行状态

};

setTimeout(tasks,5000);

return dtd;

};

$.when(wait(dtd))

.done(function(){ alert(“哈哈,成功了!”); })

.fail(function(){ alert(“出错啦!”); });

(运行代码示例7

七、deferred.promise()方法

上面这种写法,还是有问题。那就是dtd是一个全局对象,所以它的执行状态可以从外部改变。

请看下面的代码:

  var dtd = $.Deferred(); // 新建一个Deferred对象

var wait = function(dtd){

var tasks = function(){

alert(“执行完毕!”);

dtd.resolve(); // 改变Deferred对象的执行状态

};

setTimeout(tasks,5000);

return dtd;

};

$.when(wait(dtd))

.done(function(){ alert(“哈哈,成功了!”); })

.fail(function(){ alert(“出错啦!”); });

dtd.resolve();

(运行代码示例8

我在代码的尾部加了一行dtd.resolve(),这就改变了dtd对象的执行状态,因此导致done()方法立刻执行,跳出”哈哈,成功了!”的提示框,等5秒之后再跳出”执行完毕!”的提示框。

为了避免这种情况,jQuery提供了deferred.promise()方法。它的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。

请看下面的代码:

  var dtd = $.Deferred(); // 新建一个Deferred对象

var wait = function(dtd){

var tasks = function(){

alert(“执行完毕!”);

dtd.resolve(); // 改变Deferred对象的执行状态

};

setTimeout(tasks,5000);

return dtd.promise(); // 返回promise对象

};

var d = wait(dtd); // 新建一个d对象,改为对这个对象进行操作

$.when(d)

.done(function(){ alert(“哈哈,成功了!”); })

.fail(function(){ alert(“出错啦!”); });

d.resolve(); // 此时,这个语句是无效的

(运行代码示例9

在上面的这段代码中,wait()函数返回的是promise对象。然后,我们把回调函数绑定在这个对象上面,而不是原来的deferred对象上面。这样的好处是,无法改变这个对象的执行状态,要想改变执行状态,只能操作原来的deferred对象。

不过,更好的写法是allenm所指出的,将dtd对象变成wait()函数的内部对象。

  var wait = function(dtd){

var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象

var tasks = function(){

alert(“执行完毕!”);

dtd.resolve(); // 改变Deferred对象的执行状态

};

setTimeout(tasks,5000);

return dtd.promise(); // 返回promise对象

};

$.when(wait())

.done(function(){ alert(“哈哈,成功了!”); })

.fail(function(){ alert(“出错啦!”); });

(运行代码示例10

八、普通操作的回调函数接口(中)

另一种防止执行状态被外部改变的方法,是使用deferred对象的建构函数$.Deferred()。

这时,wait函数还是保持不变,我们直接把它传入$.Deferred():

  $.Deferred(wait)

.done(function(){ alert(“哈哈,成功了!”); })

.fail(function(){ alert(“出错啦!”); });

(运行代码示例11

jQuery规定,$.Deferred()可以接受一个函数名(注意,是函数名)作为参数,$.Deferred()所生成的deferred对象将作为这个函数的默认参数。

九、普通操作的回调函数接口(下)

除了上面两种方法以外,我们还可以直接在wait对象上部署deferred接口。

  var dtd = $.Deferred(); // 生成Deferred对象

var wait = function(dtd){

var tasks = function(){

alert(“执行完毕!”);

dtd.resolve(); // 改变Deferred对象的执行状态

};

setTimeout(tasks,5000);

};

dtd.promise(wait);

wait.done(function(){ alert(“哈哈,成功了!”); })

.fail(function(){ alert(“出错啦!”); });

wait(dtd);

(运行代码示例12

这里的关键是dtd.promise(wait)这一行,它的作用就是在wait对象上部署Deferred接口。正是因为有了这一行,后面才能直接在wait上面调用done()和fail()。

十、小结:deferred对象的方法

前面已经讲到了deferred对象的多种方法,下面做一个总结:

(1) $.Deferred() 生成一个deferred对象。

(2) deferred.done() 指定操作成功时的回调函数

(3) deferred.fail() 指定操作失败时的回调函数

(4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。

(5) deferred.resolve() 手动改变deferred对象的运行状态为”已完成”,从而立即触发done()方法。

(6)deferred.reject() 这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为”已失败”,从而立即触发fail()方法。

(7) $.when() 为多个操作指定回调函数。

除了这些方法以外,deferred对象还有二个重要方法,上面的教程中没有涉及到。

(8)deferred.then()

有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。

  $.when($.ajax( “/main.php” ))

.then(successFunc, failureFunc );

如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调方法。如果then()只有一个参数,那么等同于done()。

(9)deferred.always()

这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行。

  $.ajax( “test.html” )

.always( function() { alert(“已执行!”);} );

(致谢:本文第一稿发表后,allenm来信指出原文对promise()的理解是错的。现在的第二稿是根据他的文章修改的,在此我表示衷心感谢。)

文档信息

Event-Based Programming: What Async Has Over Sync

src: http://code.tutsplus.com/tutorials/event-based-programming-what-async-has-over-sync–net-30027

 

One of JavaScript’s strengths is how it handles asynchronous (async for short) code. Rather than blocking the thread, async code gets pushed to an event queue that fires after all other code executes. It can, however, be difficult for beginners to follow async code. I’ll help clear up any confusion you might have in this article.

 

Understanding Async Code

JavaScript’s most basic async functions are setTimeout and setInterval. ThesetTimeout function executes a given function after a certain amount of time passes. It accepts a callback function as the first argument and a time (in milliseconds) as the second argument. Here’s an example of its usage:

01
02
03
04
05
06
07
08
09
10
11
console.log( "a" );
setTimeout(function() {
    console.log( "c" )
}, 500 );
setTimeout(function() {
    console.log( "d" )
}, 500 );
setTimeout(function() {
    console.log( "e" )
}, 500 );
console.log( "b" );

As expected, the console outputs “a”, “b”, and then 500 ms(ish) later, we see “c”, “d”, and “e”. I use “ish” because setTimeout is actually unpredictable. In fact, even theHTML5 spec talks about this issue:

“This API does not guarantee that timers will run exactly on schedule. Delays due to CPU load, other tasks, etc, are to be expected.”

Interestingly, a timeout will not execute until all of the remaining code in a block has executed. So if a timeout is set, and then some long running function executes, the timeout will not even start until that long running function has finished. In actuality, async functions like setTimeout and setInterval are pushed onto an queue known as theEvent Loop.

The Event Loop is a queue of callback functions. When an async function executes, the callback function is pushed into the queue. The JavaScript engine doesn’t start processing the event loop until the code after an async function has executed. This means that JavaScript code is not multi-threaded even though it appears to be so. The event loop is a first-in-first-out (FIFO) queue, meaning that callbacks execute in the order they were added onto the queue. JavaScript was chosen for the language ofnode because of how easy it is to write this kind of code.

Ajax

Asynchronous JavaScript and XML (Ajax) forever changed the landscape of JavaScript. All of the sudden, a browser could update a web page without having to reload it. The code for implementing Ajax in different browsers can be long and tedious to write; however, thanks to jQuery (and other libraries), Ajax became an extremely easy and elegant solution to facilitate client-server communication.

Asynchronously retrieving data with jQuery’s $.ajax is an easy cross-browser process, but it’s not immediately evident as to what exactly happens behind the scenes. For example:

01
02
03
04
05
06
07
08
09
10
var data;      
$.ajax({
    url: "some/url/1",
    success: function( data ) {
        // But, this will!
        console.log( data );
    }
})
// Oops, this won't work...
console.log( data );

It’s common, but incorrect, to assume the data is available immediately after calling$.ajax, but what actually happens is this:

1
2
3
4
5
6
7
xmlhttp.open( "GET", "some/ur/1", true );
xmlhttp.onreadystatechange = function( data ) {
    if ( xmlhttp.readyState === 4 ) {
        console.log( data );
    }
};
xmlhttp.send( null );

The underlying XmlHttpRequest (XHR) object sends the request, and the callback function is set to handle the XHR’s readystatechange event. Then the XHR’s sendmethod executes. As the XHR performs its work, an internal readystatechange event fires every time the readyState property changes, and it’s only when the XHR finishes receiving a response from the remote host that the callback function executes.

Working With Async Code

Async programming lends itself to what’s commonly referred to as “callback hell”. Because virtually all async functions in JavaScript use callbacks, performing multiple sequential async functions result in many nested callbacks–resulting in hard to read code.

Many of the functions within node.js are async. So, code like the following is quite common.

01
02
03
04
05
06
07
08
09
10
11
var fs = require( "fs" );
fs.exists( "index.js", function() {
    fs.readFile( "index.js", "utf8", function( err, contents ) {
        contents = someFunction( contents ); // do something with contents
        fs.writeFile( "index.js", "utf8", function() {
            console.log( "whew! Done finally..." );
        });
    });
});
console.log( "executing..." );

It’s also common to see client-side code, like the following:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
GMaps.geocode({
    address: fromAddress,
    callback: function( results, status ) {
        if ( status == "OK" ) {
            fromLatLng = results[0].geometry.location;
            GMaps.geocode({
                address: toAddress,
                callback: function( results, status ) {
                    if ( status == "OK" ) {
                        toLatLng = results[0].geometry.location;
                        map.getRoutes({
                            origin: [ fromLatLng.lat(), fromLatLng.lng() ],
                            destination: [ toLatLng.lat(), toLatLng.lng() ],
                            travelMode: "driving",
                            unitSystem: "imperial",
                            callback: function( e ){
                                console.log( "ANNNND FINALLY here's the directions..." );
                                // do something with e
                            }
                        });
                    }
                }
            });
        }
    }
});

Nested callbacks can get really nasty, but there are several solutions to this style of coding.

The problem isn’t with the language itself; it’s with the way programmers use the language — Async Javascript.

Named Functions

An easy solution that cleans nested callbacks is simply avoiding nesting more than two levels. Instead of passing anonymous functions to the callback arguments, pass a named function:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var fromLatLng, toLatLng;
var routeDone = function( e ){
    console.log( "ANNNND FINALLY here's the directions..." );
    // do something with e
};
var toAddressDone = function( results, status ) {
    if ( status == "OK" ) {
        toLatLng = results[0].geometry.location;
        map.getRoutes({
            origin: [ fromLatLng.lat(), fromLatLng.lng() ],
            destination: [ toLatLng.lat(), toLatLng.lng() ],
            travelMode: "driving",
            unitSystem: "imperial",
            callback: routeDone
        });
    }
};
var fromAddressDone = function( results, status ) {
    if ( status == "OK" ) {
        fromLatLng = results[0].geometry.location;
        GMaps.geocode({
            address: toAddress,
            callback: toAddressDone
        });
    }
};
GMaps.geocode({
    address: fromAddress,
    callback: fromAddressDone
});

Additionally, the async.js library can help handle multiple Ajax requests/responses. For example:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
async.parallel([
    function( done ) {
        GMaps.geocode({
            address: toAddress,
            callback: function( result ) {
                done( null, result );
            }
        });
    },
    function( done ) {
        GMaps.geocode({
            address: fromAddress,
            callback: function( result ) {
                done( null, result );
            }
        });
    }
], function( errors, results ) {
    getRoute( results[0], results[1] );
});

This code executes the two asynchronous functions, and each function accepts a “done” callback that executes after the async function finishing running. When both “done” callbacks finish, the parallel function’s callback executes and handles any errors or results from the two async functions.

Promises

From the CommonJS/A:

A promise represents the eventual value returned from the single completion of an operation.

There are many libraries that incorporate the promise pattern, and jQuery users already have a nice promise API available to them. jQuery introduced the Deferred object in version 1.5, and using the jQuery.Deferred constructor results in a function that returns a promise. A promise-returning function performs some sort of async operation and resolves the deferred upon completion.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var geocode = function( address ) {
    var dfd = new $.Deferred();
    GMaps.geocode({
        address: address,
        callback: function( response, status ) {
            return dfd.resolve( response );
        }
    });
    return dfd.promise();
};
var getRoute = function( fromLatLng, toLatLng ) {
    var dfd = new $.Deferred();
    map.getRoutes({
        origin: [ fromLatLng.lat(), fromLatLng.lng() ],
        destination: [ toLatLng.lat(), toLatLng.lng() ],
        travelMode: "driving",
        unitSystem: "imperial",
        callback: function( e ) {
            return dfd.resolve( e );
        }
    });
    return dfd.promise();
};
var doSomethingCoolWithDirections = function( route ) {
    // do something with route
};
$.when( geocode( fromAddress ), geocode( toAddress ) ).
    then(function( fromLatLng, toLatLng ) {
        getRoute( fromLatLng, toLatLng ).then( doSomethingCoolWithDirections );
    });

This allows you to execute two asyncrhonous functions, wait for their results, and then execute another function with the results of the first two calls.

A promise represents the eventual value returned from the single completion of an operation.

In this code, the geocode method executes twice and returns a promise. The async functions then execute and call resolve in their callbacks. Then, once both have calledresolve, the then executes, returning the results of the first two calls to geocode. The results are then passed to getRoute, which also returns a promise. Finally, when the promise from getRoute is resolved, the doSomethingCoolWithDirections callback executes.

Events

Events are another solution to communicate when async callbacks finish executing. An object can become an emitter and publish events that other objects can listen for. This type of eventing is called the observer pattern. The backbone.js library has this type of functionallity built in with Backbone.Events.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
var SomeModel = Backbone.Model.extend({
   url: "/someurl"
});
var SomeView = Backbone.View.extend({
    initialize: function() {
        this.model.on( "reset", this.render, this );
        this.model.fetch();
    },
    render: function( data ) {
        // do something with data
    }
});
var view = new SomeView({
    model: new SomeModel()
});

There are other mixin examples and libraries for emitting events, such as jQuery Event EmitterEventEmittermonologue.js, and node.js has a built-in EventEmitter module.

The Event Loop is a queue of callback functions.

A similar method of publishing messages uses the mediator pattern, used in thepostal.js library. In the mediator pattern, a middleman for all objects listens to and publishes events. In this approach, one object does not have a direct reference to another, thereby de-coupling the objects from each other.

Never return a promise accross a public API. This couples the API consumers to using promises and makes refactoring difficult. However, a combination of promises for internal purposes and eventing for external APIs can lead to a nicely de-coupled and testable app.

In the previous example, the doSomethingCoolWithDirections callback function executes when the two previous geocode functions have completed. ThedoSomethingCoolWithDirections can then take the response it recieved from getRouteand publish the reponse as a message.

1
2
3
4
5
var doSomethingCoolWithDirections = function( route ) {
    postal.channel( "ui" ).publish( "directions.done",  {
        route: route
    });
};

This allows other areas of the application to respond to the asynchronous callback without needing a direct reference to the request-making object. It’s possible that multiple areas of a page need to update when directions have been retrieved. In a typical jQuery Ajax setup, the success callback would need to be adjusted when recieving a change of directions. This can become difficult to maintain, but by using messaging, updating multiple parts of the UI is much easier to work with.

01
02
03
04
05
06
07
08
09
10
var UI = function() {
    this.channel = postal.channel( "ui" );
    this.channel.subscribe( "directions.done", this.updateDirections ).withContext( this );
};
UI.prototype.updateDirections = function( data ) {
    // The route is available on data.route, now just update the UI
};   
app.ui = new UI();

Some other mediator pattern-based messaging libraries are amplifyPubSubJS, andradio.js.

Conclusion

JavaScript makes writing async code very easy. Using promises, eventing, or named functions eliminates the nasty “callback hell”. For more information on async JavaScript, checkout Async JavaScript: Build More Responsive Apps with Less Code. Many of the examples from the post reside in a Github repository calledNetTutsAsyncJS. Clone away!

———————–我是翻译———————-

Promises模型

引自 CommonJS/A

promise表示一个操作独立完成后返回的最终结果。

有很多库都包含了promise模型,其中jQuery已经有了一个可使用且很出色的promise API。jQuery在1.5版本引入了Deferred对象,并可以在返回promise的函数中使用jQuery.Deferred的构造结果。而返回promise的函数则用于执行某种异步操作并解决完成后的延迟。

01 var geocode = function( address ) {
02     var dfd = new $.Deferred();
03     GMaps.geocode({
04         address: address,
05         callback: function